File systemd_override.rb of Package smar-apparmor-profiles (Revision 2f14ba83140935a02637450507288441)
Currently displaying revision 2f14ba83140935a02637450507288441 , Show latest
299
1
# This file is included by generate_spec.rb
2
3
class OverrideFile
4
attr_accessor :conditional
5
6
def initialize profile_name
7
8
# Matches if profile_name is a a collection of
9
# key-value pairs.
10
#
11
# This means, the data is something like:
12
#
13
# load_profile_by_systemd:
14
# user:
15
# pulseaudio:
16
# profile: pulseaudio
17
#
18
# Here key of `profile_name` would be “profile”
19
# and value would be “pulseaudio”.
20
if profile_name.respond_to? :each_pair
21
@profile_name = profile_name["profile"]
22
23
# Safeguard.
24
raise "Empty profile name" if @profile_name.empty?
25
26
@no_new_privs = profile_name["no_new_privs"]
27
else
28
@profile_name = profile_name
29
end
30
end
31
32
def to_s
33
return to_s_conditional if @conditional
34
35
output = <<~EOF
36
[Service]
37
AppArmorProfile=#{@profile_name}
38
EOF
39
40
output += no_new_privs_to_s
41
42
output
43
end
44
45
# Conditional profiles shouldn’t be used but where
46
# absolutely needed (systemd-udevd load during boot
47
# seems to be broken, so it needs to be conditional),
48
# so log these.
49
private def to_s_conditional
50
verb "Conditional profile for #{@profile_name}"
51
52
output = <<~EOF
53
[Service]
54
AppArmorProfile=-#{@profile_name}
55
EOF
56
57
output += no_new_privs_to_s
58
59
output
60
end
61
62
private def no_new_privs_to_s
63
return "" if @no_new_privs.nil?
64
65
# If `NoNewPrivileges=yes`, systemd practically
66
# won’t transition to the profile mentioned in
67
# `AppArmorProfile=`.
68
output = "NoNewPrivileges=#{ @no_new_privs ? "yes" : "no" }"
69
output += "\n"
70
71
output
72
end
73
end
74
75
class SystemdOverride
76
SYSTEMD_OVERRIDES_DIR_NAME = "systemd_overrides"
77
SYSTEMD_OVERRIDES_DIR = "#{__dir__}/#{SYSTEMD_OVERRIDES_DIR_NAME}"
78
SYSTEMD_OVERRIDES_TAR = "systemd_overrides.tar"
79
80
at_exit do
81
if Dir.exist? SYSTEMD_OVERRIDES_DIR
82
FileUtils.rm_r SYSTEMD_OVERRIDES_DIR
83
end
84
end
85
86
def initialize
87
Dir.mkdir SYSTEMD_OVERRIDES_DIR
88
end
89
90
def finalize
91
return unless Dir.exists? SYSTEMD_OVERRIDES_DIR
92
return unless systemd_overrides_changed?
93
94
# Try to create reproducible tar.
95
# NOTE: --mtime is not specified here, so archive actually will be different in
96
# each generation, but that can be added to reproduce archive from certain time.
97
tar_command = "tar"
98
tar_command = "#{tar_command} --sort=name --owner=0 --group=0 --numeric-owner"
99
tar_command = "#{tar_command} --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime"
100
tar_command = "#{tar_command} -cf #{SYSTEMD_OVERRIDES_TAR} systemd_overrides/"
101
debug "Executing “#{tar_command}”."
102
result = `#{tar_command}`
103
unless $?.success?
104
puts "Failed to execute “#{tar_command}”. Output:"
105
puts result
106
exit 21
107
end
108
109
File.delete "#{SYSTEMD_OVERRIDES_TAR}.xz" if File.exist? "#{SYSTEMD_OVERRIDES_TAR}.xz"
110
# Using single thread should make xz archives reproducible across systems.
111
# Though since I always regenerate the files, metadata won’t match, so there will always
112
# be new archive.
113
# But since this is so small, this has no harm.
114
xz_command = "xz --threads=1 #{SYSTEMD_OVERRIDES_TAR}"
115
`#{xz_command}`
116
117
# Now there should be archive systemd_overrides.tar.xz.
118
end
119
120
def install_lines_for_load_profile_by_systemd(definition,
121
package,
122
service_name,
123
profile_name,
124
user: false,
125
conditional: false)
126
return if redirect_to_service definition, package, service_name, profile_name
127
128
write_systemd_override_file package, service_name, profile_name, user: user, conditional: conditional
129
130
# Matches if profile_name is a a collection of
131
# key-value pairs.
132
#
133
# This means, the data is something like:
134
#
135
# load_profile_by_systemd:
136
# user:
137
# pulseaudio:
138
# profile: pulseaudio
139
#
140
# Here key of `profile_name` would be “profile”
141
# and value would be “pulseaudio”.
142
if profile_name.respond_to? :each_pair
143
profile_name = profile_name["profile"]
144
end
145
146
if user
147
override_file = Pathname.new("systemd_overrides") / "user" / package["name"] / service_name / "#{profile_name}.conf"
148
unitdir_tag = "%{_userunitdir}"
149
else
150
override_file = Pathname.new("systemd_overrides") / package["name"] / service_name / "#{profile_name}.conf"
151
unitdir_tag = "%{_unitdir}"
152
end
153
definition << "mkdir -p %{buildroot}#{unitdir_tag}/#{service_name}.service.d/"
154
definition << "mv #{override_file} %{buildroot}#{unitdir_tag}/#{service_name}.service.d/"
155
end
156
157
# Returns true if service name is something like “user”.
158
private def redirect_to_service definition, package, service_name, profile_name
159
if service_name == "system"
160
profile_name.each do |service_name, profile_name|
161
install_lines_for_load_profile_by_systemd definition, package, service_name, profile_name
162
end
163
return true
164
end
165
if service_name == "system_conditional"
166
profile_name.each do |service_name, profile_name|
167
install_lines_for_load_profile_by_systemd definition, package, service_name, profile_name, conditional: true
168
end
169
return true
170
end
171
if service_name == "user"
172
profile_name.each do |service_name, profile_name|
173
install_lines_for_load_profile_by_systemd definition, package, service_name, profile_name, user: true
174
end
175
return true
176
end
177
if service_name == "user_conditional"
178
profile_name.each do |service_name, profile_name|
179
install_lines_for_load_profile_by_systemd definition, package, service_name, profile_name, user: true, conditional: true
180
end
181
return true
182
end
183
184
false
185
end
186
187
private def write_systemd_override_file package, service_name, profile_name, user: false, conditional: false
188
if user
189
package_dir = Pathname.new(SYSTEMD_OVERRIDES_DIR) / "user" / package["name"]
190
Dir.mkdir package_dir.parent unless Dir.exist? package_dir.parent
191
else
192
package_dir = Pathname.new(SYSTEMD_OVERRIDES_DIR) / package["name"]
193
end
194
Dir.mkdir package_dir unless Dir.exist? package_dir
195
196
service_dir = package_dir / service_name
197
Dir.mkdir service_dir unless Dir.exist? service_dir
198
199
profile = generate_service_override_conf profile_name, conditional: conditional
200
201
# Matches if profile_name is a a collection of
202
# key-value pairs.
203
#
204
# This means, the data is something like:
205
#
206
# load_profile_by_systemd:
207
# user:
208
# pulseaudio:
209
# profile: pulseaudio
210
#
211
# Here key of `profile_name` would be “profile”
212
# and value would be “pulseaudio”.
213
if profile_name.respond_to? :each_pair
214
profile_name = profile_name["profile"]
215
end
216
217
debug "Writing #{service_dir / "#{profile_name}.conf"}"
218
File.open(service_dir / "#{profile_name}.conf", "w") do |f|
219
f.write profile
220
end
221
end
222
223
private def generate_service_override_conf profile_name, conditional: false
224
override = OverrideFile.new profile_name
225
override.conditional = conditional
226
override.to_s
227
end
228
229
private def systemd_overrides_changed?
230
# First check if there is missing files.
231
232
files = Dir.glob "#{SYSTEMD_OVERRIDES_DIR_NAME}/**/*", base: __dir__
233
files = files.delete_if do |path|
234
File.directory? path
235
end
236
missing_files_command = "tar"
237
missing_files_args = %W{ tf #{SYSTEMD_OVERRIDES_TAR}.xz } + files
238
debug "Executing missing_files_command: “#{missing_files_command} #{missing_files_args.join(" ")}”."
239
240
# err: :close == Don’t output command STDERR to generate_spec.rb’s STDERR.
241
#
242
# HINT: Use latter for debugging.
243
if DEBUG
244
result = system(missing_files_command, *missing_files_args)
245
else
246
result = system(missing_files_command, *missing_files_args, out: File::NULL, err: File::NULL)
247
end
248
249
debug "missing_files_command exitstatus=#{$?.exitstatus}, result=#{result}"
250
251
if $?.exitstatus == 2
252
# A file is missing from overrides archive.
253
return true
254
end
255
256
if $?.exitstatus != 0
257
puts "ERROR: Got an exit code from tar that is not handled: #{$?.exitstatus}"
258
exit 23
259
end
260
261
# Then try to compare contents.
262
#
263
# This is a bit flaky.
264
265
compare_command = %{env LANG=C tar --compare --file='#{SYSTEMD_OVERRIDES_TAR}.xz'}
266
compare_command = %{#{compare_command} | grep -v 'Uid differs$' | grep -v 'Gid differs$'}
267
compare_command = %{#{compare_command} | grep -v 'Mod time differs$'}
268
269
debug "Executing compare_command: “#{compare_command}”."
270
271
output = nil
272
273
IO.pipe do |read_io, write_io|
274
result = system(compare_command, out: write_io, err: write_io)
275
write_io.close
276
277
output = read_io.read
278
end
279
280
debug "compare_command exitstatus=#{$?.exitstatus}, result=#{result}"
281
282
# 0 is everything is same, 1 means some files differs.
283
unless [0, 1].include? $?.exitstatus
284
puts "Failed to execute compare command. Result:"
285
puts
286
puts output
287
exit 22
288
end
289
290
debug "compare_command output: #{output}"
291
292
# If yes, there is no changed content in the archive.
293
if output.empty?
294
return false
295
end
296
297
true
298
end
299
end