1#!/usr/bin/env python3 2# SPDX-License-Identifier: LGPL-2.1-or-later 3# 4# systemd is free software; you can redistribute it and/or modify it 5# under the terms of the GNU Lesser General Public License as published by 6# the Free Software Foundation; either version 2.1 of the License, or 7# (at your option) any later version. 8 9import os 10import sys 11import socket 12import subprocess 13import tempfile 14import pwd 15import grp 16 17try: 18 from systemd import id128 19except ImportError: 20 id128 = None 21 22EX_DATAERR = 65 # from sysexits.h 23EXIT_TEST_SKIP = 77 24 25try: 26 subprocess.run 27except AttributeError: 28 sys.exit(EXIT_TEST_SKIP) 29 30exe_with_args = sys.argv[1:] 31 32def test_line(line, *, user, returncode=EX_DATAERR, extra={}): 33 args = ['--user'] if user else [] 34 print('Running {} on {!r}'.format(' '.join(exe_with_args + args), line)) 35 c = subprocess.run(exe_with_args + ['--create', '-'] + args, 36 input=line, stdout=subprocess.PIPE, universal_newlines=True, 37 **extra) 38 assert c.returncode == returncode, c 39 40def test_invalids(*, user): 41 test_line('asdfa', user=user) 42 test_line('f "open quote', user=user) 43 test_line('f closed quote""', user=user) 44 test_line('Y /unknown/letter', user=user) 45 test_line('w non/absolute/path', user=user) 46 test_line('s', user=user) # s is for short 47 test_line('f!! /too/many/bangs', user=user) 48 test_line('f++ /too/many/plusses', user=user) 49 test_line('f+!+ /too/many/plusses', user=user) 50 test_line('f!+! /too/many/bangs', user=user) 51 test_line('f== /too/many/equals', user=user) 52 test_line('w /unresolved/argument - - - - "%Y"', user=user) 53 test_line('w /unresolved/argument/sandwich - - - - "%v%Y%v"', user=user) 54 test_line('w /unresolved/filename/%Y - - - - "whatever"', user=user) 55 test_line('w /unresolved/filename/sandwich/%v%Y%v - - - - "whatever"', user=user) 56 test_line('w - - - - - "no file specified"', user=user) 57 test_line('C - - - - - "no file specified"', user=user) 58 test_line('C non/absolute/path - - - - -', user=user) 59 test_line('b - - - - - -', user=user) 60 test_line('b 1234 - - - - -', user=user) 61 test_line('c - - - - - -', user=user) 62 test_line('c 1234 - - - - -', user=user) 63 test_line('t - - -', user=user) 64 test_line('T - - -', user=user) 65 test_line('a - - -', user=user) 66 test_line('A - - -', user=user) 67 test_line('h - - -', user=user) 68 test_line('H - - -', user=user) 69 70def test_uninitialized_t(): 71 if os.getuid() == 0: 72 return 73 74 test_line('w /foo - - - - "specifier for --user %t"', 75 user=True, returncode=0, extra={'env':{}}) 76 77def test_content(line, expected, *, user, extra={}, subpath='/arg', path_cb=None): 78 d = tempfile.TemporaryDirectory(prefix='test-systemd-tmpfiles.') 79 if path_cb is not None: 80 path_cb(d.name, subpath) 81 arg = d.name + subpath 82 spec = line.format(arg) 83 test_line(spec, user=user, returncode=0, extra=extra) 84 content = open(arg).read() 85 print('expect: {!r}\nactual: {!r}'.format(expected, content)) 86 assert content == expected 87 88def test_valid_specifiers(*, user): 89 test_content('f {} - - - - two words', 'two words', user=user) 90 if id128: 91 try: 92 test_content('f {} - - - - %m', '{}'.format(id128.get_machine().hex), user=user) 93 except AssertionError as e: 94 print(e) 95 print('/etc/machine-id: {!r}'.format(open('/etc/machine-id').read())) 96 print('/proc/cmdline: {!r}'.format(open('/proc/cmdline').read())) 97 print('skipping') 98 test_content('f {} - - - - %b', '{}'.format(id128.get_boot().hex), user=user) 99 test_content('f {} - - - - %H', '{}'.format(socket.gethostname()), user=user) 100 test_content('f {} - - - - %v', '{}'.format(os.uname().release), user=user) 101 test_content('f {} - - - - %U', '{}'.format(os.getuid() if user else 0), user=user) 102 test_content('f {} - - - - %G', '{}'.format(os.getgid() if user else 0), user=user) 103 104 puser = pwd.getpwuid(os.getuid() if user else 0) 105 test_content('f {} - - - - %u', '{}'.format(puser.pw_name), user=user) 106 107 pgroup = grp.getgrgid(os.getgid() if user else 0) 108 test_content('f {} - - - - %g', '{}'.format(pgroup.gr_name), user=user) 109 110 # Note that %h is the only specifier in which we look the environment, 111 # because we check $HOME. Should we even be doing that? 112 home = os.path.expanduser("~") 113 test_content('f {} - - - - %h', '{}'.format(home), user=user) 114 115 xdg_runtime_dir = os.getenv('XDG_RUNTIME_DIR') 116 if xdg_runtime_dir is not None or not user: 117 test_content('f {} - - - - %t', 118 xdg_runtime_dir if user else '/run', 119 user=user) 120 121 xdg_config_home = os.getenv('XDG_CONFIG_HOME') 122 if xdg_config_home is not None or not user: 123 test_content('f {} - - - - %S', 124 xdg_config_home if user else '/var/lib', 125 user=user) 126 127 xdg_cache_home = os.getenv('XDG_CACHE_HOME') 128 if xdg_cache_home is not None or not user: 129 test_content('f {} - - - - %C', 130 xdg_cache_home if user else '/var/cache', 131 user=user) 132 133 if xdg_config_home is not None or not user: 134 test_content('f {} - - - - %L', 135 xdg_config_home + '/log' if user else '/var/log', 136 user=user) 137 138 test_content('f {} - - - - %%', '%', user=user) 139 140def mkfifo(parent, subpath): 141 os.makedirs(parent, mode=0o755, exist_ok=True) 142 first_component = subpath.split('/')[1] 143 path = parent + '/' + first_component 144 print('path: {}'.format(path)) 145 os.mkfifo(path) 146 147def mkdir(parent, subpath): 148 first_component = subpath.split('/')[1] 149 path = parent + '/' + first_component 150 os.makedirs(path, mode=0o755, exist_ok=True) 151 os.symlink(path, path + '/self', target_is_directory=True) 152 153def symlink(parent, subpath): 154 link_path = parent + '/link-target' 155 os.makedirs(parent, mode=0o755, exist_ok=True) 156 with open(link_path, 'wb') as f: 157 f.write(b'target') 158 first_component = subpath.split('/')[1] 159 path = parent + '/' + first_component 160 os.symlink(link_path, path, target_is_directory=True) 161 162def file(parent, subpath): 163 content = 'file-' + subpath.split('/')[1] 164 path = parent + subpath 165 os.makedirs(os.path.dirname(path), mode=0o755, exist_ok=True) 166 with open(path, 'wb') as f: 167 f.write(content.encode()) 168 169def valid_symlink(parent, subpath): 170 target = 'link-target' 171 link_path = parent + target 172 os.makedirs(link_path, mode=0o755, exist_ok=True) 173 first_component = subpath.split('/')[1] 174 path = parent + '/' + first_component 175 os.symlink(target, path, target_is_directory=True) 176 177def test_hard_cleanup(*, user): 178 type_cbs = [None, file, mkdir, symlink] 179 if 'mkfifo' in dir(os): 180 type_cbs.append(mkfifo) 181 182 for type_cb in type_cbs: 183 for subpath in ['/shallow', '/deep/1/2']: 184 label = '{}-{}'.format('None' if type_cb is None else type_cb.__name__, subpath.split('/')[1]) 185 test_content('f= {} - - - - ' + label, label, user=user, subpath=subpath, path_cb=type_cb) 186 187 # Test the case that a valid symlink is in the path. 188 label = 'valid_symlink-deep' 189 test_content('f= {} - - - - ' + label, label, user=user, subpath='/deep/1/2', path_cb=valid_symlink) 190 191if __name__ == '__main__': 192 test_invalids(user=False) 193 test_invalids(user=True) 194 test_uninitialized_t() 195 196 test_valid_specifiers(user=False) 197 test_valid_specifiers(user=True) 198 199 test_hard_cleanup(user=False) 200 test_hard_cleanup(user=True) 201