Merge pull request #7419 from keszybz/tmpfiles-fixes

Tmpfiles --user mode and various fixes
This commit is contained in:
Lennart Poettering 2017-12-06 19:50:26 +01:00 committed by GitHub
commit c7a54cd67b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 669 additions and 138 deletions

7
NEWS
View File

@ -7,6 +7,13 @@ CHANGES WITH 236 in spe:
numdummies=0, preventing the kernel from automatically creating
dummy0. All dummy interfaces must now be explicitly created.
* Unknown specifiers are now rejected. This applies to units and
tmpfiles.d configuration. Any percent characters that are followed by
a letter or digit that are not supposed to be interpreted as the
beginning of a specifier should be escaped by doubling ("%%").
(So "size=5%" is still accepted, as well as "size=5%,foo=bar", but
not "LABEL=x%y%z" since %y and %z are not valid specifiers today.)
* systemd-resolved now maintains a new dynamic
/run/systemd/resolve/stub-resolv.conf compatibility file. It is
recommended to make /etc/resolv.conf a symlink to it. This file

View File

@ -62,10 +62,16 @@
<arg choice="opt" rep="repeat"><replaceable>CONFIGFILE</replaceable></arg>
</cmdsynopsis>
<para><filename>systemd-tmpfiles-setup.service</filename></para>
<para><filename>systemd-tmpfiles-setup-dev.service</filename></para>
<para><filename>systemd-tmpfiles-clean.service</filename></para>
<para><filename>systemd-tmpfiles-clean.timer</filename></para>
<para>System units:
<literallayout><filename>systemd-tmpfiles-setup.service</filename>
<filename>systemd-tmpfiles-setup-dev.service</filename>
<filename>systemd-tmpfiles-clean.service</filename>
<filename>systemd-tmpfiles-clean.timer</filename></literallayout></para>
<para>User units:
<literallayout><filename>systemd-tmpfiles-setup.service</filename>
<filename>systemd-tmpfiles-clean.service</filename>
<filename>systemd-tmpfiles-clean.timer</filename></literallayout></para>
</refsynopsisdiv>
<refsect1>
@ -115,7 +121,7 @@
<varname>T</varname>,
<varname>a</varname>, and
<varname>A</varname> have their ownership, access mode and
security labels set. </para></listitem>
security labels set.</para></listitem>
</varlistentry>
<varlistentry>
@ -133,11 +139,19 @@
marked with <varname>r</varname> or <varname>R</varname> are
removed.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--user</option></term>
<listitem><para>Execute "user" configuration, i.e. <filename>tmpfiles.d</filename>
files in user configuration directories.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--boot</option></term>
<listitem><para>Also execute lines with an exclamation mark.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--prefix=<replaceable>path</replaceable></option></term>
<listitem><para>Only apply rules with paths that start with
@ -191,8 +205,12 @@
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned, a non-zero failure code
otherwise.</para>
<para>On success, 0 is returned. If the configuration was invalid (invalid syntax, missing
arguments, …), so some lines had to be ignored, but no other errors occurred,
<constant>65</constant> is returned (<constant>EX_DATAERR</constant> from
<filename>/usr/include/sysexits.h</filename>). Otherwise, <constant>1</constant> is returned
(<constant>EXIT_FAILURE</constant> from <filename>/usr/include/stdlib.h</filename>).
</para>
</refsect1>
<refsect1>

View File

@ -1297,7 +1297,8 @@
<para>Many settings resolve specifiers which may be used to write
generic unit files referring to runtime or unit parameters that
are replaced when the unit files are loaded. The following
are replaced when the unit files are loaded. Specifiers must be known
and resolvable for the setting to be valid. The following
specifiers are understood:</para>
<table>
@ -1356,18 +1357,18 @@
</row>
<row>
<entry><literal>%S</literal></entry>
<entry>State directory root </entry>
<entry>State directory root</entry>
<entry>This is either <filename>/var/lib</filename> (for the system manager) or the path <literal>$XDG_CONFIG_HOME</literal> resolves to (for user managers).</entry>
</row>
<row>
<entry><literal>%C</literal></entry>
<entry>Cache directory root </entry>
<entry>Cache directory root</entry>
<entry>This is either <filename>/var/cache</filename> (for the system manager) or the path <literal>$XDG_CACHE_HOME</literal> resolves to (for user managers).</entry>
</row>
<row>
<entry><literal>%L</literal></entry>
<entry>Logs directory root </entry>
<entry>This is either <filename>/var/log</filename> (for the system manager) or the path <literal>$XDG_CONFIG_HOME</literal> resolves to with <filename>/log</filename> appended (for user managers).</entry>
<entry>Log directory root</entry>
<entry>This is either <filename>/var/log</filename> (for the system manager) or the path <literal>$XDG_CONFIG_HOME</literal> resolves to with <filename noindex='true'>/log</filename> appended (for user managers).</entry>
</row>
<row>
<entry><literal>%u</literal></entry>

View File

