Class: Installation::Unmounter

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initialize(mnt_prefix = "/mnt", mounts_file_name = "/proc/mounts") ⇒ Unmounter

Unmounter constructor.

Parameters:

  • mnt_prefix (String) (defaults to: "/mnt")

    Prefix which paths should be unmounted.

  • mounts_file_name (String) (defaults to: "/proc/mounts")

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


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_mountsArray<Mount> (readonly)

Returns Ignored mounts (not starting with the mount prefix).

Returns:

  • (Array<Mount>)

    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_prefixObject (readonly)

The mount prefix (typically "/mnt")


46
47
48
# File 'src/lib/installation/unmounter.rb', line 46

def mnt_prefix
  @mnt_prefix
end

#mountsArray<Mount> (readonly)

Returns Relevant mounts to unmount.

Returns:

  • (Array<Mount>)

    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)

Parameters:

  • line (String)

    one line of /proc/mounts

Returns:

  • (Mount, nil)

    parsed mount if relevant


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

#clearObject

Clear all prevous content.


85
86
87
88
# File 'src/lib/installation/unmounter.rb', line 85

def clear
  @mounts = []
  @ignored_mounts = []
end

#executeObject

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

Returns:

  • (Boolean)

    ignore


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_pathsArray<String>

Return the paths that were ignored (in the order of /proc/mounts). This is mostly useful for debugging and testing.

Returns:

  • (Array<String>)

    paths


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.

Parameters:

  • line (String)

    one line of /proc/mounts

Returns:

  • (Mount, nil)

    parsed mount


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

Returns:

  • (Array<String>)

    paths


149
150
151
152
# File 'src/lib/installation/unmounter.rb', line 149

def unmount_paths
  paths = @mounts.map(&:mount_path)
  paths.reverse
end