File systemd_override.rb of Package smar-apparmor-profiles
# This file is included by generate_spec.rb
class OverrideFile
attr_accessor :conditional
def initialize profile_name
# Matches if profile_name is a a collection of
# key-value pairs.
#
# This means, the data is something like:
#
# load_profile_by_systemd:
# user:
# pulseaudio:
# profile: pulseaudio
#
# Here key of `profile_name` would be “profile”
# and value would be “pulseaudio”.
if profile_name.respond_to? :each_pair
@profile_name = profile_name["profile"]
# Safeguard.
raise "Empty profile name" if @profile_name.empty?
@no_new_privs = profile_name["no_new_privs"]
else
@profile_name = profile_name
end
end
def to_s
return to_s_conditional if @conditional
output = <<~EOF
[Service]
AppArmorProfile=#{@profile_name}
EOF
output += no_new_privs_to_s
output
end
# Conditional profiles shouldn’t be used but where
# absolutely needed (systemd-udevd load during boot
# seems to be broken, so it needs to be conditional),
# so log these.
private def to_s_conditional
verb "Conditional profile for #{@profile_name}"
output = <<~EOF
[Service]
AppArmorProfile=-#{@profile_name}
EOF
output += no_new_privs_to_s
output
end
private def no_new_privs_to_s
return "" if @no_new_privs.nil?
# If `NoNewPrivileges=yes`, systemd practically
# won’t transition to the profile mentioned in
# `AppArmorProfile=`.
output = "NoNewPrivileges=#{ @no_new_privs ? "yes" : "no" }"
output += "\n"
output
end
end
class SystemdOverride
SYSTEMD_OVERRIDES_DIR_NAME = "systemd_overrides"
SYSTEMD_OVERRIDES_DIR = "#{__dir__}/#{SYSTEMD_OVERRIDES_DIR_NAME}"
SYSTEMD_OVERRIDES_TAR = "systemd_overrides.tar"
at_exit do
if Dir.exist? SYSTEMD_OVERRIDES_DIR
FileUtils.rm_r SYSTEMD_OVERRIDES_DIR
end
end
def initialize
Dir.mkdir SYSTEMD_OVERRIDES_DIR
end
def finalize
return unless Dir.exists? SYSTEMD_OVERRIDES_DIR
return unless systemd_overrides_changed?
# Try to create reproducible tar.
# NOTE: --mtime is not specified here, so archive actually will be different in
# each generation, but that can be added to reproduce archive from certain time.
tar_command = "tar"
tar_command = "#{tar_command} --sort=name --owner=0 --group=0 --numeric-owner"
tar_command = "#{tar_command} --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime"
tar_command = "#{tar_command} -cf #{SYSTEMD_OVERRIDES_TAR} systemd_overrides/"
debug "Executing “#{tar_command}”."
result = `#{tar_command}`
unless $?.success?
puts "Failed to execute “#{tar_command}”. Output:"
puts result
exit 21
end
File.delete "#{SYSTEMD_OVERRIDES_TAR}.xz" if File.exist? "#{SYSTEMD_OVERRIDES_TAR}.xz"
# Using single thread should make xz archives reproducible across systems.
# Though since I always regenerate the files, metadata won’t match, so there will always
# be new archive.
# But since this is so small, this has no harm.
xz_command = "xz --threads=1 #{SYSTEMD_OVERRIDES_TAR}"
`#{xz_command}`
# Now there should be archive systemd_overrides.tar.xz.
end
def install_lines_for_load_profile_by_systemd(definition,
package,
service_name,
profile_name,
user: false,
conditional: false)
return if redirect_to_service definition, package, service_name, profile_name
write_systemd_override_file package, service_name, profile_name, user: user, conditional: conditional
# Matches if profile_name is a a collection of
# key-value pairs.
#
# This means, the data is something like:
#
# load_profile_by_systemd:
# user:
# pulseaudio:
# profile: pulseaudio
#
# Here key of `profile_name` would be “profile”
# and value would be “pulseaudio”.
if profile_name.respond_to? :each_pair
profile_name = profile_name["profile"]
end
if user
override_file = Pathname.new("systemd_overrides") / "user" / package["name"] / service_name / "#{profile_name}.conf"
unitdir_tag = "%{_userunitdir}"
else
override_file = Pathname.new("systemd_overrides") / package["name"] / service_name / "#{profile_name}.conf"
unitdir_tag = "%{_unitdir}"
end
definition << "mkdir -p %{buildroot}#{unitdir_tag}/#{service_name}.service.d/"
definition << "mv #{override_file} %{buildroot}#{unitdir_tag}/#{service_name}.service.d/"
end
# Returns true if service name is something like “user”.
private def redirect_to_service definition, package, service_name, profile_name
if service_name == "system"
profile_name.each do |service_name, profile_name|
install_lines_for_load_profile_by_systemd definition, package, service_name, profile_name
end
return true
end
if service_name == "system_conditional"
profile_name.each do |service_name, profile_name|
install_lines_for_load_profile_by_systemd definition, package, service_name, profile_name, conditional: true
end
return true
end
if service_name == "user"
profile_name.each do |service_name, profile_name|
install_lines_for_load_profile_by_systemd definition, package, service_name, profile_name, user: true
end
return true
end
if service_name == "user_conditional"
profile_name.each do |service_name, profile_name|
install_lines_for_load_profile_by_systemd definition, package, service_name, profile_name, user: true, conditional: true
end
return true
end
false
end
private def write_systemd_override_file package, service_name, profile_name, user: false, conditional: false
if user
package_dir = Pathname.new(SYSTEMD_OVERRIDES_DIR) / "user" / package["name"]
Dir.mkdir package_dir.parent unless Dir.exist? package_dir.parent
else
package_dir = Pathname.new(SYSTEMD_OVERRIDES_DIR) / package["name"]
end
Dir.mkdir package_dir unless Dir.exist? package_dir
service_dir = package_dir / service_name
Dir.mkdir service_dir unless Dir.exist? service_dir
profile = generate_service_override_conf profile_name, conditional: conditional
# Matches if profile_name is a a collection of
# key-value pairs.
#
# This means, the data is something like:
#
# load_profile_by_systemd:
# user:
# pulseaudio:
# profile: pulseaudio
#
# Here key of `profile_name` would be “profile”
# and value would be “pulseaudio”.
if profile_name.respond_to? :each_pair
profile_name = profile_name["profile"]
end
debug "Writing #{service_dir / "#{profile_name}.conf"}"
File.open(service_dir / "#{profile_name}.conf", "w") do |f|
f.write profile
end
end
private def generate_service_override_conf profile_name, conditional: false
override = OverrideFile.new profile_name
override.conditional = conditional
override.to_s
end
private def systemd_overrides_changed?
# First check if there is missing files.
files = Dir.glob "#{SYSTEMD_OVERRIDES_DIR_NAME}/**/*", base: __dir__
files = files.delete_if do |path|
File.directory? path
end
missing_files_command = "tar"
missing_files_args = %W{ tf #{SYSTEMD_OVERRIDES_TAR}.xz } + files
debug "Executing missing_files_command: “#{missing_files_command} #{missing_files_args.join(" ")}”."
# err: :close == Don’t output command STDERR to generate_spec.rb’s STDERR.
#
# HINT: Use latter for debugging.
if DEBUG
result = system(missing_files_command, *missing_files_args)
else
result = system(missing_files_command, *missing_files_args, out: File::NULL, err: File::NULL)
end
debug "missing_files_command exitstatus=#{$?.exitstatus}, result=#{result}"
if $?.exitstatus == 2
# A file is missing from overrides archive.
return true
end
if $?.exitstatus != 0
puts "ERROR: Got an exit code from tar that is not handled: #{$?.exitstatus}"
exit 23
end
# Then try to compare contents.
#
# This is a bit flaky.
compare_command = %{env LANG=C tar --compare --file='#{SYSTEMD_OVERRIDES_TAR}.xz'}
compare_command = %{#{compare_command} | grep -v 'Uid differs$' | grep -v 'Gid differs$'}
compare_command = %{#{compare_command} | grep -v 'Mod time differs$'}
debug "Executing compare_command: “#{compare_command}”."
output = nil
IO.pipe do |read_io, write_io|
result = system(compare_command, out: write_io, err: write_io)
write_io.close
output = read_io.read
end
debug "compare_command exitstatus=#{$?.exitstatus}, result=#{result}"
# 0 is everything is same, 1 means some files differs.
unless [0, 1].include? $?.exitstatus
puts "Failed to execute compare command. Result:"
puts
puts output
exit 22
end
debug "compare_command output: #{output}"
# If yes, there is no changed content in the archive.
if output.empty?
return false
end
true
end
end