@ -48,9 +48,17 @@
</refnamediv>
<refsynopsisdiv>
<para><filename>/etc/tmpfiles.d/*.conf</filename></para>
<para><filename>/run/tmpfiles.d/*.conf</filename></para>
<para><filename>/usr/lib/tmpfiles.d/*.conf</filename></para>
<para><literallayout><filename>/etc/tmpfiles.d/*.conf</filename>
<filename>/run/tmpfiles.d/*.conf</filename>
<filename>/usr/lib/tmpfiles.d/*.conf</filename>
</literallayout></para>
<para><literallayout><filename>~/.config/user-tmpfiles.d/*.conf</filename>
<filename>$XDG_RUNTIME_DIR/user-tmpfiles.d/*.conf</filename>
<filename>~/.local/share/user-tmpfiles.d/*.conf</filename>
<filename></filename>
<filename>/usr/share/user-tmpfiles.d/*.conf</filename>
</literallayout></para>
</refsynopsisdiv>
<refsect1>
@ -482,51 +490,8 @@ r! /tmp/.X[0-9]*-lock</programlisting>
<title>Path</title>
<para>The file system path specification supports simple
specifier expansion. The following expansions are
understood:</para>
<table>
<title>Specifiers available</title>
<tgroup cols='3' align='left' colsep='1' rowsep='1'>
<colspec colname="spec" />
<colspec colname="mean" />
<colspec colname="detail" />
<thead>
<row>
<entry>Specifier</entry>
<entry>Meaning</entry>
<entry>Details</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>%m</literal></entry>
<entry>Machine ID</entry>
<entry>The machine ID of the running system, formatted as string. See <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry> for more information.</entry>
</row>
<row>
<entry><literal>%b</literal></entry>
<entry>Boot ID</entry>
<entry>The boot ID of the running system, formatted as string. See <citerefentry><refentrytitle>random</refentrytitle><manvolnum>4</manvolnum></citerefentry> for more information.</entry>
</row>
<row>
<entry><literal>%H</literal></entry>
<entry>Host name</entry>
<entry>The hostname of the running system.</entry>
</row>
<row>
<entry><literal>%v</literal></entry>
<entry>Kernel release</entry>
<entry>Identical to <command>uname -r</command> output.</entry>
</row>
<row>
<entry><literal>%%</literal></entry>
<entry>Escaped %</entry>
<entry>Single percent sign.</entry>
</row>
</tbody>
</tgroup>
</table>
specifier expansion, see below. The path (after expansion) must be
absolute.</para>
</refsect2>
<refsect2>
@ -628,8 +593,94 @@ r! /tmp/.X[0-9]*-lock</programlisting>
attributes to be set. For <varname>h</varname> and
<varname>H</varname>, determines the file attributes to
set. Ignored for all other lines.</para>
</refsect2>
<para>This field can contain specifiers, see below.</para>
</refsect2>
</refsect1>
<refsect1>
<title>Specifiers</title>
<para>Specifiers can be used in the "path" and "argument" fields.
An unknown or unresolvable specifier is treated as invalid configuration.
The following expansions are understood:</para>
<table>
<title>Specifiers available</title>
<tgroup cols='3' align='left' colsep='1' rowsep='1'>
<colspec colname="spec" />
<colspec colname="mean" />
<colspec colname="detail" />
<thead>
<row>
<entry>Specifier</entry>
<entry>Meaning</entry>
<entry>Details</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>%m</literal></entry>
<entry>Machine ID</entry>
<entry>The machine ID of the running system, formatted as string. See <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry> for more information.</entry>
</row>
<row>
<entry><literal>%b</literal></entry>
<entry>Boot ID</entry>
<entry>The boot ID of the running system, formatted as string. See <citerefentry><refentrytitle>random</refentrytitle><manvolnum>4</manvolnum></citerefentry> for more information.</entry>
</row>
<row>
<entry><literal>%H</literal></entry>
<entry>Host name</entry>
<entry>The hostname of the running system.</entry>
</row>
<row>
<entry><literal>%v</literal></entry>
<entry>Kernel release</entry>
<entry>Identical to <command>uname -r</command> output.</entry>
</row>
<row>
<entry><literal>%U</literal></entry>
<entry>User UID</entry>
<entry>This is the numeric UID of the user running the service manager instance. In case of the system manager this resolves to <constant>0</constant>.</entry>
</row>
<row>
<entry><literal>%u</literal></entry>
<entry>User name</entry>
<entry>This is the name of the user running the service manager instance. In case of the system manager this resolves to <literal>root</literal>.</entry>
</row>
<row>
<entry><literal>%h</literal></entry>
<entry>User home directory</entry>
<entry>This is the home directory of the user running the service manager instance. In case of the system manager this resolves to <literal>/root</literal>.</entry>
</row>
<row>
<entry><literal>%t</literal></entry>
<entry>System or user runtime directory</entry>
<entry>In --user mode, this is the same <varname>$XDG_RUNTIME_DIR</varname>, and <filename>/run</filename> otherwise.</entry>
</row>
<row>
<entry><literal>%S</literal></entry>
<entry>System or user state directory</entry>
<entry>In <option>--user</option> mode, this is the same as <varname>$XDG_CONFIG_HOME</varname>, and <filename>/var/lib</filename> otherwise.</entry>
</row>
<row>
<entry><literal>%C</literal></entry>
<entry>System or user cache directory</entry>
<entry>In <option>--user</option> mode, this is the same as <varname>$XDG_CACHE_HOME</varname>, and <filename>/var/cache</filename> otherwise.</entry>
</row>
<row>
<entry><literal>%L</literal></entry>
<entry>System or user log directory</entry>
<entry>In <option>--user</option> mode, this is the same as <varname>$XDG_CONFIG_HOME</varname> with <filename noindex='true'>/log</filename> appended, and <filename>/var/log</filename> otherwise.</entry>
</row>
<row>
<entry><literal>%%</literal></entry>
<entry>Escaped <literal>%</literal></entry>
<entry>Single percent sign.</entry>
</row>
</tbody>
</tgroup>
</table>
</refsect1>
<refsect1>

View File

@ -2209,6 +2209,11 @@ if conf.get('ENABLE_TMPFILES') == 1
install : true,
install_dir : rootbindir)
public_programs += [exe]
test('test-systemd-tmpfiles',
test_systemd_tmpfiles_py,
args : exe.full_path())
# https://github.com/mesonbuild/meson/issues/2681
endif
if conf.get('ENABLE_HWDB') == 1
@ -2441,6 +2446,7 @@ subdir('units')
subdir('sysctl.d')
subdir('sysusers.d')
subdir('tmpfiles.d')
subdir('presets')
subdir('hwdb')
subdir('network')
subdir('man')
@ -2457,8 +2463,6 @@ install_subdir('factory/etc',
install_data('xorg/50-systemd-user.sh',
install_dir : xinitrcdir)
install_data('system-preset/90-systemd.preset',
install_dir : systempresetdir)
install_data('modprobe.d/systemd.conf',
install_dir : modprobedir)
install_data('README',

22
presets/meson.build Normal file
View File

@ -0,0 +1,22 @@
# SPDX-License-Identifier: LGPL-2.1+
#
# Copyright 2017 Zbigniew Jędrzejewski-Szmek
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# systemd is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with systemd; If not, see <http://www.gnu.org/licenses/>.
install_data('90-systemd.preset',
install_dir : systempresetdir)
install_data('user/90-systemd.preset',
install_dir : userpresetdir)

View File

@ -0,0 +1,14 @@
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
# These ones should be enabled by default, even if distributions
# generally follow a default-off policy.
enable systemd-tmpfiles-setup.service
enable systemd-tmpfiles-clean.timer

View File

@ -223,8 +223,8 @@ int path_strv_make_absolute_cwd(char **l) {
if (r < 0)
return r;
free(*s);
*s = t;
path_kill_slashes(t);
free_and_replace(*s, t);
}
return 0;

View File

@ -39,7 +39,7 @@
#include "user-util.h"
#include "util.h"
static int user_runtime_dir(char **ret, const char *suffix) {
int xdg_user_runtime_dir(char **ret, const char *suffix) {
const char *e;
char *j;
@ -58,7 +58,7 @@ static int user_runtime_dir(char **ret, const char *suffix) {
return 0;
}
static int user_config_dir(char **ret, const char *suffix) {
int xdg_user_config_dir(char **ret, const char *suffix) {
const char *e;
char *j;
int r;
@ -85,7 +85,7 @@ static int user_config_dir(char **ret, const char *suffix) {
return 0;
}
static int user_data_dir(char **ret, const char *suffix) {
int xdg_user_data_dir(char **ret, const char *suffix) {
const char *e;
char *j;
int r;
@ -131,6 +131,41 @@ static const char* const user_config_unit_paths[] = {
NULL
};
int xdg_user_dirs(char ***ret_config_dirs, char ***ret_data_dirs) {
/* Implement the mechanisms defined in
*
* http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
*
* We look in both the config and the data dirs because we
* want to encourage that distributors ship their unit files
* as data, and allow overriding as configuration.
*/
const char *e;
_cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL;
e = getenv("XDG_CONFIG_DIRS");
if (e) {
config_dirs = strv_split(e, ":");
if (!config_dirs)
return -ENOMEM;
}
e = getenv("XDG_DATA_DIRS");
if (e)
data_dirs = strv_split(e, ":");
else
data_dirs = strv_new("/usr/local/share",
"/usr/share",
NULL);
if (!data_dirs)
return -ENOMEM;
*ret_config_dirs = config_dirs;
*ret_data_dirs = data_dirs;
config_dirs = data_dirs = NULL;
return 0;
}
static char** user_dirs(
const char *persistent_config,
const char *runtime_config,
@ -144,38 +179,15 @@ static char** user_dirs(
_cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL;
_cleanup_free_ char *data_home = NULL;
_cleanup_strv_free_ char **res = NULL;
const char *e;
char **tmp;
int r;
/* Implement the mechanisms defined in
*
* http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
*
* We look in both the config and the data dirs because we
* want to encourage that distributors ship their unit files
* as data, and allow overriding as configuration.
*/
e = getenv("XDG_CONFIG_DIRS");
if (e) {
config_dirs = strv_split(e, ":");
if (!config_dirs)
return NULL;
}
r = user_data_dir(&data_home, "/systemd/user");
if (r < 0 && r != -ENXIO)
r = xdg_user_dirs(&config_dirs, &data_dirs);
if (r < 0)
return NULL;
e = getenv("XDG_DATA_DIRS");
if (e)
data_dirs = strv_split(e, ":");
else
data_dirs = strv_new("/usr/local/share",
"/usr/share",
NULL);
if (!data_dirs)
r = xdg_user_data_dir(&data_home, "/systemd/user");
if (r < 0 && r != -ENXIO)
return NULL;
/* Now merge everything we found. */
@ -311,7 +323,7 @@ static int acquire_transient_dir(
else if (scope == UNIT_FILE_SYSTEM)
transient = strdup("/run/systemd/transient");
else
return user_runtime_dir(ret, "/systemd/transient");
return xdg_user_runtime_dir(ret, "/systemd/transient");
if (!transient)
return -ENOMEM;
@ -339,11 +351,11 @@ static int acquire_config_dirs(UnitFileScope scope, char **persistent, char **ru
break;
case UNIT_FILE_USER:
r = user_config_dir(&a, "/systemd/user");
r = xdg_user_config_dir(&a, "/systemd/user");
if (r < 0 && r != -ENXIO)
return r;
r = user_runtime_dir(runtime, "/systemd/user");
r = xdg_user_runtime_dir(runtime, "/systemd/user");
if (r < 0) {
if (r != -ENXIO)
return r;
@ -399,11 +411,11 @@ static int acquire_control_dirs(UnitFileScope scope, char **persistent, char **r
}
case UNIT_FILE_USER:
r = user_config_dir(&a, "/systemd/system.control");
r = xdg_user_config_dir(&a, "/systemd/system.control");
if (r < 0 && r != -ENXIO)
return r;
r = user_runtime_dir(runtime, "/systemd/system.control");
r = xdg_user_runtime_dir(runtime, "/systemd/system.control");
if (r < 0) {
if (r != -ENXIO)
return r;

View File

@ -68,6 +68,10 @@ struct LookupPaths {
};
int lookup_paths_init(LookupPaths *p, UnitFileScope scope, LookupPathsFlags flags, const char *root_dir);
int xdg_user_dirs(char ***ret_config_dirs, char ***ret_data_dirs);
int xdg_user_runtime_dir(char **ret, const char *suffix);
int xdg_user_config_dir(char **ret, const char *suffix);
int xdg_user_data_dir(char **ret, const char *suffix);
bool path_is_user_data_dir(const char *path);
bool path_is_user_config_dir(const char *path);

View File

@ -41,6 +41,10 @@
*
*/
/* Any ASCII character or digit: our pool of potential specifiers,
* and "%" used for escaping. */
#define POSSIBLE_SPECIFIERS ALPHANUMERICAL "%"
int specifier_printf(const char *text, const Specifier table[], void *userdata, char **_ret) {
char *ret, *t;
const char *f;
@ -97,7 +101,10 @@ int specifier_printf(const char *text, const Specifier table[], void *userdata,
ret = n;
t = n + j + k;
} else {
} else if (strchr(POSSIBLE_SPECIFIERS, *f))
/* Oops, an unknown specifier. */
return -EBADSLT;
else {
*(t++) = '%';
*(t++) = *f;
}
@ -200,7 +207,7 @@ int specifier_user_name(char specifier, void *data, void *userdata, char **ret)
/* If we are UID 0 (root), this will not result in NSS, otherwise it might. This is good, as we want to be able
* to run this in PID 1, where our user ID is 0, but where NSS lookups are not allowed.
* We don't user getusername_malloc() here, because we don't want to look at $USER, to remain consistent with
* We don't use getusername_malloc() here, because we don't want to look at $USER, to remain consistent with
* specifer_user_id() below.
*/

View File

@ -54,6 +54,10 @@ test_dlopen_c = files('test-dlopen.c')
############################################################
test_systemd_tmpfiles_py = find_program('test-systemd-tmpfiles.py')
############################################################
tests += [
[['src/test/test-device-nodes.c'],
[],

140
src/test/test-systemd-tmpfiles.py Executable file
View File

@ -0,0 +1,140 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
import os
import sys
import socket
import subprocess
import tempfile
import pwd
try:
from systemd import id128
except ImportError:
id128 = None
EX_DATAERR = 65 # from sysexits.h
EXIT_TEST_SKIP = 77
try:
subprocess.run
except AttributeError:
sys.exit(EXIT_TEST_SKIP)
exe = sys.argv[1]
def test_line(line, *, user, returncode=EX_DATAERR, extra={}):
args = ['--user'] if user else []
print('Running {} {} on {!r}'.format(exe, ' '.join(args), line))
c = subprocess.run([exe, '--create', '-'] + args,
input=line, stdout=subprocess.PIPE, universal_newlines=True,
**extra)
assert c.returncode == returncode, c
def test_invalids(*, user):
test_line('asdfa', user=user)
test_line('f "open quote', user=user)
test_line('f closed quote""', user=user)
test_line('Y /unknown/letter', user=user)
test_line('w non/absolute/path', user=user)
test_line('s', user=user) # s is for short
test_line('f!! /too/many/bangs', user=user)
test_line('f++ /too/many/plusses', user=user)
test_line('f+!+ /too/many/plusses', user=user)
test_line('f!+! /too/many/bangs', user=user)
test_line('w /unresolved/argument - - - - "%Y"', user=user)
test_line('w /unresolved/argument/sandwich - - - - "%v%Y%v"', user=user)
test_line('w /unresolved/filename/%Y - - - - "whatever"', user=user)
test_line('w /unresolved/filename/sandwich/%v%Y%v - - - - "whatever"', user=user)
test_line('w - - - - - "no file specfied"', user=user)
test_line('C - - - - - "no file specfied"', user=user)
test_line('C non/absolute/path - - - - -', user=user)
test_line('b - - - - - -', user=user)
test_line('b 1234 - - - - -', user=user)
test_line('c - - - - - -', user=user)
test_line('c 1234 - - - - -', user=user)
test_line('t - - -', user=user)
test_line('T - - -', user=user)
test_line('a - - -', user=user)
test_line('A - - -', user=user)
test_line('h - - -', user=user)
test_line('H - - -', user=user)
def test_unitialized_t():
if os.getuid() == 0:
return
test_line('w /foo - - - - "specifier for --user %t"',
user=True, returncode=0, extra={'env':{}})
def test_content(line, expected, *, user, extra={}):
d = tempfile.TemporaryDirectory(prefix='test-systemd-tmpfiles.')
arg = d.name + '/arg'
spec = line.format(arg)
test_line(spec, user=user, returncode=0, extra=extra)
content = open(arg).read()
print('expect: {!r}\nactual: {!r}'.format(expected, content))
assert content == expected
def test_valid_specifiers(*, user):
test_content('f {} - - - - two words', 'two words', user=user)
if id128:
try:
test_content('f {} - - - - %m', '{}'.format(id128.get_machine().hex), user=user)
except AssertionError as e:
print(e)
print('/etc/machine-id: {!r}'.format(open('/etc/machine-id').read()))
print('/proc/cmdline: {!r}'.format(open('/proc/cmdline').read()))
print('skipping')
test_content('f {} - - - - %b', '{}'.format(id128.get_boot().hex), user=user)
test_content('f {} - - - - %H', '{}'.format(socket.gethostname()), user=user)
test_content('f {} - - - - %v', '{}'.format(os.uname().release), user=user)
test_content('f {} - - - - %U', '{}'.format(os.getuid()), user=user)
user = pwd.getpwuid(os.getuid())
test_content('f {} - - - - %u', '{}'.format(user.pw_name), user=user)
# Note that %h is the only specifier in which we look the environment,
# because we check $HOME. Should we even be doing that?
home = os.path.expanduser("~")
test_content('f {} - - - - %h', '{}'.format(home), user=user)
xdg_runtime_dir = os.getenv('XDG_RUNTIME_DIR')
if xdg_runtime_dir is not None or not user:
test_content('f {} - - - - %t',
xdg_runtime_dir if user else '/run',
user=user)
xdg_config_home = os.getenv('XDG_CONFIG_HOME')
if xdg_config_home is not None or not user:
test_content('f {} - - - - %S',
xdg_config_home if user else '/var/lib',
user=user)
xdg_cache_home = os.getenv('XDG_CACHE_HOME')
if xdg_cache_home is not None or not user:
test_content('f {} - - - - %C',
xdg_cache_home if user else '/var/cache',
user=user)
if xdg_config_home is not None or not user:
test_content('f {} - - - - %L',
xdg_config_home + '/log' if user else '/var/log',
user=user)
test_content('f {} - - - - %%', '%', user=user)
if __name__ == '__main__':
test_invalids(user=False)
test_invalids(user=True)
test_unitialized_t()
test_valid_specifiers(user=False)
test_valid_specifiers(user=True)

View File

@ -33,9 +33,12 @@
#include <string.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <sysexits.h>
#include <time.h>
#include <unistd.h>
#include "sd-path.h"
#include "acl-util.h"
#include "alloc-util.h"
#include "btrfs-util.h"
@ -59,6 +62,7 @@
#include "mkdir.h"
#include "mount-util.h"
#include "parse-util.h"
#include "path-lookup.h"
#include "path-util.h"
#include "rm-rf.h"
#include "selinux-util.h"
@ -150,6 +154,15 @@ typedef struct ItemArray {
size_t size;
} ItemArray;
typedef enum DirectoryType {
DIRECTORY_RUNTIME = 0,
DIRECTORY_STATE,
DIRECTORY_CACHE,
DIRECTORY_LOGS,
_DIRECTORY_TYPE_MAX,
} DirectoryType;
static bool arg_user = false;
static bool arg_create = false;
static bool arg_clean = false;
static bool arg_remove = false;
@ -159,20 +172,27 @@ static char **arg_include_prefixes = NULL;
static char **arg_exclude_prefixes = NULL;
static char *arg_root = NULL;
static const char conf_file_dirs[] = CONF_PATHS_NULSTR("tmpfiles.d");
#define MAX_DEPTH 256
static OrderedHashmap *items = NULL, *globs = NULL;
static Set *unix_sockets = NULL;
static int specifier_machine_id_safe(char specifier, void *data, void *userdata, char **ret);
static int specifier_directory(char specifier, void *data, void *userdata, char **ret);
static const Specifier specifier_table[] = {
{ 'm', specifier_machine_id_safe, NULL },
{ 'b', specifier_boot_id, NULL },
{ 'H', specifier_host_name, NULL },
{ 'v', specifier_kernel_release, NULL },
{ 'b', specifier_boot_id, NULL },
{ 'H', specifier_host_name, NULL },
{ 'v', specifier_kernel_release, NULL },
{ 'U', specifier_user_id, NULL },
{ 'u', specifier_user_name, NULL },
{ 'h', specifier_user_home, NULL },
{ 't', specifier_directory, UINT_TO_PTR(DIRECTORY_RUNTIME) },
{ 'S', specifier_directory, UINT_TO_PTR(DIRECTORY_STATE) },
{ 'C', specifier_directory, UINT_TO_PTR(DIRECTORY_CACHE) },
{ 'L', specifier_directory, UINT_TO_PTR(DIRECTORY_LOGS) },
{}
};
@ -185,22 +205,56 @@ static int specifier_machine_id_safe(char specifier, void *data, void *userdata,
r = specifier_machine_id(specifier, data, userdata, ret);
if (r == -ENOENT)
return -ENOKEY;
return -ENXIO;
return r;
}
static int specifier_directory(char specifier, void *data, void *userdata, char **ret) {
struct table_entry {
uint64_t type;
const char *suffix;
};
static const struct table_entry paths_system[] = {
[DIRECTORY_RUNTIME] = { SD_PATH_SYSTEM_RUNTIME },
[DIRECTORY_STATE] = { SD_PATH_SYSTEM_STATE_PRIVATE },
[DIRECTORY_CACHE] = { SD_PATH_SYSTEM_STATE_CACHE },
[DIRECTORY_LOGS] = { SD_PATH_SYSTEM_STATE_LOGS },
};
static const struct table_entry paths_user[] = {
[DIRECTORY_RUNTIME] = { SD_PATH_USER_RUNTIME },
[DIRECTORY_STATE] = { SD_PATH_USER_CONFIGURATION },
[DIRECTORY_CACHE] = { SD_PATH_USER_STATE_CACHE },
[DIRECTORY_LOGS] = { SD_PATH_USER_CONFIGURATION, "log" },
};
unsigned i;
const struct table_entry *paths;
assert_cc(ELEMENTSOF(paths_system) == ELEMENTSOF(paths_user));
paths = arg_user ? paths_user : paths_system;
i = PTR_TO_UINT(data);
assert(i < ELEMENTSOF(paths_system));
return sd_path_home(paths[i].type, paths[i].suffix, ret);
}
static int log_unresolvable_specifier(const char *filename, unsigned line) {
static bool notified = false;
/* This is called when /etc is not fully initialized (e.g. in a chroot
* environment) where some specifiers are unresolvable. These cases are
* not considered as an error so log at LOG_NOTICE only for the first
* time and then downgrade this to LOG_DEBUG for the rest. */
/* In system mode, this is called when /etc is not fully initialized (e.g.
* in a chroot environment) where some specifiers are unresolvable. In user
* mode, this is called when some variables are not defined. These cases are
* not considered as an error so log at LOG_NOTICE only for the first time
* and then downgrade this to LOG_DEBUG for the rest. */
log_full(notified ? LOG_DEBUG : LOG_NOTICE,
"[%s:%u] Failed to resolve specifier: uninitialized /etc detected, skipping",
filename, line);
"[%s:%u] Failed to resolve specifier: %s, skipping",
filename, line,
arg_user ? "Required $XDG_... variable not defined" : "uninitialized /etc detected");
if (!notified)
log_notice("All rules containing unresolvable specifiers will be skipped.");
@ -209,6 +263,57 @@ static int log_unresolvable_specifier(const char *filename, unsigned line) {
return 0;
}
static int user_config_paths(char*** ret) {
_cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL;
_cleanup_free_ char *persistent_config = NULL, *runtime_config = NULL, *data_home = NULL;
_cleanup_strv_free_ char **res = NULL;
int r;
r = xdg_user_dirs(&config_dirs, &data_dirs);
if (r < 0)
return r;
r = xdg_user_config_dir(&persistent_config, "/user-tmpfiles.d");
if (r < 0 && r != -ENXIO)
return r;
r = xdg_user_runtime_dir(&runtime_config, "/user-tmpfiles.d");
if (r < 0 && r != -ENXIO)
return r;
r = xdg_user_data_dir(&data_home, "/user-tmpfiles.d");
if (r < 0 && r != -ENXIO)
return r;
r = strv_extend_strv_concat(&res, config_dirs, "/user-tmpfiles.d");
if (r < 0)
return r;
r = strv_extend(&res, persistent_config);
if (r < 0)
return r;
r = strv_extend(&res, runtime_config);
if (r < 0)
return r;
r = strv_extend(&res, data_home);
if (r < 0)
return r;
r = strv_extend_strv_concat(&res, data_dirs, "/user-tmpfiles.d");
if (r < 0)
return r;
r = path_strv_make_absolute_cwd(res);
if (r < 0)
return r;
*ret = res;
res = NULL;
return 0;
}
static bool needs_glob(ItemType t) {
return IN_SET(t,
WRITE_FILE,
@ -670,7 +775,7 @@ static int path_set_perms(Item *i, const char *path) {
return log_error_errno(errno, "Failed to fstat() file %s: %m", path);
if (S_ISLNK(st.st_mode))
log_debug("Skipping mode an owner fix for symlink %s.", path);
log_debug("Skipping mode and owner fix for symlink %s.", path);
else {
char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
xsprintf(fn, "/proc/self/fd/%i", fd);
@ -1631,12 +1736,12 @@ static int clean_item(Item *i) {
case CREATE_SUBVOLUME:
case CREATE_SUBVOLUME_INHERIT_QUOTA:
case CREATE_SUBVOLUME_NEW_QUOTA:
case EMPTY_DIRECTORY:
case TRUNCATE_DIRECTORY:
case IGNORE_PATH:
case COPY_FILES:
clean_item_instance(i, i->path);
return 0;
case EMPTY_DIRECTORY:
case IGNORE_DIRECTORY_PATH:
return glob_item(i, clean_item_instance, false);
default:
@ -1841,7 +1946,7 @@ static int specifier_expansion_from_arg(Item *i) {
return 0;
}
static int parse_line(const char *fname, unsigned line, const char *buffer) {
static int parse_line(const char *fname, unsigned line, const char *buffer, bool *invalid_config) {
_cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL;
_cleanup_(item_free_contents) Item i = {};
@ -1865,9 +1970,15 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
&group,
&age,
NULL);
if (r < 0)
if (r < 0) {
if (IN_SET(r, -EINVAL, -EBADSLT))
/* invalid quoting and such or an unknown specifier */
*invalid_config = true;
return log_error_errno(r, "[%s:%u] Failed to parse line: %m", fname, line);
}
else if (r < 2) {
*invalid_config = true;
log_error("[%s:%u] Syntax error.", fname, line);
return -EIO;
}
@ -1879,6 +1990,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
}
if (isempty(action)) {
*invalid_config = true;
log_error("[%s:%u] Command too short '%s'.", fname, line, action);
return -EINVAL;
}
@ -1889,6 +2001,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
else if (action[pos] == '+' && !force)
force = true;
else {
*invalid_config = true;
log_error("[%s:%u] Unknown modifiers in command '%s'",
fname, line, action);
return -EINVAL;
@ -1905,10 +2018,13 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
i.force = force;
r = specifier_printf(path, specifier_table, NULL, &i.path);
if (r == -ENOKEY)
if (r == -ENXIO)
return log_unresolvable_specifier(fname, line);
if (r < 0)
if (r < 0) {
if (IN_SET(r, -EINVAL, -EBADSLT))
*invalid_config = true;
return log_error_errno(r, "[%s:%u] Failed to replace specifiers: %s", fname, line, path);
}
switch (i.type) {
@ -1945,6 +2061,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
case WRITE_FILE:
if (!i.argument) {
*invalid_config = true;
log_error("[%s:%u] Write file requires argument.", fname, line);
return -EBADMSG;
}
@ -1956,6 +2073,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
if (!i.argument)
return log_oom();
} else if (!path_is_absolute(i.argument)) {
*invalid_config = true;
log_error("[%s:%u] Source path is not absolute.", fname, line);
return -EBADMSG;
}
@ -1968,11 +2086,13 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
unsigned major, minor;
if (!i.argument) {
*invalid_config = true;
log_error("[%s:%u] Device file requires argument.", fname, line);
return -EBADMSG;
}
if (sscanf(i.argument, "%u:%u", &major, &minor) != 2) {
*invalid_config = true;
log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i.argument);
return -EBADMSG;
}
@ -1984,6 +2104,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
case SET_XATTR:
case RECURSIVE_SET_XATTR:
if (!i.argument) {
*invalid_config = true;
log_error("[%s:%u] Set extended attribute requires argument.", fname, line);
return -EBADMSG;
}
@ -1995,6 +2116,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
case SET_ACL:
case RECURSIVE_SET_ACL:
if (!i.argument) {
*invalid_config = true;
log_error("[%s:%u] Set ACLs requires argument.", fname, line);
return -EBADMSG;
}
@ -2006,21 +2128,26 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
case SET_ATTRIBUTE:
case RECURSIVE_SET_ATTRIBUTE:
if (!i.argument) {
*invalid_config = true;
log_error("[%s:%u] Set file attribute requires argument.", fname, line);
return -EBADMSG;
}
r = parse_attribute_from_arg(&i);
if (IN_SET(r, -EINVAL, -EBADSLT))
*invalid_config = true;
if (r < 0)
return r;
break;
default:
log_error("[%s:%u] Unknown command type '%c'.", fname, line, (char) i.type);
*invalid_config = true;
return -EBADMSG;
}
if (!path_is_absolute(i.path)) {
log_error("[%s:%u] Path '%s' not absolute.", fname, line, i.path);
*invalid_config = true;
return -EBADMSG;
}
@ -2030,11 +2157,14 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return 0;
r = specifier_expansion_from_arg(&i);
if (r == -ENOKEY)
if (r == -ENXIO)
return log_unresolvable_specifier(fname, line);
if (r < 0)
if (r < 0) {
if (IN_SET(r, -EINVAL, -EBADSLT))
*invalid_config = true;
return log_error_errno(r, "[%s:%u] Failed to substitute specifiers in argument: %m",
fname, line);
}
if (arg_root) {
char *p;
@ -2052,8 +2182,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
r = get_user_creds(&u, &i.uid, NULL, NULL, NULL);
if (r < 0) {
log_error("[%s:%u] Unknown user '%s'.", fname, line, user);
return r;
*invalid_config = true;
return log_error_errno(r, "[%s:%u] Unknown user '%s'.", fname, line, user);
}
i.uid_set = true;
@ -2064,6 +2194,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
r = get_group_creds(&g, &i.gid);
if (r < 0) {
*invalid_config = true;
log_error("[%s:%u] Unknown group '%s'.", fname, line, group);
return r;
}
@ -2081,6 +2212,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
}
if (parse_mode(mm, &m) < 0) {
*invalid_config = true;
log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode);
return -EBADMSG;
}
@ -2099,6 +2231,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
}
if (parse_sec(a, &i.age) < 0) {
*invalid_config = true;
log_error("[%s:%u] Invalid age '%s'.", fname, line, age);
return -EBADMSG;
}
@ -2114,8 +2247,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
for (n = 0; n < existing->count; n++) {
if (!item_compatible(existing->items + n, &i)) {
log_warning("[%s:%u] Duplicate line for path \"%s\", ignoring.",
fname, line, i.path);
log_notice("[%s:%u] Duplicate line for path \"%s\", ignoring.",
fname, line, i.path);
return 0;
}
}
@ -2142,6 +2275,7 @@ static void help(void) {
printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
"Creates, deletes and cleans up volatile and temporary files and directories.\n\n"
" -h --help Show this help\n"
" --user Execute user configuration\n"
" --version Show package version\n"
" --create Create marked files/directories\n"
" --clean Clean up marked directories\n"
@ -2149,14 +2283,15 @@ static void help(void) {
" --boot Execute actions only safe at boot\n"
" --prefix=PATH Only apply rules with the specified prefix\n"
" --exclude-prefix=PATH Ignore rules with the specified prefix\n"
" --root=PATH Operate on an alternate filesystem root\n",
program_invocation_short_name);
" --root=PATH Operate on an alternate filesystem root\n"
, program_invocation_short_name);
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_USER,
ARG_CREATE,
ARG_CLEAN,
ARG_REMOVE,
@ -2168,6 +2303,7 @@ static int parse_argv(int argc, char *argv[]) {
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "user", no_argument, NULL, ARG_USER },
{ "version", no_argument, NULL, ARG_VERSION },
{ "create", no_argument, NULL, ARG_CREATE },
{ "clean", no_argument, NULL, ARG_CLEAN },
@ -2195,6 +2331,10 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_VERSION:
return version();
case ARG_USER:
arg_user = true;
break;
case ARG_CREATE:
arg_create = true;
break;
@ -2242,7 +2382,7 @@ static int parse_argv(int argc, char *argv[]) {
return 1;
}
static int read_config_file(const char *fn, bool ignore_enoent) {
static int read_config_file(const char **config_dirs, const char *fn, bool ignore_enoent, bool *invalid_config) {
_cleanup_fclose_ FILE *_f = NULL;
FILE *f;
char line[LINE_MAX];
@ -2258,7 +2398,7 @@ static int read_config_file(const char *fn, bool ignore_enoent) {
fn = "<stdin>";
f = stdin;
} else {
r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &_f);
r = search_and_fopen(fn, "re", arg_root, config_dirs, &_f);
if (r < 0) {
if (ignore_enoent && r == -ENOENT) {
log_debug_errno(r, "Failed to open \"%s\", ignoring: %m", fn);
@ -2274,6 +2414,7 @@ static int read_config_file(const char *fn, bool ignore_enoent) {
FOREACH_LINE(line, f, break) {
char *l;
int k;
bool invalid_line = false;
v++;
@ -2281,9 +2422,15 @@ static int read_config_file(const char *fn, bool ignore_enoent) {
if (IN_SET(*l, 0, '#'))
continue;
k = parse_line(fn, v, l);
if (k < 0 && r == 0)
r = k;
k = parse_line(fn, v, l, &invalid_line);
if (k < 0) {
if (invalid_line)
/* Allow reporting with a special code if the caller requested this */
*invalid_config = true;
else if (r == 0)
/* The first error becomes our return value */
r = k;
}
}
/* we have to determine age parameter for each entry of type X */
@ -2327,6 +2474,9 @@ int main(int argc, char *argv[]) {
int r, k;
ItemArray *a;
Iterator iterator;
_cleanup_strv_free_ char **config_dirs = NULL;
bool invalid_config = false;
char **f;
r = parse_argv(argc, argv);
if (r <= 0)
@ -2350,27 +2500,48 @@ int main(int argc, char *argv[]) {
r = 0;
if (arg_user) {
r = user_config_paths(&config_dirs);
if (r < 0) {
log_error_errno(r, "Failed to initialize configuration directory list: %m");
goto finish;
}
} else {
config_dirs = strv_split_nulstr(CONF_PATHS_NULSTR("tmpfiles.d"));
if (!config_dirs) {
r = log_oom();
goto finish;
}
}
{
_cleanup_free_ char *t = NULL;
t = strv_join(config_dirs, "\n\t");
if (t)
log_debug("Looking for configuration files in (higher priority first:\n\t%s", t);
}
if (optind < argc) {
int j;
for (j = optind; j < argc; j++) {
k = read_config_file(argv[j], false);
k = read_config_file((const char**) config_dirs, argv[j], false, &invalid_config);
if (k < 0 && r == 0)
r = k;
}
} else {
_cleanup_strv_free_ char **files = NULL;
char **f;
r = conf_files_list_nulstr(&files, ".conf", arg_root, 0, conf_file_dirs);
r = conf_files_list_strv(&files, ".conf", arg_root, 0, (const char* const*) config_dirs);
if (r < 0) {
log_error_errno(r, "Failed to enumerate tmpfiles.d files: %m");
goto finish;
}
STRV_FOREACH(f, files) {
k = read_config_file(*f, true);
k = read_config_file((const char**) config_dirs, *f, true, &invalid_config);
if (k < 0 && r == 0)
r = k;
}
@ -2404,5 +2575,10 @@ finish:
mac_selinux_finish();
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
if (r < 0)
return EXIT_FAILURE;
else if (invalid_config)
return EX_DATAERR;
else
return EXIT_SUCCESS;
}

View File

@ -18,4 +18,5 @@ Before=shutdown.target
[Service]
Type=oneshot
ExecStart=@rootbindir@/systemd-tmpfiles --clean
SuccessExitStatus=65
IOSchedulingClass=idle

View File

@ -20,3 +20,4 @@ ConditionCapability=CAP_SYS_MODULE
Type=oneshot
RemainAfterExit=yes
ExecStart=@rootbindir@/systemd-tmpfiles --prefix=/dev --create --boot
SuccessExitStatus=65

View File

@ -20,3 +20,4 @@ RefuseManualStop=yes
Type=oneshot
RemainAfterExit=yes
ExecStart=@rootbindir@/systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev
SuccessExitStatus=65

View File

@ -29,6 +29,7 @@ units = [
'sockets.target',
'sound.target',
'timers.target',
'systemd-tmpfiles-clean.timer',
]
foreach file : units
@ -38,6 +39,8 @@ endforeach
in_units = [
'systemd-exit.service',
'systemd-tmpfiles-clean.service',
'systemd-tmpfiles-setup.service',
]
foreach file : in_units

View File

@ -0,0 +1,21 @@
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Cleanup of User's Temporary Files and Directories
Documentation=man:tmpfiles.d(5) man:systemd-tmpfiles(8)
DefaultDependencies=no
Conflicts=shutdown.target
Before=basic.target shutdown.target
[Service]
Type=oneshot
ExecStart=@rootbindir@/systemd-tmpfiles --user --clean
SuccessExitStatus=65
IOSchedulingClass=idle

View File

@ -0,0 +1,19 @@
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Daily Cleanup of User's Temporary Directories
Documentation=man:tmpfiles.d(5) man:systemd-tmpfiles(8)
[Timer]
OnStartupSec=5min
OnUnitActiveSec=1d
[Install]
WantedBy=timers.target

View File

@ -0,0 +1,25 @@
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Create User's Volatile Files and Directories
Documentation=man:tmpfiles.d(5) man:systemd-tmpfiles(8)
DefaultDependencies=no
Conflicts=shutdown.target
Before=basic.target shutdown.target
RefuseManualStop=yes
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=@rootbindir@/systemd-tmpfiles --user --create --remove --boot
SuccessExitStatus=65
[Install]
WantedBy=basic.target