diff options
Diffstat (limited to 'meta/recipes-core/systemd/systemd-systemctl/systemctl')
-rwxr-xr-x | meta/recipes-core/systemd/systemd-systemctl/systemctl | 476 |
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 |
2 | echo "Started $0 $*" | 2 | """systemctl: subset of systemctl used for image construction |
3 | 3 | ||
4 | ROOT= | 4 | Mask/preset systemd units |
5 | 5 | """ | |
6 | # parse command line params | 6 | |
7 | action= | 7 | import argparse |
8 | while [ $# != 0 ]; do | 8 | import fnmatch |
9 | opt="$1" | 9 | import os |
10 | 10 | import re | |
11 | case "$opt" in | 11 | import sys |
12 | enable) | 12 | |
13 | shift | 13 | from collections import namedtuple |
14 | 14 | from pathlib import Path | |
15 | action="$opt" | 15 | |
16 | services="$1" | 16 | version = 1.0 |
17 | cmd_args="1" | 17 | |
18 | shift | 18 | ROOT = Path("/") |
19 | ;; | 19 | SYSCONFDIR = Path("etc") |
20 | disable) | 20 | BASE_LIBDIR = Path("lib") |
21 | shift | 21 | LIBDIR = Path("usr", "lib") |
22 | 22 | ||
23 | action="$opt" | 23 | |
24 | services="$1" | 24 | class 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 |
59 | done | 59 | continue |
60 | if [ "$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 | |
64 | fi | 64 | m = kv_re.match(line) |
65 | 65 | k = m.group('key') | |
66 | for 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/'` | 87 | class 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 | 149 | def 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 | 170 | def 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." | 180 | def 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 | 193 | def enable(root, service, location, services): |
194 | fi | 194 | if location.is_symlink(): |
195 | done | 195 | # ignore aliases |
196 | done | 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 | |||
234 | def 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 | |||
245 | def mask(root, *services): | ||
246 | systemdir = root / SYSCONFDIR / "systemd" / "system" | ||
247 | for service in services: | ||
248 | add_link(systemdir / service, "/dev/null") | ||
249 | |||
250 | |||
251 | def 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 | |||
279 | if __name__ == '__main__': | ||
280 | main() | ||