Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package yast2-installation for openSUSE:Factory checked in at 2021-08-28 22:28:50 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/yast2-installation (Old) and /work/SRC/openSUSE:Factory/.yast2-installation.new.1899 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "yast2-installation" Sat Aug 28 22:28:50 2021 rev:477 rq:914406 version:4.4.17 Changes: -------- --- /work/SRC/openSUSE:Factory/yast2-installation/yast2-installation.changes 2021-08-19 12:49:14.337451964 +0200 +++ /work/SRC/openSUSE:Factory/.yast2-installation.new.1899/yast2-installation.changes 2021-08-28 22:28:57.617964891 +0200 @@ -1,0 +2,13 @@ +Tue Aug 24 14:58:48 UTC 2021 - Stefan Hundhammer <shundham...@suse.com> + +- Refactored umount_finish.rb (bsc#1149980) + More details: https://github.com/yast/yast-installation/pull/975 + - Moved out the unmounting part to a new (testable!) Unmounter class + - Now using a dedicated FinishClient base class + - Killed a lot of YCP zombies + - Removed dead code going back to storage-old + - Modularized the code + - Made the client invokable stand-alone +- 4.4.17 + +------------------------------------------------------------------- Old: ---- yast2-installation-4.4.16.tar.bz2 New: ---- yast2-installation-4.4.17.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ yast2-installation.spec ++++++ --- /var/tmp/diff_new_pack.cGVOn8/_old 2021-08-28 22:28:58.229965526 +0200 +++ /var/tmp/diff_new_pack.cGVOn8/_new 2021-08-28 22:28:58.229965526 +0200 @@ -17,7 +17,7 @@ Name: yast2-installation -Version: 4.4.16 +Version: 4.4.17 Release: 0 Summary: YaST2 - Installation Parts License: GPL-2.0-only ++++++ yast2-installation-4.4.16.tar.bz2 -> yast2-installation-4.4.17.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/package/yast2-installation.changes new/yast2-installation-4.4.17/package/yast2-installation.changes --- old/yast2-installation-4.4.16/package/yast2-installation.changes 2021-08-17 20:51:42.000000000 +0200 +++ new/yast2-installation-4.4.17/package/yast2-installation.changes 2021-08-26 15:46:35.000000000 +0200 @@ -1,4 +1,17 @@ ------------------------------------------------------------------- +Tue Aug 24 14:58:48 UTC 2021 - Stefan Hundhammer <shundham...@suse.com> + +- Refactored umount_finish.rb (bsc#1149980) + More details: https://github.com/yast/yast-installation/pull/975 + - Moved out the unmounting part to a new (testable!) Unmounter class + - Now using a dedicated FinishClient base class + - Killed a lot of YCP zombies + - Removed dead code going back to storage-old + - Modularized the code + - Made the client invokable stand-alone +- 4.4.17 + +------------------------------------------------------------------- Wed Aug 11 20:43:04 UTC 2021 - Dirk M??ller <dmuel...@suse.com> - only list specific files installed in common directories (metainfo, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/package/yast2-installation.spec new/yast2-installation-4.4.17/package/yast2-installation.spec --- old/yast2-installation-4.4.16/package/yast2-installation.spec 2021-08-17 20:51:42.000000000 +0200 +++ new/yast2-installation-4.4.17/package/yast2-installation.spec 2021-08-26 15:46:35.000000000 +0200 @@ -17,7 +17,7 @@ Name: yast2-installation -Version: 4.4.16 +Version: 4.4.17 Release: 0 Summary: YaST2 - Installation Parts License: GPL-2.0-only diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/src/clients/umount_finish.rb new/yast2-installation-4.4.17/src/clients/umount_finish.rb --- old/yast2-installation-4.4.16/src/clients/umount_finish.rb 2021-08-17 20:51:42.000000000 +0200 +++ new/yast2-installation-4.4.17/src/clients/umount_finish.rb 2021-08-26 15:46:35.000000000 +0200 @@ -1,2 +1,4 @@ +require "yast" require "installation/clients/umount_finish" -Yast::UmountFinishClient.new.main + +Installation::Clients::UmountFinishClient.run diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/src/lib/installation/clients/umount_finish.rb new/yast2-installation-4.4.17/src/lib/installation/clients/umount_finish.rb --- old/yast2-installation-4.4.16/src/lib/installation/clients/umount_finish.rb 2021-08-17 20:51:42.000000000 +0200 +++ new/yast2-installation-4.4.17/src/lib/installation/clients/umount_finish.rb 2021-08-26 15:46:35.000000000 +0200 @@ -1,8 +1,7 @@ # encoding: utf-8 - # ------------------------------------------------------------------------------ # Copyright (c) 2006-2012 Novell, Inc. All Rights Reserved. -# +# Copyright (c) 2013-2021 SUSE LLC # # This program is free software; you can redistribute it and/or modify it under # the terms of version 2 of the GNU General Public License as published by the @@ -11,99 +10,104 @@ # 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, contact Novell, Inc. -# -# To contact Novell about this file by physical or electronic mail, you may find -# current contact information at www.novell.com. # ------------------------------------------------------------------------------ -# File: -# umount_finish.rb -# -# Module: -# Step of base installation finish -# -# Authors: -# Jiri Srain <jsr...@suse.cz> - +require "yast" +require "installation/finish_client" +require "installation/unmounter" require "y2storage" -require "pathname" -require "shellwords" -module Yast - class UmountFinishClient < Client - include Yast::Logger - - EFIVARS_PATH = "/sys/firmware/efi/efivars".freeze - USB_PATH = "/proc/bus/usb".freeze - - def main - textdomain "installation" - - Yast.import "Installation" - Yast.import "Hotplug" - Yast.import "String" - Yast.import "FileUtils" - - @ret = nil - @func = "" - @param = {} - - # Check arguments - if Ops.greater_than(Builtins.size(WFM.Args), 0) && - Ops.is_string?(WFM.Args(0)) - @func = Convert.to_string(WFM.Args(0)) - if Ops.greater_than(Builtins.size(WFM.Args), 1) && - Ops.is_map?(WFM.Args(1)) - @param = Convert.to_map(WFM.Args(1)) - end +module Installation + module Clients + # Finish client to unmount all mounts to the target + class UmountFinishClient < FinishClient + include Yast::Logger + + # Constructor + def initialize + textdomain "installation" + Yast.import "FileUtils" + @running_standalone = false end - Builtins.y2milestone("starting umount_finish") - Builtins.y2debug("func=%1", @func) - Builtins.y2debug("param=%1", @param) - - if @func == "Info" - return { - "steps" => 1, - # progress step title - "title" => _( - "Unmounting all mounted devices..." - ), - "when" => [:installation, :live_installation, :update, :autoinst] - } - elsif @func == "Write" - - # - # !!! NO WRITE OPERATIONS TO THE TARGET HERE !!! - # !!! Use pre_umount_finish instead !!! - # - - Builtins.y2milestone( - "/proc/mounts:\n%1", - WFM.Read(path(".local.string"), "/proc/mounts") - ) - Builtins.y2milestone( - "/proc/partitions:\n%1", - WFM.Read(path(".local.string"), "/proc/partitions") - ) + # This can be used when invoking this file directly with + # ruby ./umount_finish.rb + # + def run_standalone + @running_standalone = true + Installation.destdir = "/mnt" if Installation.destdir == "/" + write + end - # get mounts at and in the target from /proc/mounts - do not use - # Storage here since Storage does not know whether other processes, - # e.g. snapper, mounted filesystems in the target - - umount_list = [] - SCR.Read(path(".proc.mounts")).each do |entry| - mountpoint = entry["file"] - if mountpoint.start_with?(Installation.destdir) - umount_list << mountpoint[Installation.destdir.length, mountpoint.length] - end + protected + + def title + # progress step title + _("Unmounting all mounted devices...") + end + + def modes + # FIXME: better use 'nil' for all modes? Then we could rely on the base + # class implementation which returns nil by default. + [:installation, :live_installation, :update, :autoinst] + end + + # Perform the final actions in the target system + def write + log.info("Starting umount_finish.rb") + + remove_target_etc_mtab + set_btrfs_defaults_as_ro # No write access to the target after this! + close_scr_on_target + umount_target_mounts + + log.info("umount_finish.rb done") + true + end + + # Unmount all mounts to the target (typically using the /mnt prefix). + # + # This uses an Installation::Unmounter object which reads /proc/mounts. + # Relying on y2storage would be risky here since other processes like + # snapper or libzypp may have mounted filesystems without y2storage + # knowing about it. + # + def umount_target_mounts + dump_file("/proc/partitions") + dump_file("/proc/mounts") + unmounter = ::Installation::Unmounter.new(Installation.destdir) + log.info("Paths to unmount: #{unmounter.unmount_paths}") + return if unmounter.mounts.empty? + + begin + unmounter.execute + rescue Cheetah::ExecutionFailed => e # Typically permissions problem + log.error(e.message) + end + unmounter.clear + unmounter.read_mounts_file("/proc/mounts") + unmount_summary(unmounter.unmount_paths) + end + + # Write a summary of the unmount operations to the log. + def unmount_summary(leftover_paths) + if leftover_paths.empty? + log.info("All unmounts successful.") + else + log.warn("Leftover paths that could not be unmounted: #{leftover_paths}") + log_running_processes(leftover_paths) + dump_file("/proc/mounts") end - umount_list.sort! - log.info("umount_list:#{umount_list}") + end + + # Dump a file in human-readable form to the log. + # Do not add the y2log header to each line so it can be easily used. + def dump_file(filename) + content = File.read(filename) + log.info("\n\n#{filename}:\n\n#{content}\n") + end + def remove_target_etc_mtab # symlink points to /proc, keep it (bnc#665437) if !FileUtils.IsLink("/etc/mtab") # remove [Installation::destdir]/etc/mtab which was faked for %post @@ -113,256 +117,115 @@ # hotfix: recreating /etc/mtab as symlink (bnc#725166) SCR.Execute(path(".target.bash"), "ln -s /proc/self/mounts /etc/mtab") end + end - # This must be done as long as the target root is still mounted - # (because the btrfs command requires that), but after the last write - # access to it (because it will be read only afterwards). - set_btrfs_defaults_as_ro - - # Stop SCR on target + def close_scr_on_target WFM.SCRClose(Installation.scr_handle) + end - # first, umount everthing mounted *in* the target. - # /proc/bus/usb - # /proc - - @umount_these = ["/proc", "/sys", "/dev", "/run"] - - @umount_these.unshift(USB_PATH) if Hotplug.haveUSB - - # exists in both inst-sys and target or in neither - @umount_these.unshift(EFIVARS_PATH) if File.exist?(EFIVARS_PATH) - - Builtins.foreach(@umount_these) do |umount_dir| - umount_this = Builtins.sformat( - "%1%2", - Installation.destdir, - umount_dir - ) - Builtins.y2milestone("Umounting: %1", umount_this) - umount_result = Convert.to_boolean( - WFM.Execute(path(".local.umount"), umount_this) - ) - if umount_result != true - log_running_processes(umount_this) - # bnc #395034 - # Don't remount them read-only! - if Builtins.contains( - ["/proc", "/sys", "/dev", USB_PATH, EFIVARS_PATH], - umount_dir - ) - Builtins.y2warning("Umount failed, trying lazy umount...") - cmd = Builtins.sformat( - "sync; umount -l -f '%1';", - String.Quote(umount_this) - ) - else - Builtins.y2warning( - "Umount failed, trying to remount read only..." - ) - cmd = Builtins.sformat( - "sync; mount -o remount,noatime,ro '%1'; umount -l -f '%1';", - String.Quote(umount_this) - ) - end - Builtins.y2milestone( - "Cmd: '%1' Ret: %2", - cmd, - WFM.Execute(path(".local.bash_output"), cmd) - ) - end - end - -# storage-ng -# rubocop:disable Style/BlockComments -=begin - - @targetMap = Storage.GetTargetMap - - # first umount all file based crypto fs since they potentially - # could mess up umounting of normale filesystems if the crypt - # file is not on the root fs - Builtins.y2milestone("umount list %1", umount_list) - Builtins.foreach( - Ops.get_list(@targetMap, ["/dev/loop", "partitions"], []) - ) do |e| - if Ops.greater_than(Builtins.size(Ops.get_string(e, "mount", "")), 0) - Storage.Umount(Ops.get_string(e, "device", ""), true) - umount_list = Builtins.filter(umount_list) do |m| - m != Ops.get_string(e, "mount", "") - end - Builtins.y2milestone( - "loop umount %1 new list %2", - Ops.get_string(e, "mount", ""), - umount_list - ) - end - end - -=end + public - # *** umount_list is lexically ordered ! - # now umount in reverse order (guarantees "/" as last umount) + # For btrfs filesystems that should be read-only, set the root subvolume + # to read-only and change the /etc/fstab entry accordingly. + # + # Since we had to install RPMs to the target, we could not set it to + # read-only right away; but now we can, and we have to. + # + # This must be done as long as the target root is still mounted + # (because the btrfs command requires that), but after the last write + # access to it (because it will be read only afterwards). + def set_btrfs_defaults_as_ro + # This operation needs root privileges, but it's also generally not a + # good idea to do this even if you have the privileges: In that case, + # it would change your root subvolume to read-only which is not a good + # idea when just invoking this standalone for testing in a development + # environment. + return if @running_standalone - @umountLength = Builtins.size(umount_list) - - while Ops.greater_than(@umountLength, 0) - @umountLength = Ops.subtract(@umountLength, 1) - @tmp = Ops.add( - Installation.destdir, - Ops.get(umount_list, @umountLength, "") - ) - - Builtins.y2milestone( - "umount target: %1, %2 more to go..", - @tmp, - @umountLength - ) - - @umount_status = Convert.to_boolean( - WFM.Execute(path(".local.umount"), @tmp) - ) - - # bnc #395034 - # Don't remount them read-only! - next if @umount_status - log_running_processes(@tmp) - - if Builtins.contains( - ["/proc", "/sys", "/dev", "/proc/bus/usb"], - @tmp - ) - Builtins.y2warning("Umount failed, trying lazy umount...") - @cmd2 = Builtins.sformat( - "sync; umount -l -f '%1';", - String.Quote(@tmp) - ) - else - Builtins.y2warning( - "Umount failed, trying to remount read only..." - ) - @cmd2 = Builtins.sformat( - "mount -o remount,ro,noatime '%1'; umount -l -f '%1';", - String.Quote(@tmp) - ) - end - Builtins.y2milestone( - "Cmd: '%1' Ret: %2", - @cmd2, - WFM.Execute(path(".local.bash_output"), @cmd2) - ) + devicegraph = Y2Storage::StorageManager.instance.staging + ro_btrfs_filesystems = devicegraph.filesystems.select do |fs| + new_filesystem?(fs) && ro_btrfs_filesystem?(fs) end - # bugzilla #326478 - Builtins.y2milestone( - "Currently mounted partitions: %1", - WFM.Execute(path(".local.bash_output"), "mount") - ) - - log_running_processes(Installation.destdir) + ro_btrfs_filesystems.each { |f| default_subvolume_as_ro(f) } + end -# storage-ng -=begin + protected - # must call .local.bash_output ! - @max_loop_dev = Storage.NumLoopDevices + # [String] Name used by btrfs tools to name the filesystem tree. + BTRFS_FS_TREE = "(FS_TREE)".freeze - # disable loop device of crypto fs - @unload_crypto = false - - while Ops.greater_than(@max_loop_dev, 0) - @unload_crypto = true - @exec_str = Builtins.sformat( - "/sbin/losetup -d /dev/loop%1", - Ops.subtract(@max_loop_dev, 1) - ) - Builtins.y2milestone("loopdev: %1", @exec_str) - WFM.Execute(path(".local.bash"), @exec_str) - @max_loop_dev = Ops.subtract(@max_loop_dev, 1) - end - - if @targetMap.any? { |_k, v| v["type"] == :CT_LVM } - Builtins.y2milestone("shutting down LVM") - WFM.Execute(path(".local.bash"), "/sbin/vgchange -a n") - end + # Set the "read-only" property for the root subvolume. + # This has to be done as long as the target root filesystem is still + # mounted. + # + # @param fs [Y2Storage::Filesystems::Btrfs] Btrfs filesystem to set read-only property on. + def default_subvolume_as_ro(fs) + output = Yast::Execute.on_target( + "btrfs", "subvolume", "get-default", fs.mount_point.path, stdout: :capture + ) + default_subvolume = output.strip.split.last + # no btrfs_default_subvolume and no snapshots + default_subvolume = "" if default_subvolume == BTRFS_FS_TREE -=end + subvolume_path = fs.btrfs_subvolume_mount_point(default_subvolume) - else - Builtins.y2error("unknown function: %1", @func) - @ret = nil + log.info("Setting root subvol read-only property on #{subvolume_path}") + Yast::Execute.on_target("btrfs", "property", "set", subvolume_path, "ro", "true") end - Builtins.y2debug("ret=%1", @ret) - Builtins.y2milestone("umount_finish finished") - deep_copy(@ret) - end - - # Set the root subvolume to read-only and change the /etc/fstab entry - # accordingly - def set_btrfs_defaults_as_ro - devicegraph = Y2Storage::StorageManager.instance.staging - - ro_btrfs_filesystems = devicegraph.filesystems.select do |fs| - new_filesystem?(fs) && ro_btrfs_filesystem?(fs) + # run "fuser" to get the details about open files + # + # @param mount_points <Array>[String] + def log_running_processes(mount_points) + paths = mount_points.join(" ") + fuser = + begin + # (the details are printed on STDERR, redirect it) + `LC_ALL=C fuser -v -m #{paths} 2>&1` + rescue => e + "fuser failed: #{e}" + end + log.warn("\n\nRunning processes using #{mount_points}:\n#{fuser}\n") end - ro_btrfs_filesystems.each { |f| default_subvolume_as_ro(f) } - end - - # [String] Name used by btrfs tools to name the filesystem tree. - BTRFS_FS_TREE = "(FS_TREE)".freeze - - # Set the "read-only" property for the root subvolume. - # This has to be done as long as the target root filesystem is still - # mounted. - # - # @param fs [Y2Storage::Filesystems::Btrfs] Btrfs filesystem to set read-only property on. - def default_subvolume_as_ro(fs) - output = Yast::Execute.on_target( - "btrfs", "subvolume", "get-default", fs.mount_point.path, stdout: :capture - ) - default_subvolume = output.strip.split.last - # no btrfs_default_subvolume and no snapshots - default_subvolume = "" if default_subvolume == BTRFS_FS_TREE - - subvolume_path = fs.btrfs_subvolume_mount_point(default_subvolume) - - log.info("Setting root subvol read-only property on #{subvolume_path}") - Yast::Execute.on_target("btrfs", "property", "set", subvolume_path, "ro", "true") - end - - # run "fuser" to get the details about open files - # - # @param mount_point [String] - def log_running_processes(mount_point) - fuser = - begin - # (the details are printed on STDERR, redirect it) - `LC_ALL=C fuser -v -m #{Shellwords.escape(mount_point)} 2>&1` - rescue => e - "fuser failed: #{e}" - end - log.warn("Running processes using #{mount_point}: #{fuser}") - end - - private - - # Check whether the given filesystem is going to be created - # - # @param filesystem [Y2Storage::Filesystems::Base] - # @return [Boolean] - def new_filesystem?(filesystem) - !filesystem.exists_in_probed? - end + # Check whether the given filesystem is going to be created + # + # @param filesystem [Y2Storage::Filesystems::Base] + # @return [Boolean] + def new_filesystem?(filesystem) + !filesystem.exists_in_probed? + end - # Check whether the given filesystem is read-only BTRFS - # - # @param filesystem [Y2Storage::Filesystems::Base] - # @return [Boolean] - def ro_btrfs_filesystem?(filesystem) - filesystem.is?(:btrfs) && filesystem.mount_point && filesystem.mount_options.include?("ro") + # Check whether the given filesystem is read-only BTRFS + # + # @param filesystem [Y2Storage::Filesystems::Base] + # @return [Boolean] + def ro_btrfs_filesystem?(filesystem) + filesystem.is?(:btrfs) && filesystem.mount_point && filesystem.mount_options.include?("ro") + end end end end + +# +#------------------------------------------------------------------------------------ +# +# This can be called standalone with +# +# ruby /usr/share/YaST2/lib/installation/clients/umount_finish.rb +# +# or (even from the git checkout directory where this file is) +# +# ruby ./umount_finish.rb +# +# with or without root permissions. Obviously, without root permissions, the +# "umount" commands will fail. But you can observe in the user's ~/.y2log what +# mounts would be unmounted. Make sure to mount something to /mnt to see anything. +# +if $PROGRAM_NAME == __FILE__ # Called direcly as standalone command? (not via rspec or require) + puts("Running UmountFinishClient standalone") + Installation::Clients::UmountFinishClient.new.run_standalone + puts("UmountFinishClient done") +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/src/lib/installation/unmounter.rb new/yast2-installation-4.4.17/src/lib/installation/unmounter.rb --- old/yast2-installation-4.4.16/src/lib/installation/unmounter.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-installation-4.4.17/src/lib/installation/unmounter.rb 2021-08-26 15:46:35.000000000 +0200 @@ -0,0 +1,197 @@ +# encoding: utf-8 +# ------------------------------------------------------------------------------ +# Copyright (c) 2021 SUSE LLC +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of version 2 of the GNU General Public License as published by the +# Free Software Foundation. +# +# 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. +# +# ------------------------------------------------------------------------------ + +require "yast" +require "pathname" + +module Installation + # Class to handle unmounting all mounts from the given subtree on in the + # right order. + # + # See also https://github.com/yast/yast-installation/pull/975 + # + # This uses /proc/mounts by default to find active mounts, but for + # testability, it can also be fed from other files or line by line. It stores + # all necessary unmount actions so they can be executed all at once, and they + # can also be inspected without the execute step. This is intended for + # logging, debugging and testing. + # + # This relies on /proc/mounts already containing the entries in canonical + # order (which it always does), i.e. in the mount hierarchy from top to + # bottom. If you add entries manually, make sure to maintain that order. + # + # Sample usage: + # + # unmounter = Installation::Unmounter.new("/mnt") + # log.info("Paths to unmount: #{unmounter.unmount_paths}") + # unmounter.execute + # + # Without specifying a file to read as the second parameter in the + # constructor, it will default to /proc/mounts which is the right thing for + # real life use. + # + class Unmounter + include Yast::Logger + # The mount prefix (typically "/mnt") + attr_reader :mnt_prefix + # @return [Array<Mount>] Relevant mounts to unmount + attr_reader :mounts + # @return [Array<Mount>] Ignored mounts (not starting with the mount prefix) + attr_reader :ignored_mounts + + # Helper class to represent one mount, i.e. one entry in /proc/mounts. + class Mount + attr_reader :device, :mount_path, :fs_type, :mount_opt + + def initialize(device, mount_path, fs_type = "", mount_opt = "") + @device = device + @mount_path = mount_path + @fs_type = fs_type + @mount_opt = mount_opt + end + + # Format this mount as a string for logging. + # + def to_s + "<Mount #{@device} -> #{@mount_path} type #{@fs_type}>" + end + end + + # Unmounter constructor. + # + # @param mnt_prefix [String] Prefix which paths should be unmounted. + # + # @param mounts_file_name [String] what to use instead of /proc/mounts. + # Use 'nil' to not read any file at all; but in that case, later use + # read_mounts_file() or add_mount(). + # + def initialize(mnt_prefix = "/mnt", mounts_file_name = "/proc/mounts") + @mnt_prefix = Pathname.new(mnt_prefix).cleanpath.to_s + clear + read_mounts_file(mounts_file_name) unless mounts_file_name.nil? + end + + # Clear all prevous content. + def clear + @mounts = [] + @ignored_mounts = [] + end + + # Read a mounts file like /proc/mounts and add the relevant entries to the + # mounts stored in this class. + # + def read_mounts_file(file_name) + log.info("Reading file #{file_name}") + open(file_name).each { |line| add_mount(line) } + end + + # Parse one entry of /proc/mounts and add it to @mounts + # if it meets the criteria (mount prefix) + # + # @param line [String] one line of /proc/mounts + # @return [Mount,nil] parsed mount if relevant + # + def add_mount(line) + mount = parse_mount(line) + return nil if mount.nil? # Empty or comment + + if ignore?(mount) + @ignored_mounts << mount + return nil + end + + log.info("Adding #{mount}") + @mounts << mount + mount + end + + # Check if a mount should be ignored, i.e. if the path doesn't start with + # the mount prefix (usually "/mnt"). + # + # @return [Boolean] ignore + # + def ignore?(mount) + return false if mount.mount_path == @mnt_prefix + + !mount.mount_path.start_with?(@mnt_prefix + "/") + end + + # Parse one entry of /proc/mounts. + # + # @param line [String] one line of /proc/mounts + # @return [Mount,nil] parsed mount + # + def parse_mount(line) + line.strip! + return nil if line.empty? || line.start_with?("#") + + (device, mount_path, fs_type, mount_opt) = line.split + Mount.new(device, mount_path, fs_type, mount_opt) + end + + # Return the paths to be unmounted in the correct unmount order. + # + # This makes use of the fact that /proc/mounts is already sorted in + # canonical order, i.e. from toplevel mount points to lower level ones. + # + # @return [Array<String>] paths + # + def unmount_paths + paths = @mounts.map(&:mount_path) + paths.reverse + end + + # Return the paths that were ignored (in the order of /proc/mounts). + # This is mostly useful for debugging and testing. + # + # @return [Array<String>] paths + # + def ignored_paths + @ignored_mounts.map(&:mount_path) + end + + # Actually execute all the pending unmount operations in the right + # sequence. + # + # This iterates over all relevant mounts and invokes the external "umount" + # command for each one separately. Notice that while "umount -R path" also + # exists, it will stop executing when it first encounters any mount that + # cannot be unmounted ("filesystem busy"), even if mounts that come after + # that could safely be unmounted. + # + # If unmounting a mount fails, this does not attempt to remount read-only + # ("umount -r"), by force ("umount -f") or lazily ("umount -l"): + # + # - Remounting read-only ("umount -r" or "mount -o remount,ro") typically + # also fails if unmounting fails. It would have to be a rare coincidence + # that a filesystem has only open files in read-only mode already; only + # then it would have a chance to succeed. + # + # - Force-unmounting ("umount -f") realistically only works for NFS mounts. + # It is intended for cases when the NFS server has become unreachable. + # + # - Lazy unmounting ("umount -l") mostly removes the entry for this + # filesytem from /proc/mounts; it actually only unmounts when the pending + # operations that prevent a simple unmount are finished which may take a + # long time; or forever. And there is no way to find out if or when this + # has ever happened, so the next mount for this filesystem may fail. + # + def execute + unmount_paths.each do |path| + log.info("Unmounting #{path}") + Yast::Execute.locally!("umount", path) + end + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/test/data/proc-mounts/README.md new/yast2-installation-4.4.17/test/data/proc-mounts/README.md --- old/yast2-installation-4.4.16/test/data/proc-mounts/README.md 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-installation-4.4.17/test/data/proc-mounts/README.md 2021-08-26 15:46:35.000000000 +0200 @@ -0,0 +1,17 @@ +# test/data/proc-mounts + +This directory contains /proc/mounts files that were collected during an +installation. The intended purpose is for testing the Unmounter class, but they +may also be useful for other tests. + +All files named proc-mounts*-raw.txt are as taken directly from the system, the +-pretty.txt variant is just formatted prettier (using `column -t`), and it +contains some comment lines at the start to describe the scenario. + +`/proc/mounts` doesn't normally include comments, but the Unmounter class can +handle comments and empty lines. + +Most scenarios include the partitions of the target system mounted below +`/mnt`. + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-btrfs-pretty.txt new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-btrfs-pretty.txt --- old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-btrfs-pretty.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-btrfs-pretty.txt 2021-08-26 15:46:35.000000000 +0200 @@ -0,0 +1,31 @@ +# /proc/mounts during installation with root on btrfs, no separate /home +# formatted with column -t +# +tmpfs / tmpfs rw,relatime,size=2030608k,nr_inodes=0 0 0 +tmpfs / tmpfs rw,relatime,size=2030608k,nr_inodes=0 0 0 +proc /proc proc rw,relatime 0 0 +sysfs /sys sysfs rw,relatime 0 0 +/dev/loop0 /parts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop1 /parts/mp_0001 squashfs ro,relatime 0 0 +devtmpfs /dev devtmpfs rw,relatime,size=959008k,nr_inodes=239752,mode=755 0 0 +devpts /dev/pts devpts rw,relatime,mode=600,ptmxmode=000 0 0 +rpc_pipefs /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0 +/dev/loop2 /mounts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop3 /mounts/mp_0001 squashfs ro,relatime 0 0 +/dev/loop5 /mounts/mp_0003 squashfs ro,relatime 0 0 +/dev/loop6 /mounts/mp_0004 squashfs ro,relatime 0 0 +/dev/sda2 /mnt btrfs rw,relatime,space_cache,subvolid=268,subvol=/@/.snapshots/1/snapshot 0 0 +/dev/sda2 /mnt/.snapshots btrfs rw,relatime,space_cache,subvolid=267,subvol=/@/.snapshots 0 0 +/dev/sda2 /mnt/boot/grub2/i386-pc btrfs rw,relatime,space_cache,subvolid=266,subvol=/@/boot/grub2/i386-pc 0 0 +/dev/sda2 /mnt/boot/grub2/x86_64-efi btrfs rw,relatime,space_cache,subvolid=265,subvol=/@/boot/grub2/x86_64-efi 0 0 +/dev/sda2 /mnt/home btrfs rw,relatime,space_cache,subvolid=264,subvol=/@/home 0 0 +/dev/sda2 /mnt/opt btrfs rw,relatime,space_cache,subvolid=263,subvol=/@/opt 0 0 +/dev/sda2 /mnt/root btrfs rw,relatime,space_cache,subvolid=262,subvol=/@/root 0 0 +/dev/sda2 /mnt/srv btrfs rw,relatime,space_cache,subvolid=261,subvol=/@/srv 0 0 +/dev/sda2 /mnt/tmp btrfs rw,relatime,space_cache,subvolid=260,subvol=/@/tmp 0 0 +/dev/sda2 /mnt/usr/local btrfs rw,relatime,space_cache,subvolid=259,subvol=/@/usr/local 0 0 +/dev/sda2 /mnt/var btrfs rw,relatime,space_cache,subvolid=258,subvol=/@/var 0 0 +devtmpfs /mnt/dev devtmpfs rw,relatime,size=959008k,nr_inodes=239752,mode=755 0 0 +proc /mnt/proc proc rw,relatime 0 0 +sysfs /mnt/sys sysfs rw,relatime 0 0 +tmpfs /mnt/run tmpfs rw,relatime,size=2030608k,nr_inodes=0 0 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-btrfs-raw.txt new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-btrfs-raw.txt --- old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-btrfs-raw.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-btrfs-raw.txt 2021-08-26 15:46:35.000000000 +0200 @@ -0,0 +1,28 @@ +tmpfs / tmpfs rw,relatime,size=2030608k,nr_inodes=0 0 0 +tmpfs / tmpfs rw,relatime,size=2030608k,nr_inodes=0 0 0 +proc /proc proc rw,relatime 0 0 +sysfs /sys sysfs rw,relatime 0 0 +/dev/loop0 /parts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop1 /parts/mp_0001 squashfs ro,relatime 0 0 +devtmpfs /dev devtmpfs rw,relatime,size=959008k,nr_inodes=239752,mode=755 0 0 +devpts /dev/pts devpts rw,relatime,mode=600,ptmxmode=000 0 0 +rpc_pipefs /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0 +/dev/loop2 /mounts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop3 /mounts/mp_0001 squashfs ro,relatime 0 0 +/dev/loop5 /mounts/mp_0003 squashfs ro,relatime 0 0 +/dev/loop6 /mounts/mp_0004 squashfs ro,relatime 0 0 +/dev/sda2 /mnt btrfs rw,relatime,space_cache,subvolid=268,subvol=/@/.snapshots/1/snapshot 0 0 +/dev/sda2 /mnt/.snapshots btrfs rw,relatime,space_cache,subvolid=267,subvol=/@/.snapshots 0 0 +/dev/sda2 /mnt/boot/grub2/i386-pc btrfs rw,relatime,space_cache,subvolid=266,subvol=/@/boot/grub2/i386-pc 0 0 +/dev/sda2 /mnt/boot/grub2/x86_64-efi btrfs rw,relatime,space_cache,subvolid=265,subvol=/@/boot/grub2/x86_64-efi 0 0 +/dev/sda2 /mnt/home btrfs rw,relatime,space_cache,subvolid=264,subvol=/@/home 0 0 +/dev/sda2 /mnt/opt btrfs rw,relatime,space_cache,subvolid=263,subvol=/@/opt 0 0 +/dev/sda2 /mnt/root btrfs rw,relatime,space_cache,subvolid=262,subvol=/@/root 0 0 +/dev/sda2 /mnt/srv btrfs rw,relatime,space_cache,subvolid=261,subvol=/@/srv 0 0 +/dev/sda2 /mnt/tmp btrfs rw,relatime,space_cache,subvolid=260,subvol=/@/tmp 0 0 +/dev/sda2 /mnt/usr/local btrfs rw,relatime,space_cache,subvolid=259,subvol=/@/usr/local 0 0 +/dev/sda2 /mnt/var btrfs rw,relatime,space_cache,subvolid=258,subvol=/@/var 0 0 +devtmpfs /mnt/dev devtmpfs rw,relatime,size=959008k,nr_inodes=239752,mode=755 0 0 +proc /mnt/proc proc rw,relatime 0 0 +sysfs /mnt/sys sysfs rw,relatime 0 0 +tmpfs /mnt/run tmpfs rw,relatime,size=2030608k,nr_inodes=0 0 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-btrfs-xfs-home-pretty.txt new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-btrfs-xfs-home-pretty.txt --- old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-btrfs-xfs-home-pretty.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-btrfs-xfs-home-pretty.txt 2021-08-26 15:46:35.000000000 +0200 @@ -0,0 +1,30 @@ +# /proc/mounts with partition-based btrfs and a separate /home with XFS +# formatted with column -t +# +tmpfs / tmpfs rw,relatime,size=4020456k,nr_inodes=0 0 0 +proc /proc proc rw,relatime 0 0 +sysfs /sys sysfs rw,relatime 0 0 +/dev/loop0 /parts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop1 /parts/mp_0001 squashfs ro,relatime 0 0 +devtmpfs /dev devtmpfs rw,relatime,size=1945116k,nr_inodes=486279,mode=755 0 0 +devpts /dev/pts devpts rw,relatime,mode=600,ptmxmode=000 0 0 +rpc_pipefs /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0 +/dev/loop2 /mounts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop3 /mounts/mp_0001 squashfs ro,relatime 0 0 +/dev/loop5 /mounts/mp_0003 squashfs ro,relatime 0 0 +/dev/sr0 /var/adm/mount/AP_0xpRErh6 iso9660 ro,relatime,nojoliet,check=s,map=n,blocksize=2048 0 0 +/dev/sda2 /mnt btrfs rw,relatime,space_cache,subvolid=256,subvol=/@ 0 0 +/dev/sda2 /mnt/boot/grub2/i386-pc btrfs rw,relatime,space_cache,subvolid=265,subvol=/@/boot/grub2/i386-pc 0 0 +/dev/sda2 /mnt/boot/grub2/x86_64-efi btrfs rw,relatime,space_cache,subvolid=264,subvol=/@/boot/grub2/x86_64-efi 0 0 +/dev/sda3 /mnt/home xfs rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0 +/dev/sda2 /mnt/opt btrfs rw,relatime,space_cache,subvolid=263,subvol=/@/opt 0 0 +/dev/sda2 /mnt/root btrfs rw,relatime,space_cache,subvolid=262,subvol=/@/root 0 0 +/dev/sda2 /mnt/srv btrfs rw,relatime,space_cache,subvolid=261,subvol=/@/srv 0 0 +/dev/sda2 /mnt/tmp btrfs rw,relatime,space_cache,subvolid=260,subvol=/@/tmp 0 0 +/dev/sda2 /mnt/usr/local btrfs rw,relatime,space_cache,subvolid=259,subvol=/@/usr/local 0 0 +/dev/sda2 /mnt/var btrfs rw,relatime,space_cache,subvolid=258,subvol=/@/var 0 0 +devtmpfs /mnt/dev devtmpfs rw,relatime,size=1945116k,nr_inodes=486279,mode=755 0 0 +proc /mnt/proc proc rw,relatime 0 0 +sysfs /mnt/sys sysfs rw,relatime 0 0 +tmpfs /mnt/run tmpfs rw,relatime,size=4020456k,nr_inodes=0 0 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-btrfs-xfs-home-raw.txt new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-btrfs-xfs-home-raw.txt --- old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-btrfs-xfs-home-raw.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-btrfs-xfs-home-raw.txt 2021-08-26 15:46:35.000000000 +0200 @@ -0,0 +1,27 @@ +tmpfs / tmpfs rw,relatime,size=4020456k,nr_inodes=0 0 0 +proc /proc proc rw,relatime 0 0 +sysfs /sys sysfs rw,relatime 0 0 +/dev/loop0 /parts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop1 /parts/mp_0001 squashfs ro,relatime 0 0 +devtmpfs /dev devtmpfs rw,relatime,size=1945116k,nr_inodes=486279,mode=755 0 0 +devpts /dev/pts devpts rw,relatime,mode=600,ptmxmode=000 0 0 +rpc_pipefs /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0 +/dev/loop2 /mounts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop3 /mounts/mp_0001 squashfs ro,relatime 0 0 +/dev/loop5 /mounts/mp_0003 squashfs ro,relatime 0 0 +/dev/sr0 /var/adm/mount/AP_0xpRErh6 iso9660 ro,relatime,nojoliet,check=s,map=n,blocksize=2048 0 0 +/dev/sda2 /mnt btrfs rw,relatime,space_cache,subvolid=256,subvol=/@ 0 0 +/dev/sda2 /mnt/boot/grub2/i386-pc btrfs rw,relatime,space_cache,subvolid=265,subvol=/@/boot/grub2/i386-pc 0 0 +/dev/sda2 /mnt/boot/grub2/x86_64-efi btrfs rw,relatime,space_cache,subvolid=264,subvol=/@/boot/grub2/x86_64-efi 0 0 +/dev/sda3 /mnt/home xfs rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0 +/dev/sda2 /mnt/opt btrfs rw,relatime,space_cache,subvolid=263,subvol=/@/opt 0 0 +/dev/sda2 /mnt/root btrfs rw,relatime,space_cache,subvolid=262,subvol=/@/root 0 0 +/dev/sda2 /mnt/srv btrfs rw,relatime,space_cache,subvolid=261,subvol=/@/srv 0 0 +/dev/sda2 /mnt/tmp btrfs rw,relatime,space_cache,subvolid=260,subvol=/@/tmp 0 0 +/dev/sda2 /mnt/usr/local btrfs rw,relatime,space_cache,subvolid=259,subvol=/@/usr/local 0 0 +/dev/sda2 /mnt/var btrfs rw,relatime,space_cache,subvolid=258,subvol=/@/var 0 0 +devtmpfs /mnt/dev devtmpfs rw,relatime,size=1945116k,nr_inodes=486279,mode=755 0 0 +proc /mnt/proc proc rw,relatime 0 0 +sysfs /mnt/sys sysfs rw,relatime 0 0 +tmpfs /mnt/run tmpfs rw,relatime,size=4020456k,nr_inodes=0 0 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-enc-lvm-btrfs-pretty.txt new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-enc-lvm-btrfs-pretty.txt --- old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-enc-lvm-btrfs-pretty.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-enc-lvm-btrfs-pretty.txt 2021-08-26 15:46:35.000000000 +0200 @@ -0,0 +1,32 @@ +# /proc/mounts during RPM installation on a btrfs on an encrypted LVM; +# no snapshots (not enough disk space), separate /home (also btrfs) +# +# formatted with column -t +# +tmpfs / tmpfs rw,relatime,size=4020488k,nr_inodes=0 0 0 +proc /proc proc rw,relatime 0 0 +sysfs /sys sysfs rw,relatime 0 0 +/dev/loop0 /parts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop1 /parts/mp_0001 squashfs ro,relatime 0 0 +devtmpfs /dev devtmpfs rw,relatime,size=1945132k,nr_inodes=486283,mode=755 0 0 +devpts /dev/pts devpts rw,relatime,mode=600,ptmxmode=000 0 0 +rpc_pipefs /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0 +/dev/loop2 /mounts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop3 /mounts/mp_0001 squashfs ro,relatime 0 0 +/dev/loop5 /mounts/mp_0003 squashfs ro,relatime 0 0 +/dev/sr0 /var/adm/mount/AP_0xftqtdx iso9660 ro,relatime,nojoliet,check=s,map=n,blocksize=2048 0 0 +/dev/mapper/system-root /mnt btrfs rw,relatime,space_cache,subvolid=256,subvol=/@ 0 0 +/dev/mapper/system-root /mnt/boot/grub2/i386-pc btrfs rw,relatime,space_cache,subvolid=265,subvol=/@/boot/grub2/i386-pc 0 0 +/dev/mapper/system-root /mnt/boot/grub2/x86_64-efi btrfs rw,relatime,space_cache,subvolid=264,subvol=/@/boot/grub2/x86_64-efi 0 0 +/dev/mapper/system-home /mnt/home btrfs rw,relatime,space_cache,subvolid=5,subvol=/ 0 0 +/dev/mapper/system-root /mnt/opt btrfs rw,relatime,space_cache,subvolid=263,subvol=/@/opt 0 0 +/dev/mapper/system-root /mnt/root btrfs rw,relatime,space_cache,subvolid=262,subvol=/@/root 0 0 +/dev/mapper/system-root /mnt/srv btrfs rw,relatime,space_cache,subvolid=261,subvol=/@/srv 0 0 +/dev/mapper/system-root /mnt/tmp btrfs rw,relatime,space_cache,subvolid=260,subvol=/@/tmp 0 0 +/dev/mapper/system-root /mnt/usr/local btrfs rw,relatime,space_cache,subvolid=259,subvol=/@/usr/local 0 0 +/dev/mapper/system-root /mnt/var btrfs rw,relatime,space_cache,subvolid=258,subvol=/@/var 0 0 +devtmpfs /mnt/dev devtmpfs rw,relatime,size=1945132k,nr_inodes=486283,mode=755 0 0 +proc /mnt/proc proc rw,relatime 0 0 +sysfs /mnt/sys sysfs rw,relatime 0 0 +tmpfs /mnt/run tmpfs rw,relatime,size=4020488k,nr_inodes=0 0 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-enc-lvm-btrfs-raw.txt new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-enc-lvm-btrfs-raw.txt --- old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-enc-lvm-btrfs-raw.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-enc-lvm-btrfs-raw.txt 2021-08-26 15:46:35.000000000 +0200 @@ -0,0 +1,27 @@ +tmpfs / tmpfs rw,relatime,size=4020488k,nr_inodes=0 0 0 +proc /proc proc rw,relatime 0 0 +sysfs /sys sysfs rw,relatime 0 0 +/dev/loop0 /parts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop1 /parts/mp_0001 squashfs ro,relatime 0 0 +devtmpfs /dev devtmpfs rw,relatime,size=1945132k,nr_inodes=486283,mode=755 0 0 +devpts /dev/pts devpts rw,relatime,mode=600,ptmxmode=000 0 0 +rpc_pipefs /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0 +/dev/loop2 /mounts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop3 /mounts/mp_0001 squashfs ro,relatime 0 0 +/dev/loop5 /mounts/mp_0003 squashfs ro,relatime 0 0 +/dev/sr0 /var/adm/mount/AP_0xftqtdx iso9660 ro,relatime,nojoliet,check=s,map=n,blocksize=2048 0 0 +/dev/mapper/system-root /mnt btrfs rw,relatime,space_cache,subvolid=256,subvol=/@ 0 0 +/dev/mapper/system-root /mnt/boot/grub2/i386-pc btrfs rw,relatime,space_cache,subvolid=265,subvol=/@/boot/grub2/i386-pc 0 0 +/dev/mapper/system-root /mnt/boot/grub2/x86_64-efi btrfs rw,relatime,space_cache,subvolid=264,subvol=/@/boot/grub2/x86_64-efi 0 0 +/dev/mapper/system-home /mnt/home btrfs rw,relatime,space_cache,subvolid=5,subvol=/ 0 0 +/dev/mapper/system-root /mnt/opt btrfs rw,relatime,space_cache,subvolid=263,subvol=/@/opt 0 0 +/dev/mapper/system-root /mnt/root btrfs rw,relatime,space_cache,subvolid=262,subvol=/@/root 0 0 +/dev/mapper/system-root /mnt/srv btrfs rw,relatime,space_cache,subvolid=261,subvol=/@/srv 0 0 +/dev/mapper/system-root /mnt/tmp btrfs rw,relatime,space_cache,subvolid=260,subvol=/@/tmp 0 0 +/dev/mapper/system-root /mnt/usr/local btrfs rw,relatime,space_cache,subvolid=259,subvol=/@/usr/local 0 0 +/dev/mapper/system-root /mnt/var btrfs rw,relatime,space_cache,subvolid=258,subvol=/@/var 0 0 +devtmpfs /mnt/dev devtmpfs rw,relatime,size=1945132k,nr_inodes=486283,mode=755 0 0 +proc /mnt/proc proc rw,relatime 0 0 +sysfs /mnt/sys sysfs rw,relatime 0 0 +tmpfs /mnt/run tmpfs rw,relatime,size=4020488k,nr_inodes=0 0 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-inst-pretty.txt new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-inst-pretty.txt --- old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-inst-pretty.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-inst-pretty.txt 2021-08-26 15:46:35.000000000 +0200 @@ -0,0 +1,14 @@ +# /proc/mounts during the installlation worflow before any target partitions are created or mounted; +# formatted with column -t +# +tmpfs / tmpfs rw,relatime,size=4020488k,nr_inodes=0 0 0 +proc /proc proc rw,relatime 0 0 +sysfs /sys sysfs rw,relatime 0 0 +/dev/loop0 /parts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop1 /parts/mp_0001 squashfs ro,relatime 0 0 +devtmpfs /dev devtmpfs rw,relatime,size=1945132k,nr_inodes=486283,mode=755 0 0 +devpts /dev/pts devpts rw,relatime,mode=600,ptmxmode=000 0 0 +rpc_pipefs /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0 +/dev/loop2 /mounts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop3 /mounts/mp_0001 squashfs ro,relatime 0 0 +/dev/loop5 /mounts/mp_0003 squashfs ro,relatime 0 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-inst-raw.txt new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-inst-raw.txt --- old/yast2-installation-4.4.16/test/data/proc-mounts/proc-mounts-inst-raw.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-installation-4.4.17/test/data/proc-mounts/proc-mounts-inst-raw.txt 2021-08-26 15:46:35.000000000 +0200 @@ -0,0 +1,12 @@ +tmpfs / tmpfs rw,relatime,size=4020488k,nr_inodes=0 0 0 +proc /proc proc rw,relatime 0 0 +sysfs /sys sysfs rw,relatime 0 0 +/dev/loop0 /parts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop1 /parts/mp_0001 squashfs ro,relatime 0 0 +devtmpfs /dev devtmpfs rw,relatime,size=1945132k,nr_inodes=486283,mode=755 0 0 +devpts /dev/pts devpts rw,relatime,mode=600,ptmxmode=000 0 0 +rpc_pipefs /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0 +/dev/loop2 /mounts/mp_0000 squashfs ro,relatime 0 0 +/dev/loop3 /mounts/mp_0001 squashfs ro,relatime 0 0 +/dev/loop5 /mounts/mp_0003 squashfs ro,relatime 0 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/test/lib/clients/umount_finish_test.rb new/yast2-installation-4.4.17/test/lib/clients/umount_finish_test.rb --- old/yast2-installation-4.4.16/test/lib/clients/umount_finish_test.rb 2021-08-17 20:51:42.000000000 +0200 +++ new/yast2-installation-4.4.17/test/lib/clients/umount_finish_test.rb 2021-08-26 15:46:35.000000000 +0200 @@ -3,7 +3,7 @@ require_relative "../../test_helper" require "installation/clients/umount_finish" -describe Yast::UmountFinishClient do +describe Installation::Clients::UmountFinishClient do before do Y2Storage::StorageManager.create_test_instance end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-installation-4.4.16/test/unmounter_test.rb new/yast2-installation-4.4.17/test/unmounter_test.rb --- old/yast2-installation-4.4.16/test/unmounter_test.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-installation-4.4.17/test/unmounter_test.rb 2021-08-26 15:46:35.000000000 +0200 @@ -0,0 +1,230 @@ +#! /usr/bin/env rspec + +require_relative "./test_helper" +require "installation/unmounter" + +PROC_MOUNTS_PATH = File.join(File.expand_path(File.dirname(__FILE__)), "data/proc-mounts") +PREFIX = "/mnt".freeze + +def stored_proc_mounts(scenario) + File.join(PROC_MOUNTS_PATH, "proc-mounts-#{scenario}-raw.txt") +end + +def mount(mount_path) + Installation::Unmounter::Mount.new("/dev/something", mount_path, "FooFS") +end + +describe Installation::Unmounter do + let(:proc_mounts) { nil } + + describe "#new" do + let(:subject) { described_class.new(PREFIX, proc_mounts) } + + context "empty" do + it "does not crash and burn" do + expect(subject.mounts).to eq [] + end + end + + context "before the installation is executed" do + let(:proc_mounts) { stored_proc_mounts("inst") } + + it "does not find any relevant mounts" do + expect(subject.mounts).to eq [] + end + end + + context "when reading the actual /proc/mounts file" do + let(:proc_mounts) { "/proc/mounts" } + + it "ignores /proc and /sys" do + expect(subject.ignored_paths).to include("/proc", "/sys") + # With checking for /, /proc, /sys, /dev this failed in Jenkins + # because that environment only seems to have /proc, /sys, /dev/pts, /dev/shm, + # so this test checks only for the least common denominator between + # a sane Linux system and that Jenkins environment. + end + + # Don't check in this context that there is nothing to unmount: + # The machine that executes the test might actually have something mounted at /mnt. + end + end + + describe "#mnt_prefix" do + it "leaves a normal mount prefix as it is" do + um = described_class.new("/foo", nil) + expect(um.mnt_prefix).to eq "/foo" + end + + it "strips off one trailing slash" do + um = described_class.new("/foo/", nil) + expect(um.mnt_prefix).to eq "/foo" + end + + it "even fixes up insanely broken prefixes" do + um = described_class.new("/foo///bar///", nil) + expect(um.mnt_prefix).to eq "/foo/bar" + end + + it "leaves a root directory prefix as it is" do + um = described_class.new("/", nil) + expect(um.mnt_prefix).to eq "/" + end + end + + describe "#ignore?" do + let(:subject) { described_class.new("/mnt", nil) } + + it "does not ignore /mnt" do + expect(subject.ignore?(mount("/mnt"))).to eq false + end + + it "does not ignore /mnt/foo" do + expect(subject.ignore?(mount("/mnt/foo"))).to eq false + end + + it "ignores /mnt2" do + expect(subject.ignore?(mount("/mnt2"))).to eq true + end + + it "ignores /mnt2/foo" do + expect(subject.ignore?(mount("/mnt2"))).to eq true + end + + it "ignores an empty path" do + expect(subject.ignore?(mount(""))).to eq true + end + end + + describe "#add_mount and #clear" do + before(:all) do + # Start with a completely empty unmounter + # and keep it alive between the tests of this group + @unmounter = described_class.new(PREFIX, nil) + end + + it "starts completely empty" do + expect(@unmounter.mounts).to eq [] + expect(@unmounter.ignored_mounts).to eq [] + expect(@unmounter.unmount_paths).to eq [] + expect(@unmounter.ignored_paths).to eq [] + end + + it "/ is ignored" do + @unmounter.add_mount("/dev/sdx1 / ext4 defaults 0 0") + expect(@unmounter.mounts).to eq [] + expect(@unmounter.unmount_paths).to eq [] + expect(@unmounter.ignored_paths).to eq ["/"] + end + + it "/home is ignored" do + @unmounter.add_mount("/dev/sdx2 /home ext4 defaults 0 0") + expect(@unmounter.mounts).to eq [] + expect(@unmounter.unmount_paths).to eq [] + expect(@unmounter.ignored_paths).to eq ["/", "/home"] + end + + it "/mnt will be unmounted" do + @unmounter.add_mount("/dev/sdy1 /mnt ext4 defaults 0 0") + expect(@unmounter.unmount_paths).to eq ["/mnt"] + expect(@unmounter.ignored_paths).to eq ["/", "/home"] + end + + it "/mnt/boot will be unmounted" do + @unmounter.add_mount("/dev/sdy2 /mnt/boot ext4 defaults 0 0") + expect(@unmounter.unmount_paths).to eq ["/mnt/boot", "/mnt"] + expect(@unmounter.ignored_paths).to eq ["/", "/home"] + end + + it "/mnt/boot/usb will be unmounted" do + @unmounter.add_mount("/dev/sdy2 /mnt/boot/usb ext4 defaults 0 0") + expect(@unmounter.unmount_paths).to eq ["/mnt/boot/usb", "/mnt/boot", "/mnt"] + expect(@unmounter.ignored_paths).to eq ["/", "/home"] + end + + it "#clear clears everything" do + @unmounter.clear + expect(@unmounter.mounts).to eq [] + expect(@unmounter.ignored_mounts).to eq [] + expect(@unmounter.unmount_paths).to eq [] + expect(@unmounter.ignored_paths).to eq [] + end + end + + describe "common scenarios" do + let(:subject) { described_class.new(PREFIX, proc_mounts) } + + context "partition-based btrfs with subvolumes, no separate /home" do + let(:proc_mounts) { stored_proc_mounts("btrfs") } # see data/proc/mounts/ + let(:expected_result) do + %w(/mnt/run + /mnt/sys + /mnt/proc + /mnt/dev + /mnt/var + /mnt/usr/local + /mnt/tmp + /mnt/srv + /mnt/root + /mnt/opt + /mnt/home + /mnt/boot/grub2/x86_64-efi + /mnt/boot/grub2/i386-pc + /mnt/.snapshots + /mnt) + end + + it "will unmount /mnt/run, /mnt/sys, /mnt/proc, /mnt/dev, all subvolumes, /mnt" do + expect(subject.unmount_paths).to eq expected_result + end + end + + context "partition-based btrfs with subvolumes and separate xfs /home" do + let(:proc_mounts) { stored_proc_mounts("btrfs-xfs-home") } + let(:expected_result) do + %w(/mnt/run + /mnt/sys + /mnt/proc + /mnt/dev + /mnt/var + /mnt/usr/local + /mnt/tmp + /mnt/srv + /mnt/root + /mnt/opt + /mnt/home + /mnt/boot/grub2/x86_64-efi + /mnt/boot/grub2/i386-pc + /mnt) + end + + it "will unmount /mnt/run, /mnt/sys, /mnt/proc, /mnt/dev, all subvolumes, /mnt/home, /mnt" do + expect(subject.unmount_paths).to eq expected_result + end + end + + context "encrypted LVM with btrfs with subvolumes and separate btrfs /home" do + let(:proc_mounts) { stored_proc_mounts("btrfs-xfs-home") } + let(:expected_result) do + %w(/mnt/run + /mnt/sys + /mnt/proc + /mnt/dev + /mnt/var + /mnt/usr/local + /mnt/tmp + /mnt/srv + /mnt/root + /mnt/opt + /mnt/home + /mnt/boot/grub2/x86_64-efi + /mnt/boot/grub2/i386-pc + /mnt) + end + + it "will unmount /mnt/run, /mnt/sys, /mnt/proc, /mnt/dev, all subvolumes, /mnt/home, /mnt" do + expect(subject.unmount_paths).to eq expected_result + end + end + end +end