mirror of
https://github.com/systemd/systemd.git
synced 2025-02-22 09:57:34 +03:00
Merge pull request #7419 from keszybz/tmpfiles-fixes
Tmpfiles --user mode and various fixes
This commit is contained in:
commit
c7a54cd67b
7
NEWS
7
NEWS
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
22
presets/meson.build
Normal 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)
|
14
presets/user/90-systemd.preset
Normal file
14
presets/user/90-systemd.preset
Normal 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
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
||||
|
@ -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
140
src/test/test-systemd-tmpfiles.py
Executable 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)
|
@ -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;
|
||||
}
|
||||
|
@ -18,4 +18,5 @@ Before=shutdown.target
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=@rootbindir@/systemd-tmpfiles --clean
|
||||
SuccessExitStatus=65
|
||||
IOSchedulingClass=idle
|
||||
|
@ -20,3 +20,4 @@ ConditionCapability=CAP_SYS_MODULE
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=@rootbindir@/systemd-tmpfiles --prefix=/dev --create --boot
|
||||
SuccessExitStatus=65
|
||||
|
@ -20,3 +20,4 @@ RefuseManualStop=yes
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=@rootbindir@/systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev
|
||||
SuccessExitStatus=65
|
||||
|
@ -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
|
||||
|
21
units/user/systemd-tmpfiles-clean.service.in
Normal file
21
units/user/systemd-tmpfiles-clean.service.in
Normal 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
|
19
units/user/systemd-tmpfiles-clean.timer
Normal file
19
units/user/systemd-tmpfiles-clean.timer
Normal 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
|
25
units/user/systemd-tmpfiles-setup.service.in
Normal file
25
units/user/systemd-tmpfiles-setup.service.in
Normal 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
|
Loading…
x
Reference in New Issue
Block a user