Class: Installation::Clients::UmountFinishClient

Inherits:
FinishClient
  • Object
show all
Includes:
Yast::Logger
Defined in:
src/lib/installation/clients/umount_finish.rb

Overview

Finish client to unmount all mounts to the target

Constant Summary collapse

BTRFS_FS_TREE =

[String] Name used by btrfs tools to name the filesystem tree.

"(FS_TREE)".freeze

Instance Method Summary collapse

Constructor Details

#initializeUmountFinishClient

Constructor



26
27
28
29
30
31
# File 'src/lib/installation/clients/umount_finish.rb', line 26

def initialize
  super
  textdomain "installation"
  Yast.import "FileUtils"
  @running_standalone = false
end

Instance Method Details

#close_scr_on_targetObject (protected)



122
123
124
# File 'src/lib/installation/clients/umount_finish.rb', line 122

def close_scr_on_target
  WFM.SCRClose(Installation.scr_handle)
end

#default_subvolume_as_ro(filesystem) ⇒ Object (protected)

Set the "read-only" property for the root subvolume. This has to be done as long as the target root filesystem is still mounted.

Parameters:

  • filesystem (Y2Storage::Filesystems::Btrfs)

    Btrfs filesystem to set read-only property on.



165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'src/lib/installation/clients/umount_finish.rb', line 165

def default_subvolume_as_ro(filesystem)
  output = Yast::Execute.on_target(
    "btrfs", "subvolume", "get-default", filesystem.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 = filesystem.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

#dump_file(filename) ⇒ Object (protected)

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.



105
106
107
108
# File 'src/lib/installation/clients/umount_finish.rb', line 105

def dump_file(filename)
  content = File.read(filename)
  log.info("\n\n#{filename}:\n\n#{content}\n")
end

#log_running_processes(mount_points) ⇒ Object (protected)

run "fuser" to get the details about open files

Parameters:

  • mount_points (Array)

    [String]



182
183
184
185
186
187
188
189
190
191
192
# File 'src/lib/installation/clients/umount_finish.rb', line 182

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 StandardError => e
      "fuser failed: #{e}"
    end
  log.warn("\n\nRunning processes using #{mount_points}:\n#{fuser}\n")
end

#modesObject (protected)



49
50
51
52
53
# File 'src/lib/installation/clients/umount_finish.rb', line 49

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

#new_filesystem?(filesystem) ⇒ Boolean (protected)

Check whether the given filesystem is going to be created

Parameters:

  • filesystem (Y2Storage::Filesystems::Base)

Returns:

  • (Boolean)


198
199
200
# File 'src/lib/installation/clients/umount_finish.rb', line 198

def new_filesystem?(filesystem)
  !filesystem.exists_in_probed?
end

#remove_target_etc_mtabObject (protected)



110
111
112
113
114
115
116
117
118
119
120
# File 'src/lib/installation/clients/umount_finish.rb', line 110

def remove_target_etc_mtab
  # symlink points to /proc, keep it (bnc#665437)
  return if FileUtils.IsLink("/etc/mtab")

  # remove [Installation::destdir]/etc/mtab which was faked for %post
  # scripts in inst_rpmcopy
  SCR.Execute(path(".target.remove"), "/etc/mtab")

  # hotfix: recreating /etc/mtab as symlink (bnc#725166)
  SCR.Execute(path(".target.bash"), "ln -s /proc/self/mounts /etc/mtab")
end

#ro_btrfs_filesystem?(filesystem) ⇒ Boolean (protected)

Check whether the given filesystem is read-only BTRFS

Parameters:

  • filesystem (Y2Storage::Filesystems::Base)

Returns:

  • (Boolean)


206
207
208
# File 'src/lib/installation/clients/umount_finish.rb', line 206

def ro_btrfs_filesystem?(filesystem)
  filesystem.is?(:btrfs) && filesystem.mount_point && filesystem.mount_options.include?("ro")
end

#run_standaloneObject

This can be used when invoking this file directly with ruby ./umount_finish.rb



36
37
38
39
40
# File 'src/lib/installation/clients/umount_finish.rb', line 36

def run_standalone
  @running_standalone = true
  Installation.destdir = "/mnt" if Installation.destdir == "/"
  write
end

#set_btrfs_defaults_as_roObject

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).



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'src/lib/installation/clients/umount_finish.rb', line 137

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

  devicegraph = Y2Storage::StorageManager.instance.staging

  ro_btrfs_filesystems = devicegraph.filesystems.select do |fs|
    new_filesystem?(fs) && ro_btrfs_filesystem?(fs)
  end

  ro_btrfs_filesystems.each { |f| default_subvolume_as_ro(f) }
end

#titleObject (protected)



44
45
46
47
# File 'src/lib/installation/clients/umount_finish.rb', line 44

def title
  # progress step title
  _("Unmounting all mounted devices...")
end

#umount_target_mountsObject (protected)

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.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'src/lib/installation/clients/umount_finish.rb', line 75

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

#unmount_summary(leftover_paths) ⇒ Object (protected)

Write a summary of the unmount operations to the log.



93
94
95
96
97
98
99
100
101
# File 'src/lib/installation/clients/umount_finish.rb', line 93

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
end

#writeObject (protected)

Perform the final actions in the target system



56
57
58
59
60
61
62
63
64
65
66
# File 'src/lib/installation/clients/umount_finish.rb', line 56

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