summaryrefslogtreecommitdiffstats
path: root/meta/recipes-core/systemd
diff options
context:
space:
mode:
Diffstat (limited to 'meta/recipes-core/systemd')
-rwxr-xr-xmeta/recipes-core/systemd/systemd-systemctl/systemctl476
1 files changed, 280 insertions, 196 deletions
diff --git a/meta/recipes-core/systemd/systemd-systemctl/systemctl b/meta/recipes-core/systemd/systemd-systemctl/systemctl
index 2bc6489617..d7d4e0d29a 100755
--- a/meta/recipes-core/systemd/systemd-systemctl/systemctl
+++ b/meta/recipes-core/systemd/systemd-systemctl/systemctl
@@ -1,196 +1,280 @@
1#!/bin/sh 1#!/usr/bin/env python3
2echo "Started $0 $*" 2"""systemctl: subset of systemctl used for image construction
3 3
4ROOT= 4Mask/preset systemd units
5 5"""
6# parse command line params 6
7action= 7import argparse
8while [ $# != 0 ]; do 8import fnmatch
9 opt="$1" 9import os
10 10import re
11 case "$opt" in 11import sys
12 enable) 12
13 shift 13from collections import namedtuple
14 14from pathlib import Path
15 action="$opt" 15
16 services="$1" 16version = 1.0
17 cmd_args="1" 17
18 shift 18ROOT = Path("/")
19 ;; 19SYSCONFDIR = Path("etc")
20 disable) 20BASE_LIBDIR = Path("lib")
21 shift 21LIBDIR = Path("usr", "lib")
22 22
23 action="$opt" 23
24 services="$1" 24class SystemdFile():
25 cmd_args="1" 25 """Class representing a single systemd configuration file"""
26 shift 26 def __init__(self, root, path):
27 ;; 27 self.sections = dict()
28 mask) 28 self._parse(root, path)
29 shift 29
30 30 def _parse(self, root, path):
31 action="$opt" 31 """Parse a systemd syntax configuration file
32 services="$1" 32
33 cmd_args="1" 33 Args:
34 shift 34 path: A pathlib.Path object pointing to the file
35 ;; 35
36 preset) 36 """
37 shift 37 skip_re = re.compile(r"^\s*([#;]|$)")
38 38 section_re = re.compile(r"^\s*\[(?P<section>.*)\]")
39 action="$opt" 39 kv_re = re.compile(r"^\s*(?P<key>[^\s]+)\s*=\s*(?P<value>.*)")
40 services="$1" 40 section = None
41 cmd_args="1" 41
42 shift 42 if path.is_symlink():
43 ;; 43 try:
44 --root=*) 44 path.resolve()
45 ROOT=${opt##--root=} 45 except FileNotFoundError:
46 cmd_args="0" 46 # broken symlink, try relative to root
47 shift 47 path = root / Path(os.readlink(str(path))).relative_to(ROOT)
48 ;; 48
49 *) 49 with path.open() as f:
50 if [ "$cmd_args" = "1" ]; then 50 for line in f:
51 services="$services $opt" 51 if skip_re.match(line):
52 shift 52 continue
53 else 53
54 echo "'$opt' is an unkown option; exiting with error" 54 line = line.rstrip("\n")
55 exit 1 55 m = section_re.match(line)
56 fi 56 if m:
57 ;; 57 section = dict()
58 esac 58 self.sections[m.group('section')] = section
59done 59 continue
60if [ "$action" = "preset" -a "$service_file" = "" ]; then 60
61 services=$(for f in `find $ROOT/etc/systemd/system $ROOT/lib/systemd/system $ROOT/usr/lib/systemd/system -type f 2>1`; do basename $f; done) 61 while line.endswith("\\"):
62 services="$services $opt" 62 line += f.readline().rstrip("\n")
63 presetall=1 63
64fi 64 m = kv_re.match(line)
65 65 k = m.group('key')
66for service in $services; do 66 v = m.group('value')
67 if [ "$presetall" = "1" ]; then 67 if k not in section:
68 action="preset" 68 section[k] = list()
69 fi 69 section[k].extend(v.split())
70 if [ "$action" = "mask" ]; then 70
71 if [ ! -d $ROOT/etc/systemd/system/ ]; then 71 def get(self, section, prop):
72 mkdir -p $ROOT/etc/systemd/system/ 72 """Get a property from section
73 fi 73
74 cmd="ln -s /dev/null $ROOT/etc/systemd/system/$service" 74 Args:
75 echo "$cmd" 75 section: Section to retrieve property from
76 $cmd 76 prop: Property to retrieve
77 exit 0 77
78 fi 78 Returns:
79 79 List representing all properties of type prop in section.
80 service_base_file=`echo $service | sed 's/\(@\).*\(\.[^.]\+\)/\1\2/'` 80
81 if [ -z `echo $service | sed '/@/p;d'` ]; then 81 Raises:
82 echo "Try to find location of $service..." 82 KeyError: if ``section`` or ``prop`` not found
83 service_template=false 83 """
84 else 84 return self.sections[section][prop]
85 echo "Try to find location of template $service_base_file of instance $service..." 85
86 service_template=true 86
87 instance_specified=`echo $service | sed 's/^.\+@\(.*\)\.[^.]\+/\1/'` 87class Presets():
88 fi 88 """Class representing all systemd presets"""
89 89 def __init__(self, scope, root):
90 # find service file 90 self.directives = list()
91 for p in $ROOT/etc/systemd/system \ 91 self._collect_presets(scope, root)
92 $ROOT/lib/systemd/system \ 92
93 $ROOT/usr/lib/systemd/system; do 93 def _parse_presets(self, presets):
94 if [ -e $p/$service_base_file ]; then 94 """Parse presets out of a set of preset files"""
95 service_file=$p/$service_base_file 95 skip_re = re.compile(r"^\s*([#;]|$)")
96 service_file=${service_file##$ROOT} 96 directive_re = re.compile(r"^\s*(?P<action>enable|disable)\s+(?P<unit_name>(.+))")
97 fi 97
98 done 98 Directive = namedtuple("Directive", "action unit_name")
99 if [ -z "$service_file" ]; then 99 for preset in presets:
100 echo "'$service_base_file' couldn't be found; exiting with error" 100 with preset.open() as f:
101 exit 1 101 for line in f:
102 fi 102 m = directive_re.match(line)
103 echo "Found $service in $service_file" 103 if m:
104 104 directive = Directive(action=m.group('action'),
105 # If any new unit types are added to systemd they should be added 105 unit_name=m.group('unit_name'))
106 # to this regular expression. 106 self.directives.append(directive)
107 unit_types_re='\.\(service\|socket\|device\|mount\|automount\|swap\|target\|target\.wants\|path\|timer\|snapshot\)\s*$' 107 elif skip_re.match(line):
108 if [ "$action" = "preset" ]; then 108 pass
109 action=`egrep -sh $service $ROOT/etc/systemd/user-preset/*.preset | cut -f1 -d' '` 109 else:
110 if [ -z "$action" ]; then 110 sys.exit("Unparsed preset line in {}".format(preset))
111 globalpreset=`egrep -sh '\*' $ROOT/etc/systemd/user-preset/*.preset | cut -f1 -d' '` 111
112 if [ -n "$globalpreset" ]; then 112 def _collect_presets(self, scope, root):
113 action="$globalpreset" 113 """Collect list of preset files"""
114 else 114 locations = [SYSCONFDIR / "systemd"]
115 action="enable" 115 # Handle the usrmerge case by ignoring /lib when it's a symlink
116 fi 116 if not BASE_LIBDIR.is_symlink():
117 fi 117 locations.append(BASE_LIBDIR / "systemd")
118 fi 118 locations.append(LIBDIR / "systemd")
119 # create the required symbolic links 119
120 wanted_by=$(sed '/^WantedBy[[:space:]]*=/s,[^=]*=,,p;d' "$ROOT/$service_file" \ 120 presets = dict()
121 | tr ',' '\n' \ 121 for location in locations:
122 | grep "$unit_types_re") 122 paths = (root / location / scope).glob("*.preset")
123 123 for path in paths:
124 required_by=$(sed '/^RequiredBy[[:space:]]*=/s,[^=]*=,,p;d' "$ROOT/$service_file" \ 124 # earlier names override later ones
125 | tr ',' '\n' \ 125 if path.name not in presets:
126 | grep "$unit_types_re") 126 presets[path.name] = path
127 127
128 for dependency in WantedBy RequiredBy; do 128 self._parse_presets([v for k, v in sorted(presets.items())])
129 if [ "$dependency" = "WantedBy" ]; then 129
130 suffix="wants" 130 def state(self, unit_name):
131 dependency_list="$wanted_by" 131 """Return state of preset for unit_name
132 elif [ "$dependency" = "RequiredBy" ]; then 132
133 suffix="requires" 133 Args:
134 dependency_list="$required_by" 134 presets: set of presets
135 fi 135 unit_name: name of the unit
136 for r in $dependency_list; do 136
137 echo "$dependency=$r found in $service" 137 Returns:
138 if [ -n "$instance_specified" ]; then 138 None: no matching preset
139 # substitute wildcards in the dependency 139 `enable`: unit_name is enabled
140 r=`echo $r | sed "s/%i/$instance_specified/g"` 140 `disable`: unit_name is disabled
141 fi 141 """
142 142 for directive in self.directives:
143 if [ "$action" = "enable" ]; then 143 if fnmatch.fnmatch(unit_name, directive.unit_name):
144 enable_service=$service 144 return directive.action
145 if [ "$service_template" = true -a -z "$instance_specified" ]; then 145
146 default_instance=$(sed '/^DefaultInstance[[:space:]]*=/s,[^=]*=,,p;d' "$ROOT/$service_file") 146 return None
147 if [ -z $default_instance ]; then 147
148 echo "Template unit without instance or DefaultInstance directive, nothing to enable" 148
149 continue 149def collect_services(root):
150 else 150 """Collect list of service files"""
151 echo "Found DefaultInstance $default_instance, enabling it" 151 locations = [SYSCONFDIR / "systemd"]
152 enable_service=$(echo $service | sed "s/@/@$(echo $default_instance | sed 's/\\/\\\\/g')/") 152 # Handle the usrmerge case by ignoring /lib when it's a symlink
153 fi 153 if not BASE_LIBDIR.is_symlink():
154 fi 154 locations.append(BASE_LIBDIR / "systemd")
155 mkdir -p $ROOT/etc/systemd/system/$r.$suffix 155 locations.append(LIBDIR / "systemd")
156 ln -s $service_file $ROOT/etc/systemd/system/$r.$suffix/$enable_service 156
157 echo "Enabled $enable_service for $r." 157 services = dict()
158 else 158 for location in locations:
159 if [ "$service_template" = true -a -z "$instance_specified" ]; then 159 paths = (root / location / "system").glob("*")
160 disable_service="$ROOT/etc/systemd/system/$r.$suffix/`echo $service | sed 's/@/@*/'`" 160 for path in paths:
161 else 161 if path.is_dir():
162 disable_service="$ROOT/etc/systemd/system/$r.$suffix/$service" 162 continue
163 fi 163 # implement earlier names override later ones
164 rm -f $disable_service 164 if path.name not in services:
165 [ -d $ROOT/etc/systemd/system/$r.$suffix ] && rmdir --ignore-fail-on-non-empty -p $ROOT/etc/systemd/system/$r.$suffix 165 services[path.name] = path
166 echo "Disabled ${disable_service##$ROOT/etc/systemd/system/$r.$suffix/} for $r." 166
167 fi 167 return services
168 done 168
169 done 169
170 170def add_link(path, target):
171 # create the required symbolic 'Alias' links 171 try:
172 alias=$(sed '/^Alias[[:space:]]*=/s,[^=]*=,,p;d' "$ROOT/$service_file" \ 172 path.parent.mkdir(parents=True)
173 | tr ',' '\n' \ 173 except FileExistsError:
174 | grep "$unit_types_re") 174 pass
175 175 if not path.is_symlink():
176 for r in $alias; do 176 print("ln -s {} {}".format(target, path))
177 if [ "$action" = "enable" ]; then 177 path.symlink_to(target)
178 mkdir -p $ROOT/etc/systemd/system 178
179 ln -s $service_file $ROOT/etc/systemd/system/$r 179
180 echo "Enabled $service for $alias." 180def process_deps(root, config, service, location, prop, dirstem):
181 else 181 systemdir = SYSCONFDIR / "systemd" / "system"
182 rm -f $ROOT/etc/systemd/system/$r 182
183 echo "Disabled $service for $alias." 183 target = ROOT / location.relative_to(root)
184 fi 184 try:
185 done 185 for dependent in config.get('Install', prop):
186 186 wants = root / systemdir / "{}.{}".format(dependent, dirstem) / service
187 # call us for the other required scripts 187 add_link(wants, target)
188 also=$(sed '/^Also[[:space:]]*=/s,[^=]*=,,p;d' "$ROOT/$service_file" \ 188
189 | tr ',' '\n') 189 except KeyError:
190 for a in $also; do 190 pass
191 echo "Also=$a found in $service" 191
192 if [ "$action" = "enable" ]; then 192
193 $0 --root=$ROOT enable $a 193def enable(root, service, location, services):
194 fi 194 if location.is_symlink():
195 done 195 # ignore aliases
196done 196 return
197
198 config = SystemdFile(root, location)
199 template = re.match(r"[^@]+@(?P<instance>[^\.]*)\.", service)
200 if template:
201 instance = template.group('instance')
202 if not instance:
203 try:
204 instance = config.get('Install', 'DefaultInstance')[0]
205 service = service.replace("@.", "@{}.".format(instance))
206 except KeyError:
207 pass
208 if instance is None:
209 return
210 else:
211 instance = None
212
213 process_deps(root, config, service, location, 'WantedBy', 'wants')
214 process_deps(root, config, service, location, 'RequiredBy', 'requires')
215
216 try:
217 for also in config.get('Install', 'Also'):
218 enable(root, also, services[also], services)
219
220 except KeyError:
221 pass
222
223 systemdir = root / SYSCONFDIR / "systemd" / "system"
224 target = ROOT / location.relative_to(root)
225 try:
226 for dest in config.get('Install', 'Alias'):
227 alias = systemdir / dest
228 add_link(alias, target)
229
230 except KeyError:
231 pass
232
233
234def preset_all(root):
235 presets = Presets('system-preset', root)
236 services = collect_services(root)
237
238 for service, location in services.items():
239 state = presets.state(service)
240
241 if state == "enable" or state is None:
242 enable(root, service, location, services)
243
244
245def mask(root, *services):
246 systemdir = root / SYSCONFDIR / "systemd" / "system"
247 for service in services:
248 add_link(systemdir / service, "/dev/null")
249
250
251def main():
252 if sys.version_info < (3, 4, 0):
253 sys.exit("Python 3.4 or greater is required")
254
255 parser = argparse.ArgumentParser()
256 parser.add_argument('command', nargs=1, choices=['mask', 'preset-all'])
257 parser.add_argument('service', nargs=argparse.REMAINDER)
258 parser.add_argument('--root')
259 parser.add_argument('--preset-mode',
260 choices=['full', 'enable-only', 'disable-only'],
261 default='full')
262
263 args = parser.parse_args()
264
265 root = Path(args.root) if args.root else ROOT
266 command = args.command[0]
267 if command == "mask":
268 mask(root, *args.service)
269 elif command == "preset-all":
270 if len(args.service) != 0:
271 sys.exit("Too many arguments.")
272 if args.preset_mode != "enable-only":
273 sys.exit("Only enable-only is supported as preset-mode.")
274 preset_all(root)
275 else:
276 raise RuntimeError()
277
278
279if __name__ == '__main__':
280 main()