2019-12-22 07:12:39 +03:00
|
|
|
# "System" helpers.
|
|
|
|
module System
|
|
|
|
class CommandError < StandardError
|
|
|
|
end
|
|
|
|
class CommandNotFound < CommandError
|
|
|
|
end
|
2019-12-23 06:32:34 +03:00
|
|
|
class MountError < StandardError
|
|
|
|
end
|
2019-12-22 07:12:39 +03:00
|
|
|
|
2019-12-24 09:46:57 +03:00
|
|
|
def self.prettify_command(*args)
|
2019-12-24 08:54:30 +03:00
|
|
|
args = args.dup
|
|
|
|
# Removes the environment hash, if present.
|
|
|
|
args.shift if args.first.is_a?(Hash)
|
2019-12-24 09:46:57 +03:00
|
|
|
if args.length == 1
|
|
|
|
args.first
|
|
|
|
else
|
|
|
|
args.shelljoin
|
|
|
|
end
|
2019-12-24 08:54:30 +03:00
|
|
|
end
|
|
|
|
|
2019-12-22 07:12:39 +03:00
|
|
|
# Runs and pretty-prints a command. Parameters and shelling-out have the same
|
|
|
|
# meaning as with +Kernel#spawn+; one parameter is shelling-out, multiple is
|
|
|
|
# direct +exec+.
|
|
|
|
#
|
|
|
|
# @param args [Array<String>] Command and parameters
|
|
|
|
# @raise [System::CommandNotFound] on exit status 127, commonly used for command not found.
|
|
|
|
# @raise [System::CommandError] on any other exit status.
|
|
|
|
def self.run(*args)
|
2019-12-24 09:46:57 +03:00
|
|
|
pretty_command = prettify_command(*args)
|
|
|
|
$logger.debug(" $ #{pretty_command}")
|
2019-12-22 07:12:39 +03:00
|
|
|
unless system(*args)
|
|
|
|
raise CommandError.new("Could not execute `#{pretty_command}`, status nil") if $?.nil?
|
|
|
|
status = $?.exitstatus
|
|
|
|
if status == 127
|
|
|
|
raise CommandNotFound.new("Command not found... `#{pretty_command}` (#{status})")
|
|
|
|
else
|
|
|
|
raise CommandError.new("Command failed... `#{pretty_command}` (#{status})")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-12-24 08:54:30 +03:00
|
|
|
# Execs and pretty-prints a command.
|
|
|
|
def self.exec(*args)
|
2019-12-24 09:46:57 +03:00
|
|
|
$logger.debug(" $ #{prettify_command(*args)}")
|
2019-12-24 08:54:30 +03:00
|
|
|
Kernel.exec(*args)
|
|
|
|
end
|
|
|
|
|
2019-12-25 08:01:25 +03:00
|
|
|
# Thin wrapper over +Kernel#exec+, but with printing.
|
|
|
|
def self.spawn(*args)
|
|
|
|
$logger.debug(" $ #{prettify_command(*args)} &")
|
|
|
|
Kernel.spawn(*args)
|
|
|
|
end
|
|
|
|
|
2019-12-24 08:54:30 +03:00
|
|
|
# Discovers the location of given program name.
|
|
|
|
def self.which(program_name)
|
|
|
|
ENV["PATH"].split(":").each do |path|
|
|
|
|
full = File.join(path, program_name)
|
|
|
|
return full if File.stat(full).executable? && !File.directory?(full)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-01-21 00:37:57 +03:00
|
|
|
def self.write(file, contents)
|
|
|
|
$logger.debug("echo #{contents.to_json} > #{file}")
|
|
|
|
File.write(file, contents)
|
|
|
|
end
|
|
|
|
|
2020-02-03 08:19:22 +03:00
|
|
|
# Lists all mount points.
|
|
|
|
# This handles temporarily mounting /proc if required.
|
|
|
|
# This will hide /proc in those instances.
|
|
|
|
# The return format is a hash, with keys being mount point paths,
|
|
|
|
# and values being their respective line from /proc/mounts.
|
|
|
|
def self.mount_points()
|
|
|
|
# This is the most horrible hack :(
|
|
|
|
mounted_proc = false
|
|
|
|
unless File.exists?("/proc/mounts")
|
|
|
|
$logger.debug("Temporarily mounting /proc...")
|
|
|
|
FileUtils.mkdir_p("/proc")
|
|
|
|
run("mount", "-t", "proc", "proc", "/proc")
|
|
|
|
mounted_proc = true
|
|
|
|
end
|
|
|
|
result = File.read("/proc/mounts").split("\n")
|
|
|
|
run("umount", "-f", "/proc") if mounted_proc
|
|
|
|
|
|
|
|
result = result.map do |line|
|
|
|
|
[
|
|
|
|
# Safe to split by space, spaces are escaped in the mount point.
|
|
|
|
# "/tmp/test a b" ->> tmpfs /tmp/test\040a\040b tmpfs rw,relatime 0 0
|
|
|
|
line.split(" ")[1].gsub('\040', " "),
|
|
|
|
line
|
|
|
|
]
|
|
|
|
end.to_h
|
|
|
|
|
|
|
|
# We mounted /proc? Hide it! We've now unmounted it.
|
|
|
|
result.delete("/proc") if mounted_proc
|
|
|
|
|
|
|
|
result
|
|
|
|
end
|
|
|
|
|
2019-12-22 07:12:39 +03:00
|
|
|
# Mounts a filesystem of type +type+ on +dest+.
|
|
|
|
#
|
|
|
|
# The +source+ parameter is optional, though kept first to keep a coherent
|
|
|
|
# order compared to the actual +mount+ command.
|
|
|
|
#
|
|
|
|
# @overload mount(source, dest, type:)
|
|
|
|
# @param source [String] (Optional) mount source, defaults to type
|
|
|
|
# @param dest [String] Destination path to mount to
|
|
|
|
# @param type [String] Type of the mount (+-t+).
|
2019-12-24 01:42:36 +03:00
|
|
|
# @param options [Array<String>] Mount options (+-o+).
|
2019-12-22 07:12:39 +03:00
|
|
|
# @overload mount(dest, type:)
|
|
|
|
# @param dest [String] Destination path to mount to
|
|
|
|
# @param type [String] Type of the mount (+-t+).
|
2019-12-24 01:42:36 +03:00
|
|
|
# @param options [Array<String>] Mount options (+-o+).
|
|
|
|
def self.mount(source, dest = nil, type: nil, options: nil)
|
2019-12-22 07:12:39 +03:00
|
|
|
# Fill-in the "reversed" optional parameters.
|
|
|
|
unless dest
|
|
|
|
dest = source
|
|
|
|
source = type
|
|
|
|
end
|
2019-12-23 06:32:34 +03:00
|
|
|
|
|
|
|
if source.nil? and type.nil?
|
|
|
|
raise MountError.new("Cannot mount when missing both source and type.")
|
|
|
|
end
|
|
|
|
|
|
|
|
args = []
|
|
|
|
if type
|
|
|
|
args << "-t"
|
|
|
|
args << type
|
|
|
|
end
|
2019-12-24 01:42:36 +03:00
|
|
|
if options
|
|
|
|
args << "-o"
|
|
|
|
args << options.join(",")
|
|
|
|
end
|
2019-12-23 06:32:34 +03:00
|
|
|
args << source
|
|
|
|
args << dest
|
|
|
|
|
2020-02-03 08:19:22 +03:00
|
|
|
# We may have some mountpoints already mounted from, e.g. early logging in
|
|
|
|
# /run/log... If we're not careful we will mount over the existing mount
|
|
|
|
# point and hide the resulting files.
|
|
|
|
# Side-step by re-mounting to have the appropriate options.
|
|
|
|
if mount_points.keys.include?(dest)
|
|
|
|
$logger.debug("#{dest} already mounted, remounting.")
|
|
|
|
run("mount", "-o", "remount", *args)
|
|
|
|
else
|
|
|
|
run("mount", *args)
|
|
|
|
end
|
2019-12-22 07:12:39 +03:00
|
|
|
end
|
2019-12-24 05:53:48 +03:00
|
|
|
|
2020-03-10 02:45:34 +03:00
|
|
|
def self.sad_phone(color, code, message)
|
2019-12-24 09:48:24 +03:00
|
|
|
begin
|
2020-05-29 23:35:59 +03:00
|
|
|
System.run(LOADER, "/applets/boot-error.mrb", color, code, message)
|
2020-03-10 02:45:34 +03:00
|
|
|
rescue CommandError => e
|
|
|
|
$logger.fatal(e.inspect)
|
2019-12-24 09:48:24 +03:00
|
|
|
end
|
2020-01-25 03:50:42 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.failure(code, message="(No details given)", color: "000000")
|
2019-12-24 05:53:48 +03:00
|
|
|
$logger.fatal("#{code}: #{message}")
|
2020-03-10 02:45:34 +03:00
|
|
|
sad_phone(color, code, message)
|
2020-01-25 00:02:18 +03:00
|
|
|
shell if respond_to?(:shell)
|
2019-12-24 22:17:03 +03:00
|
|
|
sleep(Configuration["boot"]["fail"]["delay"])
|
|
|
|
hard_reboot if Configuration["boot"]["fail"]["reboot"]
|
2019-12-24 05:53:48 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.hard_reboot()
|
2020-01-21 00:40:11 +03:00
|
|
|
System.write("/proc/sysrq-trigger", "b\n")
|
2019-12-24 05:53:48 +03:00
|
|
|
end
|
2019-12-22 07:12:39 +03:00
|
|
|
end
|