Module: Mountfd

Defined in:
lib/mountfd.rb,
lib/mountfd/core.rb,
lib/mountfd/version.rb,
lib/mountfd/namespace.rb,
lib/mountfd/attributes.rb,
lib/mountfd/mount_info.rb,
lib/mountfd/user_namespace.rb,
sig/mountfd.rbs,
ext/mountfd/mountfd.c

Defined Under Namespace

Modules: Attributes, MountInfoParser, Namespace, Native, _Fileno Classes: AttachedMount, ConfigError, DetachedMount, Diagnostic, Error, FsContext, IdmapError, MountError, MountInfo, UnsupportedError, UserNamespace

Constant Summary collapse

AT_FDCWD =

Returns:

  • (Integer)
-100
VERSION =

Returns:

  • (String)
"0.1.0"

Class Method Summary collapse

Class Method Details

.bind(source, target, recursive: false, attrs: {}, idmap: nil) ⇒ AttachedMount

Parameters:

  • source (String)
  • target (String)
  • recursive: (Boolean) (defaults to: false)
  • attrs: (Hash[Symbol, untyped]) (defaults to: {})
  • idmap: (UserNamespace, Hash[Symbol, untyped], nil) (defaults to: nil)

Returns:



252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/mountfd/core.rb', line 252

def bind(source, target, recursive: false, attrs: {}, idmap: nil)
  namespace = UserNamespace.create(**idmap) if idmap.is_a?(Hash)
  detached = open_tree(source, recursive: recursive)
  apply_attributes(detached, attrs, recursive: recursive)
  detached.idmap!(namespace || idmap) if idmap
  detached.attach(target)
rescue StandardError
  detached&.discard unless detached&.closed?
  raise
ensure
  namespace&.close unless namespace&.closed?
end

.featuresArray[Symbol]

Returns:

  • (Array[Symbol])


176
177
178
179
180
181
182
183
184
185
# File 'lib/mountfd/core.rb', line 176

def features
  return [] unless supported?

  values = [:new_mount_api]
  values.concat([:mount_setattr, :idmap]) if Native.syscall_available?("mount_setattr")
  values << :statmount if %w[statmount listmount].all? { Native.syscall_available?(_1) }
  values << :move_mount_beneath if kernel_at_least?(6, 5)
  values << :create_excl if kernel_at_least?(6, 6)
  values
end

.fileno(value) ⇒ Object



296
# File 'lib/mountfd/core.rb', line 296

def fileno(value) = value.respond_to?(:fileno) ? value.fileno : Integer(value)

.mount(source, target, type: source, options: {}, attrs: {}) ⇒ AttachedMount

Parameters:

  • source (String)
  • target (String)
  • type: (String) (defaults to: source)
  • options: (Hash[Symbol | String, untyped]) (defaults to: {})
  • attrs: (Hash[Symbol, untyped]) (defaults to: {})

Returns:



238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/mountfd/core.rb', line 238

def mount(source, target, type: source, options: {}, attrs: {})
  detached = nil
  FsContext.open(type) do |context|
    context.set("source", source) unless source.to_s == type.to_s
    options.each { |key, value| context.set(key, value) }
    context.create!
    detached = context.mount(attrs: attrs)
    detached.attach(target)
  end
rescue StandardError
  detached&.discard unless detached&.closed?
  raise
end

.mount_at(path, **options) ⇒ MountInfo?

Parameters:

  • path (String)
  • ns: (Integer, _Fileno, nil)
  • backend: (:statmount, :mountinfo)

Returns:



220
221
222
223
# File 'lib/mountfd/core.rb', line 220

def mount_at(path, **options)
  target = File.expand_path(File.path(path))
  mounts(**options).find { File.expand_path(_1.mount_point) == target }
end

.mounts(ns: nil, backend: nil) ⇒ Array[MountInfo]

Parameters:

  • ns: (Integer, _Fileno, nil) (defaults to: nil)
  • backend: (:statmount, :mountinfo) (defaults to: nil)

Returns:



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/mountfd/core.rb', line 189

