Class: Installation::Unmounter
- Inherits:
-
Object
- Object
- Installation::Unmounter
- Includes:
- Yast::Logger
- Defined in:
- src/lib/installation/unmounter.rb
Overview
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: #unmounterunmounter.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.
Defined Under Namespace
Classes: Mount
Instance Attribute Summary collapse
-
#ignored_mounts ⇒ Array<Mount>
readonly
Ignored mounts (not starting with the mount prefix).
-
#mnt_prefix ⇒ Object
readonly
The mount prefix (typically "/mnt").
-
#mounts ⇒ Array<Mount>
readonly
Relevant mounts to unmount.
Instance Method Summary collapse
-
#add_mount(line) ⇒ Mount?
Parse one entry of /proc/mounts and add it to @mounts if it meets the criteria (mount prefix).
-
#clear ⇒ Object
Clear all prevous content.
-
#execute ⇒ Object
Actually execute all the pending unmount operations in the right sequence.
-
#ignore?(mount) ⇒ Boolean
Check if a mount should be ignored, i.e.
-
#ignored_paths ⇒ Array<String>
Return the paths that were ignored (in the order of /proc/mounts).
-
#initialize(mnt_prefix = "/mnt", mounts_file_name = "/proc/mounts") ⇒ Unmounter
constructor
Unmounter constructor.
-
#parse_mount(line) ⇒ Mount?
Parse one entry of /proc/mounts.
-
#read_mounts_file(file_name) ⇒ Object
Read a mounts file like /proc/mounts and add the relevant entries to the mounts stored in this class.
-
#unmount_paths ⇒ Array<String>
Return the paths to be unmounted in the correct unmount order.
Constructor Details
#initialize(mnt_prefix = "/mnt", mounts_file_name = "/proc/mounts") ⇒ Unmounter
Unmounter constructor.
78 79 80 81 82 |
# File 'src/lib/installation/unmounter.rb', line 78 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 |
Instance Attribute Details
#ignored_mounts ⇒ Array<Mount> (readonly)
Returns Ignored mounts (not starting with the mount prefix).
50 51 52 |
# File 'src/lib/installation/unmounter.rb', line 50 def ignored_mounts @ignored_mounts end |
#mnt_prefix ⇒ Object (readonly)
The mount prefix (typically "/mnt")
46 47 48 |
# File 'src/lib/installation/unmounter.rb', line 46 def mnt_prefix @mnt_prefix end |
#mounts ⇒ Array<Mount> (readonly)
Returns Relevant mounts to unmount.
48 49 50 |
# File 'src/lib/installation/unmounter.rb', line 48 def mounts @mounts end |
Instance Method Details
#add_mount(line) ⇒ Mount?
Parse one entry of /proc/mounts and add it to @mounts if it meets the criteria (mount prefix)
104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'src/lib/installation/unmounter.rb', line 104 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 |
#clear ⇒ Object
Clear all prevous content.
85 86 87 88 |
# File 'src/lib/installation/unmounter.rb', line 85 def clear @mounts = [] @ignored_mounts = [] end |
#execute ⇒ Object
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.
189 190 191 192 193 194 |
# File 'src/lib/installation/unmounter.rb', line 189 def execute unmount_paths.each do |path| log.info("Unmounting #{path}") Yast::Execute.locally!("umount", path) end end |
#ignore?(mount) ⇒ Boolean
Check if a mount should be ignored, i.e. if the path doesn't start with the mount prefix (usually "/mnt").
123 124 125 126 127 |
# File 'src/lib/installation/unmounter.rb', line 123 def ignore?(mount) return false if mount.mount_path == @mnt_prefix !mount.mount_path.start_with?(@mnt_prefix + "/") end |
#ignored_paths ⇒ Array<String>
Return the paths that were ignored (in the order of /proc/mounts). This is mostly useful for debugging and testing.
159 160 161 |
# File 'src/lib/installation/unmounter.rb', line 159 def ignored_paths @ignored_mounts.map(&:mount_path) end |
#parse_mount(line) ⇒ Mount?
Parse one entry of /proc/mounts.
134 135 136 137 138 139 140 |
# File 'src/lib/installation/unmounter.rb', line 134 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 |
#read_mounts_file(file_name) ⇒ Object
Read a mounts file like /proc/mounts and add the relevant entries to the mounts stored in this class.
93 94 95 96 |
# File 'src/lib/installation/unmounter.rb', line 93 def read_mounts_file(file_name) log.info("Reading file #{file_name}") File.readlines(file_name).each { |line| add_mount(line) } end |
#unmount_paths ⇒ Array<String>
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.
149 150 151 152 |
# File 'src/lib/installation/unmounter.rb', line 149 def unmount_paths paths = @mounts.map(&:mount_path) paths.reverse end |