summaryrefslogtreecommitdiffstats
path: root/pyanaconda
diff options
context:
space:
mode:
Diffstat (limited to 'pyanaconda')
-rw-r--r--pyanaconda/__init__.py1
-rw-r--r--pyanaconda/anaconda_log.py30
-rw-r--r--pyanaconda/anaconda_optparse.py126
-rw-r--r--pyanaconda/baseudev.py4
-rw-r--r--pyanaconda/bootloader.py504
-rw-r--r--pyanaconda/cmdline.py72
-rw-r--r--pyanaconda/constants.py8
-rw-r--r--pyanaconda/dispatch.py2
-rw-r--r--pyanaconda/flags.py200
-rwxr-xr-xpyanaconda/gui.py43
-rw-r--r--pyanaconda/installclass.py19
-rw-r--r--pyanaconda/installclasses/fedora.py21
-rw-r--r--pyanaconda/installclasses/rhel.py4
-rw-r--r--pyanaconda/installmethod.py8
-rw-r--r--pyanaconda/isys/iface.h2
-rw-r--r--pyanaconda/isys/log.c4
-rw-r--r--pyanaconda/isys/mem.h11
-rw-r--r--pyanaconda/iutil.py46
-rw-r--r--pyanaconda/iw/account_gui.py9
-rw-r--r--pyanaconda/iw/advanced_storage.py54
-rw-r--r--pyanaconda/iw/autopart_type.py6
-rw-r--r--pyanaconda/iw/filter_type.py4
-rw-r--r--pyanaconda/iw/language_gui.py1
-rw-r--r--pyanaconda/iw/lvm_dialog_gui.py8
-rw-r--r--pyanaconda/iw/network_gui.py9
-rw-r--r--pyanaconda/iw/partition_gui.py84
-rw-r--r--pyanaconda/iw/task_gui.py1
-rw-r--r--pyanaconda/kickstart.py279
-rw-r--r--pyanaconda/language.py28
-rw-r--r--pyanaconda/livecd.py5
-rw-r--r--pyanaconda/network.py98
-rw-r--r--pyanaconda/partIntfHelpers.py19
-rw-r--r--pyanaconda/platform.py78
-rw-r--r--pyanaconda/script.py55
-rw-r--r--pyanaconda/sshd.py90
-rw-r--r--pyanaconda/storage/__init__.py237
-rw-r--r--pyanaconda/storage/dasd.py8
-rw-r--r--pyanaconda/storage/devicelibs/btrfs.py118
-rw-r--r--pyanaconda/storage/devicelibs/lvm.py17
-rw-r--r--pyanaconda/storage/devices.py330
-rw-r--r--pyanaconda/storage/devicetree.py272
-rw-r--r--pyanaconda/storage/errors.py3
-rw-r--r--pyanaconda/storage/fcoe.py21
-rw-r--r--pyanaconda/storage/formats/__init__.py4
-rw-r--r--pyanaconda/storage/formats/disklabel.py12
-rw-r--r--pyanaconda/storage/formats/fs.py45
-rw-r--r--pyanaconda/storage/formats/prepboot.py28
-rw-r--r--pyanaconda/storage/formats/swap.py1
-rw-r--r--pyanaconda/storage/iscsi.py226
-rw-r--r--pyanaconda/storage/partitioning.py182
-rw-r--r--pyanaconda/storage/partspec.py26
-rw-r--r--pyanaconda/storage/udev.py18
-rw-r--r--pyanaconda/textw/add_drive_text.py41
-rw-r--r--pyanaconda/textw/language_text.py1
-rw-r--r--pyanaconda/textw/upgrade_bootloader_text.py67
-rw-r--r--pyanaconda/textw/userauth_text.py9
-rw-r--r--pyanaconda/upgrade.py48
-rw-r--r--pyanaconda/vnc.py32
-rw-r--r--pyanaconda/yuminstall.py77
59 files changed, 2746 insertions, 1010 deletions
diff --git a/pyanaconda/__init__.py b/pyanaconda/__init__.py
index fd1e676f4..5aaaee728 100644
--- a/pyanaconda/__init__.py
+++ b/pyanaconda/__init__.py
@@ -289,6 +289,7 @@ class Anaconda(object):
self.network.write()
self.network.copyConfigToPath()
self.network.disableNMForStorageDevices(self)
+ self.network.autostartFCoEDevices(self)
self.desktop.write()
self.users.write()
self.security.write()
diff --git a/pyanaconda/anaconda_log.py b/pyanaconda/anaconda_log.py
index 235fc5439..f9d884e73 100644
--- a/pyanaconda/anaconda_log.py
+++ b/pyanaconda/anaconda_log.py
@@ -112,6 +112,9 @@ class AnacondaSyslogHandler(SysLogHandler):
record.msg = original_msg
class AnacondaLog:
+ SYSLOG_CFGFILE = "/etc/rsyslog.conf"
+ VIRTIO_PORT = "/dev/virtio-ports/org.fedoraproject.anaconda.log.0"
+
def __init__ (self):
self.tty_loglevel = DEFAULT_TTY_LEVEL
self.remote_syslog = None
@@ -202,22 +205,35 @@ class AnacondaLog:
self.anaconda_logger.warning("%s" % warnings.formatwarning(
message, category, filename, lineno, line))
+ def restartSyslog(self):
+ os.system("systemctl restart rsyslog.service")
+
def updateRemote(self, remote_syslog):
"""Updates the location of remote rsyslogd to forward to.
- Requires updating rsyslogd config and sending SIGHUP to the daemon.
+ Requires updating rsyslogd config and restarting rsyslog
"""
- PIDFILE = "/var/run/syslogd.pid"
- CFGFILE = "/etc/rsyslog.conf"
TEMPLATE = "*.* @@%s\n"
self.remote_syslog = remote_syslog
- with open(CFGFILE, 'a') as cfgfile:
+ with open(self.SYSLOG_CFGFILE, 'a') as cfgfile:
forward_line = TEMPLATE % remote_syslog
cfgfile.write(forward_line)
- with open(PIDFILE, 'r') as pidfile:
- pid = int(pidfile.read())
- os.kill(pid, signal.SIGHUP)
+ self.restartSyslog()
+
+ def setupVirtio(self):
+ """Setup virtio rsyslog logging.
+ """
+ TEMPLATE = "*.* %s\n"
+
+ if not os.path.exists(self.VIRTIO_PORT) \
+ or not os.access(self.VIRTIO_PORT, os.W_OK):
+ return
+
+ with open(self.SYSLOG_CFGFILE, 'a') as cfgfile:
+ cfgfile.write(TEMPLATE % (self.VIRTIO_PORT,))
+ self.restartSyslog()
+
logger = None
def init():
diff --git a/pyanaconda/anaconda_optparse.py b/pyanaconda/anaconda_optparse.py
new file mode 100644
index 000000000..ccb0361d7
--- /dev/null
+++ b/pyanaconda/anaconda_optparse.py
@@ -0,0 +1,126 @@
+#
+# anaconda_optparse.py: option parsing for anaconda (CLI and boot args)
+#
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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/>.
+#
+# Authors:
+# Will Woods <wwoods@redhat.com>
+
+from flags import BootArgs
+from optparse import OptionParser, OptionConflictError
+
+class AnacondaOptionParser(OptionParser):
+ """
+ Subclass of OptionParser that also examines boot arguments.
+
+ If the "bootarg_prefix" keyword argument is set, it's assumed that all
+ bootargs will start with that prefix.
+
+ "require_prefix" is a bool:
+ False: accept the argument with or without the prefix.
+ True: ignore the argument without the prefix. (default)
+ """
+ def __init__(self, *args, **kwargs):
+ self._boot_arg = dict()
+ self.bootarg_prefix = kwargs.pop("bootarg_prefix","")
+ self.require_prefix = kwargs.pop("require_prefix",True)
+ OptionParser.__init__(self, *args, **kwargs)
+
+ def add_option(self, *args, **kwargs):
+ """
+ Add a new option - like OptionParser.add_option.
+
+ The long options will be added to the list of boot args, unless
+ the keyword argument 'bootarg' is set to False.
+
+ Positional arguments that don't start with '-' are considered extra
+ boot args to look for.
+
+ NOTE: conflict_handler is currently ignored for boot args - they will
+ always raise OptionConflictError if they conflict.
+ """
+ # TODO: add kwargs to make an option commandline-only or boot-arg-only
+ flags = [a for a in args if a.startswith('-')]
+ bootargs = [a for a in args if not a.startswith('-')]
+ do_bootarg = kwargs.pop("bootarg", True)
+ option = OptionParser.add_option(self, *flags, **kwargs)
+ bootargs += [flag[2:] for flag in option._long_opts]
+ if do_bootarg:
+ for b in bootargs:
+ if b in self._boot_arg:
+ raise OptionConflictError(
+ "conflicting bootopt string: %s" % b, option)
+ else:
+ self._boot_arg[b] = option
+ return option
+
+ def _get_bootarg_option(self, arg):
+ """Find the correct Option for a given bootarg (if one exists)"""
+ if self.bootarg_prefix and arg.startswith(self.bootarg_prefix):
+ prefixed_option = True
+ arg = arg[len(self.bootarg_prefix):]
+ else:
+ prefixed_option = False
+ option = self._boot_arg.get(arg)
+
+ if self.require_prefix and not prefixed_option:
+ return None
+ if option and self.bootarg_prefix and not prefixed_option:
+ self.deprecated_bootargs.append(arg)
+ return option
+
+ def parse_boot_cmdline(self, cmdline, values=None):
+ """
+ Parse the boot cmdline and set appropriate values according to
+ the options set by add_option.
+
+ cmdline can be given as a string (to be parsed by BootArgs), or a
+ dict (or any object with .iteritems()) of {bootarg:value} pairs.
+
+ If cmdline is None, the cmdline data will be whatever BootArgs reads
+ by default (/proc/cmdline, /run/initramfs/etc/cmdline, /etc/cmdline).
+
+ If an option requires a value but the boot arg doesn't provide one,
+ we'll quietly not set anything.
+ """
+ if cmdline is None or type(cmdline) is str:
+ bootargs = BootArgs(cmdline)
+ else:
+ bootargs = cmdline
+ self.deprecated_bootargs = []
+ for arg, val in bootargs.iteritems():
+ option = self._get_bootarg_option(arg)
+ if option is None:
+ continue
+ if option.takes_value() and val is None:
+ continue # TODO: emit a warning or something there?
+ if option.action == "store_true" and val in ("0", "no", "off"):
+ # special case: "mpath=0" would otherwise set mpath to True
+ setattr(values, option.dest, False)
+ continue
+ option.process(arg, val, values, self)
+ return values
+
+ def parse_args(self, args=None, values=None, cmdline=None):
+ """
+ Like OptionParser.parse_args(), but also parses the boot cmdline.
+ (see parse_boot_cmdline for details on that process.)
+ Commandline arguments will override boot arguments.
+ """
+ if values is None:
+ values = self.get_default_values()
+ v = self.parse_boot_cmdline(cmdline, values)
+ return OptionParser.parse_args(self, args, v)
diff --git a/pyanaconda/baseudev.py b/pyanaconda/baseudev.py
index 3d9ee45bf..3c60348b9 100644
--- a/pyanaconda/baseudev.py
+++ b/pyanaconda/baseudev.py
@@ -84,9 +84,11 @@ def udev_settle():
iutil.execWithRedirect("udevadm", argv, stderr="/dev/null")
-def udev_trigger(subsystem=None, action="add"):
+def udev_trigger(subsystem=None, action="add", name=None):
argv = ["trigger", "--action=%s" % action]
if subsystem:
argv.append("--subsystem-match=%s" % subsystem)
+ if name:
+ argv.append("--sysname-match=%s" % name)
iutil.execWithRedirect("udevadm", argv, stderr="/dev/null")
diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
index 75948d078..87433bca9 100644
--- a/pyanaconda/bootloader.py
+++ b/pyanaconda/bootloader.py
@@ -33,6 +33,7 @@ from pyanaconda.product import productName
from pyanaconda.flags import flags
from pyanaconda.constants import *
from pyanaconda.storage.errors import StorageError
+from pyanaconda.storage.fcoe import fcoe
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
@@ -74,6 +75,39 @@ def is_windows_boot_block(block):
def has_windows_boot_block(device):
return is_windows_boot_block(get_boot_block(device))
+class serial_opts(object):
+ def __init__(self):
+ self.speed = None
+ self.parity = None
+ self.word = None
+ self.stop = None
+ self.flow = None
+
+def parse_serial_opt(arg):
+ """Parse and split serial console options.
+
+ Documentation/kernel-parameters.txt says:
+ ttyS<n>[,options]
+ Use the specified serial port. The options are of
+ the form "bbbbpnf", where "bbbb" is the baud rate,
+ "p" is parity ("n", "o", or "e"), "n" is number of
+ bits, and "f" is flow control ("r" for RTS or
+ omit it). Default is "9600n8".
+ but note that everything after the baud rate is optional, so these are
+ all valid: 9600, 19200n, 38400n8, 9600e7r"""
+ opts = serial_opts()
+ m = re.match('\d+', arg)
+ if m is None:
+ return opts
+ opts.speed = m.group()
+ idx = len(opts.speed)
+ try:
+ opts.parity = arg[idx+0]
+ opts.word = arg[idx+1]
+ opts.flow = arg[idx+2]
+ except IndexError:
+ pass
+ return opts
class BootLoaderError(Exception):
pass
@@ -199,6 +233,7 @@ class BootLoader(object):
stage2_format_types = ["ext4", "ext3", "ext2"]
stage2_mountpoints = ["/boot", "/"]
stage2_bootable = False
+ stage2_must_be_primary = True
stage2_description = N_("/boot filesystem")
stage2_max_end_mb = 2 * 1024 * 1024
@@ -396,6 +431,12 @@ class BootLoader(object):
% (desc, device.format.type))
ret = False
+ if mountpoints and hasattr(device.format, "mountpoint") \
+ and device.format.mountpoint not in mountpoints:
+ self.errors.append(_("%s must be mounted on one of %s.")
+ % (desc, ", ".join(mountpoints)))
+ ret = False
+
log.debug("_is_valid_format(%s) returning %s" % (device.name,ret))
return ret
@@ -443,6 +484,15 @@ class BootLoader(object):
log.debug("_is_valid_location(%s) returning %s" % (device.name,ret))
return ret
+ def _is_valid_partition(self, device, primary=None, desc=""):
+ ret = True
+ if device.type == "partition" and primary and not device.isPrimary:
+ self.errors.append(_("%s must be on a primary partition.") % desc)
+ ret = False
+
+ log.debug("_is_valid_partition(%s) returning %s" % (device.name,ret))
+ return ret
+
#
# target/stage1 device access
#
@@ -625,6 +675,10 @@ class BootLoader(object):
desc=self.stage2_description):
valid = False
+ if not self._is_valid_partition(device,
+ primary=self.stage2_must_be_primary):
+ valid = False
+
if not self._is_valid_md(device, device_types=self.stage2_device_types,
raid_levels=self.stage2_raid_levels,
metadata=self.stage2_raid_metadata,
@@ -723,6 +777,11 @@ class BootLoader(object):
dracut_devices.extend(storage.fsset.swapDevices)
+ # Does /usr have its own device? If so, we need to tell dracut
+ usr_device = self.storage.mountpoints.get("/usr")
+ if usr_device:
+ dracut_devices.extend([usr_device])
+
done = []
# When we see a device whose setup string starts with a key in this
# dict we pop that pair from the dict. When we're done looking at
@@ -778,6 +837,17 @@ class BootLoader(object):
self.boot_args.add(setup_string)
self.dracut_args.add(setup_string)
+ # This is needed for FCoE, bug #743784. The case:
+ # We discover LUN on an iface which is part of multipath setup.
+ # If the iface is disconnected after discovery anaconda doesn't
+ # write dracut ifname argument for the disconnected iface path
+ # (in Network.dracutSetupArgs).
+ # Dracut needs the explicit ifname= because biosdevname
+ # fails to rename the iface (because of BFS booting from it).
+ for nic, dcb, auto_vlan in fcoe().nics:
+ hwaddr = network.netdevices[nic].get("HWADDR")
+ self.boot_args.add("ifname=%s:%s" % (nic, hwaddr.lower()))
+
#
# preservation of some of our boot args
# FIXME: this is stupid.
@@ -900,6 +970,7 @@ class GRUB(BootLoader):
stage2_is_valid_stage1 = True
stage2_bootable = True
+ stage2_must_be_primary = False
# list of strings representing options for boot device types
stage2_device_types = ["partition", "mdarray"]
@@ -955,18 +1026,31 @@ class GRUB(BootLoader):
return ""
@property
+ def splash_dir(self):
+ """ relative path to splash image directory."""
+ return GRUB._config_dir
+
+ @property
def serial_command(self):
command = ""
if self.console and self.console.startswith("ttyS"):
unit = self.console[-1]
- speed = "9600"
- for opt in self.console_options.split(","):
- if opt.isdigit():
- speed = opt
- break
-
- command = "serial --unit=%s --speed=%s" % (unit, speed)
-
+ command = ["serial"]
+ s = parse_serial_opt(self.console_options)
+ if unit and unit != '0':
+ command.append("--unit=%s" % unit)
+ if s.speed and s.speed != '9600':
+ command.append("--speed=%s" % s.speed)
+ if s.parity:
+ if s.parity == 'o':
+ command.append("--parity=odd")
+ elif s.parity == 'e':
+ command.append("--parity=even")
+ if s.word and s.word != '8':
+ command.append("--word=%s" % s.word)
+ if s.stop and s.stop != '1':
+ command.append("--stop=%s" % s.stop)
+ command = " ".join(command)
return command
def write_config_console(self, config):
@@ -1049,13 +1133,13 @@ class GRUB(BootLoader):
if not flags.serial:
splash = "splash.xpm.gz"
- splash_path = os.path.normpath("%s%s/%s" % (ROOT_PATH,
- self.config_dir,
+ splash_path = os.path.normpath("%s/boot/%s/%s" % (ROOT_PATH,
+ self.splash_dir,
splash))
if os.access(splash_path, os.R_OK):
grub_root_grub_name = self.grub_device_name(self.stage2_device)
config.write("splashimage=%s/%s/%s\n" % (grub_root_grub_name,
- self.grub_config_dir,
+ self.splash_dir,
splash))
config.write("hiddenmenu\n")
@@ -1236,111 +1320,6 @@ class GRUB(BootLoader):
self.warnings = warnings
return ret
-
-class EFIGRUB(GRUB):
- packages = ["grub-efi", "efibootmgr"]
- can_dual_boot = False
- _config_dir = "efi/EFI/redhat"
-
- stage2_is_valid_stage1 = False
- stage2_bootable = False
- stage2_max_end_mb = None
-
- def efibootmgr(self, *args, **kwargs):
- if kwargs.pop("capture", False):
- exec_func = iutil.execWithCapture
- else:
- exec_func = iutil.execWithRedirect
-
- return exec_func("efibootmgr", list(args), **kwargs)
-
- #
- # configuration
- #
-
- @property
- def efi_product_path(self):
- """ The EFI product path.
-
- eg: HD(1,800,64000,faacb4ef-e361-455e-bd97-ca33632550c3)
- """
- buf = self.efibootmgr("-v", stderr="/dev/tty5", capture=True)
- matches = re.search(productName + r'\s+(HD\(.+?\))', buf)
- if matches and matches.groups():
- return matches.group(1)
- return ""
-
- @property
- def grub_conf_device_line(self):
- return "device %s %s\n" % (self.grub_device_name(self.stage2_device),
- self.efi_product_path)
-
- #
- # installation
- #
-
- def remove_efi_boot_target(self):
- buf = self.efibootmgr(capture=True)
- for line in buf.splitlines():
- try:
- (slot, _product) = line.split(None, 1)
- except ValueError:
- continue
-
- if _product == productName:
- slot_id = slot[4:8]
- # slot_id is hex, we can't use .isint and use this regex:
- if not re.match("^[0-9a-fA-F]+$", slot_id):
- log.warning("failed to parse efi boot slot (%s)" % slot)
- continue
-
- rc = self.efibootmgr("-b", slot_id, "-B",
- root=ROOT_PATH,
- stdout="/dev/tty5", stderr="/dev/tty5")
- if rc:
- raise BootLoaderError("failed to remove old efi boot entry")
-
- def add_efi_boot_target(self):
- boot_efi = self.stage1_device
- if boot_efi.type == "partition":
- boot_disk = boot_efi.disk
- boot_part_num = boot_efi.partedPartition.number
- elif boot_efi.type == "mdarray":
- # FIXME: I'm just guessing here. This probably needs the full
- # treatment, ie: multiple targets for each member.
- boot_disk = boot_efi.parents[0].disk
- boot_part_num = boot_efi.parents[0].partedPartition.number
- boot_part_num = str(boot_part_num)
-
- rc = self.efibootmgr("-c", "-w", "-L", productName,
- "-d", boot_disk.path, "-p", boot_part_num,
- "-l", "\\EFI\\redhat\\grub.efi",
- root=ROOT_PATH,
- stdout="/dev/tty5", stderr="/dev/tty5")
- if rc:
- raise BootLoaderError("failed to set new efi boot target")
-
- def install(self):
- self.remove_efi_boot_target()
- self.add_efi_boot_target()
-
- def update(self):
- self.write()
-
- #
- # installation
- #
- def write(self, install_root=""):
- """ Write the bootloader configuration and install the bootloader. """
- if self.update_only:
- self.update(install_root=install_root)
- return
-
- sync()
- self.stage2_device.format.sync(root=install_root)
- self.install(install_root=install_root)
- self.write_config(install_root=install_root)
-
class GRUB2(GRUB):
""" GRUBv2
@@ -1374,10 +1353,15 @@ class GRUB2(GRUB):
# requirements for boot devices
stage2_format_types = ["ext4", "ext3", "ext2", "btrfs"]
- stage2_device_types = ["partition", "mdarray", "lvmlv"]
+ stage2_device_types = ["partition", "mdarray", "lvmlv", "btrfs volume"]
stage2_raid_levels = [mdraid.RAID0, mdraid.RAID1, mdraid.RAID4,
mdraid.RAID5, mdraid.RAID6, mdraid.RAID10]
+ def __init__(self, storage):
+ super(GRUB2, self).__init__(self, storage)
+ self.boot_args.add("$([ -x /usr/sbin/rhcrashkernel-param ] && "\
+ "/usr/sbin/rhcrashkernel-param)")
+
# XXX we probably need special handling for raid stage1 w/ gpt disklabel
# since it's unlikely there'll be a bios boot partition on each disk
@@ -1402,7 +1386,8 @@ class GRUB2(GRUB):
if disk is not None:
name = "(hd%d" % self.disks.index(disk)
if hasattr(device, "disk"):
- name += ",%d" % device.partedPartition.number
+ lt = device.disk.format.labelType
+ name += ",%s%d" % (lt, device.partedPartition.number)
name += ")"
return name
@@ -1421,25 +1406,31 @@ class GRUB2(GRUB):
if os.access(map_path, os.R_OK):
os.rename(map_path, map_path + ".anacbak")
- dev_map = open(map_path, "w")
- dev_map.write("# this device map was generated by anaconda\n")
devices = self.disks
if self.stage1_device not in devices:
devices.append(self.stage1_device)
- if self.stage2_device not in devices:
- devices.append(self.stage2_device)
+ for disk in self.stage2_device.disks:
+ if disk not in devices:
+ devices.append(disk)
- for disk in devices:
- dev_map.write("%s %s\n" % (self.grub_device_name(disk),
- disk.path))
+ devices = [d for d in devices if d.isDisk]
+
+ if len(devices) == 0:
+ return
+
+ dev_map = open(map_path, "w")
+ dev_map.write("# this device map was generated by anaconda\n")
+ for drive in devices:
+ dev_map.write("%s %s\n" % (self.grub_device_name(drive),
+ drive.path))
dev_map.close()
def write_defaults(self):
defaults_file = "%s%s" % (ROOT_PATH, self.defaults_file)
defaults = open(defaults_file, "w+")
defaults.write("GRUB_TIMEOUT=%d\n" % self.timeout)
- defaults.write("GRUB_DISTRIBUTOR=\"%s\"\n" % productName)
+ defaults.write("GRUB_DISTRIBUTOR=\"$(sed 's, release .*$,,g' /etc/system-release)\""))
defaults.write("GRUB_DEFAULT=saved\n")
if self.console and self.console.startswith("ttyS"):
defaults.write("GRUB_TERMINAL=\"serial console\"\n")
@@ -1450,6 +1441,8 @@ class GRUB2(GRUB):
# boot arguments
log.info("bootloader.py: used boot args: %s " % self.boot_args)
defaults.write("GRUB_CMDLINE_LINUX=\"%s\"\n" % self.boot_args)
+ defaults.write("GRUB_DISABLE_RECOVERY=\"true\"")
+ defaults.write("GRUB_THEME=\"/boot/grub2/themes/system/theme.txt\"\n")
defaults.close()
def _encrypt_password(self):
@@ -1482,16 +1475,16 @@ class GRUB2(GRUB):
header.write("cat << EOF\n")
# XXX FIXME: document somewhere that the username is "root"
header.write("set superusers=\"root\"\n")
+ header.write("export superusers\n")
self._encrypt_password()
password_line = "password_pbkdf2 root " + self.encrypted_password
header.write("%s\n" % password_line)
header.write("EOF\n")
header.close()
- os.chmod(users_file, 0755)
+ os.chmod(users_file, 0700)
def write_config(self):
self.write_config_console(None)
- self.write_device_map()
self.write_defaults()
# if we fail to setup password auth we should complete the
@@ -1523,10 +1516,13 @@ class GRUB2(GRUB):
# installation
#
- def install(self):
- # XXX will installing to multiple disks work as expected with GRUBv2?
+ def install(self, args=None):
+ if args is None:
+ args = []
+
+ # XXX will installing to multiple drives work as expected with GRUBv2?
for (stage1dev, stage2dev) in self.install_targets:
- args = ["--no-floppy", self.grub_device_name(stage1dev)]
+ args += ["--no-floppy", stage1dev.path]
if stage1dev == stage2dev:
# This is hopefully a temporary hack. GRUB2 currently refuses
# to install to a partition's boot block without --force.
@@ -1534,10 +1530,130 @@ class GRUB2(GRUB):
rc = iutil.execWithRedirect("grub2-install", args,
stdout="/dev/tty5", stderr="/dev/tty5",
- root=ROOT_PATH)
+ root=ROOT_PATH,
+ env_prune=['MALLOC_PERTURB_'])
if rc:
raise BootLoaderError("bootloader install failed")
+ def write(self):
+ """ Write the bootloader configuration and install the bootloader. """
+ if self.update_only:
+ self.update()
+ return
+
+ self.write_device_map()
+ self.stage2_device.format.sync(root=ROOT_PATH)
+ sync()
+ self.install()
+ sync()
+ self.stage2_device.format.sync(root=ROOT_PATH)
+ self.write_config()
+ sync()
+ self.stage2_device.format.sync(root=ROOT_PATH)
+
+class EFIGRUB(GRUB2):
+ packages = ["grub2-efi", "efibootmgr"]
+ can_dual_boot = False
+
+ @property
+ def _config_dir(self):
+ return "efi/EFI/%s" % (self.storage.anaconda.instClass.efi_dir,)
+
+ def efibootmgr(self, *args, **kwargs):
+ if kwargs.pop("capture", False):
+ exec_func = iutil.execWithCapture
+ else:
+ exec_func = iutil.execWithRedirect
+
+ return exec_func("efibootmgr", list(args), **kwargs)
+
+ #
+ # installation
+ #
+
+ def remove_efi_boot_target(self):
+ buf = self.efibootmgr(capture=True)
+ for line in buf.splitlines():
+ try:
+ (slot, _product) = line.split(None, 1)
+ except ValueError:
+ continue
+
+ if _product == productName:
+ slot_id = slot[4:8]
+ # slot_id is hex, we can't use .isint and use this regex:
+ if not re.match("^[0-9a-fA-F]+$", slot_id):
+ log.warning("failed to parse efi boot slot (%s)" % slot)
+ continue
+
+ rc = self.efibootmgr("-b", slot_id, "-B",
+ root=ROOT_PATH,
+ stdout="/dev/tty5", stderr="/dev/tty5")
+ if rc:
+ raise BootLoaderError("failed to remove old efi boot entry")
+
+ @property
+ def efi_dir_as_efifs_dir(self):
+ ret = self._config_dir.replace('efi/', '')
+ return ret.replace('/', '\\')
+
+ def add_efi_boot_target(self):
+ boot_efi = self.storage.mountpoints["/boot/efi"]
+ if boot_efi.type == "partition":
+ boot_disk = boot_efi.disk
+ boot_part_num = boot_efi.partedPartition.number
+ elif boot_efi.type == "mdarray":
+ # FIXME: I'm just guessing here. This probably needs the full
+ # treatment, ie: multiple targets for each member.
+ boot_disk = boot_efi.parents[0].disk
+ boot_part_num = boot_efi.parents[0].partedPartition.number
+ boot_part_num = str(boot_part_num)
+
+ rc = self.efibootmgr("-c", "-w", "-L", productName,
+ "-d", boot_disk.path, "-p", boot_part_num,
+ "-l",
+ self.efi_dir_as_efifs_dir + "\\grubx64.efi",
+ root=ROOT_PATH,
+ stdout="/dev/tty5", stderr="/dev/tty5")
+ if rc:
+ raise BootLoaderError("failed to set new efi boot target")
+
+ def install(self):
+ if not flags.leavebootorder:
+ self.remove_efi_boot_target()
+ self.add_efi_boot_target()
+
+ def update(self):
+ self.install()
+
+ #
+ # installation
+ #
+ def write(self):
+ """ Write the bootloader configuration and install the bootloader. """
+ if self.update_only:
+ self.update()
+ return
+
+ sync()
+ self.stage2_device.format.sync(root=ROOT_PATH)
+ self.install()
+ self.write_config()
+
+
+class MacEFIGRUB(EFIGRUB):
+ def mactel_config(self):
+ if os.path.exists(ROOT_PATH + "/usr/libexec/mactel-boot-setup"):
+ rc = iutil.execWithRedirect("/usr/libexec/mactel-boot-setup", [],
+ root=ROOT_PATH,
+ stdout="/dev/tty5", stderr="/dev/tty5")
+ if rc:
+ log.error("failed to configure Mac bootloader")
+
+ def install(self):
+ super(MacEFIGRUB, self).install()
+ self.mactel_config()
+
class YabootSILOBase(BootLoader):
def write_config_password(self, config):
@@ -1683,6 +1799,126 @@ class IPSeriesYaboot(Yaboot):
config.write("nonvram\n") # only on pSeries?
config.write("fstype=raw\n")
+ #
+ # installation
+ #
+
+ def install(self):
+ self.updatePowerPCBootList()
+
+ super(IPSeriesYaboot, self).install()
+
+ def updatePowerPCBootList(self):
+
+ log.debug("updatePowerPCBootList: self.stage1_device.path = %s" % self.stage1_device.path)
+
+ buf = iutil.execWithCapture("nvram",
+ ["--print-config=boot-device"],
+ stderr="/dev/tty5")
+
+ if len(buf) == 0:
+ log.error ("FAIL: nvram --print-config=boot-device")
+ return
+
+ boot_list = buf.strip().split()
+ log.debug("updatePowerPCBootList: boot_list = %s" % boot_list)
+
+ buf = iutil.execWithCapture("ofpathname",
+ [self.stage1_device.path],
+ stderr="/dev/tty5")
+
+ if len(buf) > 0:
+ boot_disk = buf.strip()
+ log.debug("updatePowerPCBootList: boot_disk = %s" % boot_disk)
+ else:
+ log.error("FAIL: ofpathname %s" % self.stage1_device.path)
+ return
+
+ # Place the disk containing the PReP partition first.
+ # Remove all other occurances of it.
+ boot_list = [boot_disk] + filter(lambda x: x != boot_disk, boot_list)
+
+ log.debug("updatePowerPCBootList: updated boot_list = %s" % boot_list)
+
+ update_value = "boot-device=%s" % " ".join(boot_list)
+
+ rc = iutil.execWithRedirect("nvram", ["--update-config", update_value],
+ stdout="/dev/tty5", stderr="/dev/tty5")
+ if rc:
+ log.error("FAIL: nvram --update-config %s" % update_value)
+ else:
+ log.info("Updated PPC boot list with the command: nvram --update-config %s" % update_value)
+
+
+class IPSeriesGRUB2(GRUB2):
+
+ # GRUB2 sets /boot bootable and not the PReP partition. This causes the Open Firmware BIOS not
+ # to present the disk as a bootable target. If stage2_bootable is False, then the PReP partition
+ # will be marked bootable. Confusing.
+ stage2_bootable = False
+
+ #
+ # installation
+ #
+
+ def install(self):
+ if flags.leavebootorder:
+ log.info("leavebootorder passed as an option. Will not update the NVRAM boot list.")
+ else:
+ self.updateNVRAMBootList()
+
+ super(IPSeriesGRUB2, self).install(args=["--no-nvram"])
+
+ # This will update the PowerPC's (ppc) bios boot devive order list
+ def updateNVRAMBootList(self):
+
+ log.debug("updateNVRAMBootList: self.stage1_device.path = %s" % self.stage1_device.path)
+
+ buf = iutil.execWithCapture("nvram",
+ ["--print-config=boot-device"],
+ stderr="/dev/tty5")
+
+ if len(buf) == 0:
+ log.error ("Failed to determine nvram boot device")
+ return
+
+ boot_list = buf.strip().replace("\"", "").split()
+ log.debug("updateNVRAMBootList: boot_list = %s" % boot_list)
+
+ buf = iutil.execWithCapture("ofpathname",
+ [self.stage1_device.path],
+ stderr="/dev/tty5")
+
+ if len(buf) > 0:
+ boot_disk = buf.strip()
+ else:
+ log.error("Failed to translate boot path into device name")
+ return
+
+ # Place the disk containing the PReP partition first.
+ # Remove all other occurances of it.
+ boot_list = [boot_disk] + filter(lambda x: x != boot_disk, boot_list)
+
+ update_value = "boot-device=%s" % " ".join(boot_list)
+
+ rc = iutil.execWithRedirect("nvram", ["--update-config", update_value],
+ stdout="/dev/tty5", stderr="/dev/tty5")
+ if rc:
+ log.error("Failed to update new boot device order")
+
+ #
+ # In addition to the normal grub configuration variable, add one more to set the size of the
+ # console's window to a standard 80x24
+ #
+ def write_defaults(self):
+ super(IPSeriesGRUB2, self).write_defaults()
+
+ defaults_file = "%s%s" % (ROOT_PATH, self.defaults_file)
+ defaults = open(defaults_file, "a+")
+ # The terminfo's X and Y size, and output location could change in the future
+ defaults.write("GRUB_TERMINFO=\"terminfo -g 80x24 console\"\n")
+ defaults.close()
+
class MacYaboot(Yaboot):
prog = "mkofboot"
@@ -1731,7 +1967,7 @@ class ZIPL(BootLoader):
image.initrd)
else:
initrd_line = ""
- args.add("root=%s/%s" % (self.boot_dir, image.kernel))
+ args.add("root=%s" % image.device.fstabSpec)
args.update(self.boot_args)
log.info("bootloader.py: used boot args: %s " % args)
stanza = ("[%(label)s]\n"
@@ -1746,6 +1982,8 @@ class ZIPL(BootLoader):
def write_config_header(self, config):
header = ("[defaultboot]\n"
+ "defaultauto\n"
+ "prompt=1\n"
"timeout=%(timeout)d\n"
"default=%(default)s\n"
"target=/boot\n"
diff --git a/pyanaconda/cmdline.py b/pyanaconda/cmdline.py
index b5ab47970..eb05e98ba 100644
--- a/pyanaconda/cmdline.py
+++ b/pyanaconda/cmdline.py
@@ -84,13 +84,9 @@ class InstallInterface(InstallInterfaceBase):
pass
def reinitializeWindow(self, title, path, size, description):
- print(_("Command line mode requires all choices to be specified in a "
- "kickstart configuration file."))
- print(title)
-
- # don't exit
- while 1:
- time.sleep(5)
+ errtxt = _("(%s)\nCommand line mode requires all choices to be specified in a "
+ "kickstart configuration file." % (title,))
+ raise RuntimeError(errtxt)
def shutdown(self):
pass
@@ -105,26 +101,17 @@ class InstallInterface(InstallInterfaceBase):
return ProgressWindow(title, text, total, updpct, pulse)
def kickstartErrorWindow(self, text):
- s = _("The following error was found while parsing the "
- "kickstart configuration file:\n\n%s") %(text,)
- print(s)
-
- while 1:
- time.sleep(5)
+ errtxt = _("The following error was found while parsing the "
+ "kickstart configuration file:\n\n%s") % (text,)
+ raise RuntimeError(errtxt)
def messageWindow(self, title, text, type="ok", default = None,
custom_icon = None, custom_buttons = []):
if type == "ok":
print(text)
else:
- print(_("Command line mode requires all choices to be specified in a kickstart configuration file."))
- print(title)
- print(text)
- print(type, custom_buttons)
-
- # don't exit
- while 1:
- time.sleep(5)
+ errtxt = _("(%s)\n%s" % (title, text))
+ raise RuntimeError(errtxt)
def detailedMessageWindow(self, title, text, longText=None, type="ok",
default=None, custom_buttons=None,
@@ -136,25 +123,24 @@ class InstallInterface(InstallInterfaceBase):
custom_buttons=custom_buttons, custom_icon=custom_icon)
def passphraseEntryWindow(self, device):
- print(_("Can't have a question in command line mode!"))
- print("(passphraseEntryWindow: '%s')" % device)
- # don't exit
- while 1:
- time.sleep(5)
+ errtxt = _("Can't have a question in command line mode!")
+ errtxt += "\n(passphraseEntryWindow: '%s')" % (device,)
+ raise RuntimeError(errtxt)
def getLUKSPassphrase(self, passphrase = "", isglobal = False):
- print(_("Can't have a question in command line mode!"))
- print("(getLUKSPassphrase)")
- # don't exit
- while 1:
- time.sleep(5)
+ errtxt = _("Can't have a question in command line mode!")
+ errtxt += "\n(getLUKSPassphrase)"
+ raise RuntimeError(errtxt)
def enableNetwork(self):
- print(_("Can't have a question in command line mode!"))
- print("(enableNetwork)")
- # don't exit
- while 1:
- time.sleep(5)
+ errtxt = "(enableNetwork)\n"
+ errtxt += _("Can't have a question in command line mode!")
+ raise RuntimeError(errtxt)
+
+ def questionInitializeDASD(self, c, devs):
+ errtxt = "(questionInitializeDASD)\n"
+ errtxt += _("Can't have a question in command line mode!")
+ raise RuntimeError(errtxt)
def mainExceptionWindow(self, shortText, longTextFile):
print(shortText)
@@ -173,9 +159,8 @@ class InstallInterface(InstallInterfaceBase):
if stepToClasses.has_key(step):
stepToClasses[step](self.anaconda)
else:
- print("In interactive step %s, can't continue" %(step,))
- while 1:
- time.sleep(1)
+ errtxt = _("In interactive step can't continue. (%s)" %(step,))
+ raise RuntimeError(errtxt)
def setInstallProgressClass(self, c):
self.instProgress = c
@@ -203,3 +188,12 @@ class progressDisplay:
if stripped != self.display:
self.display = stripped
print(self.display)
+
+def setupProgressDisplay(anaconda):
+ if anaconda.dir == DISPATCH_BACK:
+ anaconda.intf.setInstallProgressClass(None)
+ return DISPATCH_BACK
+ else:
+ anaconda.intf.setInstallProgressClass(progressDisplay())
+
+ return DISPATCH_FORWARD
diff --git a/pyanaconda/constants.py b/pyanaconda/constants.py
index 822427bf2..1baf6bdd7 100644
--- a/pyanaconda/constants.py
+++ b/pyanaconda/constants.py
@@ -75,3 +75,11 @@ MOUNT_DIR = "/mnt/install"
ISO_DIR = MOUNT_DIR + "/isodir"
INSTALL_TREE = MOUNT_DIR + "/source"
BASE_REPO_NAME = "anaconda"
+
+# NOTE: this should be LANG.CODESET, e.g. en_US.UTF-8
+DEFAULT_LANG = "en_US.UTF-8"
+
+DRACUT_SHUTDOWN_EJECT = "/run/initramfs/usr/lib/dracut/hooks/shutdown/99anaconda-eject.sh"
+
+# DMI information paths
+DMI_CHASSIS_VENDOR = "/sys/class/dmi/id/chassis_vendor"
diff --git a/pyanaconda/dispatch.py b/pyanaconda/dispatch.py
index fcf8861dc..8880dd971 100644
--- a/pyanaconda/dispatch.py
+++ b/pyanaconda/dispatch.py
@@ -241,7 +241,6 @@ class Dispatcher(object):
# Note that not only a subset of the steps is executed for a particular
# run, depending on the kind of installation, user selection, kickstart
# commands, used installclass and used user interface.
- self.add_step("sshd", doSshd)
self.add_step("rescue", doRescue)
self.add_step("language")
self.add_step("keyboard")
@@ -258,6 +257,7 @@ class Dispatcher(object):
self.add_step("autopartitionexecute", doAutoPartition)
self.add_step("partition")
self.add_step("upgrademigratefs")
+ self.add_step("upgradeusr", upgradeUsr)
self.add_step("storagedone", storageComplete)
self.add_step("upgbootloader")
self.add_step("bootloader")
diff --git a/pyanaconda/flags.py b/pyanaconda/flags.py
index ba1060384..3a68369c2 100644
--- a/pyanaconda/flags.py
+++ b/pyanaconda/flags.py
@@ -20,32 +20,115 @@
import os
import selinux
import shlex
+import glob
from constants import *
+from collections import OrderedDict
# A lot of effort, but it only allows a limited set of flags to be referenced
-class Flags:
-
- def __getattr__(self, attr):
- if self.__dict__['flags'].has_key(attr):
- return self.__dict__['flags'][attr]
- raise AttributeError, attr
-
+class Flags(object):
def __setattr__(self, attr, val):
- if self.__dict__['flags'].has_key(attr):
- self.__dict__['flags'][attr] = val
- else:
+ if attr not in self.__dict__ and not self._in_init:
raise AttributeError, attr
-
- def get(self, attr, val=None):
- if self.__dict__['flags'].has_key(attr):
- return self.__dict__['flags'][attr]
else:
- return val
+ self.__dict__[attr] = val
- def createCmdlineDict(self):
- cmdlineDict = {}
- cmdline = open("/proc/cmdline", "r").read().strip()
+ def get(self, attr, val=None):
+ return getattr(self, attr, val)
+
+ def set_cmdline_bool(self, flag):
+ if flag in self.cmdline:
+ setattr(self, flag, self.cmdline.getbool(flag))
+
+ def __init__(self, read_cmdline=True):
+ self.__dict__['_in_init'] = True
+ self.test = 0
+ self.livecdInstall = 0
+ self.dlabel = 0
+ self.ibft = 1
+ self.iscsi = 0
+ self.serial = 0
+ self.autostep = 0
+ self.autoscreenshot = 0
+ self.usevnc = 0
+ self.vncquestion = True
+ self.mpath = 1
+ self.dmraid = 1
+ self.selinux = SELINUX_DEFAULT
+ self.debug = 0
+ self.targetarch = None
+ self.useIPv4 = True
+ self.useIPv6 = True
+ self.preexisting_x11 = False
+ self.noverifyssl = False
+ self.imageInstall = False
+ # for non-physical consoles like some ppc and sgi altix,
+ # we need to preserve the console device and not try to
+ # do things like bogl on them. this preserves what that
+ # device is
+ self.virtpconsole = None
+ self.gpt = False
+ self.leavebootorder = False
+ # parse the boot commandline
+ self.cmdline = BootArgs()
+ # Lock it down: no more creating new flags!
+ self.__dict__['_in_init'] = False
+ if read_cmdline:
+ self.read_cmdline()
+
+ def read_cmdline(self):
+ for f in ("selinux", "debug", "leavebootorder", "testing"):
+ self.set_cmdline_bool(f)
+
+ if "rpmarch" in self.cmdline:
+ self.targetarch = self.cmdline.get("rpmarch")
+ if not selinux.is_selinux_enabled():
+ self.selinux = 0
+
+ if "gpt" in self.cmdline:
+ self.gpt = True
+
+cmdline_files = ['/proc/cmdline', '/run/initramfs/etc/cmdline',
+ '/run/initramfs/etc/cmdline.d/*.conf', '/etc/cmdline']
+class BootArgs(OrderedDict):
+ """
+ Hold boot arguments as an OrderedDict.
+ """
+ def __init__(self, cmdline=None, files=cmdline_files):
+ """
+ Create a BootArgs object.
+ Reads each of the "files", then parses "cmdline" if it was provided.
+ """
+ OrderedDict.__init__(self)
+ if files:
+ self.read(files)
+ if cmdline:
+ self.readstr(cmdline)
+
+ def read(self, filenames):
+ """
+ Read and parse a filename (or a list of filenames).
+ Files that can't be read are silently ignored.
+ Returns a list of successfully read files.
+ filenames can contain *, ?, and character ranges expressed with []
+ """
+ readfiles = []
+ if type(filenames) == str:
+ filenames = [filenames]
+
+ # Expand any filename globs
+ filenames = [f for g in filenames for f in glob.glob(g)]
+
+ for f in filenames:
+ try:
+ self.readstr(open(f).read())
+ readfiles.append(f)
+ except IOError:
+ continue
+ return readfiles
+
+ def readstr(self, cmdline):
+ cmdline = cmdline.strip()
# if the BOOT_IMAGE contains a space, pxelinux will strip one of the
# quotes leaving one at the end that shlex doesn't know what to do
# with
@@ -56,73 +139,30 @@ class Flags:
lst = shlex.split(cmdline)
for i in lst:
- try:
+ if "=" in i:
(key, val) = i.split("=", 1)
- except:
+ else:
key = i
val = None
- cmdlineDict[key] = val
-
- return cmdlineDict
-
- def decideCmdlineFlag(self, flag):
- if self.__dict__['flags']['cmdline'].has_key(flag) \
- and not self.__dict__['flags']['cmdline'].has_key("no" + flag) \
- and self.__dict__['flags']['cmdline'][flag] != "0":
- self.__dict__['flags'][flag] = 1
-
- def __init__(self):
- self.__dict__['flags'] = {}
- self.__dict__['flags']['automatedInstall'] = False
- self.__dict__['flags']['test'] = 0
- self.__dict__['flags']['livecdInstall'] = 0
- self.__dict__['flags']['dlabel'] = 0
- self.__dict__['flags']['ibft'] = 1
- self.__dict__['flags']['iscsi'] = 0
- self.__dict__['flags']['serial'] = 0
- self.__dict__['flags']['autostep'] = 0
- self.__dict__['flags']['autoscreenshot'] = 0
- self.__dict__['flags']['usevnc'] = 0
- self.__dict__['flags']['vncquestion'] = True
- self.__dict__['flags']['mpath'] = 1
- self.__dict__['flags']['dmraid'] = 1
- self.__dict__['flags']['selinux'] = SELINUX_DEFAULT
- self.__dict__['flags']['debug'] = 0
- self.__dict__['flags']['targetarch'] = None
- self.__dict__['flags']['cmdline'] = self.createCmdlineDict()
- self.__dict__['flags']['useIPv4'] = True
- self.__dict__['flags']['useIPv6'] = True
- self.__dict__['flags']['sshd'] = 0
- self.__dict__['flags']['preexisting_x11'] = False
- self.__dict__['flags']['noverifyssl'] = False
- self.__dict__['flags']['imageInstall'] = False
- # for non-physical consoles like some ppc and sgi altix,
- # we need to preserve the console device and not try to
- # do things like bogl on them. this preserves what that
- # device is
- self.__dict__['flags']['virtpconsole'] = None
-
- for x in ['selinux']:
- if self.__dict__['flags']['cmdline'].has_key(x):
- if self.__dict__['flags']['cmdline'][x]:
- self.__dict__['flags'][x] = 1
+ self[key] = val
+
+ def getbool(self, arg, default=False):
+ """
+ Return the value of the given arg, as a boolean. The rules are:
+ - "arg", "arg=val": True
+ - "noarg", "noarg=val", "arg=[0|off|no]": False
+ """
+ result = default
+ for a in self:
+ if a == arg:
+ if self[arg] in ("0", "off", "no"):
+ result = False
else:
- self.__dict__['flags'][x] = 0
-
- self.decideCmdlineFlag('sshd')
-
- if self.__dict__['flags']['cmdline'].has_key("debug"):
- self.__dict__['flags']['debug'] = self.__dict__['flags']['cmdline']['debug']
-
- if self.__dict__['flags']['cmdline'].has_key("rpmarch"):
- self.__dict__['flags']['targetarch'] = self.__dict__['flags']['cmdline']['rpmarch']
-
- if not selinux.is_selinux_enabled():
- self.__dict__['flags']['selinux'] = 0
-
- self.__dict__['flags']['nogpt'] = self.__dict__['flags']['cmdline'].has_key("nogpt")
- self.__dict__['flags']['testing'] = self.__dict__['flags']['cmdline'].has_key("testing")
+ result = True
+ elif a == 'no'+arg:
+ result = False # XXX: should noarg=off -> True?
+ return result
global flags
flags = Flags()
diff --git a/pyanaconda/gui.py b/pyanaconda/gui.py
index 6b7ecfb55..d8360a91c 100755
--- a/pyanaconda/gui.py
+++ b/pyanaconda/gui.py
@@ -41,6 +41,7 @@ import network
from installinterfacebase import InstallInterfaceBase
import imp
import iw
+import re
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
@@ -1363,21 +1364,33 @@ class InstallControlWindow:
if gtk.gdk.screen_height() < 600:
i.hide()
- width = None
- height = None
- xrandr = iutil.execWithCapture("xrandr", ["-q"], stderr="/dev/tty5")
- lines = xrandr.splitlines()
- xrandr = filter(lambda x: "current" in x, lines)
- if xrandr and len(xrandr) == 1:
- fields = xrandr[0].split()
- pos = fields.index('current')
- if len(fields) > pos + 3:
- width = int(fields[pos + 1])
- height = int(fields[pos + 3].replace(',', ''))
-
- if width and height:
- self.window.set_size_request(min(width, 800), min(height, 600))
-
+ # Find a window size that will fit on whatever display gets picked
+ # Parse the connected lines from xrandr, which look like this:
+ # DVI-I-1 connected 1680x1050+1680+0 (normal left inverted right x axis y axis) 473mm x 296mm
+ try:
+ widths = []
+ heights= []
+ xrandr = iutil.execWithCapture("xrandr", ["-q"], stderr="/dev/tty5")
+ lines = [l.split() for l in xrandr.splitlines()]
+ displays = filter(lambda x: "connected" in x, lines)
+ for fields in displays:
+ log.debug("display: %s", (fields,))
+ m = re.match("(\d+)x(\d+).*", fields[2])
+ if m and len(m.groups()) == 2:
+ widths.append(int(m.group(1)))
+ heights.append(int(m.group(2)))
+
+ # Pick the smallest size that will fit
+ width = min(widths)
+ height = min(heights)
+ except Exception as e:
+ log.info("screen size detection failed: %s", (str(e),))
+ width = 800
+ height= 600
+
+ # Set the window size, but no smaller than 800x600
+ log.info("Setting window size to %dx%d" % (width, height))
+ self.window.set_size_request(max(width, 800), max(height, 600))
self.window.show()
if flags.debug:
diff --git a/pyanaconda/installclass.py b/pyanaconda/installclass.py
index 75a039853..144df917b 100644
--- a/pyanaconda/installclass.py
+++ b/pyanaconda/installclass.py
@@ -84,7 +84,6 @@ class BaseInstallClass(object):
def setSteps(self, anaconda):
dispatch = anaconda.dispatch
dispatch.schedule_steps(
- "sshd",
"language",
"keyboard",
"filtertype",
@@ -172,10 +171,10 @@ class BaseInstallClass(object):
def setDefaultPartitioning(self, storage, platform):
autorequests = [PartSpec(mountpoint="/", fstype=storage.defaultFSType,
size=1024, maxSize=50*1024, grow=True,
- asVol=True, encrypted=True),
+ btr=True, lv=True, encrypted=True),
PartSpec(mountpoint="/home", fstype=storage.defaultFSType,
size=500, grow=True, requiredSpace=50*1024,
- asVol=True, encrypted=True)]
+ btr=True, lv=True, encrypted=True)]
bootreq = platform.setDefaultPartitioning()
if bootreq:
@@ -183,7 +182,7 @@ class BaseInstallClass(object):
(minswap, maxswap) = iutil.swapSuggestion()
autorequests.append(PartSpec(fstype="swap", size=minswap, maxSize=maxswap,
- grow=True, asVol=True, encrypted=True))
+ grow=True, lv=True, encrypted=True))
storage.autoPartitionRequests = autorequests
@@ -198,6 +197,11 @@ class BaseInstallClass(object):
pass
def productUpgradable(self, arch, oldprod, oldver):
+ """ Return a tuple with:
+ (Upgradable True|False, dict of tests and status)
+
+ The dict has True|False for: product, version, arch tests.
+ """
def archesEq(a, b):
import re
@@ -206,7 +210,12 @@ class BaseInstallClass(object):
else:
return a == b
- return self.productMatches(oldprod) and self.versionMatches(oldver) and archesEq(arch, productArch)
+ result = { "product" : self.productMatches(oldprod),
+ "version" : self.versionMatches(oldver),
+ "arch" : archesEq(arch, productArch)
+ }
+
+ return (all(result.values()), result)
def setNetworkOnbootDefault(self, network):
pass
diff --git a/pyanaconda/installclasses/fedora.py b/pyanaconda/installclasses/fedora.py
index 4a694097e..bc575ea8d 100644
--- a/pyanaconda/installclasses/fedora.py
+++ b/pyanaconda/installclasses/fedora.py
@@ -32,6 +32,7 @@ from pyanaconda import installmethod
from pyanaconda import yuminstall
import rpmUtils.arch
+from decimal import Decimal
class InstallClass(BaseInstallClass):
# name has underscore used for mnemonics, strip if you dont need it
@@ -55,7 +56,7 @@ class InstallClass(BaseInstallClass):
["base", "base-x", "core", "development-libs",
"development-tools", "editors", "fonts", "gnome-desktop",
"gnome-software-development", "graphical-internet", "graphics",
- "hardware-support", "input-methods", "java", "text-internet",
+ "hardware-support", "input-methods", "java", "sound-and-video", "text-internet",
"x-software-development"]),
(N_("Web Server"),
["admin-tools", "base", "base-x", "core", "editors",
@@ -65,6 +66,8 @@ class InstallClass(BaseInstallClass):
_l10n_domain = "anaconda"
+ efi_dir = "fedora"
+
def getPackagePaths(self, uri):
if not type(uri) == types.ListType:
uri = [uri,]
@@ -105,11 +108,14 @@ class InstallClass(BaseInstallClass):
return False
def versionMatches(self, oldver):
+ if oldver is None:
+ return False
+
try:
- oldVer = float(oldver)
+ oldVer = Decimal(oldver)
# Trim off any "-Alpha" or "-Beta".
- newVer = float(productVersion.split('-')[0])
- except ValueError:
+ newVer = Decimal(productVersion.split('-')[0])
+ except Exception:
return True
# This line means we do not support upgrading from anything older
@@ -117,9 +123,12 @@ class InstallClass(BaseInstallClass):
return newVer >= oldVer and newVer - oldVer <= 2
def setNetworkOnbootDefault(self, network):
- if hasActiveNetDev():
- return
+ # if something's already enabled, we can just leave the config alone
+ for devName, dev in network.netdevices.items():
+ if dev.get('ONBOOT') == 'yes':
+ return
+ # the default otherwise: bring up the first wired netdev with link
for devName, dev in network.netdevices.items():
if (not isys.isWirelessDevice(devName) and
isys.getLinkStatus(devName)):
diff --git a/pyanaconda/installclasses/rhel.py b/pyanaconda/installclasses/rhel.py
index 9cbd5f27f..339b5b833 100644
--- a/pyanaconda/installclasses/rhel.py
+++ b/pyanaconda/installclasses/rhel.py
@@ -38,13 +38,15 @@ class InstallClass(BaseInstallClass):
hidden = 1
bootloaderTimeoutDefault = 5
- bootloaderExtraArgs = ["crashkernel=auto"]
+ bootloaderExtraArgs = []
tasks = [(N_("Minimal"),
["core"])]
_l10n_domain = "comps"
+ efi_dir = "redhat"
+
def getPackagePaths(self, uri):
if not type(uri) == types.ListType:
uri = [uri,]
diff --git a/pyanaconda/installmethod.py b/pyanaconda/installmethod.py
index e96c54dd7..b6e089a2f 100644
--- a/pyanaconda/installmethod.py
+++ b/pyanaconda/installmethod.py
@@ -20,6 +20,7 @@
import os, shutil, string
from constants import *
+from iutil import dracut_eject
import logging
log = logging.getLogger("anaconda")
@@ -28,8 +29,7 @@ import isys, product
def doMethodComplete(anaconda):
def _ejectDevice():
- # Ejecting the CD/DVD for kickstart is handled only after %post scripts
- # have been run.
+ # Ejecting the CD/DVD for kickstart is handled at the end of anaconda
if anaconda.ksdata:
return None
@@ -41,7 +41,7 @@ def doMethodComplete(anaconda):
dev = anaconda.stage2[8:].split(':')[0]
return anaconda.storage.devicetree.getDeviceByName(dev)
- anaconda.backend.complete(anaconda)
dev = _ejectDevice()
if dev:
- dev.eject()
+ dracut_eject(dev.path)
+ anaconda.backend.complete(anaconda)
diff --git a/pyanaconda/isys/iface.h b/pyanaconda/isys/iface.h
index 77e66ed84..b24939447 100644
--- a/pyanaconda/isys/iface.h
+++ b/pyanaconda/isys/iface.h
@@ -29,7 +29,7 @@
#include <glib.h>
#include <NetworkManager.h>
-/* Enumerated types used in iface.c as well as loader's network code */
+/* Enumerated types used in iface.c */
enum { IPUNUSED = -1, IPV4, IPV6 };
enum { IPV4_UNUSED_METHOD, IPV4_DHCP_METHOD, IPV4_MANUAL_METHOD, IPV4_IBFT_METHOD, IPV4_IBFT_DHCP_METHOD };
diff --git a/pyanaconda/isys/log.c b/pyanaconda/isys/log.c
index 5be9ba2a0..c0e8ca4e4 100644
--- a/pyanaconda/isys/log.c
+++ b/pyanaconda/isys/log.c
@@ -38,7 +38,7 @@ static FILE * main_log_tty = NULL;
static FILE * main_log_file = NULL;
static FILE * program_log_file = NULL;
static loglevel_t minLevel = INFO;
-static const char * main_tag = "loader";
+static const char * main_tag = "anaconda";
static const char * program_tag = "program";
static const int syslog_facility = LOG_LOCAL1;
@@ -157,7 +157,7 @@ int tty_logfd = -1;
int file_logfd = -1;
void openLog() {
- /* init syslog logging (so loader messages can also be forwarded to a remote
+ /* init syslog logging (so log messages can also be forwarded to a remote
syslog daemon */
openlog(main_tag, 0, syslog_facility);
diff --git a/pyanaconda/isys/mem.h b/pyanaconda/isys/mem.h
index 3c4051c26..092999e2f 100644
--- a/pyanaconda/isys/mem.h
+++ b/pyanaconda/isys/mem.h
@@ -25,15 +25,16 @@
#include <glib.h>
+/* The *_RAM sizes are all in KB */
#if defined(__powerpc64__) || defined(__sparc__)
- #define MIN_RAM 1024*1024 // 1 GB
- #define GUI_INSTALL_EXTRA_RAM 512*1024 // 512 MB
+ #define MIN_RAM 768*1024
+ #define GUI_INSTALL_EXTRA_RAM 512*1024
#else
- #define MIN_RAM 768 * 1024 // 768 MB
- #define GUI_INSTALL_EXTRA_RAM 0 * 1024 // 0 MB
+ #define MIN_RAM 512 * 1024
+ #define GUI_INSTALL_EXTRA_RAM 0 * 1024
#endif
#define MIN_GUI_RAM MIN_RAM + GUI_INSTALL_EXTRA_RAM
-#define EARLY_SWAP_RAM 1152 * 1024 // 1152 MB
+#define EARLY_SWAP_RAM 896 * 1024
#define MEMINFO "/proc/meminfo"
diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py
index 69ad6bf2d..abf720fd4 100644
--- a/pyanaconda/iutil.py
+++ b/pyanaconda/iutil.py
@@ -90,7 +90,7 @@ def augmentEnv():
# @param root The directory to chroot to before running command.
# @return The return code of command.
def execWithRedirect(command, argv, stdin = None, stdout = None,
- stderr = None, root = '/'):
+ stderr = None, root = '/', env_prune=[]):
if flags.testing:
log.info("not running command because we're testing: %s %s"
% (command, " ".join(argv)))
@@ -113,6 +113,7 @@ def execWithRedirect(command, argv, stdin = None, stdout = None,
elif stdin is None or not isinstance(stdin, file):
stdin = sys.stdin.fileno()
+ orig_stdout = stdout
if isinstance(stdout, str):
stdout = os.open(stdout, os.O_RDWR|os.O_CREAT)
stdoutclose = lambda : os.close(stdout)
@@ -121,7 +122,9 @@ def execWithRedirect(command, argv, stdin = None, stdout = None,
elif stdout is None or not isinstance(stdout, file):
stdout = sys.stdout.fileno()
- if isinstance(stderr, str):
+ if isinstance(stderr, str) and isinstance(orig_stdout, str) and stderr == orig_stdout:
+ stderr = stdout
+ elif isinstance(stderr, str):
stderr = os.open(stderr, os.O_RDWR|os.O_CREAT)
stderrclose = lambda : os.close(stderr)
elif isinstance(stderr, int):
@@ -135,6 +138,10 @@ def execWithRedirect(command, argv, stdin = None, stdout = None,
pstdout, pstdin = os.pipe()
perrout, perrin = os.pipe()
+ for var in env_prune:
+ if env.has_key(var):
+ del env[var]
+
try:
#prepare tee proceses
proc_std = tee(pstdout, stdout, program_log.info, command)
@@ -697,11 +704,10 @@ def isMactel():
if not isX86():
mactel = False
- elif not os.path.exists("/usr/sbin/dmidecode"):
+ elif not os.path.isfile(DMI_CHASSIS_VENDOR):
mactel = False
else:
- buf = execWithCapture("/usr/sbin/dmidecode",
- ["dmidecode", "-s", "system-manufacturer"])
+ buf = open(DMI_CHASSIS_VENDOR).read()
if buf.lower().find("apple") != -1:
mactel = True
else:
@@ -776,6 +782,9 @@ def isAlpha():
def isSparc():
return os.uname()[4].startswith('sparc')
+def isARM():
+ return os.uname()[4].startswith('arm')
+
def getArch():
if isX86(bits=32):
return 'i386'
@@ -789,6 +798,8 @@ def getArch():
return 'alpha'
elif isSparc():
return 'sparc'
+ elif isARM():
+ return 'arm'
else:
return os.uname()[4]
@@ -1043,3 +1054,28 @@ def service_running(service):
ret = _run_systemctl("status", service)
return ret == 0
+
+def dracut_eject(device):
+ """
+ Use dracut shutdown hook to eject media after the system is shutdown.
+ This is needed because we are running from the squashfs.img on the media
+ so ejecting too early will crash the installer.
+ """
+ if not device:
+ return
+
+ try:
+ if not os.path.exists(DRACUT_SHUTDOWN_EJECT):
+ f = open(DRACUT_SHUTDOWN_EJECT, "w")
+ f.write("#!/bin/sh\n")
+ f.write("# Created by Anaconda\n")
+ else:
+ f = open(DRACUT_SHUTDOWN_EJECT, "a")
+
+ f.write("eject %s\n" % (device,))
+ f.close()
+ os.chmod(DRACUT_SHUTDOWN_EJECT, 0755)
+ log.info("Wrote dracut shutdown eject hook for %s" % (device,))
+ except Exception, e:
+ log.error("Error writing dracut shutdown eject hook for %s: %s" % (device, e))
+
diff --git a/pyanaconda/iw/account_gui.py b/pyanaconda/iw/account_gui.py
index 67859e15d..be8c4b5e6 100644
--- a/pyanaconda/iw/account_gui.py
+++ b/pyanaconda/iw/account_gui.py
@@ -25,7 +25,7 @@ from pyanaconda import gui
from iw_gui import *
from pyanaconda.flags import flags
from pyanaconda.constants import *
-import cracklib
+import pwquality
from pyanaconda import _isys
import gettext
@@ -121,9 +121,10 @@ class AccountWindow (InstallWindow):
self.passwordError()
try:
- cracklib.FascistCheck(pw)
- except ValueError as e:
- msg = gettext.ldgettext("cracklib", e)
+ settings = pwquality.PWQSettings()
+ settings.read_config()
+ settings.check(pw, None, "root")
+ except pwquality.PWQError as (e, msg):
ret = self.intf.messageWindow(_("Weak Password"),
_("You have provided a weak password: %s") % msg,
type="custom", custom_icon="error",
diff --git a/pyanaconda/iw/advanced_storage.py b/pyanaconda/iw/advanced_storage.py
index ade113590..1375d7eec 100644
--- a/pyanaconda/iw/advanced_storage.py
+++ b/pyanaconda/iw/advanced_storage.py
@@ -172,6 +172,7 @@ class iSCSILoginDialog(iSCSICredentialsDialog):
class iSCSIGuiWizard(pih.iSCSIWizard):
NODE_NAME_COL = DeviceSelector.IMMUTABLE_COL + 1
+ NODE_INTERFACE_COL = DeviceSelector.IMMUTABLE_COL + 2
def __init__(self):
self.login_dialog = None
@@ -222,7 +223,7 @@ class iSCSIGuiWizard(pih.iSCSIWizard):
return self._run_dialog(self.login_dialog.dialog)
- def display_nodes_dialog(self, found_nodes):
+ def display_nodes_dialog(self, found_nodes, ifaces):
def _login_button_disabler(device_selector, login_button, checked, item):
login_button.set_sensitive(len(device_selector.getSelected()) > 0)
@@ -232,14 +233,16 @@ class iSCSIGuiWizard(pih.iSCSIWizard):
gobject.TYPE_BOOLEAN, # visible
gobject.TYPE_BOOLEAN, # active (checked)
gobject.TYPE_BOOLEAN, # immutable
- gobject.TYPE_STRING # node name
+ gobject.TYPE_STRING, # node name
+ gobject.TYPE_STRING # node interface
)
map(lambda node : store.append(None, (
node, # the object
True, # visible
True, # active
False, # not immutable
- node.name)), # node's name
+ node.name, # node's name
+ ifaces.get(node.iface, node.iface))), # node's interface
found_nodes)
# create and setup the device selector
@@ -253,7 +256,7 @@ class iSCSIGuiWizard(pih.iSCSIWizard):
ds,
xml.get_widget("button_login"))
ds.createSelectionCol(toggledCB=callback)
- ds.addColumn(_("Node Name"), self.NODE_NAME_COL)
+ ds.addColumn(_("Interface"), self.NODE_INTERFACE_COL)
# attach the treeview to the dialog
sw = xml.get_widget("nodes_scrolled_window")
sw.add(view)
@@ -266,7 +269,8 @@ class iSCSIGuiWizard(pih.iSCSIWizard):
dialog.destroy()
return (rc, selected_nodes)
- def display_success_dialog(self, success_nodes, fail_nodes, fail_reason):
+ def display_success_dialog(self, success_nodes, fail_nodes, fail_reason,
+ ifaces):
(xml, dialog) = gui.getGladeWidget("iscsi-dialogs.glade", "success_dialog")
w_success = xml.get_widget("label_success")
w_success_win = xml.get_widget("scroll_window_success")
@@ -280,14 +284,14 @@ class iSCSIGuiWizard(pih.iSCSIWizard):
w_separator = xml.get_widget("separator")
if success_nodes:
- markup = "\n".join(map(lambda n: n.name, success_nodes))
+ markup = "\n".join(map(lambda n: "%s via %s" % (n.name, ifaces.get(n.iface, n.iface)), success_nodes))
buf = gtk.TextBuffer()
buf.set_text(markup)
w_success.show()
w_success_val.set_buffer(buf)
w_success_win.show()
if fail_nodes:
- markup = "\n".join(map(lambda n: n.name, fail_nodes))
+ markup = "\n".join(map(lambda n: "%s via %s" % (n.name, ifaces.get(n.iface, n.iface)), fail_nodes))
buf = gtk.TextBuffer()
buf.set_text(markup)
w_fail.show()
@@ -322,6 +326,7 @@ def addFcoeDrive(anaconda):
(dxml, dialog) = gui.getGladeWidget("fcoe-config.glade", "fcoeDialog")
combo = dxml.get_widget("fcoeNicCombo")
dcb_cb = dxml.get_widget("dcbCheckbutton")
+ auto_vlan_cb = dxml.get_widget("autovlanCheckbutton")
# Populate the combo
cell = gtk.CellRendererText()
@@ -382,8 +387,9 @@ def addFcoeDrive(anaconda):
try:
anaconda.storage.fcoe.addSan(store.get_value(iter, 1),
- dcb=dcb_cb.get_active(),
- intf=anaconda.intf)
+ dcb=dcb_cb.get_active(),
+ auto_vlan=auto_vlan_cb.get_active(),
+ intf=anaconda.intf)
except IOError as e:
anaconda.intf.messageWindow(_("Error"), str(e))
rc = gtk.RESPONSE_CANCEL
@@ -393,7 +399,7 @@ def addFcoeDrive(anaconda):
dialog.destroy()
return rc
-def addIscsiDrive(anaconda):
+def addIscsiDrive(anaconda, bind=False):
"""
Displays a series of dialogs that walk the user through discovering and
logging into iscsi nodes.
@@ -407,6 +413,15 @@ def addIscsiDrive(anaconda):
log.info("addIscsiDrive(): early exit, network disabled.")
return gtk.RESPONSE_CANCEL
+ # This will modify behaviour of iscsi.discovery() function
+ if pyanaconda.storage.iscsi.iscsi().mode == "none" and not bind:
+ pyanaconda.storage.iscsi.iscsi().delete_interfaces()
+ elif (pyanaconda.storage.iscsi.iscsi().mode == "none" and bind) \
+ or pyanaconda.storage.iscsi.iscsi().mode == "bind":
+ active = set(network.getActiveNetDevs())
+ created = set(pyanaconda.storage.iscsi.iscsi().ifaces.values())
+ pyanaconda.storage.iscsi.iscsi().create_interfaces(active - created)
+
wizard = iSCSIGuiWizard()
login_ok_nodes = pih.drive_iscsi_addition(anaconda, wizard)
if len(login_ok_nodes):
@@ -454,11 +469,27 @@ def addDrive(anaconda):
if not pyanaconda.storage.iscsi.has_iscsi():
dxml.get_widget("iscsiRadio").set_sensitive(False)
dxml.get_widget("iscsiRadio").set_active(False)
+ dxml.get_widget("iscsiBindCheck").set_sensitive(False)
+ else:
+ dxml.get_widget("iscsiBindCheck").set_active(bool(pyanaconda.storage.iscsi.iscsi().ifaces))
+ dxml.get_widget("iscsiBindCheck").set_sensitive(pyanaconda.storage.iscsi.iscsi().mode == "none")
if not pyanaconda.storage.fcoe.has_fcoe():
dxml.get_widget("fcoeRadio").set_sensitive(False)
dxml.get_widget("fcoeRadio").set_active(False)
+ def update_active_ifaces():
+ active_ifaces = network.getActiveNetDevs()
+ dxml.get_widget("ifaceLabel").set_text(", ".join(active_ifaces))
+
+ def netconfButton_clicked(*args):
+ from pyanaconda.iw.network_gui import setupNetwork
+ setupNetwork(anaconda.intf)
+ update_active_ifaces()
+
+ dxml.get_widget("netconfButton").connect("clicked", netconfButton_clicked)
+ update_active_ifaces()
+
#figure out what advanced devices we have available and put focus on the first one
group = dxml.get_widget("iscsiRadio").get_group()
for button in reversed(group):
@@ -474,7 +505,8 @@ def addDrive(anaconda):
return False
if dxml.get_widget("iscsiRadio").get_active() and pyanaconda.storage.iscsi.has_iscsi():
- rc = addIscsiDrive(anaconda)
+ bind = dxml.get_widget("iscsiBindCheck").get_active()
+ rc = addIscsiDrive(anaconda, bind)
elif dxml.get_widget("fcoeRadio").get_active() and pyanaconda.storage.fcoe.has_fcoe():
rc = addFcoeDrive(anaconda)
elif dxml.get_widget("zfcpRadio") is not None and dxml.get_widget("zfcpRadio").get_active():
diff --git a/pyanaconda/iw/autopart_type.py b/pyanaconda/iw/autopart_type.py
index 6dca7e159..5ea243d24 100644
--- a/pyanaconda/iw/autopart_type.py
+++ b/pyanaconda/iw/autopart_type.py
@@ -191,9 +191,9 @@ class PartitionTypeWindow(InstallWindow):
self.dispatch.request_steps_gently("cleardiskssel")
if self.lvmButton.get_active():
- self.storage.lvmAutoPart = True
+ self.storage.autoPartType = AUTOPART_TYPE_LVM
else:
- self.storage.lvmAutoPart = False
+ self.storage.autoPartType = AUTOPART_TYPE_PLAIN
if self.encryptButton.get_active():
self.storage.encryptedAutoPart = True
@@ -263,7 +263,7 @@ class PartitionTypeWindow(InstallWindow):
self.reviewButton.set_active(
step_data.get("review_checked", self.dispatch.step_enabled("partition")))
self.encryptButton.set_active(self.storage.encryptedAutoPart)
- self.lvmButton.set_active(self.storage.lvmAutoPart)
+ self.lvmButton.set_active(self.storage.autoPartType == AUTOPART_TYPE_LVM)
self.buttonGroup = pixmapRadioButtonGroup()
self.buttonGroup.addEntry("all", _("Use _All Space"),
diff --git a/pyanaconda/iw/filter_type.py b/pyanaconda/iw/filter_type.py
index a26a02c42..9f8207990 100644
--- a/pyanaconda/iw/filter_type.py
+++ b/pyanaconda/iw/filter_type.py
@@ -39,6 +39,10 @@ class FilterTypeWindow(InstallWindow):
ics.setTitle("Filter Type")
ics.setNextEnabled(True)
+ if flags.cmdline.getbool("traceback"):
+ ics.getICW().anaconda.intf.enableNetwork()
+ raise RuntimeError("Intentionally raised exception")
+
def getNext(self):
if self.buttonGroup.getCurrent() == "simple":
self.anaconda.simpleFilter = True
diff --git a/pyanaconda/iw/language_gui.py b/pyanaconda/iw/language_gui.py
index 75fa66b43..c144f8481 100644
--- a/pyanaconda/iw/language_gui.py
+++ b/pyanaconda/iw/language_gui.py
@@ -50,7 +50,6 @@ class LanguageWindow (InstallWindow):
self.instLang.instLang = self.lang
self.instLang.systemLang = self.lang
- self.instLang.buildLocale()
anaconda.timezone.setTimezoneInfo(anaconda.instLanguage.getDefaultTimeZone())
self.ics.getICW().setLanguage()
diff --git a/pyanaconda/iw/lvm_dialog_gui.py b/pyanaconda/iw/lvm_dialog_gui.py
index d2b6b0b4b..abc47e8a6 100644
--- a/pyanaconda/iw/lvm_dialog_gui.py
+++ b/pyanaconda/iw/lvm_dialog_gui.py
@@ -462,7 +462,8 @@ class VolumeGroupEditor:
if lv['name']:
lvnameentry.set_text(lv['name'])
else:
- lvnameentry.set_text(self.storage.createSuggestedLVName(self.getTempVG()))
+ lvnameentry.set_text(self.storage.suggestDeviceName(parent=self.getTempVG(),
+ prefix="lv"))
# Logical Volume size label & entry
lvsizelabel = createAlignedLabel(_("_Size (MB):"))
@@ -868,7 +869,7 @@ class VolumeGroupEditor:
return
tempvg = self.getTempVG()
- name = self.storage.createSuggestedLVName(tempvg)
+ name = self.storage.suggestDeviceName(parent=tempvg, prefix="lv")
format = getFormat(self.storage.defaultFSType)
self.lvs[name] = {'name': name,
'size': free,
@@ -1375,7 +1376,8 @@ class VolumeGroupEditor:
if not self.isNew:
self.volnameEntry.set_text(self.vg.name)
else:
- self.volnameEntry.set_text(self.storage.createSuggestedVGName(anaconda.network.hostname))
+ self.volnameEntry.set_text(self.storage.suggestContainerName(hostname=anaconda.network.hostname,
+ prefix="vg"))
else:
lbl = createAlignedLabel(_("Volume Group Name:"))
self.volnameEntry = gtk.Label(self.vg.name)
diff --git a/pyanaconda/iw/network_gui.py b/pyanaconda/iw/network_gui.py
index a7bf32131..1521db022 100644
--- a/pyanaconda/iw/network_gui.py
+++ b/pyanaconda/iw/network_gui.py
@@ -50,7 +50,7 @@ class NetworkWindow(InstallWindow):
self.hostnameEntry.set_text(self.hostname)
self.netconfButton = self.xml.get_widget("netconfButton")
- self.netconfButton.connect("clicked", self._setupNetwork)
+ self.netconfButton.connect("clicked", self._netconfButton_clicked)
if (len(self.anaconda.network.netdevices) == 0
or flags.imageInstall
or flags.livecdInstall):
@@ -65,8 +65,8 @@ class NetworkWindow(InstallWindow):
return self.align
- def _setupNetwork(self, *args):
- self.intf.enableNetwork(just_setup=True)
+ def _netconfButton_clicked(self, *args):
+ setupNetwork(self.intf)
def focus(self):
self.hostnameEntry.grab_focus()
@@ -102,6 +102,9 @@ def NMCEExited(pid, condition, anaconda):
if anaconda:
anaconda.intf.icw.window.set_sensitive(True)
+def setupNetwork(intf):
+ intf.enableNetwork(just_setup=True)
+
# TODORV: get rid of setting sensitive completely?
def runNMCE(anaconda=None, blocking=True):
if not blocking and anaconda:
diff --git a/pyanaconda/iw/partition_gui.py b/pyanaconda/iw/partition_gui.py
index c62d0a6c5..3a6eb5f66 100644
--- a/pyanaconda/iw/partition_gui.py
+++ b/pyanaconda/iw/partition_gui.py
@@ -50,7 +50,9 @@ from pyanaconda.constants import *
from partition_ui_helpers_gui import *
from pyanaconda.storage.partitioning import doPartitioning
from pyanaconda.storage.devicelibs import lvm
-from pyanaconda.storage.devices import devicePathToName, PartitionDevice
+from pyanaconda.storage.devices import devicePathToName
+from pyanaconda.storage.devices import PartitionDevice
+from pyanaconda.storage.devices import BTRFSVolumeDevice
from pyanaconda.storage.devices import deviceNameToDiskByPath
from pyanaconda.storage.errors import DeviceNotFoundError
@@ -492,27 +494,32 @@ class LVMStripeGraph(StripeGraph):
return stripe
-class MDRaidArrayStripeGraph(StripeGraph):
+class MDStripeGraph(StripeGraph):
+ desc = "MD"
"""
storage -- the storage object
cCB -- call back function used when the user clicks on a slice. This function
is passed a device object when its executed.
dcCB -- call back function used when the user double clicks on a slice.
- md -- RAID device to display.
+ md -- md device to display.
"""
- def __init__(self, storage, md=None, cCB=lambda x:None, dcCB=lambda:None):
+ def __init__(self, storage, device=None, cCB=lambda x:None, dcCB=lambda:None):
StripeGraph.__init__(self)
self.storage = storage
self.cCB = cCB
self.dcCB = dcCB
self.part_type_colors = \
{"sel_md": "cornsilk1", "unsel_md": "white"}
- if md:
- self.setDisplayed(md)
+ if device:
+ self.setDisplayed(device)
+
+ def _get_text(self, md):
+ return (_("%(desc)s %(mdPath)s (%(mdSize)-0.f MB)")
+ % {"mdPath": md.path, "mdSize": md.size, "desc": self.desc})
def _createStripe(self, md):
- mdtext = _("MD RAID ARRAY %(mdPath)s (%(mdSize)-0.f MB)") % {"mdPath": md.path, "mdSize": md.size}
+ mdtext = self._get_text(md)
stripe = Stripe(self.getCanvas(), mdtext, self.dcCB, obj = md)
# Since we can't really create subslices with md devices we will only
@@ -531,6 +538,16 @@ class MDRaidArrayStripeGraph(StripeGraph):
return stripe
+class MDRaidArrayStripeGraph(MDStripeGraph):
+ desc = "MD RAID Array"
+
+class BTRFSStripeGraph(MDStripeGraph):
+ desc = "BTRFS Pool"
+
+ def _get_text(self, md):
+ return (_("%(desc)s %(mdUUID)s (%(mdSize)-0.f MB)")
+ % {"mdUUID": md.uuid, "mdSize": md.size, "desc": self.desc})
+
class MessageGraph:
def __init__(self, canvas, message):
self.canvas = canvas
@@ -925,11 +942,16 @@ class PartitionWindow(InstallWindow):
break
mnt_str = getattr(array, "name", "")
+ elif format.type == "btrfs" and not isinstance(device, BTRFSVolumeDevice):
+ btrfs_dev = self.storage.devicetree.getChildren(device)[0]
+ mnt_str = btrfs_dev.name
else:
mnt_str = getattr(format, "mountpoint", "")
if mnt_str is None:
mnt_str = ""
+ isleaf = True
+
# device name
name_str = getattr(device, "lvname", device.name)
@@ -944,10 +966,30 @@ class PartitionWindow(InstallWindow):
self.tree[treeiter]['IsFormattable'] = format.formattable
self.tree[treeiter]['Format'] = format_icon
self.tree[treeiter]['Mount Point'] = mnt_str
- self.tree[treeiter]['IsLeaf'] = True
+ self.tree[treeiter]['IsLeaf'] = isleaf
self.tree[treeiter]['Type'] = format.name
self.tree[treeiter]['Label'] = label_str
+ # XXX can this move up one level?
+ if isinstance(device, BTRFSVolumeDevice):
+ # list subvolumes as children of the main volume
+ for s in device.subvolumes:
+ log.debug("%r" % s.format)
+ isleaf = False
+ if s.format.exists:
+ sub_format_icon = None
+ else:
+ sub_format_icon = self.checkmark_pixbuf
+ subvol_iter = self.tree.append(treeiter)
+ self.tree[subvol_iter]['Device'] = s.name
+ self.tree[subvol_iter]['PyObject'] = s
+ self.tree[subvol_iter]['IsFormattable'] = True
+ self.tree[subvol_iter]['Format'] = sub_format_icon
+ self.tree[subvol_iter]['Mount Point'] = s.format.mountpoint
+ self.tree[subvol_iter]['Type'] = s.type
+ self.tree[subvol_iter]['IsLeaf'] = True
+
+
def populate(self, initial = 0):
self.tree.resetSelection()
@@ -985,6 +1027,15 @@ class PartitionWindow(InstallWindow):
(array.name, array.path)
self.tree[iter]['Device'] = name
+ # BTRFS volumes
+ btrfs_devs = self.storage.btrfsVolumes
+ if btrfs_devs:
+ btrfsparent = self.tree.append(None)
+ self.tree[btrfsparent]['Device'] = _("BTRFS Volumes")
+ for dev in btrfs_devs:
+ iter = self.tree.append(btrfsparent)
+ self.addDevice(dev, iter)
+
# now normal partitions
disks = self.storage.partitioned
# also include unpartitioned disks that aren't mpath or biosraid
@@ -1262,13 +1313,26 @@ class PartitionWindow(InstallWindow):
if not isinstance(self.stripeGraph, MDRaidArrayStripeGraph):
self.stripeGraph.shutDown()
self.stripeGraph = MDRaidArrayStripeGraph(self.storage,
- md = device,
+ device = device,
cCB = self.tree.selectRowFromObj,
dcCB = self.barviewActivateCB)
self.stripeGraph.setDisplayed(device)
self.deleteButton.set_sensitive(True)
self.editButton.set_sensitive(True)
+ elif isinstance(device, storage.BTRFSDevice):
+ # BTRFSDevice can be edited but not explicitly deleted. It is
+ # deleted when its last member device is removed.
+ if not isinstance(self.stripeGraph, BTRFSStripeGraph):
+ self.stripeGraph.shutDown()
+ self.stripeGraph = BTRFSStripeGraph(self.storage,
+ device = device,
+ cCB = self.tree.selectRowFromObj,
+ dcCB = self.barviewActivateCB)
+ self.stripeGraph.setDisplayed(device)
+ self.deleteButton.set_sensitive(False)
+ self.editButton.set_sensitive(True)
+
else:
# This means that the user selected something that is not showable
# in the bar view. Just show the information message.
@@ -1719,7 +1783,7 @@ class PartitionWindow(InstallWindow):
vgeditor = l_d_g.VolumeGroupEditor(self.anaconda, self.intf, self.parent,
vg, isNew = False)
tempvg = vgeditor.getTempVG()
- name = self.storage.createSuggestedLVName(tempvg)
+ name = self.storage.suggestDeviceName(parent=tempvg, prefix="lv")
format = getFormat(self.storage.defaultFSType)
vgeditor.lvs[name] = {'name': name,
'size': vg.freeSpace,
diff --git a/pyanaconda/iw/task_gui.py b/pyanaconda/iw/task_gui.py
index 21b558758..1efdaa8fe 100644
--- a/pyanaconda/iw/task_gui.py
+++ b/pyanaconda/iw/task_gui.py
@@ -21,7 +21,6 @@ import gtk
import gtk.glade
import gobject
from pyanaconda import gui
-import gzip
from iw_gui import *
from pyanaconda.image import *
from pyanaconda.constants import *
diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
index f3ba8b728..7a78d2769 100644
--- a/pyanaconda/kickstart.py
+++ b/pyanaconda/kickstart.py
@@ -243,8 +243,8 @@ class AutoPart(commands.autopart.F16_AutoPart):
storage.autoPartEscrowCert = getEscrowCertificate(self.anaconda, self.escrowcert)
storage.autoPartAddBackupPassphrase = self.backuppassphrase
- if not self.lvm:
- storage.lvmAutoPart = False
+ if self.type is not None:
+ storage.autoPartType = self.type
doAutoPartition(storage, ksdata)
@@ -298,9 +298,86 @@ class Bootloader(commands.bootloader.F17_Bootloader):
drive = storage.devicetree.getDeviceByName(spec)
storage.bootloader.stage1_disk = drive
-class ClearPart(commands.clearpart.FC3_ClearPart):
+ if self.leavebootorder:
+ flags.leavebootorder = True
+
+class BTRFSData(commands.btrfs.F17_BTRFSData):
+ def execute(self):
+ storage = self.anaconda.storage
+ devicetree = storage.devicetree
+
+ storage.doAutoPart = False
+
+ members = []
+
+ # Get a list of all the devices that make up this volume.
+ for member in self.devices:
+ # if using --onpart, use original device
+ member_name = self.anaconda.ksdata.onPart.get(member, member)
+ if member_name:
+ dev = devicetree.getDeviceByName(member_name)
+ if not dev:
+ dev = devicetree.resolveDevice(member)
+
+ if dev and dev.format.type == "luks":
+ try:
+ dev = devicetree.getChildren(dev)[0]
+ except IndexError:
+ dev = None
+ if not dev:
+ raise KickstartValueError, formatErrorMsg(self.lineno, msg="Tried to use undefined partition %s in BTRFS volume specification" % member)
+
+ members.append(dev)
+
+ if self.subvol:
+ name = self.name
+ elif self.label:
+ name = self.label
+ else:
+ name = None
+
+ if len(members) == 0 and not self.preexist:
+ raise KickstartValueError, formatErrorMsg(self.lineno, msg="BTRFS volume defined without any member devices. Either specify member devices or use --useexisting.")
+
+ # allow creating btrfs vols/subvols without specifying mountpoint
+ if self.mountpoint in ("none", "None"):
+ self.mountpoint = ""
+
+ # Sanity check mountpoint
+ if self.mountpoint != "" and self.mountpoint[0] != '/':
+ raise KickstartValueError, formatErrorMsg(self.lineno, msg="The mount point \"%s\" is not valid." % (self.mountpoint,))
+
+ if self.preexist:
+ device = devicetree.getDeviceByName(self.name)
+ if not device:
+ device = udev_resolve_devspec(self.name)
+
+ if not device:
+ raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent BTRFS volume %s in btrfs command" % self.name)
+ else:
+ # If a previous device has claimed this mount point, delete the
+ # old one.
+ try:
+ if self.mountpoint:
+ device = storage.mountpoints[self.mountpoint]
+ storage.destroyDevice(device)
+ except KeyError:
+ pass
+
+ request = storage.newBTRFS(name=name,
+ subvol=self.subvol,
+ mountpoint=self.mountpoint,
+ metaDataLevel=self.metaDataLevel,
+ dataLevel=self.dataLevel,
+ parents=members)
+
+ storage.createDevice(request)
+
+ self.anaconda.dispatch.skip_steps("partition", "parttype")
+
+class ClearPart(commands.clearpart.F17_ClearPart):
def parse(self, args):
- retval = commands.clearpart.FC3_ClearPart.parse(self, args)
+ retval = commands.clearpart.F17_ClearPart.parse(self, args)
if self.type is None:
self.type = CLEARPART_TYPE_NONE
@@ -317,11 +394,25 @@ class ClearPart(commands.clearpart.FC3_ClearPart):
self.drives = drives
+ # Do any glob expansion now, since we need to have the real list of
+ # devices available before the execute methods run.
+ devices = []
+ for spec in self.devices:
+ matched = deviceMatches(spec)
+ if matched:
+ devices.extend(matched)
+ else:
+ raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent device %s in clearpart device list" % spec)
+
+ self.devices = devices
+
return retval
def execute(self, storage, ksdata, instClass):
storage.config.clearPartType = self.type
storage.config.clearPartDisks = self.drives
+ storage.config.clearPartDevices = self.devices
+
if self.initAll:
storage.config.reinitializeDisks = self.initAll
@@ -334,7 +425,7 @@ class Fcoe(commands.fcoe.F13_Fcoe):
if fc.nic not in isys.getDeviceProperties():
raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent nic %s in fcoe command" % fc.nic)
- storage.fcoe.fcoe().addSan(nic=fc.nic, dcb=fc.dcb)
+ storage.fcoe.fcoe().addSan(nic=fc.nic, dcb=fc.dcb, auto_vlan=True)
return fc
@@ -365,36 +456,32 @@ class IgnoreDisk(commands.ignoredisk.RHEL6_IgnoreDisk):
return retval
-class Iscsi(commands.iscsi.F10_Iscsi):
- class Login(object):
- def __init__(self, iscsi_obj, tg_data):
- self.iscsi_obj = iscsi_obj
- self.tg_data = tg_data
-
- def login(self, node):
- if self.tg_data.target and self.tg_data.target != node.name:
- log.debug("kickstart: skipping logging to iscsi node '%s'" %
- node.name)
- return False
- (rc, _) = self.iscsi_obj.log_into_node(
- node, self.tg_data.user, self.tg_data.password,
- self.tg_data.user_in, self.tg_data.password_in)
- return rc
-
+class Iscsi(commands.iscsi.F17_Iscsi):
def parse(self, args):
- tg = commands.iscsi.F10_Iscsi.parse(self, args)
+ tg = commands.iscsi.F17_Iscsi.parse(self, args)
+
+ if tg.iface:
+ active_ifaces = network.getActiveNetDevs()
+ if tg.iface not in active_ifaces:
+ raise KickstartValueError, formatErrorMsg(self.lineno, msg="network interface %s required by iscsi %s target is not up" % (tg.iface, tg.target))
+
+ mode = storage.iscsi.iscsi().mode
+ if mode == "none":
+ if tg.iface:
+ storage.iscsi.iscsi().create_interfaces(active_ifaces)
+ elif ((mode == "bind" and not tg.iface)
+ or (mode == "default" and tg.iface)):
+ raise KickstartValueError, formatErrorMsg(self.lineno, msg="iscsi --iface must be specified (binding used) either for all targets or for none")
try:
- iscsi_obj = storage.iscsi.iscsi()
- discovered_nodes = iscsi_obj.discover(
- tg.ipaddr, tg.port, tg.user, tg.password,
- tg.user_in, tg.password_in)
- login = self.Login(iscsi_obj, tg)
- logged_into_nodes = filter(login.login, discovered_nodes)
- if len(logged_into_nodes) < 1:
- msg = _("Could not log into any iSCSI nodes at the portal.")
- raise KickstartValueError, formatErrorMsg(self.lineno,
- msg=msg)
+ storage.iscsi.iscsi().addTarget(tg.ipaddr, tg.port, tg.user,
+ tg.password, tg.user_in,
+ tg.password_in,
+ target=tg.target,
+ iface=tg.iface)
+ log.info("added iscsi target %s at %s via %s" %(tg.target,
+ tg.ipaddr,
+ tg.iface))
except (IOError, ValueError) as e:
raise KickstartValueError, formatErrorMsg(self.lineno,
msg=str(e))
@@ -408,7 +495,7 @@ class IscsiName(commands.iscsiname.FC6_IscsiName):
storage.iscsi.iscsi().initiator = self.iscsiname
return retval
-class LogVolData(commands.logvol.F15_LogVolData):
+class LogVolData(commands.logvol.F17_LogVolData):
def execute(self):
storage = self.anaconda.storage
devicetree = storage.devicetree
@@ -446,6 +533,23 @@ class LogVolData(commands.logvol.F15_LogVolData):
if not dev:
raise KickstartValueError, formatErrorMsg(self.lineno, msg="No preexisting logical volume with the name \"%s\" was found." % self.name)
+ if self.resize:
+ if self.size < dev.currentSize:
+ # shrink
+ try:
+ devicetree.registerAction(ActionResizeFormat(dev, self.size))
+ devicetree.registerAction(ActionResizeDevice(dev, self.size))
+ except ValueError:
+ raise KickstartValueError(formatErrorMsg(self.lineno,
+ msg="Invalid target size (%d) for device %s" % (self.size, dev.name)))
+ else:
+ # grow
+ try:
+ devicetree.registerAction(ActionResizeDevice(dev, self.size))
+ devicetree.registerAction(ActionResizeFormat(dev, self.size))
+ except ValueError:
+ raise KickstartValueError(formatErrorMsg(self.lineno,
+ msg="Invalid target size (%d) for device %s" % (self.size, dev.name)))
dev.format.mountpoint = self.mountpoint
dev.format.mountopts = self.fsopts
self.anaconda.dispatch.skip_steps("partition", "parttype")
@@ -472,7 +576,7 @@ class LogVolData(commands.logvol.F15_LogVolData):
label=self.label,
fsprofile=self.fsprofile,
mountopts=self.fsopts)
- if not format:
+ if not format.type:
raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % type)
# If we were given a pre-existing LV to create a filesystem on, we need
@@ -485,6 +589,14 @@ class LogVolData(commands.logvol.F15_LogVolData):
raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent LV %s in logvol command" % self.name)
removeExistingFormat(device, storage)
+
+ if self.resize:
+ try:
+ devicetree.registerAction(ActionResizeDevice(device, self.size))
+ except ValueError:
+ raise KickstartValueError(formatErrorMsg(self.lineno,
+ msg="Invalid target size (%d) for device %s" % (self.size, device.name)))
+
devicetree.registerAction(ActionCreateFormat(device, format))
else:
# If a previous device has claimed this mount point, delete the
@@ -581,7 +693,15 @@ class NetworkData(commands.network.F16_NetworkData):
else:
if self.device.lower() == "ibft":
return
- if self.device.lower() == "bootif":
+ if self.device.lower() == "link":
+ for dev in sorted(devices):
+ if isys.getLinkStatus(dev):
+ device = dev
+ break
+ else:
+ raise KickstartValueError, formatErrorMsg(self.lineno, msg="No device with link found")
+
+ elif self.device.lower() == "bootif":
if "BOOTIF" in flags.cmdline:
# MAC address like 01-aa-bb-cc-dd-ee-ff
device = flags.cmdline["BOOTIF"][3:]
@@ -671,7 +791,7 @@ class DmRaid(commands.dmraid.FC6_DmRaid):
def parse(self, args):
raise NotImplementedError("The dmraid kickstart command is not currently supported")
-class PartitionData(commands.partition.F12_PartData):
+class PartitionData(commands.partition.F17_PartData):
def execute(self):
storage = self.anaconda.storage
devicetree = storage.devicetree
@@ -681,7 +801,7 @@ class PartitionData(commands.partition.F12_PartData):
if self.onbiosdisk != "":
for (disk, biosdisk) in storage.eddDict.iteritems():
- if str(biosdisk) == self.onbiosdisk:
+ if "%x" % biosdisk == self.onbiosdisk:
self.disk = disk
break
@@ -703,17 +823,14 @@ class PartitionData(commands.partition.F12_PartData):
else:
type = storage.defaultFSType
elif self.mountpoint == 'appleboot':
- type = "Apple Bootstrap"
+ type = "appleboot"
self.mountpoint = ""
- kwargs["weight"] = self.anaconda.platform.weight(fstype="appleboot")
elif self.mountpoint == 'prepboot':
- type = "PPC PReP Boot"
+ type = "prepboot"
self.mountpoint = ""
- kwargs["weight"] = self.anaconda.platform.weight(fstype="prepboot")
elif self.mountpoint == 'biosboot':
type = "biosboot"
self.mountpoint = ""
- kwargs["weight"] = self.anaconda.platform.weight(fstype="biosboot")
elif self.mountpoint.startswith("raid."):
type = "mdmember"
kwargs["name"] = self.mountpoint
@@ -736,10 +853,23 @@ class PartitionData(commands.partition.F12_PartData):
if self.onPart:
self.anaconda.ksdata.onPart[kwargs["name"]] = self.onPart
self.mountpoint = ""
+ elif self.mountpoint.startswith("btrfs."):
+ type = "btrfs"
+ kwargs["name"] = self.mountpoint
+
+ if devicetree.getDeviceByName(kwargs["name"]):
+ raise KickstartValueError, formatErrorMsg(self.lineno, msg="BTRFS partition defined multiple times")
+
+ # store "btrfs." alias for other ks partitioning commands
+ if self.onPart:
+ self.anaconda.ksdata.onPart[kwargs["name"]] = self.onPart
+ self.mountpoint = ""
elif self.mountpoint == "/boot/efi":
- type = "EFI System Partition"
- self.fsopts = "defaults,uid=0,gid=0,umask=0077,shortname=winnt"
- kwargs["weight"] = self.anaconda.platform.weight(fstype="efi")
+ if iutil.isMactel():
+ type = "hfs+"
+ else:
+ type = "EFI System Partition"
+ self.fsopts = "defaults,uid=0,gid=0,umask=0077,shortname=winnt"
else:
if self.fstype != "":
type = self.fstype
@@ -758,6 +888,23 @@ class PartitionData(commands.partition.F12_PartData):
if not dev:
raise KickstartValueError, formatErrorMsg(self.lineno, msg="No preexisting partition with the name \"%s\" was found." % self.onPart)
+ if self.resize:
+ if self.size < dev.currentSize:
+ # shrink
+ try:
+ devicetree.registerAction(ActionResizeFormat(dev, self.size))
+ devicetree.registerAction(ActionResizeDevice(dev, self.size))
+ except ValueError:
+ raise KickstartValueError(formatErrorMsg(self.lineno,
+ msg="Invalid target size (%d) for device %s" % (self.size, dev.name)))
+ else:
+ # grow
+ try:
+ devicetree.registerAction(ActionResizeDevice(dev, self.size))
+ devicetree.registerAction(ActionResizeFormat(dev, self.size))
+ except ValueError:
+ raise KickstartValueError(formatErrorMsg(self.lineno,
+ msg="Invalid target size (%d) for device %s" % (self.size, dev.name)))
dev.format.mountpoint = self.mountpoint
dev.format.mountopts = self.fsopts
self.anaconda.dispatch.skip_steps("partition", "parttype")
@@ -769,7 +916,7 @@ class PartitionData(commands.partition.F12_PartData):
label=self.label,
fsprofile=self.fsprofile,
mountopts=self.fsopts)
- if not kwargs["format"]:
+ if not kwargs["format"].type:
raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % type)
# If we were given a specific disk to create the partition on, verify
@@ -792,7 +939,8 @@ class PartitionData(commands.partition.F12_PartData):
should_clear = shouldClear(disk,
storage.config.clearPartType,
- storage.config.clearPartDisks)
+ storage.config.clearPartDisks,
+ storage.config.clearPartDevices)
if disk and (disk.partitioned or should_clear):
kwargs["disks"] = [disk]
break
@@ -817,6 +965,13 @@ class PartitionData(commands.partition.F12_PartData):
raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent partition %s in partition command" % self.onPart)
removeExistingFormat(device, storage)
+ if self.resize:
+ try:
+ devicetree.registerAction(ActionResizeDevice(device, self.size))
+ except ValueError:
+ raise KickstartValueError(formatErrorMsg(self.lineno,
+ msg="Invalid target size (%d) for device %s" % (self.size, device.name)))
+
devicetree.registerAction(ActionCreateFormat(device, kwargs["format"]))
else:
# If a previous device has claimed this mount point, delete the
@@ -828,6 +983,13 @@ class PartitionData(commands.partition.F12_PartData):
except KeyError:
pass
+ if "format" in kwargs:
+ # set weight based on fstype and mountpoint
+ mpt = getattr(kwargs["format"], "mountpoint", None)
+ fstype = kwargs["format"].type
+ kwargs["weight"] = self.anaconda.platform.weight(fstype=fstype,
+ mountpoint=mpt)
+
request = storage.newPartition(**kwargs)
storage.createDevice(request)
@@ -880,6 +1042,15 @@ class RaidData(commands.raid.F15_RaidData):
raise KickstartValueError, formatErrorMsg(self.lineno, msg="PV partition defined multiple times")
self.mountpoint = ""
+ elif self.mountpoint.startswith("btrfs."):
+ type = "btrfs"
+ kwargs["name"] = self.mountpoint
+ self.anaconda.ksdata.onPart[kwargs["name"]] = devicename
+
+ if devicetree.getDeviceByName(kwargs["name"]):
+ raise KickstartValueError, formatErrorMsg(self.lineno, msg="BTRFS partition defined multiple times")
+
+ self.mountpoint = ""
else:
if self.fstype != "":
type = self.fstype
@@ -929,7 +1100,7 @@ class RaidData(commands.raid.F15_RaidData):
fsprofile=self.fsprofile,
mountpoint=self.mountpoint,
mountopts=self.fsopts)
- if not kwargs["format"]:
+ if not kwargs["format"].type:
raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % type)
kwargs["name"] = devicename
@@ -1021,7 +1192,7 @@ class Timezone(commands.timezone.F18_Timezone):
ntp.save_servers_to_config(self.ntpservers,
conf_file_path=chronyd_conf_path)
-class VolGroupData(commands.volgroup.FC3_VolGroupData):
+class VolGroupData(commands.volgroup.FC16_VolGroupData):
def execute(self):
pvs = []
@@ -1120,6 +1291,7 @@ commandMap = {
}
dataMap = {
+ "BTRFSData": BTRFSData,
"LogVolData": LogVolData,
"NetworkData": NetworkData,
"PartData": PartitionData,
@@ -1219,7 +1391,7 @@ def preScriptPass(anaconda, f):
runPreScripts(ksparser.handler.scripts)
def parseKickstart(anaconda, f):
- # preprocessing the kickstart file has already been handled by loader.
+ # preprocessing the kickstart file has already been handled in initramfs.
handler = AnacondaKSHandler(anaconda)
ksparser = AnacondaKSParser(handler)
@@ -1316,7 +1488,12 @@ def selectPackages(ksdata, payload):
ksdata.packages.groupList.insert(0, Group("Core"))
if ksdata.packages.addBase:
- ksdata.packages.groupList.insert(1, Group("Base"))
+ # Only add @base if it's not already in the group list. If the
+ # %packages section contains something like "@base --optional",
+ # addBase will take effect first and yum will think the group is
+ # already selected.
+ if not Group("Base") in ksdata.packages.groupList:
+ ksdata.packages.groupList.insert(1, Group("Base"))
else:
log.warning("not adding Base group")
diff --git a/pyanaconda/language.py b/pyanaconda/language.py
index 4968df0ed..f1663d749 100644
--- a/pyanaconda/language.py
+++ b/pyanaconda/language.py
@@ -21,12 +21,11 @@
#
import os
-import re
import string
import locale
import gettext
-from pyanaconda.constants import ROOT_PATH
+from pyanaconda.constants import ROOT_PATH, DEFAULT_LANG
import localeinfo
from simpleconfig import SimpleConfigFile
import system_config_keyboard.keyboard as keyboard
@@ -34,11 +33,6 @@ import system_config_keyboard.keyboard as keyboard
import logging
log = logging.getLogger("anaconda")
-def langComponents(astring):
- pattern = re.compile("(?P<language>[A-Za-z]+)(_(?P<territory>[A-Za-z]+))?(\.(?P<codeset>[-\w]+))?(@(?P<modifier>[-\w]+))?")
- m = pattern.match(astring)
- return m.groupdict()
-
class Language(object):
def _setInstLang(self, value):
# Always store in its full form so we know what we're comparing with.
@@ -51,7 +45,7 @@ class Language(object):
self._instLang = value
# If we're running in text mode, value may not be a supported language
- # to display. We need to default to en_US.UTF-8 for now.
+ # to display. Fall back to the default for now.
if self.displayMode == 't':
for (lang, info) in self.localeInfo.iteritems():
if lang == self._instLang and info[2] == "False":
@@ -102,10 +96,10 @@ class Language(object):
if not self.localeInfo.has_key(self._systemLang):
return
- if self.localeInfo[self._systemLang][2] == "none":
+ if self.localeInfo[self._systemLang][2] == "False":
self.info["SYSFONT"] = None
else:
- self.info["SYSFONT"] = self.localeInfo[self._systemLang][2]
+ self.info["SYSFONT"] = "latarcyrheb-sun16"
# XXX hack - because of exceptional cases on the var - zh_CN.GB2312
if self._systemLang == "zh_CN.GB18030":
@@ -117,7 +111,7 @@ class Language(object):
systemLang = property(lambda s: s._systemLang, lambda s, v: s._setSystemLang(v))
def __init__ (self, display_mode = 'g'):
- self._default = "en_US.UTF-8"
+ self._default = DEFAULT_LANG
self.displayMode = display_mode
self.info = {}
self.nativeLangNames = {}
@@ -159,18 +153,6 @@ class Language(object):
def available(self):
return self.nativeLangNames.keys()
- def buildLocale(self):
- import iutil
-
- c = langComponents(self._instLang)
- locale_p = c["language"]
- if c["territory"]:
- locale_p += "_" + c["territory"]
- if c["modifier"]:
- locale_p += "@" + c["modifier"]
-
- iutil.execWithRedirect("localedef", ["-i", locale_p, "-f", c["codeset"] or "UTF-8", self._instLang])
-
def dracutSetupArgs(self):
args=set()
diff --git a/pyanaconda/livecd.py b/pyanaconda/livecd.py
index 0c03d38c4..cb1166aab 100644
--- a/pyanaconda/livecd.py
+++ b/pyanaconda/livecd.py
@@ -199,6 +199,11 @@ class LiveCDCopyBackend(backend.AnacondaBackend):
# resize rootfs first, since it is 100% full due to genMinInstDelta
rootDevice = anaconda.storage.rootDevice
rootDevice.setup()
+
+ # This is a workaround to make sure the m_time it set to current time so that
+ # the e2fsck before resizing doesn't get confused by times in the future
+ rootDevice.format.testMount()
+
rootDevice.format.targetSize = rootDevice.size
rootDevice.format.doResize(intf=anaconda.intf)
diff --git a/pyanaconda/network.py b/pyanaconda/network.py
index 7e4796fef..859c36818 100644
--- a/pyanaconda/network.py
+++ b/pyanaconda/network.py
@@ -49,6 +49,7 @@ log = logging.getLogger("anaconda")
sysconfigDir = "/etc/sysconfig"
netscriptsDir = "%s/network-scripts" % (sysconfigDir)
networkConfFile = "%s/network" % (sysconfigDir)
+ipv6ConfFile = "/etc/modprobe.d/ipv6.conf"
ifcfgLogFile = "/tmp/ifcfg.log"
CONNECTION_TIMEOUT = 45
@@ -113,7 +114,7 @@ def getDefaultHostname(anaconda):
hn = hinfo[0]
break
- if hn and hn != 'localhost' and hn != 'localhost.localdomain':
+ if hn and hn not in ('(none)', 'localhost', 'localhost.localdomain'):
return hn
try:
@@ -121,10 +122,10 @@ def getDefaultHostname(anaconda):
except:
hn = None
- if not hn or hn == '(none)' or hn == 'localhost' or hn == 'localhost.localdomain':
+ if not hn or hn in ('(none)', 'localhost', 'localhost.localdomain'):
hn = socket.gethostname()
- if not hn or hn == '(none)' or hn == 'localhost':
+ if not hn or hn in ('(none)', 'localhost'):
hn = 'localhost.localdomain'
return hn
@@ -241,6 +242,28 @@ class NetworkDevice(IfcfgFile):
return s
+ # anaconda doesn't actually need this configuration, but if we don't write
+ # it to the installed system then 'ifup' doesn't work after install.
+ # FIXME: make 'ifup' use its own defaults!
+ def setDefaultConfig(self):
+ ifcfglog.debug("NetworkDevice %s: setDefaultConfig()" % self.iface)
+ self.set(("DEVICE", self.iface),
+ ("BOOTPROTO", "dhcp"),
+ ("ONBOOT", "no")) # for "security", or something
+
+ try:
+ mac = open("/sys/class/net/%s/address" % self.iface).read().strip()
+ self.set(("HWADDR", mac.upper()))
+ except IOError as e:
+ ifcfglog.warning("HWADDR: %s" % str(e))
+
+ try:
+ uuid = open("/proc/sys/kernel/random/uuid").read().strip()
+ self.set(("UUID", uuid))
+ except IOError as e:
+ ifcfglog.warning("UUID: %s" % str(e))
+
+ self.writeIfcfgFile()
def loadIfcfgFile(self):
ifcfglog.debug("%s:\n%s" % (self.path, self.fileContent()))
@@ -310,6 +333,8 @@ class NetworkDevice(IfcfgFile):
shutil.move(newifcfg, keyfile)
def fileContent(self):
+ if not os.path.exists(self.path):
+ return ""
f = open(self.path, 'r')
content = f.read()
f.close()
@@ -318,7 +343,7 @@ class NetworkDevice(IfcfgFile):
def usedByFCoE(self, anaconda):
import storage
for d in anaconda.storage.devices:
- if (isinstance(d, storage.devices.NetworkStorageDevice) and
+ if (isinstance(d, storage.devices.FcoeDiskDevice) and
d.nic == self.iface):
return True
return False
@@ -327,20 +352,14 @@ class NetworkDevice(IfcfgFile):
import storage
rootdev = anaconda.storage.rootDevice
for d in anaconda.storage.devices:
- if (isinstance(d, storage.devices.NetworkStorageDevice) and
- d.host_address and
+ if (isinstance(d, storage.devices.iScsiDiskDevice) and
rootdev.dependsOn(d)):
- if self.iface == ifaceForHostIP(d.host_address):
+ if d.nic == "default":
+ if self.iface == ifaceForHostIP(d.host_address):
+ return True
+ elif d.nic == self.iface:
return True
- return False
- def usedByISCSI(self, anaconda):
- import storage
- for d in anaconda.storage.devices:
- if (isinstance(d, storage.devices.NetworkStorageDevice) and
- d.host_address):
- if self.iface == ifaceForHostIP(d.host_address):
- return True
return False
class WirelessNetworkDevice(NetworkDevice):
@@ -415,9 +434,7 @@ class Network:
if os.access(device.path, os.R_OK):
device.loadIfcfgFile()
else:
- log.info("Network.update(): %s file not found" %
- device.path)
- continue
+ device.setDefaultConfig()
# TODORV - the last iface in loop wins, might be ok,
# not worthy of special juggling
@@ -434,7 +451,8 @@ class Network:
bootif_mac = None
if ksdevice == 'bootif' and "BOOTIF" in flags.cmdline:
bootif_mac = flags.cmdline["BOOTIF"][3:].replace("-", ":").upper()
- for dev in self.netdevices:
+ # sort for ksdevice=link (to select the same device as in initrd))
+ for dev in sorted(self.netdevices):
mac = self.netdevices[dev].get('HWADDR').upper()
if ksdevice == 'link' and isys.getLinkStatus(dev):
self.ksdevice = dev
@@ -467,6 +485,10 @@ class Network:
def setHostname(self, hn):
self.hostname = hn
+ if flags.imageInstall:
+ log.info("image install -- not setting hostname")
+ return
+
log.info("setting installation environment hostname to %s" % hn)
iutil.execWithRedirect("hostname", ["-v", hn ],
stdout="/dev/tty5", stderr="/dev/tty5")
@@ -656,6 +678,9 @@ class Network:
self._copyFileToPath("/etc/udev/rules.d/70-persistent-net.rules",
ROOT_PATH, overwrite=flags.livecdInstall)
+ self._copyFileToPath(ipv6ConfFile, ROOT_PATH,
+ overwrite=flags.livecdInstall)
+
def disableNMForStorageDevices(self, anaconda):
for devName, device in self.netdevices.items():
if (device.usedByFCoE(anaconda) or
@@ -671,6 +696,20 @@ class Network:
log.warning("disableNMForStorageDevices: %s file not found" %
device.path)
+ def autostartFCoEDevices(self, anaconda):
+ for devName, device in self.netdevices.items():
+ if device.usedByFCoE(anaconda):
+ dev = NetworkDevice(ROOT_PATH + netscriptsDir, devName)
+ if os.access(dev.path, os.R_OK):
+ dev.loadIfcfgFile()
+ dev.set(('ONBOOT', 'yes'))
+ dev.writeIfcfgFile()
+ log.debug("setting ONBOOT=yes for network device %s used by fcoe"
+ % device.path)
+ else:
+ log.warning("autoconnectFCoEDevices: %s file not found" %
+ device.path)
+
def write(self):
ifcfglog.debug("Network.write() called")
@@ -716,6 +755,19 @@ class Network:
# /etc/resolv.conf is managed by NM
+ # disable ipv6
+ if ('noipv6' in flags.cmdline
+ and not [dev for dev in devices
+ if dev.get('IPV6INIT') == "yes"]):
+ if os.path.exists(ipv6ConfFile):
+ log.warning('Not disabling ipv6, %s exists' % ipv6ConfFile)
+ else:
+ log.info('Disabling ipv6 on target system')
+ f = open(ipv6ConfFile, "w")
+ f.write("# Anaconda disabling ipv6\n")
+ f.write("options ipv6 disable=1\n")
+ f.close()
+
def waitForDevicesActivation(self, devices):
waited_devs_props = {}
@@ -779,14 +831,12 @@ class Network:
def dracutSetupArgs(self, networkStorageDevice):
netargs=set()
- if networkStorageDevice.nic:
- # Storage bound to a specific nic (ie FCoE)
- nic = networkStorageDevice.nic
- else:
- # Storage bound through ip, find out which interface leads to host
+ if networkStorageDevice.nic == "default":
nic = ifaceForHostIP(networkStorageDevice.host_address)
if not nic:
return ""
+ else:
+ nic = networkStorageDevice.nic
if nic not in self.netdevices.keys():
log.error('Unknown network interface: %s' % nic)
diff --git a/pyanaconda/partIntfHelpers.py b/pyanaconda/partIntfHelpers.py
index 6dbfad9ef..e26f9b7b5 100644
--- a/pyanaconda/partIntfHelpers.py
+++ b/pyanaconda/partIntfHelpers.py
@@ -408,11 +408,12 @@ class iSCSIWizard():
pass
@abstractmethod
- def display_nodes_dialog(self, found_nodes):
+ def display_nodes_dialog(self, found_nodes, ifaces):
pass
@abstractmethod
- def display_success_dialog(self, success_nodes, fail_nodes, fail_reason):
+ def display_success_dialog(self, success_nodes, fail_nodes, fail_reason,
+ ifaces):
pass
@abstractmethod
@@ -463,16 +464,15 @@ def drive_iscsi_addition(anaconda, wizard):
discovery_dict = wizard.get_discovery_dict()
discovery_dict["intf"] = anaconda.intf
found_nodes = anaconda.storage.iscsi.discover(**discovery_dict)
- map(lambda node: log.debug("discovered iSCSI node: %s" % node.name),
- found_nodes)
step = STEP_NODES
elif step == STEP_NODES:
- if len(found_nodes) < 1:
- log.debug("iscsi: no new iscsi nodes discovered")
+ if not found_nodes:
+ log.debug("iscsi: no iSCSI nodes to log in")
anaconda.intf.messageWindow(_("iSCSI Nodes"),
- _("No new iSCSI nodes discovered"))
+ _("No iSCSI nodes to log in"))
break
- (rc, selected_nodes) = wizard.display_nodes_dialog(found_nodes)
+ (rc, selected_nodes) = wizard.display_nodes_dialog(found_nodes,
+ anaconda.id.storage.iscsi.ifaces)
if not rc or len(selected_nodes) == 0:
break
step = STEP_LOGIN
@@ -498,7 +498,8 @@ def drive_iscsi_addition(anaconda, wizard):
elif step == STEP_SUMMARY:
rc = wizard.display_success_dialog(login_ok_nodes,
login_fail_nodes,
- login_fail_msg)
+ login_fail_msg,
+ anaconda.id.storage.iscsi.ifaces)
if rc:
step = STEP_STABILIZE
else:
diff --git a/pyanaconda/platform.py b/pyanaconda/platform.py
index 0889cdbeb..9abf0f57a 100644
--- a/pyanaconda/platform.py
+++ b/pyanaconda/platform.py
@@ -19,11 +19,15 @@
#
# Authors: Chris Lumens <clumens@redhat.com>
#
+import os
+import logging
+log = logging.getLogger("anaconda")
import parted
from pyanaconda import bootloader
from pyanaconda.storage.devicelibs import mdraid
+from pyanaconda.constants import DMI_CHASSIS_VENDOR
import iutil
from flags import flags
@@ -65,9 +69,10 @@ class Platform(object):
returned by getPlatform below. Not all subclasses need to provide
all the methods in this class."""
- if flags.nogpt and "gpt" in self._disklabel_types and \
- len(self._disklabel_types) > 1:
+ if flags.gpt and "gpt" in self._disklabel_types:
+ # move GPT to the top of the list
self._disklabel_types.remove("gpt")
+ self._disklabel_types.insert(0, "gpt")
@property
def diskLabelTypes(self):
@@ -101,14 +106,20 @@ class Platform(object):
# if there's a required type for this device type, use that
labelType = self.requiredDiskLabelType(device.partedDevice.type)
+ log.debug("required disklabel type for %s (%s) is %s"
+ % (device.name, device.partedDevice.type, labelType))
if not labelType:
# otherwise, use the first supported type for this platform
# that is large enough to address the whole device
labelType = self.defaultDiskLabelType
+ log.debug("default disklabel type for %s is %s" % (device.name,
+ labelType))
for lt in self.diskLabelTypes:
l = parted.freshDisk(device=device.partedDevice, ty=lt)
- if l.maxPartitionStartSector < device.partedDevice.length:
+ if l.maxPartitionStartSector > device.partedDevice.length:
labelType = lt
+ log.debug("selecting %s disklabel for %s based on size"
+ % (labelType, device.name))
break
return labelType
@@ -156,7 +167,6 @@ class X86(Platform):
def __init__(self):
super(X86, self).__init__()
- self.blackListGPT()
def setDefaultPartitioning(self):
"""Return the default platform-specific partitioning information."""
@@ -175,21 +185,11 @@ class X86(Platform):
else:
return 0
- def blackListGPT(self):
- buf = iutil.execWithCapture("dmidecode",
- ["-s", "chassis-manufacturer"],
- stderr="/dev/tty5")
- if "LENOVO" in buf.splitlines() and "gpt" in self._disklabel_types:
- self._disklabel_types.remove("gpt")
-
class EFI(Platform):
bootloaderClass = bootloader.EFIGRUB
_boot_stage1_format_types = ["efi"]
- _boot_stage1_device_types = ["partition", "mdarray"]
- _boot_stage1_raid_levels = [mdraid.RAID1]
- _boot_stage1_raid_metadata = ["1.0"]
- _boot_stage1_raid_member_types = ["partition"]
+ _boot_stage1_device_types = ["partition"]
_boot_stage1_mountpoints = ["/boot/efi"]
_boot_efi_description = N_("EFI System Partition")
_boot_descriptions = {"partition": _boot_efi_description,
@@ -203,6 +203,7 @@ class EFI(Platform):
from storage.partspec import PartSpec
ret = Platform.setDefaultPartitioning(self)
ret.append(PartSpec(mountpoint="/boot/efi", fstype="efi", size=20,
+ maxSize=200,
grow=True, weight=self.weight(fstype="efi")))
return ret
@@ -215,9 +216,25 @@ class EFI(Platform):
else:
return 0
+class MacEFI(EFI):
+ _bootloaderClass = bootloader.MacEFIGRUB
+
+ _boot_stage1_format_types = ["hfs+"]
+ _boot_efi_description = N_("Apple EFI Boot Partition")
+ _non_linux_format_types = ["hfs+"]
+ _packages = ["mactel-boot"]
+
+ def setDefaultPartitioning(self):
+ from storage.partspec import PartSpec
+ ret = Platform.setDefaultPartitioning(self)
+ ret.append(PartSpec(mountpoint="/boot/efi", fstype="hfs+", size=20,
+ maxSize=200,
+ grow=True, weight=self.weight(mountpoint="/boot/efi")))
+ return ret
+
class PPC(Platform):
_ppcMachine = iutil.getPPCMachine()
- bootloaderClass = bootloader.Yaboot
+ _bootloaderClass = bootloader.GRUB2
_boot_stage1_device_types = ["partition"]
@property
@@ -225,7 +242,7 @@ class PPC(Platform):
return self._ppcMachine
class IPSeriesPPC(PPC):
- bootloaderClass = bootloader.IPSeriesYaboot
+ _bootloaderClass = bootloader.IPSeriesGRUB2
_boot_stage1_format_types = ["prepboot"]
_boot_stage1_max_end_mb = 10
_boot_prep_description = N_("PReP Boot Partition")
@@ -280,6 +297,11 @@ class S390(Platform):
_packages = ["s390utils"]
_disklabel_types = ["msdos", "dasd"]
_boot_stage1_device_types = ["disk", "partition"]
+ _boot_dasd_description = N_("DASD")
+ _boot_zfcp_description = N_("zFCP")
+ _boot_descriptions = {"dasd": _boot_dasd_description,
+ "zfcp": _boot_zfcp_description,
+ "partition": Platform._boot_partition_description}
def __init__(self):
Platform.__init__(self)
@@ -288,12 +310,14 @@ class S390(Platform):
"""Return the default platform-specific partitioning information."""
from storage.partspec import PartSpec
return [PartSpec(mountpoint="/boot", size=500,
- weight=self.weight(mountpoint="/boot"), asVol=True,
+#TODO: need to pass this info in w/o using anaconda object
+# fstype=self.anaconda.storage.defaultBootFSType,
+ weight=self.weight(mountpoint="/boot"), lv=True,
singlePV=True)]
def requiredDiskLabelType(self, device_type):
"""The required disklabel type for the specified device type."""
- if device_type == "dasd":
+ if device_type == parted.DEVICE_DASD:
return "dasd"
return super(S390, self).requiredDiskLabelType(device_type)
@@ -312,6 +336,15 @@ class Sparc(Platform):
start /= long(1024 / disk.device.sectorSize)
return start+1
+class ARM(Platform):
+ _bootloaderClass = bootloader.GRUB2
+ _boot_stage1_device_types = ["disk"]
+ _boot_mbr_description = N_("Master Boot Record")
+ _boot_descriptions = {"disk": _boot_mbr_description,
+ "partition": Platform._boot_partition_description}
+
+ _disklabel_types = ["msdos"]
+
def getPlatform():
"""Check the architecture of the system and return an instance of a
Platform subclass to match. If the architecture could not be determined,
@@ -332,8 +365,13 @@ def getPlatform():
elif iutil.isSparc():
return Sparc()
elif iutil.isEfi():
- return EFI()
+ if iutil.isMactel():
+ return MacEFI()
+ else:
+ return EFI()
elif iutil.isX86():
return X86()
+ elif iutil.isARM():
+ return ARM()
else:
raise SystemError, "Could not determine system architecture."
diff --git a/pyanaconda/script.py b/pyanaconda/script.py
new file mode 100644
index 000000000..c4e0074f0
--- /dev/null
+++ b/pyanaconda/script.py
@@ -0,0 +1,55 @@
+#
+# script.py - non-interactive, script based anaconda interface
+#
+# Copyright (C) 2011
+# Red Hat, Inc. All rights reserved.
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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/>.
+#
+# Author(s): Brian C. Lane <bcl@redhat.com>
+#
+
+from installinterfacebase import InstallInterfaceBase
+import cmdline
+from cmdline import setupProgressDisplay
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("anaconda")
+
+stepToClasses = { "install" : "setupProgressDisplay",
+ "complete": "Finished" }
+
+class InstallInterface(cmdline.InstallInterface):
+ def enableNetwork(self):
+ # Assume we want networking
+ return True
+
+ def display_step(self, step):
+ if stepToClasses.has_key(step):
+ s = "nextWin = %s" % (stepToClasses[step],)
+ exec s
+ nextWin(self.anaconda)
+ else:
+ errtxt = _("In interactive step can't continue. (%s)" % (step,))
+ print(errtxt)
+ raise RuntimeError(errtxt)
+
+def Finished(anaconda):
+ """ Install is finished. Lets just exit.
+ """
+ return 0
+
diff --git a/pyanaconda/sshd.py b/pyanaconda/sshd.py
deleted file mode 100644
index b6c9372ed..000000000
--- a/pyanaconda/sshd.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# sshd.py
-# Configuring the sshd daemon from Anaconda.
-#
-# Copyright (C) 2009 Red Hat, Inc.
-#
-# This copyrighted material is made available to anyone wishing to use,
-# modify, copy, or redistribute it subject to the terms and conditions of
-# the GNU General Public License v.2, or (at your option) any later version.
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
-# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
-# source code or documentation are not subject to the GNU General Public
-# License and may only be used or replicated with the express permission of
-# Red Hat, Inc.
-#
-
-
-import logging
-import os, sys
-log = logging.getLogger("anaconda")
-
-import iutil
-import users
-from flags import flags
-from constants import ROOT_PATH
-
-def createSshKey(algorithm, keyfile):
- path = '/etc/ssh/%s' % (keyfile,)
- argv = ['-q','-t',algorithm,'-f',path,'-C','','-N','']
- if os.access(path, os.R_OK):
- return
- log.debug("running \"%s\"" % (" ".join(['ssh-keygen']+argv),))
-
- so = "/tmp/ssh-keygen-%s-stdout.log" % (algorithm,)
- se = "/tmp/ssh-keygen-%s-stderr.log" % (algorithm,)
- iutil.execWithRedirect('ssh-keygen', argv, stdout=so, stderr=se)
-
-def doSshd(anaconda):
- if flags.sshd:
- # we need to have a libuser.conf that points to the installer root for
- # sshpw, but after that we start sshd, we need one that points to the
- # install target.
- luserConf = users.createLuserConf(instPath="")
- handleSshPw(anaconda)
- startSsh()
- del(os.environ["LIBUSER_CONF"])
- else:
- log.info("sshd: not enabled, skipping.")
-
- users.createLuserConf(ROOT_PATH)
-
-def handleSshPw(anaconda):
- if not anaconda.ksdata:
- return
-
- u = users.Users(anaconda)
-
- userdata = anaconda.ksdata.sshpw.dataList()
- for ud in userdata:
- if u.checkUserExists(ud.username, root="/"):
- u.setUserPassword(username=ud.username, password=ud.password,
- isCrypted=ud.isCrypted, lock=ud.lock)
- else:
- kwargs = ud.__dict__
- kwargs.update({"root": "/", "mkmailspool": False})
- u.createUser(ud.username, **kwargs)
-
- del u
-
-def startSsh():
- if iutil.isS390():
- return
-
- if not iutil.fork_orphan():
- os.open("/var/log/lastlog", os.O_RDWR | os.O_CREAT, 0644)
- ssh_keys = {
- 'rsa1':'ssh_host_key',
- 'rsa':'ssh_host_rsa_key',
- 'dsa':'ssh_host_dsa_key',
- }
- for (algorithm, keyfile) in ssh_keys.items():
- createSshKey(algorithm, keyfile)
- sshd = iutil.find_program_in_path("sshd")
- args = [sshd, "-f", "/etc/ssh/sshd_config.anaconda"]
- os.execv(sshd, args)
- sys.exit(1)
diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py
index 77d2ad6e7..4a3f63047 100644
--- a/pyanaconda/storage/__init__.py
+++ b/pyanaconda/storage/__init__.py
@@ -45,7 +45,6 @@ from deviceaction import *
from formats import getFormat
from formats import get_device_format_class
from formats import get_default_filesystem_type
-from devicelibs.lvm import safeLvmName
from devicelibs.dm import name_from_dm_node
from devicelibs.crypto import generateBackupPassphrase
from devicelibs.mpath import MultipathConfigWriter
@@ -317,6 +316,7 @@ class StorageDiscoveryConfig(object):
self.exclusiveDisks = []
self.clearPartType = None
self.clearPartDisks = []
+ self.clearPartDevices = []
self.reinitializeDisks = False
self.zeroMbr = None
self.protectedDevSpecs = []
@@ -368,7 +368,7 @@ class Storage(object):
self.doAutoPart = False
self.clearPartChoice = None
self.encryptedAutoPart = False
- self.lvmAutoPart = True
+ self.autoPartType = AUTOPART_TYPE_LVM
self.encryptionPassphrase = None
self.escrowCertificates = {}
self.autoPartEscrowCert = None
@@ -426,10 +426,24 @@ class Storage(object):
p.getFlag(parted.PARTITION_BOOT):
skip = True
break
+
+ # GPT labeled disks should only have bootable set on the
+ # EFI system partition (parted sets the EFI System GUID on
+ # GPT partitions with the boot flag)
+ if dev.disk.format.labelType == "gpt" and \
+ dev.format.type != "efi":
+ skip = True
+
if skip:
- log.info("not setting boot flag on %s as there is"
- "another active partition" % dev.name)
+ log.info("not setting boot flag on %s" % dev.name)
continue
+ # hfs+ partitions on gpt can't be marked bootable via
+ # parted
+ if dev.disk.format.partedDisk.type == "gpt" and \
+ dev.format.type == "hfs+":
+ log.info("not setting boot flag on hfs+ partition"
+ " %s" % dev.name)
+ continue
log.info("setting boot flag on %s" % dev.name)
dev.bootable = True
@@ -479,6 +493,10 @@ class Storage(object):
if self.data and self.data.upgrade.upgrade:
self.config.clearPartType = CLEARPART_TYPE_NONE
+ if self.dasd:
+ # Reset the internal dasd list (823534)
+ self.dasd.clear_device_list()
+
self.devicetree.reset(conf=self.config,
passphrase=self.encryptionPassphrase,
luksDict=self.__luksDevs,
@@ -686,6 +704,11 @@ class Storage(object):
return raidMinors
@property
+ def btrfsVolumes(self):
+ return sorted(self.devicetree.getDevicesByType("btrfs volume"),
+ key=lambda d: d.name)
+
+ @property
def swaps(self):
""" A list of the swap devices in the device tree.
@@ -758,6 +781,10 @@ class Storage(object):
return free
+ @property
+ def names(self):
+ return self.devicetree.names
+
def exceptionDisks(self):
""" Return a list of removable devices to save exceptions to.
@@ -927,9 +954,9 @@ class Storage(object):
hostname = nd.hostname
break
- name = self.createSuggestedVGName(hostname=hostname)
+ name = self.suggestContainerName(hostname=hostname, prefix="vg")
- if name in [d.name for d in self.devices]:
+ if name in self.names:
raise ValueError("name already in use")
return LVMVolumeGroupDevice(name, pvs, *args, **kwargs)
@@ -951,15 +978,69 @@ class Storage(object):
swap = True
else:
swap = False
- name = self.createSuggestedLVName(vg,
- swap=swap,
- mountpoint=mountpoint)
+ name = self.suggestDeviceName(prefix="lv",
+ parent=vg,
+ swap=swap,
+ mountpoint=mountpoint)
- if name in [d.name for d in self.devices]:
+ if name in self.names:
raise ValueError("name already in use")
return LVMLogicalVolumeDevice(name, vg, *args, **kwargs)
+ def newBTRFS(self, *args, **kwargs):
+ """ Return a new BTRFSVolumeDevice or BRFSSubVolumeDevice. """
+ log.debug("newBTRFS: args = %s ; kwargs = %s" % (args, kwargs))
+ name = kwargs.pop("name", None)
+ if args:
+ name = args[0]
+
+ mountpoint = kwargs.pop("mountpoint", None)
+ fmt_kwargs = {"mountpoint": mountpoint}
+
+ if kwargs.pop("subvol", False):
+ dev_class = BTRFSSubVolumeDevice
+ # make sure there's a valid parent device
+ parents = kwargs.get("parents", [])
+ if not parents or len(parents) != 1 or \
+ not isinstance(parents[0], BTRFSVolumeDevice):
+ raise ValueError("new btrfs subvols require a parent volume")
+
+ # set up the subvol name, using mountpoint if necessary
+ if not name:
+ # for btrfs this only needs to ensure the subvol name is not
+ # already in use within the parent volume
+ name = self.suggestDeviceName(mountpoint=mountpoint)
+ fmt_kwargs["mountopts"] = "subvol=%s" % name
+ kwargs.pop("metaDataLevel", None)
+ kwargs.pop("dataLevel", None)
+ else:
+ dev_class = BTRFSVolumeDevice
+ # set up the volume label, using hostname if necessary
+ if not name:
+ hostname = ""
+ if hasattr(self.anaconda, "network"):
+ hostname = self.anaconda.network.hostname
+
+ name = self.suggestContainerName(prefix="btrfs",
+ hostname=hostname)
+ #if name and name.startswith("btrfs_"):
+ # fmt_kwargs["label"] = name[6:]
+ #elif name and not name.startswith("btrfs"):
+ fmt_kwargs["label"] = name
+
+ # do we want to prevent a subvol with a name that matches another dev?
+ if name in self.names:
+ raise ValueError("name already in use")
+
+ # discard fmt_type since it's btrfs always
+ kwargs.pop("fmt_type", None)
+
+ # this is to avoid auto-scheduled format create actions
+ device = dev_class(name, **kwargs)
+ device.format = getFormat("btrfs", **fmt_kwargs)
+ return device
+
def createDevice(self, device):
""" Schedule creation of a device.
@@ -1029,71 +1110,96 @@ class Storage(object):
return True
return False
- def createSuggestedVGName(self, hostname=None):
- """ Return a reasonable, unused VG name. """
- # try to create a volume group name incorporating the hostname
+ def safeDeviceName(self, name):
+ """ Convert a device name to something safe and return that.
+
+ LVM limits lv names to 128 characters. I don't know the limits for
+ the other various device types, so I'm going to pick a number so
+ that we don't have to have an entire fucking library to determine
+ device name limits.
+ """
+ max_len = 96 # No, you don't need longer names than this. Really.
+ tmp = name.strip()
+ tmp = tmp.replace("/", "_")
+ tmp = re.sub("[^0-9a-zA-Z._-]", "", tmp)
+ tmp = tmp.lstrip("_")
+
+ if len(tmp) > max_len:
+ tmp = tmp[:max_len]
+
+ return tmp
+
+ def suggestContainerName(self, hostname=None, prefix=""):
+ """ Return a reasonable, unused device name. """
+ # try to create a device name incorporating the hostname
if hostname not in (None, "", 'localhost', 'localhost.localdomain'):
- template = "vg_%s" % (hostname.split('.')[0].lower(),)
- vgtemplate = safeLvmName(template)
+ template = "%s_%s" % (prefix, hostname.split('.')[0].lower())
+ template = self.safeDeviceName(template)
elif flags.imageInstall:
- vgtemplate = "vg_image"
- else:
- vgtemplate = "VolGroup"
-
- vgnames = [vg.name for vg in self.vgs]
- if vgtemplate not in vgnames and \
- vgtemplate not in lvm.lvm_vg_blacklist:
- return vgtemplate
+ template = "%s_image" % prefix
else:
- i = 0
- while 1:
- tmpname = "%s%02d" % (vgtemplate, i,)
- if not tmpname in vgnames and \
- tmpname not in lvm.lvm_vg_blacklist:
+ template = prefix
+
+ names = self.names
+ name = template
+ if name in names:
+ name = None
+ for i in range(100):
+ tmpname = "%s%02d" % (template, i,)
+ if tmpname not in names:
+ name = tmpname
break
- i += 1
- if i > 99:
- tmpname = ""
+ if not name:
+ log.error("failed to create device name based on prefix "
+ "'%s' and hostname '%s'" % (prefix, hostname))
+ raise RuntimeError("unable to find suitable device name")
- return tmpname
+ return name
- def createSuggestedLVName(self, vg, swap=None, mountpoint=None):
+ def suggestDeviceName(self, parent=None, swap=None,
+ mountpoint=None, prefix=""):
""" Return a suitable, unused name for a new logical volume. """
- # FIXME: this is not at all guaranteed to work
+ body = ""
if mountpoint:
- # try to incorporate the mountpoint into the name
- if mountpoint == '/':
- lvtemplate = 'lv_root'
+ if mountpoint == "/":
+ body = "_root"
else:
- if mountpoint.startswith("/"):
- template = "lv_%s" % mountpoint[1:]
+ body = mountpoint.replace("/", "_")
+ elif swap:
+ body = "_swap"
+
+ template = self.safeDeviceName(prefix + body)
+ names = self.names
+ name = template
+ def full_name(name, parent):
+ full = ""
+ if parent:
+ full = "%s-" % parent.name
+ full += name
+ return full
+
+ # also include names of any lvs in the parent for the case of the
+ # temporary vg in the lvm dialogs, which can contain lvs that are
+ # not yet in the devicetree and therefore not in self.names
+ if hasattr(parent, "lvs"):
+ names.extend([full_name(d.lvname, parent) for d in parent.lvs])
+
+ if full_name(name, parent) in names or not body:
+ for i in range(100):
+ name = "%s%02d" % (template, i)
+ if full_name(name, parent) not in names:
+ break
else:
- template = "lv_%s" % (mountpoint,)
+ name = ""
- lvtemplate = safeLvmName(template)
- else:
- if swap:
- if len([s for s in self.swaps if s in vg.lvs]):
- idx = len([s for s in self.swaps if s in vg.lvs])
- while True:
- lvtemplate = "lv_swap%02d" % idx
- if lvtemplate in [lv.lvname for lv in vg.lvs]:
- idx += 1
- else:
- break
- else:
- lvtemplate = "lv_swap"
- else:
- idx = len(vg.lvs)
- while True:
- lvtemplate = "LogVol%02d" % idx
- if lvtemplate in [l.lvname for l in vg.lvs]:
- idx += 1
- else:
- break
+ if not name:
+ log.error("failed to create device name based on parent '%s', "
+ "prefix '%s', mountpoint '%s', swap '%s'"
+ % (parent.name, prefix, mountpoint, swap))
+ raise RuntimeError("unable to find suitable device name")
- return lvtemplate
+ return name
def doEncryptionPassphraseRetrofits(self):
""" Add the global passphrase to all preexisting LUKS devices.
@@ -1844,7 +1950,9 @@ class FSSet(object):
elif ":" in devspec and fstype.startswith("nfs"):
# NFS -- preserve but otherwise ignore
device = NFSDevice(devspec,
+ exists=True,
format=getFormat(fstype,
+ exists=True,
device=devspec))
elif devspec.startswith("/") and fstype == "swap":
# swap file
@@ -1878,18 +1986,18 @@ class FSSet(object):
if devspec == "none" or \
isinstance(format, get_device_format_class("nodev")):
device = NoDevice(format=format)
- else:
- device = StorageDevice(devspec, format=format)
if device is None:
log.error("failed to resolve %s (%s) from fstab" % (devspec,
fstype))
raise UnrecognizedFSTabEntryError()
+ device.setup()
fmt = getFormat(fstype, device=device.path, exists=True)
if fstype != "auto" and None in (device.format.type, fmt.type):
log.info("Unrecognized filesystem type for %s (%s)"
% (device.name, fstype))
+ device.teardown()
raise UnrecognizedFSTabEntryError()
# make sure, if we're using a device from the tree, that
@@ -1902,6 +2010,7 @@ class FSSet(object):
# XXX we should probably disallow migration for this fs
device.format = fmt
else:
+ device.teardown()
raise FSTabTypeMismatchError("%s: detected as %s, fstab says %s"
% (mountpoint, dtype, ftype))
del ftype
diff --git a/pyanaconda/storage/dasd.py b/pyanaconda/storage/dasd.py
index 800ffe557..4f5fda217 100644
--- a/pyanaconda/storage/dasd.py
+++ b/pyanaconda/storage/dasd.py
@@ -26,6 +26,7 @@ from pyanaconda.storage.errors import DasdFormatError
from pyanaconda.storage.devices import deviceNameToDiskByPath
from pyanaconda.constants import *
from pyanaconda.flags import flags
+from pyanaconda.baseudev import udev_trigger
import logging
log = logging.getLogger("anaconda")
@@ -86,6 +87,9 @@ class DASD:
if not iutil.isS390():
return
+ # Trigger udev data about the dasd devices on the system
+ udev_trigger(action="change", name="dasd*")
+
log.info("Checking for unformatted DASD devices:")
for device in os.listdir("/sys/block"):
@@ -196,6 +200,10 @@ class DASD:
if dasd:
self._devices.append(dasd)
+ def clear_device_list(self):
+ """ Clear the device list to force re-populate on next access. """
+ self._devices = []
+
def write(self):
""" Write /etc/dasd.conf to target system for all DASD devices
configured during installation.
diff --git a/pyanaconda/storage/devicelibs/btrfs.py b/pyanaconda/storage/devicelibs/btrfs.py
new file mode 100644
index 000000000..3fe12188c
--- /dev/null
+++ b/pyanaconda/storage/devicelibs/btrfs.py
@@ -0,0 +1,118 @@
+#
+# btrfs.py
+# btrfs functions
+#
+# Copyright (C) 2011 Red Hat, Inc. All rights reserved.
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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/>.
+#
+# Author(s): David Lehman <dlehman@redhat.com>
+#
+
+import os
+import re
+
+from pyanaconda import iutil
+from ..errors import *
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+def btrfs(args, progress=None, capture=False):
+ kwargs = {}
+ kwargs["stderr"] = "/dev/tty5"
+ if capture:
+ # execWithCapture can't take a progress window so it gets ignored
+ exec_func = iutil.execWithCapture
+ else:
+ exec_func = iutil.execWithPulseProgress
+ kwargs["stdout"] = "/dev/tty5"
+ kwargs["progress"] = progress
+
+ ret = exec_func("btrfs", args, **kwargs)
+ if not capture and ret.rc:
+ raise BTRFSError(ret.stderr)
+
+ return ret
+
+def create_volume(devices, label=None, data=None, metadata=None, progress=None):
+ """ For now, data and metadata must be strings mkfs.btrfs understands. """
+ if not devices:
+ raise ValueError("no devices specified")
+ elif any([not os.path.exists(d) for d in devices]):
+ raise ValueError("one or more specified devices not present")
+
+ args = []
+ if data:
+ args.append("--data=%s" % data)
+
+ if metadata:
+ args.append("--metadata=%s" % metadata)
+
+ if label:
+ args.append("--label=%s" % label)
+
+ args.extend(devices)
+
+ ret = iutil.execWithPulseProgress("mkfs.btrfs", args,
+ stdout="/dev/tty5", stderr="/dev/tty5",
+ progress=progress)
+ if ret.rc:
+ raise BTRFSError(ret.stderr)
+
+ return ret
+
+# destroy is handled using wipefs
+
+# add device
+
+# remove device
+
+def create_subvolume(mountpoint, name, progress=None):
+ if not os.path.ismount(mountpoint):
+ raise ValueError("volume not mounted")
+
+ path = os.path.normpath("%s/%s" % (mountpoint, name))
+ args = ["subvol", "create", path]
+ return btrfs(args, progress=progress)
+
+def delete_subvolume(mountpoint, name, progress=None):
+ if not os.path.ismount(mountpoint):
+ raise ValueError("volume not mounted")
+
+ path = os.path.normpath("%s/%s" % (mountpoint, name))
+ args = ["subvol", "delete", path]
+ return btrfs(args, progress=progress)
+
+def create_snapshot(source, dest):
+ pass
+
+def scan_device(path):
+ return btrfs(["device", "scan", path])
+
+# get a list of subvolumes from a mounted btrfs filesystem
+def list_subvolumes(mountpoint):
+ if not os.path.ismount(mountpoint):
+ raise ValueError("volume not mounted")
+
+ args = ["subvol", "list", mountpoint]
+ buf = btrfs(args, capture=True)
+ vols = []
+ for group in re.findall(r'ID (\d+) top level (\d+) path (.+)\n', buf):
+ vols.append({"id": int(group[0]), "path": group[2]})
+
+ return vols
diff --git a/pyanaconda/storage/devicelibs/lvm.py b/pyanaconda/storage/devicelibs/lvm.py
index 058fa9c73..36fd9802a 100644
--- a/pyanaconda/storage/devicelibs/lvm.py
+++ b/pyanaconda/storage/devicelibs/lvm.py
@@ -122,21 +122,6 @@ def getMaxLVSize():
else:
return (16*1024*1024) #Max is 16TiB
-# LVM sources set the maximum length limit on VG and LV names at 128. Set
-# our default to 2 below that to account for 0 through 99 entries we may
-# make with this name as a prefix. LVM doesn't seem to impose a limit of
-# 99, but we do in anaconda.
-def safeLvmName(name, maxlen=126):
- tmp = name.strip()
- tmp = tmp.replace("/", "_")
- tmp = re.sub("[^0-9a-zA-Z._]", "", tmp)
- tmp = tmp.lstrip("_")
-
- if len(tmp) > maxlen:
- tmp = tmp[:maxlen]
-
- return tmp
-
def clampSize(size, pesize, roundup=None):
if roundup:
round = math.ceil
@@ -177,7 +162,7 @@ def pvresize(device, size):
raise LVMError("pvresize failed for %s: %s" % (device, msg))
def pvremove(device):
- args = ["pvremove"] + \
+ args = ["pvremove", "--force", "--force"] + \
config_args + \
[device]
diff --git a/pyanaconda/storage/devices.py b/pyanaconda/storage/devices.py
index 2bb975c6a..9c69dfbe6 100644
--- a/pyanaconda/storage/devices.py
+++ b/pyanaconda/storage/devices.py
@@ -97,12 +97,14 @@ import os
import math
import copy
import pprint
+import tempfile
# device backend modules
from devicelibs import mdraid
from devicelibs import lvm
from devicelibs import dm
from devicelibs import loop
+from devicelibs import btrfs
import parted
import _ped
import block
@@ -437,7 +439,7 @@ class StorageDevice(Device):
_partitionable = False
_isDisk = False
- def __init__(self, name, format=None,
+ def __init__(self, name, format=None, uuid=None,
size=None, major=None, minor=None,
sysfsPath='', parents=None, exists=False, serial=None,
vendor="", model="", bus=""):
@@ -454,6 +456,7 @@ class StorageDevice(Device):
minor -- the device minor
sysfsPath -- sysfs device path
format -- a DeviceFormat instance
+ uuid -- universally unique identifier
parents -- a list of required Device instances
serial -- the ID_SERIAL_SHORT for this device
vendor -- the manufacturer of this Device
@@ -468,7 +471,7 @@ class StorageDevice(Device):
self.exists = exists
Device.__init__(self, name, parents=parents)
- self.uuid = None
+ self.uuid = uuid
self._format = None
self._size = numeric_type(size)
self.major = numeric_type(major)
@@ -1846,7 +1849,7 @@ class DMLinearDevice(DMDevice):
udev_settle()
def deactivate(self, recursive=False):
- StorageDevice.teardown(self)
+ StorageDevice.teardown(self, recursive=recursive)
def teardown(self, recursive=None):
""" Close, or tear down, a device. """
@@ -2449,6 +2452,8 @@ class LVMLogicalVolumeDevice(DMDevice):
validpvs = filter(lambda x: float(x.size) >= self.req_size,
self.vg.pvs)
if not validpvs:
+ for dev in self.parents:
+ dev.removeChild()
raise SinglePhysicalVolumeError(self.singlePVerr)
# here we go with the circular references
@@ -2698,11 +2703,15 @@ class MDRaidArrayDevice(StorageDevice):
self.level = mdraid.raidLevel(level)
if self.growable and self.level != 0:
+ for dev in self.parents:
+ dev.removeChild()
raise ValueError("Only RAID0 arrays can contain growable members")
# For new arrays check if we have enough members
if (not exists and parents and
len(parents) < mdraid.get_raid_min_members(self.level)):
+ for dev in self.parents:
+ dev.removeChild()
raise ValueError, P_("A RAID%(raidLevel)d set requires at least %(minMembers)d member",
"A RAID%(raidLevel)d set requires at least %(minMembers)d members",
mdraid.get_raid_min_members(self.level)) % \
@@ -2715,7 +2724,7 @@ class MDRaidArrayDevice(StorageDevice):
self.chunkSize = 512.0 / 1024.0 # chunk size in MB
self.superBlockSize = 2.0 # superblock size in MB
- if not isinstance(metadataVersion, str):
+ if not self.exists and not isinstance(metadataVersion, str):
self.metadataVersion = "default"
else:
self.metadataVersion = metadataVersion
@@ -2731,6 +2740,8 @@ class MDRaidArrayDevice(StorageDevice):
self.formatClass = get_device_format_class("mdmember")
if not self.formatClass:
+ for dev in self.parents:
+ dev.removeChild()
raise DeviceError("cannot find class for 'mdmember'", self.name)
if self.exists and self.uuid and not flags.testing:
@@ -3620,10 +3631,16 @@ class iScsiDiskDevice(DiskDevice, NetworkStorageDevice):
def __init__(self, device, **kwargs):
self.node = kwargs.pop("node")
self.ibft = kwargs.pop("ibft")
+ self.nic = kwargs.pop("nic")
self.initiator = kwargs.pop("initiator")
DiskDevice.__init__(self, device, **kwargs)
- NetworkStorageDevice.__init__(self, host_address=self.node.address)
- log.debug("created new iscsi disk %s %s:%d" % (self.node.name, self.node.address, self.node.port))
+ NetworkStorageDevice.__init__(self, host_address=self.node.address,
+ nic=self.nic)
+ log.debug("created new iscsi disk %s %s:%d via %s:%s" % (self.node.name,
+ self.node.address,
+ self.node.port,
+ self.node.iface,
+ self.nic))
def dracutSetupArgs(self):
if self.ibft:
@@ -3642,7 +3659,13 @@ class iScsiDiskDevice(DiskDevice, NetworkStorageDevice):
netroot += ":%s:%s" % (auth.reverse_username,
auth.reverse_password)
- netroot += "@%s::%d::%s" % (address, self.node.port, self.node.name)
+ iface_spec = ""
+ if self.nic != "default":
+ iface_spec = ":%s:%s" % (self.node.iface, self.nic)
+ netroot += "@%s::%d%s::%s" % (address,
+ self.node.port,
+ iface_spec,
+ self.node.name)
initiator = "iscsi_initiator=%s" % self.initiator
@@ -3665,7 +3688,7 @@ class FcoeDiskDevice(DiskDevice, NetworkStorageDevice):
dcb = True
from .fcoe import fcoe
- for nic, dcb in fcoe().nics:
+ for nic, dcb, auto_vlan in fcoe().nics:
if nic == self.nic:
break
@@ -3786,27 +3809,52 @@ class DASDDevice(DiskDevice):
return "DASD device %s" % self.busid
def getOpts(self):
- return map(lambda (k, v): "%s=%s" % (k, v,), self.opts.items())
+ return ["%s=%s" % (k, v) for k, v in self.opts.items() if v == '1']
def dracutSetupArgs(self):
conf = "/etc/dasd.conf"
- opts = {}
-
+ line = None
if os.path.isfile(conf):
f = open(conf)
- lines = filter(lambda y: not y.startswith('#') and y != '',
- map(lambda x: x.strip(), f.readlines()))
+ # grab the first line that starts with our busID
+ line = [line for line in f.readlines()
+ if line.startswith(self.busid)][:1]
f.close()
- for line in lines:
- parts = line.split()
- if parts != []:
- opts[parts[0]] = parts
-
- if self.busid in opts.keys():
- return set(["rd.dasd=%s" % ",".join(opts[self.busid])])
+ # See if we got a line. If not, grab our getOpts
+ if not line:
+ line = self.busid
+ for devopt in self.getOpts():
+ line += " %s" % devopt
+
+ # Create a translation mapping from dasd.conf format to module format
+ translate = {'use_diag': 'diag',
+ 'readonly': 'ro',
+ 'erplog': 'erplog',
+ 'failfast': 'failfast'}
+
+ # this is a really awkward way of determining if the
+ # feature found is actually desired (1, not 0), plus
+ # translating that feature into the actual kernel module
+ # value
+ opts = []
+ parts = line.split()
+ for chunk in parts[1:]:
+ try:
+ feat, val = chunk.split('=')
+ if int(val):
+ opts.append(translate[feat])
+ except:
+ # If we don't know what the feature is (feat not in translate
+ # or if we get a val that doesn't cleanly convert to an int
+ # we can't do anything with it.
+ log.warning("failed to parse dasd feature %s" % chunk)
+
+ if opts:
+ return set(["rd.dasd=%s(%s)" % (self.busid,
+ ":".join(opts))])
else:
- return set(["rd.dasd=%s" % ",".join([self.busid] + self.getOpts())])
+ return set(["rd.dasd=%s" % self.busid])
class NFSDevice(StorageDevice, NetworkStorageDevice):
""" An NFS device """
@@ -3841,3 +3889,243 @@ class NFSDevice(StorageDevice, NetworkStorageDevice):
def destroy(self):
""" Destroy the device. """
log_method_call(self, self.name, status=self.status)
+
+
+class BTRFSDevice(StorageDevice):
+ """ Base class for BTRFS volume and sub-volume devices. """
+ _type = "btrfs"
+ _packages = ["btrfs-progs"]
+
+ def __init__(self, *args, **kwargs):
+ """ Passing None or no name means auto-generate one like btrfs.%d """
+ if not args or not args[0]:
+ args = ("btrfs.%d" % Device._id,)
+
+ super(BTRFSDevice, self).__init__(*args, **kwargs)
+
+ def updateSysfsPath(self):
+ """ Update this device's sysfs path. """
+ log_method_call(self, self.name, status=self.status)
+ self.sysfsPath = self.parents[0].sysfsPath
+ log.debug("%s sysfsPath set to %s" % (self.name, self.sysfsPath))
+
+ def _statusWindow(self, intf=None, title="", msg=""):
+ return self._progressWindow(intf=intf, title=title, msg=msg)
+
+ def _postCreate(self):
+ super(BTRFSDevice, self)._postCreate()
+ self.format.exists = True
+ self.format.device = self.path
+
+ def _preDestroy(self):
+ """ Preparation and precondition checking for device destruction. """
+ super(BTRFSDevice, self)._preDestroy()
+ self.setupParents(orig=True)
+
+ def _getSize(self):
+ size = sum([d.size for d in self.parents])
+ return size
+
+ def _setSize(self, size):
+ raise RuntimeError("cannot directly set size of btrfs volume")
+
+ @property
+ def status(self):
+ return not any([not d.status for d in self.parents])
+
+ @property
+ def _temp_dir_prefix(self):
+ return "btrfs-tmp.%s" % self.id
+
+ def _do_temp_mount(self):
+ if self.format.status or not self.exists:
+ return
+
+ tmpdir = tempfile.mkdtemp(prefix=self._temp_dir_prefix)
+ self.format.mount(mountpoint=tmpdir)
+
+ def _undo_temp_mount(self):
+ if self.format.status:
+ mountpoint = self.format._mountpoint
+ if os.path.basename(mountpoint).startswith(self._temp_dir_prefix):
+ self.format.unmount()
+ os.rmdir(mountpoint)
+
+ @property
+ def path(self):
+ return self.parents[0].path
+
+
+class BTRFSVolumeDevice(BTRFSDevice):
+ _type = "btrfs volume"
+
+ def __init__(self, *args, **kwargs):
+ self.dataLevel = kwargs.pop("dataLevel", None)
+ self.metaDataLevel = kwargs.pop("metaDataLevel", None)
+
+ super(BTRFSVolumeDevice, self).__init__(*args, **kwargs)
+
+ self.subvolumes = []
+
+ for parent in self.parents:
+ if parent.format.type != "btrfs":
+ raise ValueError("member device %s is not BTRFS" % parent.name)
+
+ if parent.format.exists and self.exists and \
+ parent.format.volUUID != self.uuid:
+ raise ValueError("BTRFS member device %s UUID %s does not "
+ "match volume UUID %s" % (parent.name,
+ parent.format.volUUID, self.uuid))
+
+ if self.parents and not self.format.type:
+ label = getattr(self.parents[0].format, "label", None)
+ self.format = getFormat("btrfs",
+ exists=self.exists,
+ label=label,
+ volUUID=self.uuid,
+ device=self.path)
+
+ label = getattr(self.format, "label", None)
+ if label:
+ self._name = label
+
+ def _setFormat(self, format):
+ """ Set the Device's format. """
+ super(BTRFSVolumeDevice, self)._setFormat(format)
+ self._name = getattr(self.format, "label", "btrfs.%d" % self.id)
+
+ def _addDevice(self, device):
+ """ Add a new device to this volume.
+
+ XXX This is for use by device probing routines and is not
+ intended for modification of the volume.
+ """
+ log_method_call(self,
+ self.name,
+ device=device.name,
+ status=self.status)
+ if not self.exists:
+ raise DeviceError("device does not exist", self.name)
+
+ if device.format.type != "btrfs":
+ raise ValueError("addDevice requires a btrfs device as sole arg")
+
+ if device.format.volUUID != self.uuid:
+ raise ValueError("device UUID does not match the volume UUID")
+
+ if device in self.parents:
+ raise ValueError("device is already a member of this volume")
+
+ self.parents.append(device)
+ device.addChild()
+
+ def _removeDevice(self, device):
+ """ Remove a device from the volume.
+
+ This is for cases like clearing of preexisting partitions.
+ """
+ log_method_call(self,
+ self.name,
+ device=device.name,
+ status=self.status)
+ try:
+ self.parents.remove(device)
+ except ValueError:
+ raise ValueError("cannot remove non-member device from volume")
+
+ device.removeChild()
+
+ def _addSubVolume(self, vol):
+ if vol.name in [v.name for v in self.subvolumes]:
+ raise ValueError("subvolume %s already exists" % vol.name)
+
+ self.subvolumes.append(vol)
+
+ def _removeSubVolume(self, name):
+ if name not in [v.name for v in self.subvolumes]:
+ raise ValueError("cannot remove non-existent subvolume %s" % name)
+
+ names = [v.name for v in self.subvolumes]
+ self.subvolumes.pop(names.index(name))
+
+ def listSubVolumes(self):
+ subvols = []
+ self.setup(orig=True)
+ try:
+ self._do_temp_mount()
+ except FSError as e:
+ log.debug("btrfs temp mount failed: %s" % e)
+ return subvols
+
+ try:
+ subvols = btrfs.list_subvolumes(self.format._mountpoint)
+ except BTRFSError as e:
+ log.debug("failed to list subvolumes: %s" % e)
+ finally:
+ self._undo_temp_mount()
+
+ return subvols
+
+ def createSubVolumes(self, intf=None):
+ self._do_temp_mount()
+ for name, subvol in self.subvolumes:
+ if subvol.exists:
+ continue
+ subvolume.create(mountpoint=self._temp_dir_prefix, intf=intf)
+ self._undo_temp_mount()
+
+ def removeSubVolume(self, name):
+ raise NotImplementedError()
+
+ def _create(self, w):
+ log_method_call(self, self.name, status=self.status)
+ btrfs.create_volume(devices=[d.path for d in self.parents],
+ label=self.format.label,
+ data=self.dataLevel,
+ metadata=self.metaDataLevel,
+ progress=w)
+
+ def _destroy(self):
+ log_method_call(self, self.name, status=self.status)
+ for device in self.parents:
+ device.setup(orig=True)
+ DeviceFormat(device=device.path, exists=True).destroy()
+
+class BTRFSSubVolumeDevice(BTRFSDevice):
+ """ A btrfs subvolume pseudo-device. """
+ _type = "btrfs subvolume"
+
+ def __init__(self, *args, **kwargs):
+ self.vol_id = kwargs.pop("vol_id", None)
+ super(BTRFSSubVolumeDevice, self).__init__(*args, **kwargs)
+
+ self.volume._addSubVolume(self)
+
+ @property
+ def volume(self):
+ return self.parents[0]
+
+ def setupParents(self, orig=False):
+ """ Run setup method of all parent devices. """
+ log_method_call(self, name=self.name, orig=orig, kids=self.kids)
+ self.volume.setup(orig=orig)
+
+ def _create(self, w):
+ log_method_call(self, self.name, status=self.status)
+ self.volume._do_temp_mount()
+ mountpoint = self.volume.format._mountpoint
+ if not mountpoint:
+ raise RuntimeError("btrfs subvol create requires mounted volume")
+
+ btrfs.create_subvolume(mountpoint, self.name, progress=w)
+ self.volume._undo_temp_mount()
+
+ def _destroy(self):
+ log_method_call(self, self.name, status=self.status)
+ self.volume._do_temp_mount()
+ mountpoint = self.volume.format._mountpoint
+ if not mountpoint:
+ raise RuntimeError("btrfs subvol destroy requires mounted volume")
+ btrfs.delete_subvolume(mountpoint, self.name)
+ self.volume._removeSubVolume()
+ self.volume._undo_temp_mount()
diff --git a/pyanaconda/storage/devicetree.py b/pyanaconda/storage/devicetree.py
index b449516d2..25258d7ee 100644
--- a/pyanaconda/storage/devicetree.py
+++ b/pyanaconda/storage/devicetree.py
@@ -1,7 +1,7 @@
# devicetree.py
# Device management for anaconda's storage configuration module.
#
-# Copyright (C) 2009 Red Hat, Inc.
+# Copyright (C) 2009, 2010, 2011 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
@@ -164,12 +164,16 @@ class DeviceTree(object):
self._devices = []
self._actions = []
+ # a list of all device names we encounter
+ self.names = []
+
# indicates whether or not the tree has been fully populated
self.populated = False
self.exclusiveDisks = getattr(conf, "exclusiveDisks", [])
self.clearPartType = getattr(conf, "clearPartType", CLEARPART_TYPE_NONE)
self.clearPartDisks = getattr(conf, "clearPartDisks", [])
+ self.clearPartDevices = getattr(conf, "clearPartDevices", [])
self.zeroMbr = getattr(conf, "zeroMbr", False)
self.reinitializeDisks = getattr(conf, "reinitializeDisks", False)
self.iscsi = iscsi
@@ -186,6 +190,7 @@ class DeviceTree(object):
# protected device specs as provided by the user
self.protectedDevSpecs = getattr(conf, "protectedDevSpecs", [])
+ self.liveBackingDevice = None
# names of protected devices at the time of tree population
self.protectedDevNames = []
@@ -338,7 +343,7 @@ class DeviceTree(object):
Raise ValueError if the device's identifier is already
in the list.
"""
- if newdev.path in [d.path for d in self._devices] and \
+ if newdev.uuid and newdev.uuid in [d.uuid for d in self._devices] and \
not isinstance(newdev, NoDevice):
raise ValueError("device is already in tree")
@@ -348,6 +353,12 @@ class DeviceTree(object):
raise DeviceTreeError("parent device not in tree")
self._devices.append(newdev)
+
+ # don't include "req%d" partition names
+ if ((newdev.type != "partition" or
+ not newdev.name.startswith("req")) and
+ newdev.name not in self.names):
+ self.names.append(newdev.name)
log.info("added %s %s (id %d) to device tree" % (newdev.type,
newdev.name,
newdev.id))
@@ -385,6 +396,8 @@ class DeviceTree(object):
device.updateName()
self._devices.remove(dev)
+ if dev.name in self.names:
+ self.names.remove(dev.name)
log.info("removed %s %s (id %d) from device tree" % (dev.type,
dev.name,
dev.id))
@@ -404,14 +417,8 @@ class DeviceTree(object):
action.device not in self._devices:
raise DeviceTreeError("device is not in the tree")
elif (action.isCreate and action.isDevice):
- # this allows multiple create actions w/o destroy in between;
- # we will clean it up before processing actions
- #raise DeviceTreeError("device is already in the tree")
if action.device in self._devices:
- self._removeDevice(action.device)
- for d in self._devices:
- if d.path == action.device.path:
- self._removeDevice(d)
+ raise DeviceTreeError("device is already in the tree")
if action.isCreate and action.isDevice:
self._addDevice(action.device)
@@ -610,7 +617,7 @@ class DeviceTree(object):
pv_info = udev_get_block_device(pv_sysfs_path)
self.addUdevDevice(pv_info)
- vg_name = udev_device_get_vg_name(info)
+ vg_name = udev_device_get_lv_vg_name(info)
device = self.getDeviceByName(vg_name)
if not device:
log.error("failed to find vg '%s' after scanning pvs" % vg_name)
@@ -657,7 +664,7 @@ class DeviceTree(object):
if slave_name.startswith("dm-"):
dev_name = dm.name_from_dm_node(slave_name)
else:
- dev_name = slave_name
+ dev_name = slave_name.replace("!", "/") # handles cciss
slave_dev = self.getDeviceByName(dev_name)
path = os.path.normpath("%s/%s" % (dir, slave_name))
new_info = udev_get_block_device(os.path.realpath(path)[4:])
@@ -841,11 +848,14 @@ class DeviceTree(object):
kwargs = { "serial": serial, "vendor": vendor, "bus": bus }
if udev_device_is_iscsi(info):
diskType = iScsiDiskDevice
- kwargs["node"] = self.iscsi.getNode(
+ node = self.iscsi.getNode(
udev_device_get_iscsi_name(info),
udev_device_get_iscsi_address(info),
- udev_device_get_iscsi_port(info))
- kwargs["ibft"] = kwargs["node"] in self.iscsi.ibftNodes
+ udev_device_get_iscsi_port(info),
+ udev_device_get_iscsi_nic(info))
+ kwargs["node"] = node
+ kwargs["nic"] = self.iscsi.ifaces.get(node.iface, node.iface)
+ kwargs["ibft"] = node in self.iscsi.ibftNodes
kwargs["initiator"] = self.iscsi.initiator
log.info("%s is an iscsi disk" % name)
elif udev_device_is_fcoe(info):
@@ -855,7 +865,12 @@ class DeviceTree(object):
log.info("%s is an fcoe disk" % name)
elif udev_device_get_md_container(info):
diskType = MDRaidArrayDevice
- parentName = devicePathToName(udev_device_get_md_container(info))
+ parentPath = udev_device_get_md_container(info)
+ if os.path.islink(parentPath):
+ parentPath = os.path.join(os.path.dirname(parentPath),
+ os.readlink(parentPath))
+
+ parentName = devicePathToName(os.path.normpath(parentPath))
container = self.getDeviceByName(parentName)
if not container:
container_sysfs = "/class/block/" + parentName
@@ -949,6 +964,10 @@ class DeviceTree(object):
uuid = udev_device_get_uuid(info)
sysfs_path = udev_device_get_sysfs_path(info)
+ # make sure we note the name of every device we see
+ if name not in self.names:
+ self.names.append(name)
+
if self.isIgnored(info):
log.info("ignoring %s (%s)" % (name, sysfs_path))
if name not in self._ignoredDisks:
@@ -1025,37 +1044,6 @@ class DeviceTree(object):
log.error("Unknown block device type for: %s" % name)
return
- def get_live_backing_device_path():
- """ Return a path to the live block device or an empty string.
-
- The line we're looking for will be of the form
-
- root='block:/dev/disk/by-uuid/<UUID>'
-
- """
- root_info = "/run/initramfs/tmp/root.info"
- dev_live = "/dev/live"
- root_path = ""
- if os.path.exists(dev_live):
- root_path = os.path.realpath(dev_live)
- elif os.path.exists(root_info):
- prefix = "root='"
- for line in open(root_info):
- if not line.startswith(prefix) or \
- (line[6:12] != "block:" and line[6:11] != "live:"):
- continue
-
- start_idx = line.index(":") + 1
- root_path = os.path.realpath(line[start_idx:-1])
- root_name = devicePathToName(root_path)
- if root_name.startswith("dm-"):
- root_name = dm.name_from_dm_node(root_name)
- root_path = "/dev/mapper/%s" % root_name
-
- break
-
- return root_path
-
# If this device is protected, mark it as such now. Once the tree
# has been populated, devices' protected attribute is how we will
# identify protected devices.
@@ -1063,8 +1051,7 @@ class DeviceTree(object):
device.protected = True
# if this is the live backing device we want to mark its parents
# as protected also
- live_path = get_live_backing_device_path()
- if live_path and device.path == live_path:
+ if device.name == self.liveBackingDevice:
for parent in device.parents:
parent.protected = True
@@ -1081,7 +1068,7 @@ class DeviceTree(object):
device.originalFormat = device.format
def handleUdevDiskLabelFormat(self, info, device):
- disklabel_type = info.get("PART_TABLE_TYPE")
+ disklabel_type = info.get("ID_PART_TABLE_TYPE")
log_method_call(self, device=device.name, label_type=disklabel_type)
# if there is no disklabel on the device
if disklabel_type is None and \
@@ -1139,13 +1126,10 @@ class DeviceTree(object):
labelType = self.platform.bestDiskLabelType(device)
try:
- # XXX if initlabel is True we don't ever instantiate a format
- # for the original disklabel, so we will only have a
- # DeviceFormat instance to destroy.
format = getFormat("disklabel",
device=device.path,
labelType=labelType,
- exists=not initlabel)
+ exists=True)
except InvalidDiskLabelError:
log.info("no usable disklabel on %s" % device.name)
return
@@ -1233,9 +1217,26 @@ class DeviceTree(object):
log.debug("no LVs listed for VG %s" % vg_name)
return False
- # make a list of indices with snapshots at the end
+ def lv_attr_cmp(a, b):
+ """ Sort so that mirror images come first and snapshots last. """
+ mirror_chars = "Iil"
+ snapshot_chars = "Ss"
+ if a[0] in mirror_chars and b[0] not in mirror_chars:
+ return -1
+ elif a[0] not in mirror_chars and b[0] in mirror_chars:
+ return 1
+ elif a[0] not in snapshot_chars and b[0] in snapshot_chars:
+ return -1
+ elif a[0] in snapshot_chars and b[0] not in snapshot_chars:
+ return 1
+ else:
+ return 0
+
+ # make a list of indices with mirror volumes up front and snapshots at
+ # the end
indices = range(len(lv_names))
- indices.sort(key=lambda i: lv_attr[i][0] in 'Ss')
+ indices.sort(key=lambda i: lv_attr[i], cmp=lv_attr_cmp)
+ mirrors = {}
for index in indices:
lv_name = lv_names[index]
name = "%s-%s" % (vg_name, lv_name)
@@ -1263,24 +1264,25 @@ class DeviceTree(object):
% (lv_sizes[index], origin.name))
origin.snapshotSpace += lv_sizes[index]
continue
- elif lv_attr[index][0] in 'Iilv':
- # skip mirror images, log volumes, and vorigins
+ elif lv_attr[index][0] == 'v':
+ # skip vorigins
continue
-
- log_size = 0
- if lv_attr[index][0] in 'Mm':
- stripes = 0
- # identify mirror stripes/copies and mirror logs
- for (j, _lvname) in enumerate(lv_names):
- if lv_attr[j][0] not in 'Iil':
- continue
-
- if _lvname == "[%s_mlog]" % lv_name:
- log_size = lv_sizes[j]
- elif _lvname.startswith("[%s_mimage_" % lv_name):
- stripes += 1
- else:
- stripes = 1
+ elif lv_attr[index][0] in 'Ii':
+ # mirror image
+ lv_name = re.sub(r'_mimage.+', '', lv_name[1:-1])
+ name = "%s-%s" % (vg_name, lv_name)
+ if name not in mirrors:
+ mirrors[name] = {"stripes": 0, "log": 0}
+
+ mirrors[name]["stripes"] += 1
+ elif lv_attr[index][0] == 'l':
+ # log volume
+ lv_name = re.sub(r'_mlog.*', '', lv_name[1:-1])
+ name = "%s-%s" % (vg_name, lv_name)
+ if name not in mirrors:
+ mirrors[name] = {"stripes": 0, "log": 0}
+
+ mirrors[name]["log"] = lv_sizes[index]
lv_dev = self.getDeviceByName(name)
if lv_dev is None:
@@ -1290,13 +1292,19 @@ class DeviceTree(object):
vg_device,
uuid=lv_uuid,
size=lv_size,
- stripes=stripes,
- logSize=log_size,
exists=True)
self._addDevice(lv_device)
lv_device.setup()
ret = True
+ for name, mirror in mirrors.items():
+ lv_dev = self.getDeviceByName(name)
+ lv_dev.stripes = mirror["stripes"]
+ lv_dev.logSize = mirror["log"]
+ log.debug("set %s stripes to %d, log size to %dMB, total size %dMB"
+ % (lv_dev.name, lv_dev.stripes, lv_dev.logSize,
+ lv_dev.vgSpaceUsed))
+
return ret
def handleUdevLVMPVFormat(self, info, device):
@@ -1358,6 +1366,10 @@ class DeviceTree(object):
vg_device.lv_sizes.append(lv_sizes[i])
vg_device.lv_attr.append(lv_attr[i])
+ name = "%s-%s" % (vg_name, lv_names[i])
+ if name not in self.names:
+ self.names.append(name)
+
def handleUdevMDMemberFormat(self, info, device):
log_method_call(self, name=device.name, type=device.format.type)
# either look up or create the array device
@@ -1379,6 +1391,7 @@ class DeviceTree(object):
return
md_name = None
+ md_metadata = None
minor = None
# check the list of devices udev knows about to see if the array
@@ -1399,10 +1412,12 @@ class DeviceTree(object):
if dev_uuid == md_uuid and dev_level == md_level:
md_name = udev_device_get_name(dev)
minor = udev_device_get_minor(dev)
+ md_metadata = dev.get("MD_METADATA")
break
md_info = devicelibs.mdraid.mdexamine(device.path)
- md_metadata = md_info.get("metadata")
+ if not md_metadata:
+ md_metadata = md_info.get("metadata", "0.90")
if not md_name:
# try to name the array based on the preferred minor
@@ -1527,6 +1542,42 @@ class DeviceTree(object):
#device.format.raidmem = block.getMemFromRaidSet(dm_array,
# major=major, minor=minor, uuid=uuid, name=name)
+ def handleBTRFSFormat(self, info, device):
+ log_method_call(self, name=device.name)
+ name = udev_device_get_name(info)
+ sysfs_path = udev_device_get_sysfs_path(info)
+ uuid = udev_device_get_uuid(info)
+
+ btrfs_dev = None
+ for d in self.devices:
+ if isinstance(d, BTRFSVolumeDevice) and d.uuid == uuid:
+ btrfs_dev = d
+ break
+
+ if btrfs_dev:
+ log.info("found btrfs volume %s" % btrfs_dev.name)
+ btrfs_dev._addDevice(device)
+ else:
+ label = udev_device_get_label(info)
+ log.info("creating btrfs volume btrfs.%s" % label)
+ btrfs_dev = BTRFSVolumeDevice(label, parents=[device], uuid=uuid,
+ exists=True)
+ self._addDevice(btrfs_dev)
+
+ if not btrfs_dev.subvolumes:
+ for subvol_dict in btrfs_dev.listSubVolumes():
+ vol_id = subvol_dict["id"]
+ vol_path = subvol_dict["path"]
+ if vol_path in [sv.name for sv in btrfs_dev.subvolumes]:
+ continue
+ fmt = getFormat("btrfs", device=btrfs_dev.path, exists=True,
+ mountopts="subvol=%d" % vol_id)
+ subvol = BTRFSSubVolumeDevice(vol_path,
+ vol_id=vol_id,
+ format=fmt,
+ parents=[btrfs_dev])
+ self._addDevice(subvol)
+
def handleUdevDeviceFormat(self, info, device):
log_method_call(self, name=getattr(device, "name", None))
name = udev_device_get_name(info)
@@ -1605,6 +1656,11 @@ class DeviceTree(object):
apple = formats.getFormat("appleboot")
if apple.minSize <= device.size <= apple.maxSize:
args[0] = "appleboot"
+ elif format_type == "btrfs":
+ # the format's uuid attr will contain the UUID_SUB, while the
+ # overarching volume UUID will be stored as volUUID
+ kwargs["uuid"] = info["ID_FS_UUID_SUB"]
+ kwargs["volUUID"] = uuid
try:
log.info("type detected on '%s' is '%s'" % (name, format_type,))
@@ -1616,7 +1672,8 @@ class DeviceTree(object):
return
if shouldClear(device, self.clearPartType,
- clearPartDisks=self.clearPartDisks):
+ clearPartDisks=self.clearPartDisks,
+ clearPartDevices=self.clearPartDevices):
# if this is a device that will be cleared by clearpart,
# don't bother with format-specific processing
return
@@ -1634,6 +1691,8 @@ class DeviceTree(object):
self.handleUdevLVMPVFormat(info, device)
elif device.format.type == "multipath_member":
self.handleMultipathMemberFormat(info, device)
+ elif device.format.type == "btrfs":
+ self.handleBTRFSFormat(info, device)
def updateDeviceFormat(self, device):
log.info("updating format of device: %s" % device)
@@ -1791,13 +1850,17 @@ class DeviceTree(object):
# FIXME: the backing dev for the live image can't be used as an
# install target. note that this is a little bit of a hack
- # since we're assuming that /dev/live will exist
- if os.path.exists("/dev/live") and \
- stat.S_ISBLK(os.stat("/dev/live")[stat.ST_MODE]):
- livetarget = devicePathToName(os.path.realpath("/dev/live"))
+ # since we're assuming that /run/initramfs/live will exist
+ for mnt in open("/proc/mounts").readlines():
+ if " /run/initramfs/live " not in mnt:
+ continue
+
+ live_device_name = mnt.split()[0].split("/")[-1]
log.info("%s looks to be the live device; marking as protected"
- % (livetarget,))
- self.protectedDevNames.append(livetarget)
+ % (live_device_name,))
+ self.protectedDevNames.append(live_device_name)
+ self.liveBackingDevice = live_device_name
+ break
cfg = self.__multipathConfigWriter.write(self.mpathFriendlyNames)
old_devices = {}
@@ -1810,6 +1873,12 @@ class DeviceTree(object):
log.info("devices to scan: %s" %
[d['name'] for d in self.topology.devices_iter()])
for dev in self.topology.devices_iter():
+ # avoid the problems caused by encountering multipath devices in
+ # this loop by simply skipping all dm devices here
+ if dev['name'].startswith("dm-"):
+ log.debug("Skipping a device mapper drive (%s) for now" % dev['name'])
+ continue
+
old_devices[dev['name']] = dev
self.addUdevDevice(dev)
@@ -1906,6 +1975,7 @@ class DeviceTree(object):
found = device
break
+ log_method_return(self, found)
return found
def getDeviceByUuid(self, uuid):
@@ -1921,6 +1991,7 @@ class DeviceTree(object):
found = device
break
+ log_method_return(self, found)
return found
def getDevicesBySerial(self, serial):
@@ -1931,6 +2002,8 @@ class DeviceTree(object):
continue
if device.serial == serial:
devices.append(device)
+
+ log_method_return(self, devices)
return devices
def getDeviceByLabel(self, label):
@@ -1947,6 +2020,7 @@ class DeviceTree(object):
found = device
break
+ log_method_return(self, found)
return found
def getDeviceByName(self, name):
@@ -1968,21 +2042,31 @@ class DeviceTree(object):
log_method_return(self, str(found))
return found
- def getDeviceByPath(self, path):
+ def getDeviceByPath(self, path, preferLeaves=True):
log_method_call(self, path=path)
if not path:
log_method_return(self, None)
return None
found = None
+ leaf = None
+ other = None
for device in self._devices:
- if device.path == path:
- found = device
- break
- elif (device.type == "lvmlv" or device.type == "lvmvg") and \
- device.path == path.replace("--","-"):
- found = device
- break
+ if (device.path == path or
+ ((device.type == "lvmlv" or device.type == "lvmvg") and
+ device.path == path.replace("--","-"))):
+ if device.isleaf and not leaf:
+ leaf = device
+ elif not other:
+ other = device
+
+ if preferLeaves:
+ all_devs = [leaf, other]
+ else:
+ all_devs = [other, leaf]
+ all_devs = [d for d in all_devs if d]
+ if all_devs:
+ found = all_devs[0]
log_method_return(self, str(found))
return found
@@ -1999,9 +2083,9 @@ class DeviceTree(object):
""" List of device instances """
devices = []
for device in self._devices:
- if device.path in [d.path for d in devices] and \
+ if device.uuid and device.uuid in [d.uuid for d in devices] and \
not isinstance(device, NoDevice):
- raise DeviceTreeError("duplicate paths in device tree")
+ raise DeviceTreeError("duplicate uuids in device tree")
devices.append(device)
@@ -2049,7 +2133,9 @@ class DeviceTree(object):
"""
labels = {}
for dev in self._devices:
- if dev.format and getattr(dev.format, "label", None):
+ # don't include btrfs member devices
+ if getattr(dev.format, "label", None) and \
+ (dev.format.type != "btrfs" or isinstance(dev, BTRFSDevice)):
labels[dev.format.label] = dev
return labels
diff --git a/pyanaconda/storage/errors.py b/pyanaconda/storage/errors.py
index 6b7e5fc78..90a9b1387 100644
--- a/pyanaconda/storage/errors.py
+++ b/pyanaconda/storage/errors.py
@@ -139,6 +139,9 @@ class MPathError(StorageError):
class LoopError(StorageError):
pass
+class BTRFSError(StorageError):
+ pass
+
# DeviceTree
class DeviceTreeError(StorageError):
pass
diff --git a/pyanaconda/storage/fcoe.py b/pyanaconda/storage/fcoe.py
index c8497de4e..d263dcf80 100644
--- a/pyanaconda/storage/fcoe.py
+++ b/pyanaconda/storage/fcoe.py
@@ -85,7 +85,7 @@ class fcoe(object):
return
log.info("FCoE NIC found in EDD: %s" % val)
- self.addSan(val, dcb=True)
+ self.addSan(val, dcb=True, auto_vlan=True)
def startup(self):
if self.started:
@@ -105,11 +105,12 @@ class fcoe(object):
stdout = "/dev/tty5", stderr="/dev/tty5")
self.lldpadStarted = True
- def addSan(self, nic, dcb=False):
+ def addSan(self, nic, dcb=False, auto_vlan=True):
if not has_fcoe():
raise IOError, _("FCoE not available")
- log.info("Activating FCoE SAN attached to %s, dcb: %s" % (nic, dcb))
+ log.info("Activating FCoE SAN attached to %s, dcb: %s autovlan: %s" %
+ (nic, dcb, auto_vlan))
iutil.execWithRedirect("ip", [ "link", "set", nic, "up" ],
stdout = "/dev/tty5", stderr="/dev/tty5")
@@ -124,10 +125,7 @@ class fcoe(object):
iutil.execWithRedirect("fipvlan", [ nic, "-c", "-s" ],
stdout = "/dev/tty5", stderr="/dev/tty5")
else:
- # Use fipvlan instead of fcoe's create if nic uses bnx2x driver.
- # Ideally, this should be done by checking a "AUTO_VLAN" parameter,
- # not bnx2x driver usage
- if 'bnx2x' in os.path.realpath('/sys/class/net/%s/device/driver' %(nic)):
+ if auto_vlan:
# certain network configrations require the VLAN layer module:
iutil.execWithRedirect("modprobe", ["8021q"],
stdout = "/dev/tty5", stderr="/dev/tty5")
@@ -139,7 +137,7 @@ class fcoe(object):
f.close()
self._stabilize()
- self.nics.append((nic, dcb))
+ self.nics.append((nic, dcb, auto_vlan))
def writeKS(self, f):
# fixme plenty (including add ks support for fcoe in general)
@@ -152,7 +150,7 @@ class fcoe(object):
if not os.path.isdir(ROOT_PATH + "/etc/fcoe"):
os.makedirs(ROOT_PATH + "/etc/fcoe", 0755)
- for nic, dcb in self.nics:
+ for nic, dcb, auto_vlan in self.nics:
fd = os.open(ROOT_PATH + "/etc/fcoe/cfg-" + nic,
os.O_RDWR | os.O_CREAT)
os.write(fd, '# Created by anaconda\n')
@@ -164,7 +162,10 @@ class fcoe(object):
else:
os.write(fd, 'DCB_REQUIRED="no"\n')
os.write(fd, '# Indicate if VLAN discovery should be handled by fcoemon\n')
- os.write(fd, 'AUTO_VLAN="yes"\n')
+ if auto_vlan:
+ os.write(fd, 'AUTO_VLAN="yes"\n')
+ else:
+ os.write(fd, 'AUTO_VLAN="no"\n')
os.close(fd)
return
diff --git a/pyanaconda/storage/formats/__init__.py b/pyanaconda/storage/formats/__init__.py
index 4b0c122fe..3df470ebf 100644
--- a/pyanaconda/storage/formats/__init__.py
+++ b/pyanaconda/storage/formats/__init__.py
@@ -108,7 +108,9 @@ def collect_device_format_classes():
try:
globals()[mod_name] = __import__(mod_name, globals(), locals(), [], -1)
except ImportError:
- log.debug("import of device format module '%s' failed" % mod_name)
+ log.error("import of device format module '%s' failed" % mod_name)
+ from traceback import format_exc
+ log.debug(format_exc())
def get_device_format_class(fmt_type):
""" Return an appropriate format class based on fmt_type. """
diff --git a/pyanaconda/storage/formats/disklabel.py b/pyanaconda/storage/formats/disklabel.py
index c9509cf1a..16def28f7 100644
--- a/pyanaconda/storage/formats/disklabel.py
+++ b/pyanaconda/storage/formats/disklabel.py
@@ -26,6 +26,7 @@ import copy
from pyanaconda.flags import flags
from pyanaconda.anaconda_log import log_method_call
+from pyanaconda import iutil
import parted
import _ped
from ..errors import *
@@ -165,6 +166,17 @@ class DiskLabel(DeviceFormat):
if self._partedDisk.isFlagAvailable(parted.DISK_CYLINDER_ALIGNMENT):
self._partedDisk.unsetFlag(parted.DISK_CYLINDER_ALIGNMENT)
+ # Set the boot flag on the GPT PMBR, this helps some BIOS systems boot
+ if self._partedDisk.isFlagAvailable(parted.DISK_GPT_PMBR_BOOT):
+ # MAC canboot as EFI or as BIOS, neither should have PMBR boot set
+ if iutil.isEfi() or iutil.isMactel():
+ log.debug("Not setting pmbr_boot on %s" % (self._partedDisk,))
+ else:
+ self._partedDisk.setFlag(parted.DISK_GPT_PMBR_BOOT)
+ log.debug("Set pmbr_boot on %s" % (self._partedDisk,))
+ else:
+ log.debug("Did not set pmbr_boot on %s" % (self._partedDisk,))
+
return self._partedDisk
@property
diff --git a/pyanaconda/storage/formats/fs.py b/pyanaconda/storage/formats/fs.py
index 175f7edda..248dd9c3f 100644
--- a/pyanaconda/storage/formats/fs.py
+++ b/pyanaconda/storage/formats/fs.py
@@ -152,7 +152,7 @@ class FS(DeviceFormat):
self._size = kwargs.get("size", 0)
self._minInstanceSize = None # min size of this FS instance
self._mountpoint = None # the current mountpoint when mounted
- if self.exists and self.supported:
+ if self.exists:
self._size = self._getExistingSize()
foo = self.minSize # force calculation of minimum size
@@ -254,7 +254,8 @@ class FS(DeviceFormat):
"""
size = self._size
- if self.infofsProg and self.exists and not size:
+ if self.infofsProg and self.exists and not size and \
+ iutil.find_program_in_path(self.infofsProg):
try:
values = []
argv = self._defaultInfoOptions + [ self.device ]
@@ -1029,6 +1030,12 @@ class Ext3FS(Ext2FS):
_defaultMigrateOptions = ["-O", "extents"]
partedSystem = fileSystemType["ext3"]
+ # It is possible for a user to specify an fsprofile that defines a blocksize
+ # smaller than the default of 4096 bytes and therefore to make liars of us
+ # with regard to this maximum filesystem size, but if they're doing such
+ # things they should know the implications of their chosen block size.
+ _maxSize = 16 * 1024 * 1024
+
register_device_format(Ext3FS)
@@ -1095,7 +1102,7 @@ class BTRFS(FS):
_mkfs = "mkfs.btrfs"
_modules = ["btrfs"]
_resizefs = "btrfsctl"
- _formattable = True
+ _formattable = False # this disables creation via the UI
_linuxNative = True
_maxLabelChars = 256
_supported = True
@@ -1104,10 +1111,28 @@ class BTRFS(FS):
_packages = ["btrfs-progs"]
_minSize = 256
_maxSize = 16 * 1024 * 1024
- # FIXME parted needs to be thaught about btrfs so that we can set the
+ # FIXME parted needs to be taught about btrfs so that we can set the
# partition table type correctly for btrfs partitions
# partedSystem = fileSystemType["btrfs"]
+ def __init__(self, *args, **kwargs):
+ super(BTRFS, self).__init__(*args, **kwargs)
+ self.volUUID = kwargs.pop("volUUID", None)
+
+ def create(self, *args, **kwargs):
+ # filesystem creation is done in storage.devicelibs.btrfs.create_volume
+ pass
+
+ def setup(self, *args, **kwargs):
+ log_method_call(self, type=self.mountType, device=self.device,
+ mountpoint=self.mountpoint)
+ if not self.mountpoint and "mountpoint" not in kwargs:
+ # Since btrfs vols have subvols the format setup is automatic.
+ # Don't try to mount it if there's no mountpoint.
+ return
+
+ return self.mount(*args, **kwargs)
+
def _getFormatOptions(self, options=None):
argv = []
if options and isinstance(options, list):
@@ -1308,11 +1333,18 @@ class AppleBootstrapFS(HFS):
register_device_format(AppleBootstrapFS)
-# this doesn't need to be here
class HFSPlus(FS):
_type = "hfs+"
_modules = ["hfsplus"]
_udevTypes = ["hfsplus"]
+ _mkfs = "mkfs.hfsplus"
+ _fsck = "fsck.hfsplus"
+ _packages = ["hfsplus-tools"]
+ _formattable = True
+ _mountType = "hfsplus"
+ _minSize = 1
+ _maxSize = 2 * 1024 * 1024
+ _check = True
partedSystem = fileSystemType["hfs+"]
register_device_format(HFSPlus)
@@ -1349,7 +1381,8 @@ class NTFS(FS):
if self._minInstanceSize is None:
# we try one time to determine the minimum size.
size = self._minSize
- if self.exists and os.path.exists(self.device):
+ if self.exists and os.path.exists(self.device) and \
+ iutil.find_program_in_path(self.resizefsProg):
minSize = None
buf = iutil.execWithCapture(self.resizefsProg,
["-m", self.device],
diff --git a/pyanaconda/storage/formats/prepboot.py b/pyanaconda/storage/formats/prepboot.py
index 102d957bf..f671e9563 100644
--- a/pyanaconda/storage/formats/prepboot.py
+++ b/pyanaconda/storage/formats/prepboot.py
@@ -23,6 +23,9 @@
from ..errors import *
from . import DeviceFormat, register_device_format
from parted import PARTITION_PREP
+import os
+import logging
+log = logging.getLogger("storage")
class PPCPRePBoot(DeviceFormat):
""" Generic device format. """
@@ -46,6 +49,31 @@ class PPCPRePBoot(DeviceFormat):
"""
DeviceFormat.__init__(self, *args, **kwargs)
+ def create(self, *args, **kwargs):
+ if self.exists:
+ raise FSError("PReP Boot format already exists")
+
+ DeviceFormat.create(self, *args, **kwargs)
+
+ try:
+ fd = os.open(self.device, os.O_RDWR)
+ length = os.lseek(fd, 0, os.SEEK_END)
+ os.lseek(fd, 0, os.SEEK_SET)
+ buf = '\0' * 1024 * 1024
+ while length > 0:
+ if length >= len(buf):
+ os.write(fd, buf)
+ length -= len(buf)
+ else:
+ buf = '0' * length
+ os.write(fd, buf)
+ length = 0
+ os.close(fd)
+ except OSError as e:
+ log.error("error zeroing out %s: %s" % (self.device, e))
+ if fd:
+ os.close(fd)
+
@property
def status(self):
return False
diff --git a/pyanaconda/storage/formats/swap.py b/pyanaconda/storage/formats/swap.py
index 479b4f14b..42458d595 100644
--- a/pyanaconda/storage/formats/swap.py
+++ b/pyanaconda/storage/formats/swap.py
@@ -162,6 +162,7 @@ class SwapSpace(DeviceFormat):
raise
else:
self.exists = True
+ self.notifyKernel()
def writeKS(self, f):
f.write("swap")
diff --git a/pyanaconda/storage/iscsi.py b/pyanaconda/storage/iscsi.py
index 9c086e4d3..ac3501cb8 100644
--- a/pyanaconda/storage/iscsi.py
+++ b/pyanaconda/storage/iscsi.py
@@ -28,6 +28,7 @@ import shutil
import time
import hashlib
import random
+import itertools
log = logging.getLogger("anaconda")
import gettext
@@ -80,7 +81,7 @@ class iscsi(object):
This class will automatically discover and login to iBFT (or
other firmware) configured iscsi devices when the startup() method
gets called. It can also be used to manually configure iscsi devices
- through the discover() and log_into_node() methods.
+ through the addTarget() method.
As this class needs to make sure certain things like starting iscsid
and logging in to firmware discovered disks only happens once
@@ -89,13 +90,15 @@ class iscsi(object):
"""
def __init__(self):
- # This list contains all nodes
- self.nodes = []
+ # Dictionary of discovered targets containing list of (node,
+ # logged_in) tuples.
+ self.discovered_targets = {}
# This list contains nodes discovered through iBFT (or other firmware)
self.ibftNodes = []
self._initiator = ""
self.initiatorSet = False
self.started = False
+ self.ifaces = {}
if flags.ibft:
try:
@@ -124,21 +127,57 @@ class iscsi(object):
initiator = property(_getInitiator, _setInitiator)
+ def active_nodes(self, target=None):
+ """Nodes logged in to"""
+ if target:
+ return [node for (node, logged_in) in
+ self.discovered_targets.get(target, [])
+ if logged_in]
+ else:
+ return [node for (node, logged_in) in
+ itertools.chain(*self.discovered_targets.values())
+ if logged_in] + self.ibftNodes
+
+ def _getMode(self):
+ if not self.active_nodes():
+ return "none"
+ if self.ifaces:
+ return "bind"
+ else:
+ return "default"
+
+ mode = property(_getMode)
+
+ def _mark_node_active(self, node, active=True):
+ """Mark node as one logged in to
+
+ Returns False if not found
+ """
+ for target_nodes in self.discovered_targets.values():
+ for nodeinfo in target_nodes:
+ if nodeinfo[0] is node:
+ nodeinfo[1] = active
+ return True
+ return False
+
+
def _startIBFT(self):
+>>>>>>> master
if not flags.ibft:
return
try:
found_nodes = libiscsi.discover_firmware()
except Exception:
+ log.info("iscsi: No IBFT info found.");
# an exception here means there is no ibft firmware, just return
return
for node in found_nodes:
try:
node.login()
- log.info("iscsi._startIBFT logged in to %s %s %s" % (node.name, node.address, node.port))
- self.nodes.append(node)
+ log.info("iscsi IBFT: logged into %s at %s:%s through %s" % (
+ node.name, node.address, node.port, node.iface))
self.ibftNodes.append(node)
except IOError as e:
log.error("Could not log into ibft iscsi target %s: %s" %
@@ -155,6 +194,37 @@ class iscsi(object):
time.sleep(2)
udev_settle()
+ def create_interfaces(self, ifaces):
+ for iface in ifaces:
+ iscsi_iface_name = "iface%d" % len(self.ifaces)
+ #iscsiadm -m iface -I iface0 --op=new
+ iutil.execWithRedirect("iscsiadm",
+ ["-m", "iface", "-I", iscsi_iface_name, "--op=new"],
+ stdout="/dev/tty5",
+ stderr="/dev/tty5")
+ #iscsiadm -m iface -I iface0 --op=update -n iface.net_ifacename -v eth0
+ iutil.execWithRedirect("iscsiadm",
+ ["-m", "iface", "-I", iscsi_iface_name,
+ "--op=update", "-n",
+ "iface.net_ifacename", "-v", iface],
+ stdout="/dev/tty5",
+ stderr="/dev/tty5")
+
+ self.ifaces[iscsi_iface_name] = iface
+ log.debug("created_interface %s:%s" % (iscsi_iface_name, iface))
+
+ def delete_interfaces(self):
+ if not self.ifaces:
+ return None
+ for iscsi_iface_name in self.ifaces:
+ #iscsiadm -m iface -I iface0 --op=delete
+ iutil.execWithRedirect("iscsiadm",
+ ["-m", "iface", "-I", iscsi_iface_name,
+ "--op=delete"],
+ stdout="/dev/tty5",
+ stderr="/dev/tty5")
+ self.ifaces = {}
+
def startup(self):
if self.started:
return
@@ -185,16 +255,16 @@ class iscsi(object):
log.info("iSCSI startup")
iutil.execWithRedirect('modprobe', ['-a'] + ISCSI_MODULES,
stdout="/dev/tty5", stderr="/dev/tty5")
- # brcm_iscsiuio is needed by Broadcom offload cards (bnx2i). Currently
+ # iscsiuio is needed by Broadcom offload cards (bnx2i). Currently
# not present in iscsi-initiator-utils for Fedora.
try:
- brcm_iscsiuio = iutil.find_program_in_path('brcm_iscsiuio',
+ iscsiuio = iutil.find_program_in_path('iscsiuio',
raise_on_error=True)
except RuntimeError:
- log.info("iscsi: brcm_iscsiuio not found.")
+ log.info("iscsi: iscsiuio not found.")
else:
- log.debug("iscsi: brcm_iscsiuio is at %s" % brcm_iscsiuio)
- iutil.execWithRedirect(brcm_iscsiuio, [],
+ log.debug("iscsi: iscsiuio is at %s" % iscsiuio)
+ iutil.execWithRedirect(iscsiuio, [],
stdout="/dev/tty5", stderr="/dev/tty5")
# run the daemon
iutil.execWithRedirect(ISCSID, [],
@@ -207,35 +277,51 @@ class iscsi(object):
def discover(self, ipaddr, port="3260", username=None, password=None,
r_username=None, r_password=None, intf=None):
"""
- Discover iSCSI nodes on the target.
+ Discover iSCSI nodes on the target available for login.
+
+ If we are logged in a node discovered for specified target
+ do not do the discovery again as it can corrupt credentials
+ stored for the node (setAuth and getAuth are using database
+ in /var/lib/iscsi/nodes which is filled by discovery). Just
+ return nodes obtained and stored in the first discovery
+ instead.
- Returns list of new found nodes.
+ Returns list of nodes user can log in.
"""
authinfo = None
- found = 0
- logged_in = 0
if not has_iscsi():
raise IOError, _("iSCSI not available")
if self._initiator == "":
raise ValueError, _("No initiator name set")
- if username or password or r_username or r_password:
- # Note may raise a ValueError
- authinfo = libiscsi.chapAuthInfo(username=username,
- password=password,
- reverse_username=r_username,
- reverse_password=r_password)
- self.startup()
-
- # Note may raise an IOError
- found_nodes = libiscsi.discover_sendtargets(address=ipaddr,
- port=int(port),
- authinfo=authinfo)
- if found_nodes is None:
- return []
+ if self.active_nodes((ipaddr, port)):
+ log.debug("iSCSI: skipping discovery of %s:%s due to active nodes" %
+ (ipaddr, port))
+ else:
+ if username or password or r_username or r_password:
+ # Note may raise a ValueError
+ authinfo = libiscsi.chapAuthInfo(username=username,
+ password=password,
+ reverse_username=r_username,
+ reverse_password=r_password)
+ self.startup()
+
+ # Note may raise an IOError
+ found_nodes = libiscsi.discover_sendtargets(address=ipaddr,
+ port=int(port),
+ authinfo=authinfo)
+ if found_nodes is None:
+ return None
+ self.discovered_targets[(ipaddr, port)] = []
+ for node in found_nodes:
+ self.discovered_targets[(ipaddr, port)].append([node, False])
+ log.debug("discovered iSCSI node: %s" % node.name)
+
# only return the nodes we are not logged into yet
- return [n for n in found_nodes if n not in self.nodes]
+ return [node for (node, logged_in) in
+ self.discovered_targets[(ipaddr, port)]
+ if not logged_in]
def log_into_node(self, node, username=None, password=None,
r_username=None, r_password=None, intf=None):
@@ -259,10 +345,10 @@ class iscsi(object):
node.setAuth(authinfo)
node.login()
rc = True
- log.info("iSCSI: logged into %s %s:%s" % (node.name,
- node.address,
- node.port))
- self.nodes.append(node)
+ log.info("iSCSI: logged into %s at %s:%s through %s" % (
+ node.name, node.address, node.port, node.iface))
+ if not self._mark_node_active(node):
+ log.error("iSCSI: node not found among discovered")
except (IOError, ValueError) as e:
msg = str(e)
log.warning("iSCSI: could not log into %s: %s" % (node.name, msg))
@@ -271,22 +357,70 @@ class iscsi(object):
return (rc, msg)
+ # NOTE: the same credentials are used for discovery and login
+ # (unlike in UI)
+ def addTarget(self, ipaddr, port="3260", user=None, pw=None,
+ user_in=None, pw_in=None, intf=None, target=None, iface=None):
+ found = 0
+ logged_in = 0
+
+ found_nodes = self.discover(ipaddr, port, user, pw, user_in, pw_in,
+ intf)
+ if found_nodes == None:
+ raise IOError, _("No iSCSI nodes discovered")
+
+ for node in found_nodes:
+ if target and target != node.name:
+ log.debug("iscsi: skipping logging to iscsi node '%s'" %
+ node.name)
+ continue
+ if iface:
+ node_net_iface = self.ifaces.get(node.iface, node.iface)
+ if iface != node_net_iface:
+ log.debug("iscsi: skipping logging to iscsi node '%s' via %s" %
+ (node.name, node_net_iface))
+ continue
+
+ found = found + 1
+
+ (rc, msg) = self.log_into_node(node, user, pw, user_in, pw_in,
+ intf)
+ if rc:
+ logged_in = logged_in +1
+
+ if found == 0:
+ raise IOError, _("No new iSCSI nodes discovered")
+
+ if logged_in == 0:
+ raise IOError, _("Could not log in to any of the discovered nodes")
+
+ self.stabilize(intf)
+
def writeKS(self, f):
+
if not self.initiatorSet:
return
- f.write("iscsiname %s\n" %(self.initiator,))
- for n in self.nodes:
- f.write("iscsi --ipaddr %s --port %s --target %s" %
- (n.address, n.port, n.name))
+
+ nodes = ""
+ for n in self.active_nodes():
+ if n in self.ibftNodes:
+ continue
+ nodes += "iscsi --ipaddr %s --port %s --target %s" % (n.address, n.port, n.name)
+ if n.iface != "default":
+ nodes += " --iface %s" % self.ifaces[n.iface]
auth = n.getAuth()
if auth:
- f.write(" --user %s" % auth.username)
- f.write(" --password %s" % auth.password)
+ nodes += " --user %s" % auth.username
+ nodes += " --password %s" % auth.password
if len(auth.reverse_username):
- f.write(" --reverse-user %s" % auth.reverse_username)
+ nodes += " --reverse-user %s" % auth.reverse_username
if len(auth.reverse_password):
- f.write(" --reverse-password %s" % auth.reverse_password)
- f.write("\n")
+ nodes += " --reverse-password %s" % auth.reverse_password
+ nodes += "\n"
+
+ if nodes:
+ f.write("iscsiname %s\n" %(self.initiator,))
+ f.write("%s" % nodes)
def write(self, storage):
if not self.initiatorSet:
@@ -294,7 +428,7 @@ class iscsi(object):
# set iscsi nodes to autostart
root = storage.rootDevice
- for node in self.nodes:
+ for node in self.active_nodes():
autostart = True
disks = self.getNodeDisks(node, storage)
for disk in disks:
@@ -318,10 +452,10 @@ class iscsi(object):
shutil.copytree("/var/lib/iscsi", ROOT_PATH + "/var/lib/iscsi",
symlinks=True)
- def getNode(self, name, address, port):
- for node in self.nodes:
+ def getNode(self, name, address, port, iface):
+ for node in self.active_nodes():
if node.name == name and node.address == address and \
- node.port == int(port):
+ node.port == int(port) and node.iface == iface:
return node
return None
diff --git a/pyanaconda/storage/partitioning.py b/pyanaconda/storage/partitioning.py
index 851e44562..7b051236b 100644
--- a/pyanaconda/storage/partitioning.py
+++ b/pyanaconda/storage/partitioning.py
@@ -63,13 +63,13 @@ def _getCandidateDisks(storage):
return disks
-def _schedulePVs(storage, disks):
- """ Schedule creation of an lvm pv partition on each disk in disks. """
- # create a separate pv partition for each disk with free space
+def _scheduleImplicitPartitions(storage, disks):
+ """ Schedule creation of a lvm/btrfs partition on each disk in disks. """
+ # create a separate pv or btrfs partition for each disk with free space
devs = []
- # only schedule PVs if there are LV autopart reqs
- if not storage.lvmAutoPart:
+ # only schedule the partitions if either lvm or btrfs autopart was chosen
+ if storage.autoPartType not in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_BTRFS):
return devs
for disk in disks:
@@ -78,7 +78,10 @@ def _schedulePVs(storage, disks):
fmt_args = {"escrow_cert": storage.autoPartEscrowCert,
"add_backup_passphrase": storage.autoPartAddBackupPassphrase}
else:
- fmt_type = "lvmpv"
+ if storage.autoPartType == AUTOPART_TYPE_LVM:
+ fmt_type = "lvmpv"
+ else:
+ fmt_type = "btrfs"
fmt_args = {}
part = storage.newPartition(fmt_type=fmt_type,
fmt_args=fmt_args,
@@ -120,7 +123,8 @@ def _schedulePartitions(storage, disks):
# First pass is for partitions only. We'll do LVs later.
#
for request in storage.autoPartitionRequests:
- if request.asVol and storage.lvmAutoPart:
+ if (request.lv and storage.autoPartType == AUTOPART_TYPE_LVM) or \
+ (request.btr and storage.autoPartType == AUTOPART_TYPE_BTRFS):
continue
if request.requiredSpace and request.requiredSpace > free:
@@ -132,7 +136,8 @@ def _schedulePartitions(storage, disks):
else:
request.fstype = storage.defaultFSType
- elif request.fstype in ("prepboot", "efi", "biosboot") and stage1_device:
+ elif request.fstype in ("prepboot", "efi", "biosboot", "hfs+") and \
+ stage1_device:
# there should never be a need for more than one of these
# partitions, so skip them.
log.info("skipping unneeded stage1 %s request" % request.fstype)
@@ -179,16 +184,27 @@ def _schedulePartitions(storage, disks):
# make sure preexisting broken lvm/raid configs get out of the way
return
-def _scheduleLVs(storage, devs):
- """ Schedule creation of autopart lvm lvs. """
+def _scheduleVolumes(storage, devs):
+ """ Schedule creation of autopart lvm/btrfs volumes. """
if not devs:
return
+ if storage.autoPartType == AUTOPART_TYPE_LVM:
+ new_container = storage.newVG
+ new_volume = storage.newLV
+ format_name = "lvmpv"
+ parent_kw = "pvs"
+ else:
+ new_container = storage.newBTRFS
+ new_volume = storage.newBTRFS
+ format_name = "btrfs"
+ parent_kw = "parents"
+
if storage.encryptedAutoPart:
pvs = []
for dev in devs:
pv = LUKSDevice("luks-%s" % dev.name,
- format=getFormat("lvmpv", device=dev.path),
+ format=getFormat(format_name, device=dev.path),
size=dev.size,
parents=dev)
pvs.append(pv)
@@ -197,10 +213,8 @@ def _scheduleLVs(storage, devs):
pvs = devs
# create a vg containing all of the autopart pvs
- vg = storage.newVG(pvs=pvs)
- storage.createDevice(vg)
-
- initialVGSize = vg.size
+ container = new_container(**{parent_kw: pvs})
+ storage.createDevice(container)
#
# Convert storage.autoPartitionRequests into Device instances and
@@ -208,28 +222,46 @@ def _scheduleLVs(storage, devs):
#
# Second pass, for LVs only.
for request in storage.autoPartitionRequests:
- if not request.asVol or not storage.lvmAutoPart:
+ btr = storage.autoPartType == AUTOPART_TYPE_BTRFS and request.btr
+ lv = storage.autoPartType == AUTOPART_TYPE_LVM and request.lv
+
+ if not btr and not lv:
continue
- if request.requiredSpace and request.requiredSpace > initialVGSize:
+ # required space isn't relevant on btrfs
+ if lv and \
+ request.requiredSpace and request.requiredSpace > container.size:
continue
if request.fstype is None:
- request.fstype = storage.defaultFSType
+ if btr:
+ # btrfs volumes can only contain btrfs filesystems
+ request.fstype = "btrfs"
+ else:
+ request.fstype = storage.defaultFSType
# This is a little unfortunate but let the backend dictate the rootfstype
# so that things like live installs can do the right thing
+ # XXX FIXME: yes, unfortunate. Disallow btrfs autopart on live install.
if request.mountpoint == "/" and storage.liveImage:
+ if btr:
+ raise PartitioningError("live install can't do btrfs autopart")
+
request.fstype = storage.liveImage.format.type
- # FIXME: move this to a function and handle exceptions
- dev = storage.newLV(vg=vg,
- fmt_type=request.fstype,
- mountpoint=request.mountpoint,
- grow=request.grow,
- maxsize=request.maxSize,
- size=request.size,
- singlePV=request.singlePV)
+ kwargs = {"mountpoint": request.mountpoint,
+ "fmt_type": request.fstype}
+ if lv:
+ kwargs.update({"vg": container,
+ "grow": request.grow,
+ "maxsize": request.maxSize,
+ "size": request.size,
+ "singlePV": request.singlePV})
+ else:
+ kwargs.update({"parents": [container],
+ "subvol": True})
+
+ dev = new_volume(**kwargs)
# schedule the device for creation
storage.createDevice(dev)
@@ -237,8 +269,8 @@ def _scheduleLVs(storage, devs):
def scheduleShrinkActions(storage):
""" Schedule actions to shrink partitions as per autopart selection. """
for (path, size) in storage.shrinkPartitions.items():
- device = storage.devicetree.getDeviceByPath(path)
- if not device:
+ device = storage.devicetree.getDeviceByPath(path, preferLeaves=False)
+ if not device or not isinstance(device, PartitionDevice):
raise StorageError("device %s scheduled for shrink disappeared"
% path)
storage.devicetree.registerAction(ActionResizeFormat(device, size))
@@ -267,7 +299,7 @@ def doAutoPartition(storage, data):
clearPartitions(storage)
disks = _getCandidateDisks(storage)
- devs = _schedulePVs(storage, disks)
+ devs = _scheduleImplicitPartitions(storage, disks)
log.debug("candidate disks: %s" % disks)
log.debug("devs: %s" % devs)
@@ -281,7 +313,7 @@ def doAutoPartition(storage, data):
doPartitioning(storage)
if storage.doAutoPart:
- _scheduleLVs(storage, devs)
+ _scheduleVolumes(storage, devs)
# grow LVs
growLVM(storage)
@@ -309,8 +341,8 @@ def doAutoPartition(storage, data):
storage.reset()
raise exn
-def shouldClear(device, clearPartType, clearPartDisks=None):
- if clearPartType not in [CLEARPART_TYPE_LINUX, CLEARPART_TYPE_ALL]:
+def shouldClear(device, clearPartType, clearPartDisks=None, clearPartDevices=None):
+ if clearPartType in [CLEARPART_TYPE_NONE, None]:
return False
if isinstance(device, PartitionDevice):
@@ -356,8 +388,31 @@ def shouldClear(device, clearPartType, clearPartDisks=None):
if device.protected:
return False
+ if clearPartType == CLEARPART_TYPE_LIST and \
+ not clearPartDevices or device.name not in clearPartDevices:
+ return False
+
return True
+def recursiveRemove(storage, device):
+ log.debug("clearing %s" % device.name)
+
+ # XXX is there any argument for not removing incomplete devices?
+ # -- maybe some RAID devices
+ devices = storage.deviceDeps(device)
+ while devices:
+ log.debug("devices to remove: %s" % ([d.name for d in devices],))
+ leaves = [d for d in devices if d.isleaf]
+ log.debug("leaves to remove: %s" % ([d.name for d in leaves],))
+ for leaf in leaves:
+ storage.destroyDevice(leaf)
+ devices.remove(leaf)
+
+ if device.isDisk:
+ storage.destroyFormat(device)
+ else:
+ storage.destroyDevice(device)
+
def clearPartitions(storage):
""" Clear partitions and dependent devices from disks.
@@ -390,45 +445,33 @@ def clearPartitions(storage):
partitions.sort(key=lambda p: p.partedPartition.number, reverse=True)
for part in partitions:
log.debug("clearpart: looking at %s" % part.name)
- if not shouldClear(part, storage.config.clearPartType, storage.config.clearPartDisks):
+ if not shouldClear(part, storage.config.clearPartType, storage.config.clearPartDisks, storage.config.clearPartDevices):
continue
- log.debug("clearing %s" % part.name)
+ recursiveRemove(storage, part)
+ log.debug("partitions: %s" % [p.getDeviceNodeName() for p in part.partedPartition.disk.partitions])
+
+ # now clear devices that are not partitions and were not implicitly cleared
+ # when clearing partitions
+ not_visited = [d for d in storage.devices if not d.partitioned]
+ while not_visited:
+ for device in [d for d in not_visited if d.isleaf]:
+ not_visited.remove(device)
- # XXX is there any argument for not removing incomplete devices?
- # -- maybe some RAID devices
- devices = storage.deviceDeps(part)
- while devices:
- log.debug("devices to remove: %s" % ([d.name for d in devices],))
- leaves = [d for d in devices if d.isleaf]
- log.debug("leaves to remove: %s" % ([d.name for d in leaves],))
- for leaf in leaves:
- storage.destroyDevice(leaf)
- devices.remove(leaf)
+ if not shouldClear(device, storage.config.clearPartType, storage.config.clearPartDisks, storage.config.clearPartDevices):
+ continue
- log.debug("partitions: %s" % [p.getDeviceNodeName() for p in part.partedPartition.disk.partitions])
- storage.destroyDevice(part)
-
- for disk in [d for d in storage.disks if d not in storage.partitioned]:
- # clear any whole-disk formats that need clearing
- if shouldClear(disk, storage.config.clearPartType, storage.config.clearPartDisks):
- log.debug("clearing %s" % disk.name)
- devices = storage.deviceDeps(disk)
- while devices:
- log.debug("devices to remove: %s" % ([d.name for d in devices],))
- leaves = [d for d in devices if d.isleaf]
- log.debug("leaves to remove: %s" % ([d.name for d in leaves],))
- for leaf in leaves:
- storage.destroyDevice(leaf)
- devices.remove(leaf)
-
- destroy_action = ActionDestroyFormat(disk)
- labelType = storage.platform.bestDiskLabelType(disk)
- newLabel = getFormat("disklabel", device=disk.path,
- labelType=labelType)
- create_action = ActionCreateFormat(disk, format=newLabel)
- storage.devicetree.registerAction(destroy_action)
- storage.devicetree.registerAction(create_action)
+ recursiveRemove(storage, device)
+
+ # put disklabels on unpartitioned disks
+ if device.isDisk and not d.partitioned:
+ labelType = storage.platform.bestDiskLabelType(disk)
+ newLabel = getFormat("disklabel", device=disk.path,
+ labelType=labelType)
+ create_action = ActionCreateFormat(disk, format=newLabel)
+ storage.devicetree.registerAction(create_action)
+
+ not_visited = [d for d in storage.devices if d in not_visited]
# now remove any empty extended partitions
removeEmptyExtendedPartitions(storage)
@@ -437,10 +480,11 @@ def clearPartitions(storage):
# we're going to completely clear it.
boot_disk = storage.bootDisk
for disk in storage.partitioned:
- if not boot_disk:
+ if not boot_disk and not storage.config.reinitializeDisks:
break
- if disk != boot_disk:
+ if not storage.config.reinitializeDisks and \
+ (boot_disk is not None and disk != boot_disk):
continue
if storage.config.clearPartType != CLEARPART_TYPE_ALL or \
diff --git a/pyanaconda/storage/partspec.py b/pyanaconda/storage/partspec.py
index 1602b8a29..97cb33ec3 100644
--- a/pyanaconda/storage/partspec.py
+++ b/pyanaconda/storage/partspec.py
@@ -21,25 +21,27 @@
class PartSpec(object):
def __init__(self, mountpoint=None, fstype=None, size=None, maxSize=None,
- grow=False, asVol=False, singlePV=False, weight=0,
+ grow=False, btr=False, lv=False, singlePV=False, weight=0,
requiredSpace=0, encrypted=False):
""" Create a new storage specification. These are used to specify
the default partitioning layout as an object before we have the
storage system up and running. The attributes are obvious
except for the following:
- asVol -- Should this be allocated as a logical volume? If not,
- it will be allocated as a partition.
+ btr -- Should this be allocated as a btrfs subvolume? If not,
+ it will be allocated as a partition.
+ lv -- Should this be allocated as a logical volume? If not,
+ it will be allocated as a partition.
singlePV -- Should this logical volume map to a single physical
- volume in the volume group? Implies asVol=True
+ volume in the volume group? Implies lv=True
weight -- An integer that modifies the sort algorithm for partition
requests. A larger value means the partition will end up
closer to the front of the disk. This is mainly used to
make sure /boot ends up in front, and any special (PReP,
appleboot, etc.) partitions end up in front of /boot.
- This value means nothing if asVol=False.
+ This value means nothing unless lv and btr are both False.
requiredSpace -- This value is only taken into account if
- asVol=True, and specifies the size in MB that the
+ lv=True, and specifies the size in MB that the
containing VG must be for this PartSpec to even
get used. The VG's size is calculated before any
other LVs are created inside it. If not enough
@@ -55,22 +57,24 @@ class PartSpec(object):
self.size = size
self.maxSize = maxSize
self.grow = grow
- self.asVol = asVol
+ self.lv = lv
+ self.btr = btr
self.singlePV = singlePV
self.weight = weight
self.requiredSpace = requiredSpace
self.encrypted = encrypted
- if self.singlePV and not self.asVol:
- self.asVol = True
+ if self.singlePV and not self.lv:
+ self.lv = True
def __str__(self):
s = ("%(type)s instance (%(id)s) -- \n"
- " mountpoint = %(mountpoint)s asVol = %(asVol)s singlePV = %(singlePV)s\n"
+ " mountpoint = %(mountpoint)s lv = %(lv)s singlePV = %(singlePV)s"
+ " btrfs = %(btrfs)s\n"
" weight = %(weight)s fstype = %(fstype)s encrypted = %(enc)s\n"
" size = %(size)s maxSize = %(maxSize)s grow = %(grow)s\n" %
{"type": self.__class__.__name__, "id": "%#x" % id(self),
- "mountpoint": self.mountpoint, "asVol": self.asVol,
+ "mountpoint": self.mountpoint, "lv": self.lv, "btrfs": self.btr,
"singlePV": self.singlePV, "weight": self.weight,
"fstype": self.fstype, "size": self.size, "enc": self.encrypted,
"maxSize": self.maxSize, "grow": self.grow})
diff --git a/pyanaconda/storage/udev.py b/pyanaconda/storage/udev.py
index 746497465..09e9c25dd 100644
--- a/pyanaconda/storage/udev.py
+++ b/pyanaconda/storage/udev.py
@@ -320,7 +320,10 @@ def udev_device_get_md_name(info):
return info.get("MD_DEVNAME")
def udev_device_get_vg_name(info):
- return info.get('LVM2_VG_NAME', info.get('DM_VG_NAME'))
+ return info['LVM2_VG_NAME']
+
+def udev_device_get_lv_vg_name(info):
+ return info['DM_VG_NAME']
def udev_device_get_vg_uuid(info):
return info['LVM2_VG_UUID']
@@ -553,6 +556,19 @@ def udev_device_get_iscsi_port(info):
# IPV6 contains : within the address, the part after the last : is the port
return path_components[address_field].split(":")[-1]
+def udev_device_get_iscsi_nic(info):
+ # '/devices/pci0000:00/0000:00:02.0/0000:09:00.0/0000:0a:01.0/0000:0e:00.2/host3/session1/target3:0:0/3:0:0:0/block/sda'
+ # The position of sessionX part depends on device
+ # (e.g. offload vs. sw; also varies for different offload devs)
+ match = re.match('/.*/(session\d+)', info["sysfs_path"])
+ if match:
+ session = match.groups()[0]
+ iface = open("/sys/class/iscsi_session/%s/ifacename" %
+ session).read().strip()
+ else:
+ iface = None
+ return iface
+
# fcoe disks have ID_PATH in the form of:
# For FCoE directly over the NIC (so no VLAN and thus no DCB):
# pci-eth#-fc-${id}
diff --git a/pyanaconda/textw/add_drive_text.py b/pyanaconda/textw/add_drive_text.py
index 72f9b463b..b28af4fa1 100644
--- a/pyanaconda/textw/add_drive_text.py
+++ b/pyanaconda/textw/add_drive_text.py
@@ -187,7 +187,7 @@ class iSCSITextWizard(pih.iSCSIWizard):
# should never stop us:
return True
- def display_nodes_dialog(self, found_nodes):
+ def display_nodes_dialog(self, found_nodes, ifaces):
grid_height = 4
basic_grid = None
if self.listbox_login.current() not in \
@@ -210,7 +210,10 @@ class iSCSITextWizard(pih.iSCSIWizard):
# unfortunately, Listbox.add won't accept node directly as the second
# argument, we have to remember the list and use an index
for i, node in enumerate(found_nodes):
- listbox.append(node.name, i, selected=True)
+ node_description = "%s via %s" % (node.name,
+ ifaces.get(node.iface,
+ node.iface))
+ listbox.append(node_description, i, selected=True)
grid.add(listbox, 0, 1, padding=(0, 1, 0, 1))
if basic_grid:
@@ -229,7 +232,8 @@ class iSCSITextWizard(pih.iSCSIWizard):
if i in listbox.getSelection()]
return (rc, selected_nodes)
- def display_success_dialog(self, success_nodes, fail_nodes, fail_reason):
+ def display_success_dialog(self, success_nodes, fail_nodes, fail_reason,
+ ifaces):
buttons = [TEXT_OK_BUTTON]
msg = _("Successfully logged into all the selected nodes.")
msg_reason = _("Reason:")
@@ -294,7 +298,13 @@ class addDriveDialog(object):
def addDriveDialog(self, screen):
newdrv = []
if iscsi.has_iscsi():
- newdrv.append("Add iSCSI target")
+ if iscsi.iscsi().mode == "none":
+ newdrv.append("Add iSCSI target")
+ newdrv.append("Add iSCSI target - use interface binding")
+ elif iscsi.iscsi().mode == "bind":
+ newdrv.append("Add iSCSI target - use interface binding")
+ elif iscsi.iscsi().mode == "default":
+ newdrv.append("Add iSCSI target")
if iutil.isS390():
newdrv.append( "Add zFCP LUN" )
if fcoe.has_fcoe():
@@ -325,9 +335,10 @@ class addDriveDialog(object):
except ValueError as e:
ButtonChoiceWindow(screen, _("Error"), str(e))
return INSTALL_BACK
- else:
+ elif newdrv[choice].startswith("Add iSCSI target"):
+ bind = newdrv[choice] == "Add iSCSI target - use interface binding"
try:
- return self.addIscsiDriveDialog(screen)
+ return self.addIscsiDriveDialog(screen, bind)
except (ValueError, IOError) as e:
ButtonChoiceWindow(screen, _("Error"), str(e))
return INSTALL_BACK
@@ -383,9 +394,11 @@ class addDriveDialog(object):
dcbCheckbox = Checkbox(_("Use DCB"), 1)
grid.add(dcbCheckbox, 0, 2, anchorLeft = 1)
+ autovlanCheckbox = Checkbox(_("Use auto vlan"), 1)
+ grid.add(autovlanCheckbox, 0, 3, anchorLeft = 1)
buttons = ButtonBar(screen, [TEXT_OK_BUTTON, TEXT_BACK_BUTTON] )
- grid.add(buttons, 0, 3, anchorLeft = 1, growx = 1)
+ grid.add(buttons, 0, 4, anchorLeft = 1, growx = 1)
result = grid.run()
if buttons.buttonPressed(result) == TEXT_BACK_CHECK:
@@ -394,14 +407,15 @@ class addDriveDialog(object):
nic = interfaceList.current()
dcb = dcbCheckbox.selected()
+ auto_vlan = autovlanCheckbox.selected()
- fcoe.fcoe().addSan(nic=nic, dcb=dcb,
+ fcoe.fcoe().addSan(nic=nic, dcb=dcb, auto_vlan=auto_vlan,
intf=self.anaconda.intf)
screen.popWindow()
return INSTALL_OK
- def addIscsiDriveDialog(self, screen):
+ def addIscsiDriveDialog(self, screen, bind=False):
if not network.hasActiveNetDev():
ButtonChoiceWindow(screen, _("Error"),
"Must have a network configuration set up "
@@ -410,6 +424,15 @@ class addDriveDialog(object):
log.info("addIscsiDriveDialog(): early exit, network disabled.")
return INSTALL_BACK
+ # This will modify behaviour of iscsi.discovery() function
+ if iscsi.iscsi().mode == "none" and not bind:
+ iscsi.iscsi().delete_interfaces()
+ elif (iscsi.iscsi().mode == "none" and bind) \
+ or iscsi.iscsi().mode == "bind":
+ active = set(network.getActiveNetDevs())
+ created = set(iscsi.iscsi().ifaces.values())
+ iscsi.iscsi().create_interfaces(active - created)
+
wizard = iSCSITextWizard(screen)
login_ok_nodes = pih.drive_iscsi_addition(self.anaconda, wizard)
if len(login_ok_nodes):
diff --git a/pyanaconda/textw/language_text.py b/pyanaconda/textw/language_text.py
index 7bbdf1e07..81461bd56 100644
--- a/pyanaconda/textw/language_text.py
+++ b/pyanaconda/textw/language_text.py
@@ -63,7 +63,6 @@ class LanguageWindow:
anaconda.instLanguage.instLang = choice
anaconda.instLanguage.systemLang = choice
- anaconda.instLanguage.buildLocale()
anaconda.timezone.setTimezoneInfo(anaconda.instLanguage.getDefaultTimeZone())
anaconda.intf.drawFrame()
diff --git a/pyanaconda/textw/upgrade_bootloader_text.py b/pyanaconda/textw/upgrade_bootloader_text.py
index 10c0ccef1..78740cb5d 100644
--- a/pyanaconda/textw/upgrade_bootloader_text.py
+++ b/pyanaconda/textw/upgrade_bootloader_text.py
@@ -19,10 +19,9 @@
# Author(s): Jeremy Katz <katzj@redhat.com>
#
-from snack import *
-from constants_text import *
-from pyanaconda.flags import flags
-from pyanaconda.constants import *
+import snack
+from constants_text import INSTALL_OK, INSTALL_BACK, TEXT_BACK_CHECK
+from constants_text import TEXT_OK_BUTTON, TEXT_BACK_BUTTON
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
@@ -34,47 +33,29 @@ class UpgradeBootloaderWindow:
def __call__(self, screen, anaconda):
self.screen = screen
- self.type = None
- self.bootDev = None
+ self.dispatch = anaconda.dispatch
+ self.anaconda = anaconda
- blradio = RadioGroup()
-
- update = False
- nobl = False
- if anaconda.dispatch.step_disabled("instbootloader"):
+ (newbl, nobl) = (False, False)
+ if self.dispatch.step_enabled("bootloader"):
+ newbl = True
+ elif self.dispatch.step_disabled("instbootloader"):
nobl = True
- elif self.type and self.bootDev:
- update = True
-
- if (not anaconda.bootloader.can_update) or \
- (self.type is None or self.bootDev is None):
- t = TextboxReflowed(53,
- _("The installer is unable to detect the boot loader "
- "currently in use on your system."))
-
- self.update_radio = blradio.add(_("Update boot loader configuration"),
- "update", update)
- self.update_radio.w.checkboxSetFlags(FLAG_DISABLED, FLAGS_SET)
else:
- t = TextboxReflowed(53,
- _("The installer has detected the %(type)s "
- "boot loader currently installed on "
- "%(bootDev)s.")
- % {'type': self.type, 'bootDev': self.bootDev})
-
- self.update_radio = blradio.add(_("Update boot loader configuration"),
- "update", update)
+ newbl = True
- self.nobl_radio = blradio.add(_("Skip boot loader updating"),
+ blradio = snack.RadioGroup()
+ self.newbl_radio = blradio.add(_("_Create new boot loader configuration"),
+ "newbl", newbl)
+ self.nobl_radio = blradio.add(_("_Skip boot loader updating"),
"nobl", nobl)
- buttons = ButtonBar(screen, [TEXT_OK_BUTTON, TEXT_BACK_BUTTON])
+ buttons = snack.ButtonBar(screen, [TEXT_OK_BUTTON, TEXT_BACK_BUTTON])
- grid = GridFormHelp(screen, _("Upgrade Boot Loader Configuration"),
- "bl-upgrade", 1, 5)
+ grid = snack.GridFormHelp(screen, _("Upgrade Boot Loader Configuration"),
+ "bl-upgrade", 1, 5)
- grid.add(t, 0, 0, (0,0,0,1))
- grid.add(self.update_radio, 0, 1, (0,0,0,0))
+ grid.add(self.newbl_radio, 0, 1, (0,0,0,0))
grid.add(self.nobl_radio, 0, 2, (0,0,0,0))
grid.add(buttons, 0, 3, growx = 1)
@@ -88,13 +69,13 @@ class UpgradeBootloaderWindow:
return INSTALL_BACK
if blradio.getSelection() == "nobl":
- anaconda.dispatch.skip_steps("bootloader")
- anaconda.dispatch.skip_steps("instbootloader")
- anaconda.bootloader.update_only = False
+ self.dispatch.skip_steps("bootloader")
+ self.dispatch.skip_steps("instbootloader")
+ self.anaconda.bootloader.skip_bootloader = True
else:
- anaconda.dispatch.skip_steps("bootloader")
- anaconda.dispatch.request_steps("instbootloader")
- anaconda.bootloader.update_only = anaconda.bootloader.can_update
+ self.dispatch.request_steps_gently("bootloader")
+ self.anaconda.bootloader.skip_bootloader = False
screen.popWindow()
return INSTALL_OK
+
diff --git a/pyanaconda/textw/userauth_text.py b/pyanaconda/textw/userauth_text.py
index b488962d3..8008a636c 100644
--- a/pyanaconda/textw/userauth_text.py
+++ b/pyanaconda/textw/userauth_text.py
@@ -19,7 +19,7 @@
from snack import *
from constants_text import *
-import cracklib
+import pwquality
from pyanaconda.constants import *
import gettext
@@ -77,9 +77,10 @@ class RootPasswordWindow:
buttons = [ TEXT_OK_BUTTON ], width = 50)
else:
try:
- cracklib.FascistCheck(entry1.value())
- except ValueError as e:
- msg = gettext.ldgettext("cracklib", e)
+ settings = pwquality.PWQSettings()
+ settings.read_config()
+ settings.check(entry1.value(), None, "root")
+ except pwquality.PWQError as (e, msg):
ret = anaconda.intf.messageWindow(_("Weak Password"),
_("You have provided a weak password: %s\n\n"
"Would you like to continue with this password?"
diff --git a/pyanaconda/upgrade.py b/pyanaconda/upgrade.py
index 44c08c116..6b128559b 100644
--- a/pyanaconda/upgrade.py
+++ b/pyanaconda/upgrade.py
@@ -82,11 +82,18 @@ def findRootParts(anaconda):
if notUpgradable and not anaconda.rootParts:
oldInstalls = ""
- for info in notUpgradable:
- if None in info[:2]:
- oldInstalls += _("Unknown release on %s") % (info[2])
+ for product, version, arch, name, tests in notUpgradable:
+ if None in (product, version):
+ oldInstalls = _("Unknown release on %s -") % (name)
else:
- oldInstalls += "%s %s on %s" % (info)
+ oldInstalls = "%s %s %s on %s -" % (product, version, arch, name)
+
+ if not tests["product"]:
+ oldInstalls += _(" Product mismatch.")
+ if not tests["version"]:
+ oldInstalls += _(" Version mismatch.")
+ if not tests["arch"]:
+ oldInstalls += _(" Architecture mismatch.")
oldInstalls += "\n"
rc = anaconda.intf.messageWindow(_("Cannot Upgrade"),
_("Your current installation cannot be upgraded. This "
@@ -256,6 +263,38 @@ def upgradeMountFilesystems(anaconda):
except Exception as e:
log.warning("error checking selinux state: %s" %(e,))
+def upgradeUsr(anaconda):
+ """
+ Handle the upgrade of /bin, /sbin, /lib, /lib64 to symlinks into /usr/
+ This uses dracut's convertfs module
+ """
+ dirs = ["/bin", "/sbin", "/lib", "/lib64"]
+ dirs = [ROOT_PATH+d for d in dirs]
+ if all(map(os.path.islink, dirs)):
+ log.info("upgradeusr dirs are already symlinks")
+ return
+
+ if anaconda.intf is not None:
+ w = anaconda.intf.waitWindow(_("Upgrade /usr symlinks"),
+ _("Running /usr merge script"))
+
+ if iutil.execWithRedirect("/usr/lib/dracut/modules.d/30convertfs/convertfs.sh",
+ [ROOT_PATH],
+ stdout="/dev/tty5", stderr="/dev/tty5"):
+ log.error("convertfs failed")
+
+ if anaconda.intf is not None:
+ w.pop()
+ rc = anaconda.intf.messageWindow(_("/usr merge failed"),
+ _("The /usr merge script failed. This is required"
+ " for Fedora 17 to work. The upgrade cannot continue."
+ "\n\n"))
+ sys.exit(0)
+ log.info("convertfs was successful")
+
+ if anaconda.intf is not None:
+ w.pop()
+
def setSteps(anaconda):
dispatch = anaconda.dispatch
dispatch.reset_scheduling() # scrap what is scheduled
@@ -278,6 +317,7 @@ def setSteps(anaconda):
"upgrademigratefs",
"enablefilesystems",
"upgradecontinue",
+ "upgradeusr",
"reposetup",
"upgbootloader",
"postselection",
diff --git a/pyanaconda/vnc.py b/pyanaconda/vnc.py
index 47e42eb29..a7ccc7643 100644
--- a/pyanaconda/vnc.py
+++ b/pyanaconda/vnc.py
@@ -44,7 +44,7 @@ class VncServer:
def __init__(self, display="1", root="/", ip=None, name=None,
desktop="Desktop", password="", vncconnecthost="",
vncconnectport="", log_file="/tmp/vncserver.log",
- pw_file="/tmp/vncpassword", pw_init_file="/tmp/vncpassword.dat"):
+ pw_file="/tmp/vncpassword"):
self.display = display
self.root = root
self.ip = ip
@@ -55,27 +55,10 @@ class VncServer:
self.vncconnectport = vncconnectport
self.log_file = log_file
self.pw_file = pw_file
- self.pw_init_file = pw_init_file
self.connxinfo = None
self.anaconda = None
self.log = logging.getLogger("anaconda.stdout")
- def recoverVNCPassword(self):
- """Rescue the vncpassword from where loader left it
-
- We are not to check for validity yet, if there is a file
- pass it to the variable, if there is not, set the var
- to ''. We will check valididty later.
- """
- # see if there is a vnc password file
- try:
- pfile = open(self.pw_init_file, "r")
- self.password=pfile.readline().strip()
- pfile.close()
- os.unlink(self.pw_init_file)
- except (OSError, IOError):
- self.password=""
-
def setVNCPassword(self):
"""Set the vnc server password. Output to file. """
@@ -115,9 +98,18 @@ class VncServer:
ips))
self.ip = ips[0]
- self.name = network.getDefaultHostname(self.anaconda)
ipstr = self.ip
+ try:
+ hinfo = socket.gethostbyaddr(ipstr)
+ except Exception as e:
+ log.debug("Exception caught trying to get host name of %s: %s" %
+ (ipstr, e))
+ self.name = network.getDefaultHostname(self.anaconda)
+ else:
+ if len(hinfo) == 3:
+ self.name = hinfo[0]
+
if self.ip.find(':') != -1:
ipstr = "[%s]" % (self.ip,)
@@ -210,7 +202,7 @@ class VncServer:
rc = self.setVNCPassword()
# Lets start the xvnc.
- xvnccommand = [ "Xvnc", ":%s" % self.display, "-nevershared",
+ xvnccommand = [ "Xvnc", ":%s" % self.display,
"-depth", "16", "-br",
"IdleTimeout=0", "-auth", "/dev/null", "-once",
"DisconnectClients=false", "desktop=%s" % (self.desktop,),
diff --git a/pyanaconda/yuminstall.py b/pyanaconda/yuminstall.py
index 413ff10da..0a3f29229 100644
--- a/pyanaconda/yuminstall.py
+++ b/pyanaconda/yuminstall.py
@@ -74,6 +74,9 @@ urlgrabber.grabber.default_grabber.opts.user_agent = "%s (anaconda)/%s" %(produc
import iutil
import isys
+class NoSuchGroup(Exception):
+ pass
+
def size_string (size):
def number_format(s):
return locale.format("%s", s, 1)
@@ -246,11 +249,17 @@ class AnacondaCallback:
# we should only error out for fatal script errors or the cpio and
# unpack problems.
if what != rpm.RPMCALLBACK_SCRIPT_ERROR or total:
+ if what == rpm.RPMCALLBACK_CPIO_ERROR:
+ error_type = _("cpio")
+ elif what == rpm.RPMCALLBACK_UNPACK_ERROR:
+ error_type = _("unpack")
+ else:
+ error_type = _("script")
self.messageWindow(_("Error Installing Package"),
- _("A fatal error occurred when installing the %s "
+ _("A %s error occurred when installing the %s "
"package. This could indicate errors when reading "
"the installation media. Installation cannot "
- "continue.") % name,
+ "continue.") % (error_type, name),
type="custom", custom_icon="error",
custom_buttons=[_("_Exit installer")])
sys.exit(1)
@@ -389,33 +398,17 @@ class AnacondaYum(yum.YumBase):
if verifyMedia(self.tree, None):
return
- dev.format.unmount()
-
- dev.eject()
-
- while True:
- if self.anaconda.intf:
- self.anaconda.intf.beep()
-
- self.anaconda.intf.messageWindow(_("Change Disc"),
- _("Please insert the %(productName)s disc to continue.")
- % {'productName': productName})
+ dev.format.unmount()
- try:
- dev.format.mount()
-
- if verifyMedia(self.tree, self._timestamp):
- break
+ log.error("Wrong disc found on %s" % (self.tree))
+ if self.anaconda.intf:
+ self.anaconda.intf.beep()
- self.anaconda.intf.messageWindow(_("Wrong Disc"),
- _("That's not the correct %s disc.")
- % (productName,))
-
- dev.format.unmount()
- dev.eject()
- except Exception:
- self.anaconda.intf.messageWindow(_("Error"),
- _("Unable to access the disc."))
+ self.messageWindow(_("Wrong Disc"),
+ _("That's not the correct %s disc.") % (productName),
+ type="custom", custom_icon="error",
+ custom_buttons=[_("_Exit installer")])
+ sys.exit(1)
def _mountInstallImage(self):
umountImage(self.tree)
@@ -428,14 +421,16 @@ class AnacondaYum(yum.YumBase):
def configBaseURL(self):
# We only have a methodstr if method= or repo= was passed to
# anaconda. No source for this base repo (the CD media, NFS,
- # whatever) is mounted yet since loader only mounts the source
+ # whatever) is mounted yet since initramfs only mounts the source
# for the stage2 image. We need to set up the source mount
# now.
+ # methodstr == cdrom is a special case, meaning the first cdrom found
+ # by scanning or previously mounted as the install source.
if flags.cmdline.has_key("preupgrade"):
path = "/var/cache/yum/preupgrade"
self.anaconda.methodstr = "hd::%s" % path
self._baseRepoURL = "file:///mnt/sysimage/%s" % path
- elif self.anaconda.methodstr:
+ elif self.anaconda.methodstr and self.anaconda.methodstr != "cdrom":
m = self.anaconda.methodstr
if m.startswith("hd:"):
@@ -470,7 +465,7 @@ class AnacondaYum(yum.YumBase):
(opts, server, path) = iutil.parseNfsUrl(m)
isys.mount(server+":"+path, self.tree, "nfs", options=opts)
- # This really should be fixed in loader instead but for now see
+ # This really should be fixed in initrd instead but for now see
# if there's images and if so go with this being an NFSISO
# install instead.
image = findFirstIsoImage(self.tree, self.anaconda.intf.messageWindow)
@@ -483,6 +478,21 @@ class AnacondaYum(yum.YumBase):
self._mountInstallCD()
self.mediagrabber = self.mediaHandler
self._baseRepoURL = "file://%s" % self.tree
+ elif os.path.isdir("/run/initramfs/live/repodata"):
+ # No methodstr was given. In order to find an installation source
+ # we first check to see if dracut has already mounted the source
+ # on /run/initramfs/live/ and if not we check to see if there's a
+ # CD/DVD with packages on it. If both those fail we default to the
+ # mirrorlist URL. The user can always change the repo with the
+ # repo editor later.
+ isys.mount("/run/initramfs/live/", self.tree, bindMount=True)
+ self.mediagrabber = self.mediaHandler
+ self._baseRepoURL = "file://%s" % self.tree
+ elif os.path.isdir("/run/install/repo/repodata"):
+ # Same hack as above. FIXME: make scanForMedia do this, dammit
+ isys.mount("/run/install/repo", self.tree, bindMount=True)
+ self.mediagrabber = self.mediaHandler
+ self._baseRepoURL = "file://%s" % self.tree
else:
# No methodstr was given. In order to find an installation source,
# we should first check to see if there's a CD/DVD with packages
@@ -591,9 +601,6 @@ class AnacondaYum(yum.YumBase):
dest has dest.proxy set to the host and port (no un/pw)
dest.proxy_username and dest.proxy_password are set if present in src
"""
- # This is the same pattern as from loader/urls.c:splitProxyParam
- # except that the POSIX classes have been replaced with character
- # ranges
# NOTE: If this changes, update tests/regex/proxy.py
#
# proxy=[protocol://][username[:password]@]host[:port][path]
@@ -768,6 +775,10 @@ class AnacondaYum(yum.YumBase):
filelogger.setLevel(logging.INFO)
filelogger.propagate = False
+ def doFileLogSetup(self, uid, logfile):
+ # don't do the file log as it can lead to open fds
+ # being left and an inability to clean up after ourself
+ pass
def doConfigSetup(self, fn='/tmp/anaconda-yum.conf', root='/'):
if hasattr(self, "preconf"):