def mounts(ns: nil, backend: nil)
  selected = backend || preferred_mounts_backend(ns)
  if selected == :statmount
    @mounts_backend = :statmount
    values = if ns.nil?
               Native.statmounts(nil)
             elsif ns.respond_to?(:fileno)
               Native.statmounts(fileno(ns))
             else
               File.open("/proc/#{Integer(ns)}/ns/mnt") { Native.statmounts(_1.fileno) }
             end
    return values.map { mount_info_from_statmount(_1) }
  end

  raise ArgumentError, "backend must be :statmount or :mountinfo" unless selected == :mountinfo
  if ns&.respond_to?(:fileno)
    raise UnsupportedError, "mount namespace descriptors require statmount on Linux 6.11 or newer"
  end

  @mounts_backend = :mountinfo
  pid = ns.nil? ? "self" : Integer(ns)
  read_mountinfo(pid)
rescue SystemCallError, UnsupportedError
  raise if backend == :statmount || ns&.respond_to?(:fileno)

  @mounts_backend = :mountinfo
  read_mountinfo(ns ? Integer(ns) : "self")
end

.mounts_backend:statmount, :mountinfo

Returns:

  • (:statmount, :mountinfo)


218
# File 'lib/mountfd/core.rb', line 218

def mounts_backend = @mounts_backend || preferred_mounts_backend(nil)

.open_treevoid .open_tree(path, recursive:) ⇒ DetachedMount

Overloads:

  • .open_treevoid

    This method returns an undefined value.

  • .open_tree(path, recursive:) ⇒ DetachedMount

    Parameters:

    • path (String)
    • recursive: (Boolean)

    Returns:



225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/mountfd/core.rb', line 225

def open_tree(path, recursive: false)
  flags = Native::OPEN_TREE_CLONE | Native::OPEN_TREE_CLOEXEC
  flags |= Native::AT_RECURSIVE if recursive
  mount = DetachedMount.new(Native.open_tree(AT_FDCWD, File.path(path), flags))
  return mount unless block_given?

  begin
    yield mount
  ensure
    mount.discard unless mount.closed?
  end
end

.pending_mountsArray[DetachedMount]

Returns:



187
# File 'lib/mountfd/core.rb', line 187

def pending_mounts = (@pending_mounts || []).reject(&:closed?).dup

.pivot_root(new_root, put_old) ⇒ nil

Parameters:

  • new_root (String)
  • put_old (String)

Returns:

  • (nil)


270
# File 'lib/mountfd/core.rb', line 270

def pivot_root(new_root, put_old) = Native.pivot_root(File.path(new_root), File.path(put_old))

.replace(path) { ... } ⇒ AttachedMount

Parameters:

  • path (String)

Yields:

Yield Returns:

Returns:



283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/mountfd/core.rb', line 283

def replace(path)
  target = File.path(path)
  detached = yield
  raise ArgumentError, "replace block must return a DetachedMount" unless detached.is_a?(DetachedMount)

  detached.attach(target, beneath: true)
  umount(target)
  AttachedMount.new(target)
rescue StandardError
  detached&.discard unless detached&.closed?
  raise
end

.set_attributes(path, attrs: {}, propagation: nil, recursive: false) ⇒ nil

Parameters:

  • path (String)
  • attrs: (Hash[Symbol, untyped]) (defaults to: {})
  • propagation: (Symbol, nil) (defaults to: nil)
  • recursive: (Boolean) (defaults to: false)

Returns:

  • (nil)


272
273
274
275
276
277
278
279
280
281
# File 'lib/mountfd/core.rb', line 272

def set_attributes(path, attrs: {}, propagation: nil, recursive: false)
  set, clr = Attributes.build(attrs)
  flags = recursive ? Native::AT_RECURSIVE : 0
  Native.mount_setattr(
    AT_FDCWD, File.path(path), flags, set, clr, Attributes.propagation(propagation), nil
  )
  nil
rescue SystemCallError => error
  raise MountError, "mount_setattr: #{error.message}", cause: error
end

.supported?Boolean

Returns:

  • (Boolean)


174
# File 'lib/mountfd/core.rb', line 174

def supported? = Native.syscall_available?("fsopen")

.umount(path, detach: true, force: false) ⇒ nil

Parameters:

  • path (String)
  • detach: (Boolean) (defaults to: true)
  • force: (Boolean) (defaults to: false)

Returns:

  • (nil)


265
266
267
268
# File 'lib/mountfd/core.rb', line 265

def umount(path, detach: true, force: false)
  flags = (detach ? 2 : 0) | (force ? 1 : 0)
  Native.umount2(File.path(path), flags)
end