Compare commits
24 Commits
49d5aae503
...
60daebdec2
Author | SHA1 | Date |
---|---|---|
Lennart Poettering | 60daebdec2 | |
Lennart Poettering | 38344f1a79 | |
Lennart Poettering | e0d70f7691 | |
Lennart Poettering | 6d06dfad85 | |
Daan De Meyer | a5925354bb | |
Daan De Meyer | 676339a191 | |
Daan De Meyer | 97a1a1103c | |
Daan De Meyer | 4926ceaff3 | |
Daan De Meyer | b4909a3fd0 | |
Daan De Meyer | 2da3dc69e7 | |
Daan De Meyer | 90c81688ff | |
Daan De Meyer | 494735f3d0 | |
Daan De Meyer | 1fbc95d388 | |
Lennart Poettering | bb2294e454 | |
Lennart Poettering | 6bce17455e | |
Michael Biebl | f978844eb6 | |
Benjamin Berg | 2ad7597e44 | |
Benjamin Berg | 98e07533a2 | |
Benjamin Berg | 4540e698e8 | |
Benjamin Berg | 8feca2472c | |
Benjamin Berg | 8746820b87 | |
Andreas Rammhold | 02e9e34bd9 | |
Andreas Rammhold | 171f625b9e | |
Andreas Rammhold | 863b99cdd9 |
2
TODO
2
TODO
|
@ -256,8 +256,6 @@ Features:
|
||||||
* systemd-firstboot: teach it dissector magic, so that you can point it to some
|
* systemd-firstboot: teach it dissector magic, so that you can point it to some
|
||||||
disk image and it will just set everything in it all behind the scenes.
|
disk image and it will just set everything in it all behind the scenes.
|
||||||
|
|
||||||
* systemd-firstboot: add --force mode that replaces existing configuration.
|
|
||||||
|
|
||||||
* We should probably replace /var/log/README, /etc/rc.d/README with symlinks
|
* We should probably replace /var/log/README, /etc/rc.d/README with symlinks
|
||||||
that are linked to these places instead of copied. After all they are
|
that are linked to these places instead of copied. After all they are
|
||||||
constant vendor data.
|
constant vendor data.
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||||
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
|
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
|
||||||
|
|
||||||
<refentry id="homed.conf" conditional='ENABLE_RESOLVE'
|
<refentry id="homed.conf" conditional='ENABLE_HOMED'
|
||||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||||
<refentryinfo>
|
<refentryinfo>
|
||||||
<title>homed.conf</title>
|
<title>homed.conf</title>
|
||||||
|
|
|
@ -975,6 +975,7 @@ manpages = [
|
||||||
['systemd-veritysetup'],
|
['systemd-veritysetup'],
|
||||||
'HAVE_LIBCRYPTSETUP'],
|
'HAVE_LIBCRYPTSETUP'],
|
||||||
['systemd-volatile-root.service', '8', ['systemd-volatile-root'], ''],
|
['systemd-volatile-root.service', '8', ['systemd-volatile-root'], ''],
|
||||||
|
['systemd-xdg-autostart-generator', '8', [], 'ENABLE_XDG_AUTOSTART'],
|
||||||
['systemd', '1', ['init'], ''],
|
['systemd', '1', ['init'], ''],
|
||||||
['systemd.automount', '5', [], ''],
|
['systemd.automount', '5', [], ''],
|
||||||
['systemd.device', '5', [], ''],
|
['systemd.device', '5', [], ''],
|
||||||
|
|
|
@ -150,18 +150,27 @@
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>--root-password=<replaceable>PASSWORD</replaceable></option></term>
|
<term><option>--root-password=<replaceable>PASSWORD</replaceable></option></term>
|
||||||
<term><option>--root-password-file=<replaceable>PATH</replaceable></option></term>
|
<term><option>--root-password-file=<replaceable>PATH</replaceable></option></term>
|
||||||
|
<term><option>--root-password-hashed=<replaceable>HASHED_PASSWORD</replaceable></option></term>
|
||||||
|
|
||||||
<listitem><para>Sets the password of the system's root user.
|
<listitem><para>Sets the password of the system's root user. This creates a
|
||||||
This creates a
|
|
||||||
<citerefentry project='die-net'><refentrytitle>shadow</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
<citerefentry project='die-net'><refentrytitle>shadow</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||||
file. This setting exists in two forms:
|
file. This setting exists in three forms: <option>--root-password=</option> accepts the password to
|
||||||
<option>--root-password=</option> accepts the password to set
|
set directly on the command line, <option>--root-password-file=</option> reads it from a file and
|
||||||
directly on the command line, and
|
<option>--root-password-hashed=</option> accepts an already hashed password on the command line. See
|
||||||
<option>--root-password-file=</option> reads it from a file.
|
<citerefentry project='die-net'><refentrytitle>shadow</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||||
Note that it is not recommended to specify passwords on the
|
for more information on the format of the hashed password. Note that it is not recommended to specify
|
||||||
command line, as other users might be able to see them simply
|
plaintext passwords on the command line, as other users might be able to see them simply by invoking
|
||||||
by invoking
|
<citerefentry project='die-net'><refentrytitle>ps</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
|
||||||
<citerefentry project='die-net'><refentrytitle>ps</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem>
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--kernel-command-line=<replaceable>CMDLINE</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>Sets the system's kernel command line. This controls the
|
||||||
|
<filename>/etc/kernel/cmdline</filename> file which is used by
|
||||||
|
<citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
|
||||||
|
</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
|
@ -221,6 +230,23 @@
|
||||||
<option>--root=</option>.</para></listitem>
|
<option>--root=</option>.</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--force</option></term>
|
||||||
|
|
||||||
|
<listitem><para>systemd-firstboot doesn't modify existing files unless <option>--force</option>
|
||||||
|
is specified. For modifications to <filename>/etc/passwd</filename> and
|
||||||
|
<filename>/etc/shadow</filename>, systemd-firstboot only modifies the entry of the
|
||||||
|
<literal>root</literal> user instead of overwriting the entire file.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--delete-root-password</option></term>
|
||||||
|
|
||||||
|
<listitem><para>Removes the password of the system's root user, enabling login as root without a
|
||||||
|
password unless the root account is locked. Note that this is extremely insecure and hence this
|
||||||
|
option should not be used lightly.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<xi:include href="standard-options.xml" xpointer="help" />
|
<xi:include href="standard-options.xml" xpointer="help" />
|
||||||
<xi:include href="standard-options.xml" xpointer="version" />
|
<xi:include href="standard-options.xml" xpointer="version" />
|
||||||
</variablelist>
|
</variablelist>
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--*-nxml-*-->
|
||||||
|
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||||
|
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||||
|
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
|
||||||
|
<refentry id="systemd-xdg-autostart-generator" conditional="ENABLE_XDG_AUTOSTART">
|
||||||
|
|
||||||
|
<refentryinfo>
|
||||||
|
<title>systemd-xdg-autostart-generator</title>
|
||||||
|
<productname>systemd</productname>
|
||||||
|
</refentryinfo>
|
||||||
|
|
||||||
|
<refmeta>
|
||||||
|
<refentrytitle>systemd-xdg-autostart-generator</refentrytitle>
|
||||||
|
<manvolnum>8</manvolnum>
|
||||||
|
</refmeta>
|
||||||
|
|
||||||
|
<refnamediv>
|
||||||
|
<refname>systemd-xdg-autostart-generator</refname>
|
||||||
|
<refpurpose>User unit generator for XDG autostart files</refpurpose>
|
||||||
|
</refnamediv>
|
||||||
|
|
||||||
|
<refsynopsisdiv>
|
||||||
|
<para><filename>/usr/lib/systemd/system-generators/systemd-xdg-autostart-generator</filename></para>
|
||||||
|
</refsynopsisdiv>
|
||||||
|
|
||||||
|
<refsect1>
|
||||||
|
<title>Description</title>
|
||||||
|
|
||||||
|
<para><filename>systemd-xdg-autostart-generator</filename> is a generator
|
||||||
|
that creates .service units for
|
||||||
|
<ulink url="https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html">XDG autostart</ulink>
|
||||||
|
files.
|
||||||
|
This permits desktop environments to delegate startup of these applications to
|
||||||
|
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||||
|
.</para>
|
||||||
|
|
||||||
|
<para>Units created by <filename>systemd-xdg-autostart-generator</filename>
|
||||||
|
can be started by the desktop environment using <literal>xdg-desktop-autostart.target</literal>.
|
||||||
|
See
|
||||||
|
<citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||||
|
for more details.</para>
|
||||||
|
|
||||||
|
<para><filename>systemd-xdg-autostart-generator</filename> implements
|
||||||
|
<citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1>
|
||||||
|
<title>See Also</title>
|
||||||
|
<para>
|
||||||
|
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||||
|
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||||
|
<citerefentry><refentrytitle>systemd.target</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||||
|
</para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
</refentry>
|
|
@ -310,6 +310,7 @@ find $dir</programlisting>
|
||||||
<citerefentry><refentrytitle>systemd-rc-local-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
<citerefentry><refentrytitle>systemd-rc-local-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||||
<citerefentry><refentrytitle>systemd-system-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
<citerefentry><refentrytitle>systemd-system-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||||
<citerefentry><refentrytitle>systemd-sysv-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
<citerefentry><refentrytitle>systemd-sysv-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||||
|
<citerefentry><refentrytitle>systemd-xdg-autostart-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||||
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||||
<citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
<citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||||
<citerefentry><refentrytitle>systemd.environment-generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>
|
<citerefentry><refentrytitle>systemd.environment-generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>
|
||||||
|
|
|
@ -720,6 +720,16 @@
|
||||||
sections for more configuration options.
|
sections for more configuration options.
|
||||||
</para></listitem>
|
</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>IPv6PDSubnetId=</varname></term>
|
||||||
|
<listitem><para>Configure a specific subnet ID on the interface from a (previously) received prefix delegation.
|
||||||
|
You can either set "auto" (the default) or a specific subnet ID
|
||||||
|
(as defined in <ulink url="https://tools.ietf.org/html/rfc4291#section-2.5.4">RFC 4291</ulink>, section 2.5.4),
|
||||||
|
in which case the allowed value is hexadecimal, from 0 to 0x7fffffffffffffff inclusive.
|
||||||
|
This option is only effective when used together with <varname>IPv6PrefixDelegation=</varname>
|
||||||
|
and the corresponding configuration on the upstream interface.
|
||||||
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><varname>IPv6MTUBytes=</varname></term>
|
<term><varname>IPv6MTUBytes=</varname></term>
|
||||||
<listitem><para>Configures IPv6 maximum transmission unit (MTU).
|
<listitem><para>Configures IPv6 maximum transmission unit (MTU).
|
||||||
|
|
|
@ -1150,6 +1150,18 @@
|
||||||
<filename>gnome-session.target</filename>.</para>
|
<filename>gnome-session.target</filename>.</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><filename>xdg-desktop-autostart.target</filename></term>
|
||||||
|
<listitem>
|
||||||
|
<para>The XDG specification defines a way to autostart applications using XDG desktop files.
|
||||||
|
systemd ships
|
||||||
|
<citerefentry><refentrytitle>systemd-xdg-autostart-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||||
|
for the XDG desktop files in autostart directories.
|
||||||
|
Desktop Environments can opt-in to use this service by adding a <varname>Wants=</varname>
|
||||||
|
dependency on <literal>xdg-desktop-autostart.target</literal></para>.
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</refsect2>
|
</refsect2>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
24
meson.build
24
meson.build
|
@ -1417,6 +1417,7 @@ foreach term : ['utmp',
|
||||||
'tmpfiles',
|
'tmpfiles',
|
||||||
'hwdb',
|
'hwdb',
|
||||||
'rfkill',
|
'rfkill',
|
||||||
|
'xdg-autostart',
|
||||||
'ldconfig',
|
'ldconfig',
|
||||||
'efi',
|
'efi',
|
||||||
'tpm',
|
'tpm',
|
||||||
|
@ -1520,6 +1521,7 @@ includes = include_directories('src/basic',
|
||||||
'src/libudev',
|
'src/libudev',
|
||||||
'src/core',
|
'src/core',
|
||||||
'src/shutdown',
|
'src/shutdown',
|
||||||
|
'src/xdg-autostart-generator',
|
||||||
'src/libsystemd/sd-bus',
|
'src/libsystemd/sd-bus',
|
||||||
'src/libsystemd/sd-device',
|
'src/libsystemd/sd-device',
|
||||||
'src/libsystemd/sd-event',
|
'src/libsystemd/sd-event',
|
||||||
|
@ -2301,6 +2303,27 @@ if conf.get('HAVE_SYSV_COMPAT') == 1
|
||||||
install_dir : systemgeneratordir)
|
install_dir : systemgeneratordir)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if conf.get('ENABLE_XDG_AUTOSTART') == 1
|
||||||
|
executable(
|
||||||
|
'systemd-xdg-autostart-generator',
|
||||||
|
'src/xdg-autostart-generator/xdg-autostart-generator.c',
|
||||||
|
'src/xdg-autostart-generator/xdg-autostart-service.c',
|
||||||
|
include_directories : includes,
|
||||||
|
link_with : [libshared],
|
||||||
|
install_rpath : rootlibexecdir,
|
||||||
|
install : true,
|
||||||
|
install_dir : usergeneratordir)
|
||||||
|
|
||||||
|
executable(
|
||||||
|
'systemd-xdg-autostart-condition',
|
||||||
|
'src/xdg-autostart-generator/xdg-autostart-condition.c',
|
||||||
|
include_directories : includes,
|
||||||
|
link_with : [libshared],
|
||||||
|
install_rpath : rootlibexecdir,
|
||||||
|
install : true,
|
||||||
|
install_dir : rootlibexecdir)
|
||||||
|
endif
|
||||||
|
|
||||||
if conf.get('ENABLE_HOSTNAMED') == 1
|
if conf.get('ENABLE_HOSTNAMED') == 1
|
||||||
executable(
|
executable(
|
||||||
'systemd-hostnamed',
|
'systemd-hostnamed',
|
||||||
|
@ -3566,6 +3589,7 @@ foreach tuple : [
|
||||||
['randomseed'],
|
['randomseed'],
|
||||||
['backlight'],
|
['backlight'],
|
||||||
['rfkill'],
|
['rfkill'],
|
||||||
|
['xdg-autostart'],
|
||||||
['logind'],
|
['logind'],
|
||||||
['machined'],
|
['machined'],
|
||||||
['portabled'],
|
['portabled'],
|
||||||
|
|
|
@ -142,6 +142,8 @@ option('hwdb', type : 'boolean',
|
||||||
description : 'support for the hardware database')
|
description : 'support for the hardware database')
|
||||||
option('rfkill', type : 'boolean',
|
option('rfkill', type : 'boolean',
|
||||||
description : 'support for the rfkill tools')
|
description : 'support for the rfkill tools')
|
||||||
|
option('xdg-autostart', type : 'boolean',
|
||||||
|
description : 'install the xdg-autostart-generator and unit')
|
||||||
option('man', type : 'combo', choices : ['auto', 'true', 'false'],
|
option('man', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||||
value : 'false',
|
value : 'false',
|
||||||
description : 'build and install man pages')
|
description : 'build and install man pages')
|
||||||
|
|
|
@ -1187,3 +1187,25 @@ int warn_file_is_world_accessible(const char *filename, struct stat *st, const c
|
||||||
filename, st->st_mode & 07777);
|
filename, st->st_mode & 07777);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int sync_rights(int from, int to) {
|
||||||
|
struct stat st;
|
||||||
|
|
||||||
|
if (fstat(from, &st) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
return fchmod_and_chown(to, st.st_mode & 07777, st.st_uid, st.st_gid);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rename_and_apply_smack_floor_label(const char *from, const char *to) {
|
||||||
|
int r = 0;
|
||||||
|
if (rename(from, to) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
#ifdef SMACK_RUN_LABEL
|
||||||
|
r = mac_smack_apply(to, SMACK_ATTR_ACCESS, SMACK_FLOOR_LABEL);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
#endif
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
|
@ -106,3 +106,7 @@ static inline int read_nul_string(FILE *f, size_t limit, char **ret) {
|
||||||
int safe_fgetc(FILE *f, char *ret);
|
int safe_fgetc(FILE *f, char *ret);
|
||||||
|
|
||||||
int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line);
|
int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line);
|
||||||
|
|
||||||
|
int sync_rights(int from, int to);
|
||||||
|
|
||||||
|
int rename_and_apply_smack_floor_label(const char *temp_path, const char *dest_path);
|
||||||
|
|
|
@ -177,47 +177,89 @@ int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen)
|
||||||
assert(u);
|
assert(u);
|
||||||
|
|
||||||
/* Increases the network part of an address by one. Returns
|
/* Increases the network part of an address by one. Returns
|
||||||
* positive it that succeeds, or 0 if this overflows. */
|
* positive if that succeeds, or -ERANGE if this overflows. */
|
||||||
|
|
||||||
|
return in_addr_prefix_nth(family, u, prefixlen, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculates the nth prefix of size prefixlen starting from the address denoted by u.
|
||||||
|
*
|
||||||
|
* On success 1 will be returned and the calculated prefix will be available in
|
||||||
|
* u. In the case nth == 0 the input will be left unchanged and 1 will be returned.
|
||||||
|
* In case the calculation cannot be performed (invalid prefix length,
|
||||||
|
* overflows would occur) -ERANGE is returned. If the address family given isn't
|
||||||
|
* supported -EAFNOSUPPORT will be returned.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* - in_addr_prefix_nth(AF_INET, 192.168.0.0, 24, 2), returns 1, writes 192.168.2.0 to u
|
||||||
|
* - in_addr_prefix_nth(AF_INET, 192.168.0.0, 24, 0), returns 1, no data written
|
||||||
|
* - in_addr_prefix_nth(AF_INET, 255.255.255.0, 24, 1), returns -ERANGE, no data written
|
||||||
|
* - in_addr_prefix_nth(AF_INET, 255.255.255.0, 0, 1), returns -ERANGE, no data written
|
||||||
|
* - in_addr_prefix_nth(AF_INET6, 2001:db8, 64, 0xff00) returns 1, writes 2001:0db8:0000:ff00:: to u
|
||||||
|
*/
|
||||||
|
int in_addr_prefix_nth(int family, union in_addr_union *u, unsigned prefixlen, uint64_t nth) {
|
||||||
|
assert(u);
|
||||||
|
|
||||||
if (prefixlen <= 0)
|
if (prefixlen <= 0)
|
||||||
return 0;
|
return -ERANGE;
|
||||||
|
|
||||||
|
if (nth == 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
if (family == AF_INET) {
|
if (family == AF_INET) {
|
||||||
uint32_t c, n;
|
uint32_t c, n, t;
|
||||||
|
|
||||||
if (prefixlen > 32)
|
if (prefixlen > 32)
|
||||||
prefixlen = 32;
|
prefixlen = 32;
|
||||||
|
|
||||||
c = be32toh(u->in.s_addr);
|
c = be32toh(u->in.s_addr);
|
||||||
n = c + (1UL << (32 - prefixlen));
|
|
||||||
if (n < c)
|
|
||||||
return 0;
|
|
||||||
n &= 0xFFFFFFFFUL << (32 - prefixlen);
|
|
||||||
|
|
||||||
|
t = nth << (32 - prefixlen);
|
||||||
|
|
||||||
|
/* Check for wrap */
|
||||||
|
if (c > UINT32_MAX - t)
|
||||||
|
return -ERANGE;
|
||||||
|
|
||||||
|
n = c + t;
|
||||||
|
|
||||||
|
n &= UINT32_C(0xFFFFFFFF) << (32 - prefixlen);
|
||||||
u->in.s_addr = htobe32(n);
|
u->in.s_addr = htobe32(n);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (family == AF_INET6) {
|
if (family == AF_INET6) {
|
||||||
struct in6_addr add = {}, result;
|
struct in6_addr result = {};
|
||||||
uint8_t overflow = 0;
|
uint8_t overflow = 0;
|
||||||
unsigned i;
|
uint64_t delta; /* this assumes that we only ever have to up to 1<<64 subnets */
|
||||||
|
unsigned start_byte = (prefixlen - 1) / 8;
|
||||||
|
|
||||||
if (prefixlen > 128)
|
if (prefixlen > 128)
|
||||||
prefixlen = 128;
|
prefixlen = 128;
|
||||||
|
|
||||||
/* First calculate what we have to add */
|
/* First calculate what we have to add */
|
||||||
add.s6_addr[(prefixlen-1) / 8] = 1 << (7 - (prefixlen-1) % 8);
|
delta = nth << ((128 - prefixlen) % 8);
|
||||||
|
|
||||||
for (i = 16; i > 0; i--) {
|
for (unsigned i = 16; i > 0; i--) {
|
||||||
unsigned j = i - 1;
|
unsigned j = i - 1;
|
||||||
|
unsigned d = 0;
|
||||||
|
|
||||||
result.s6_addr[j] = u->in6.s6_addr[j] + add.s6_addr[j] + overflow;
|
if (j <= start_byte) {
|
||||||
overflow = (result.s6_addr[j] < u->in6.s6_addr[j]);
|
int16_t t;
|
||||||
|
|
||||||
|
d = delta & 0xFF;
|
||||||
|
delta >>= 8;
|
||||||
|
|
||||||
|
t = u->in6.s6_addr[j] + d + overflow;
|
||||||
|
overflow = t > UINT8_MAX ? t - UINT8_MAX : 0;
|
||||||
|
|
||||||
|
result.s6_addr[j] = (uint8_t)t;
|
||||||
|
} else
|
||||||
|
result.s6_addr[j] = u->in6.s6_addr[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overflow)
|
if (overflow || delta != 0)
|
||||||
return 0;
|
return -ERANGE;
|
||||||
|
|
||||||
u->in6 = result;
|
u->in6 = result;
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -36,6 +36,7 @@ bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b);
|
||||||
int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b);
|
int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b);
|
||||||
int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen);
|
int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen);
|
||||||
int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
|
int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
|
||||||
|
int in_addr_prefix_nth(int family, union in_addr_union *u, unsigned prefixlen, uint64_t nth);
|
||||||
int in_addr_random_prefix(int family, union in_addr_union *u, unsigned prefixlen_fixed_part, unsigned prefixlen);
|
int in_addr_random_prefix(int family, union in_addr_union *u, unsigned prefixlen_fixed_part, unsigned prefixlen);
|
||||||
int in_addr_to_string(int family, const union in_addr_union *u, char **ret);
|
int in_addr_to_string(int family, const union in_addr_union *u, char **ret);
|
||||||
int in_addr_prefix_to_string(int family, const union in_addr_union *u, unsigned prefixlen, char **ret);
|
int in_addr_prefix_to_string(int family, const union in_addr_union *u, unsigned prefixlen, char **ret);
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "strv.h"
|
#include "strv.h"
|
||||||
#include "terminal-util.h"
|
#include "terminal-util.h"
|
||||||
#include "time-util.h"
|
#include "time-util.h"
|
||||||
|
#include "tmpfile-util-label.h"
|
||||||
#include "umask-util.h"
|
#include "umask-util.h"
|
||||||
#include "user-util.h"
|
#include "user-util.h"
|
||||||
|
|
||||||
|
@ -41,6 +42,7 @@ static char *arg_timezone = NULL;
|
||||||
static char *arg_hostname = NULL;
|
static char *arg_hostname = NULL;
|
||||||
static sd_id128_t arg_machine_id = {};
|
static sd_id128_t arg_machine_id = {};
|
||||||
static char *arg_root_password = NULL;
|
static char *arg_root_password = NULL;
|
||||||
|
static char *arg_kernel_cmdline = NULL;
|
||||||
static bool arg_prompt_locale = false;
|
static bool arg_prompt_locale = false;
|
||||||
static bool arg_prompt_keymap = false;
|
static bool arg_prompt_keymap = false;
|
||||||
static bool arg_prompt_timezone = false;
|
static bool arg_prompt_timezone = false;
|
||||||
|
@ -50,6 +52,9 @@ static bool arg_copy_locale = false;
|
||||||
static bool arg_copy_keymap = false;
|
static bool arg_copy_keymap = false;
|
||||||
static bool arg_copy_timezone = false;
|
static bool arg_copy_timezone = false;
|
||||||
static bool arg_copy_root_password = false;
|
static bool arg_copy_root_password = false;
|
||||||
|
static bool arg_force = false;
|
||||||
|
static bool arg_delete_root_password = false;
|
||||||
|
static bool arg_root_password_is_hashed = false;
|
||||||
|
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_locale, freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_locale, freep);
|
||||||
|
@ -273,7 +278,7 @@ static int process_locale(void) {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
|
etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
|
||||||
if (laccess(etc_localeconf, F_OK) >= 0)
|
if (laccess(etc_localeconf, F_OK) >= 0 && !arg_force)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (arg_copy_locale && arg_root) {
|
if (arg_copy_locale && arg_root) {
|
||||||
|
@ -340,7 +345,7 @@ static int process_keymap(void) {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf");
|
etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf");
|
||||||
if (laccess(etc_vconsoleconf, F_OK) >= 0)
|
if (laccess(etc_vconsoleconf, F_OK) >= 0 && !arg_force)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (arg_copy_keymap && arg_root) {
|
if (arg_copy_keymap && arg_root) {
|
||||||
|
@ -412,7 +417,7 @@ static int process_timezone(void) {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
etc_localtime = prefix_roota(arg_root, "/etc/localtime");
|
etc_localtime = prefix_roota(arg_root, "/etc/localtime");
|
||||||
if (laccess(etc_localtime, F_OK) >= 0)
|
if (laccess(etc_localtime, F_OK) >= 0 && !arg_force)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (arg_copy_timezone && arg_root) {
|
if (arg_copy_timezone && arg_root) {
|
||||||
|
@ -492,7 +497,7 @@ static int process_hostname(void) {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
etc_hostname = prefix_roota(arg_root, "/etc/hostname");
|
etc_hostname = prefix_roota(arg_root, "/etc/hostname");
|
||||||
if (laccess(etc_hostname, F_OK) >= 0)
|
if (laccess(etc_hostname, F_OK) >= 0 && !arg_force)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
r = prompt_hostname();
|
r = prompt_hostname();
|
||||||
|
@ -503,7 +508,8 @@ static int process_hostname(void) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
r = write_string_file(etc_hostname, arg_hostname,
|
r = write_string_file(etc_hostname, arg_hostname,
|
||||||
WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755);
|
WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
|
||||||
|
(arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
|
return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
|
||||||
|
|
||||||
|
@ -517,14 +523,15 @@ static int process_machine_id(void) {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
|
etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
|
||||||
if (laccess(etc_machine_id, F_OK) >= 0)
|
if (laccess(etc_machine_id, F_OK) >= 0 && !arg_force)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (sd_id128_is_null(arg_machine_id))
|
if (sd_id128_is_null(arg_machine_id))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id),
|
r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id),
|
||||||
WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755);
|
WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
|
||||||
|
(arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to write machine id: %m");
|
return log_error_errno(r, "Failed to write machine id: %m");
|
||||||
|
|
||||||
|
@ -533,7 +540,7 @@ static int process_machine_id(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int prompt_root_password(void) {
|
static int prompt_root_password(void) {
|
||||||
const char *msg1, *msg2, *etc_shadow;
|
const char *msg1, *msg2;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
if (arg_root_password)
|
if (arg_root_password)
|
||||||
|
@ -542,10 +549,6 @@ static int prompt_root_password(void) {
|
||||||
if (!arg_prompt_root_password)
|
if (!arg_prompt_root_password)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
etc_shadow = prefix_roota(arg_root, "/etc/shadow");
|
|
||||||
if (laccess(etc_shadow, F_OK) >= 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
print_welcome();
|
print_welcome();
|
||||||
putchar('\n');
|
putchar('\n');
|
||||||
|
|
||||||
|
@ -586,29 +589,105 @@ static int prompt_root_password(void) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int write_root_shadow(const char *path, const struct spwd *p) {
|
static int write_root_passwd(const char *passwd_path, const char *password) {
|
||||||
_cleanup_fclose_ FILE *f = NULL;
|
_cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
|
||||||
|
_cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(path);
|
r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
|
||||||
assert(p);
|
|
||||||
|
|
||||||
RUN_WITH_UMASK(0777)
|
|
||||||
f = fopen(path, "wex");
|
|
||||||
if (!f)
|
|
||||||
return -errno;
|
|
||||||
|
|
||||||
r = putspent_sane(p, f);
|
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
return fflush_sync_and_check(f);
|
original = fopen(passwd_path, "re");
|
||||||
|
if (original) {
|
||||||
|
struct passwd *i;
|
||||||
|
|
||||||
|
r = sync_rights(fileno(original), fileno(passwd));
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
while ((r = fgetpwent_sane(original, &i)) > 0) {
|
||||||
|
|
||||||
|
if (streq(i->pw_name, "root"))
|
||||||
|
i->pw_passwd = (char *) password;
|
||||||
|
|
||||||
|
r = putpwent_sane(i, passwd);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
struct passwd root = {
|
||||||
|
.pw_name = (char *) "root",
|
||||||
|
.pw_passwd = (char *) password,
|
||||||
|
.pw_uid = 0,
|
||||||
|
.pw_gid = 0,
|
||||||
|
.pw_gecos = (char *) "Super User",
|
||||||
|
.pw_dir = (char *) "/root",
|
||||||
|
.pw_shell = (char *) "/bin/sh",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (errno != ENOENT)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
r = fchmod(fileno(passwd), 0000);
|
||||||
|
if (r < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
r = putpwent_sane(&root, passwd);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_root_password(void) {
|
r = fflush_sync_and_check(passwd);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
struct spwd item = {
|
r = rename_and_apply_smack_floor_label(passwd_tmp, passwd_path);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int write_root_shadow(const char *shadow_path, const char *hashed_password) {
|
||||||
|
_cleanup_fclose_ FILE *original = NULL, *shadow = NULL;
|
||||||
|
_cleanup_(unlink_and_freep) char *shadow_tmp = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
original = fopen(shadow_path, "re");
|
||||||
|
if (original) {
|
||||||
|
struct spwd *i;
|
||||||
|
|
||||||
|
r = sync_rights(fileno(original), fileno(shadow));
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
while ((r = fgetspent_sane(original, &i)) > 0) {
|
||||||
|
|
||||||
|
if (streq(i->sp_namp, "root")) {
|
||||||
|
i->sp_pwdp = (char *) hashed_password;
|
||||||
|
i->sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = putspent_sane(i, shadow);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
struct spwd root = {
|
||||||
.sp_namp = (char*) "root",
|
.sp_namp = (char*) "root",
|
||||||
|
.sp_pwdp = (char *) hashed_password,
|
||||||
|
.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY),
|
||||||
.sp_min = -1,
|
.sp_min = -1,
|
||||||
.sp_max = -1,
|
.sp_max = -1,
|
||||||
.sp_warn = -1,
|
.sp_warn = -1,
|
||||||
|
@ -616,15 +695,39 @@ static int process_root_password(void) {
|
||||||
.sp_expire = -1,
|
.sp_expire = -1,
|
||||||
.sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
|
.sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
|
||||||
};
|
};
|
||||||
_cleanup_free_ char *salt = NULL;
|
|
||||||
|
if (errno != ENOENT)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
r = fchmod(fileno(shadow), 0000);
|
||||||
|
if (r < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
r = putspent_sane(&root, shadow);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = fflush_sync_and_check(shadow);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_root_password(void) {
|
||||||
_cleanup_close_ int lock = -1;
|
_cleanup_close_ int lock = -1;
|
||||||
struct crypt_data cd = {};
|
struct crypt_data cd = {};
|
||||||
|
const char *hashed_password;
|
||||||
const char *etc_shadow;
|
const char *etc_shadow;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
etc_shadow = prefix_roota(arg_root, "/etc/shadow");
|
etc_shadow = prefix_roota(arg_root, "/etc/shadow");
|
||||||
if (laccess(etc_shadow, F_OK) >= 0)
|
if (laccess(etc_shadow, F_OK) >= 0 && !arg_force)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
(void) mkdir_parents(etc_shadow, 0755);
|
(void) mkdir_parents(etc_shadow, 0755);
|
||||||
|
@ -633,6 +736,21 @@ static int process_root_password(void) {
|
||||||
if (lock < 0)
|
if (lock < 0)
|
||||||
return log_error_errno(lock, "Failed to take a lock: %m");
|
return log_error_errno(lock, "Failed to take a lock: %m");
|
||||||
|
|
||||||
|
if (arg_delete_root_password) {
|
||||||
|
const char *etc_passwd;
|
||||||
|
|
||||||
|
/* Mixing alloca() and other stuff that touches the stack in one expression is not portable. */
|
||||||
|
etc_passwd = prefix_roota(arg_root, "/etc/passwd");
|
||||||
|
|
||||||
|
r = write_root_passwd(etc_passwd, "");
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to write %s: %m", etc_passwd);
|
||||||
|
|
||||||
|
log_info("%s written", etc_passwd);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (arg_copy_root_password && arg_root) {
|
if (arg_copy_root_password && arg_root) {
|
||||||
struct spwd *p;
|
struct spwd *p;
|
||||||
|
|
||||||
|
@ -646,7 +764,7 @@ static int process_root_password(void) {
|
||||||
return log_error_errno(errno, "Failed to find shadow entry for root: %m");
|
return log_error_errno(errno, "Failed to find shadow entry for root: %m");
|
||||||
}
|
}
|
||||||
|
|
||||||
r = write_root_shadow(etc_shadow, p);
|
r = write_root_shadow(etc_shadow, p->sp_pwdp);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
|
return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
|
||||||
|
|
||||||
|
@ -662,19 +780,24 @@ static int process_root_password(void) {
|
||||||
if (!arg_root_password)
|
if (!arg_root_password)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
if (arg_root_password_is_hashed)
|
||||||
|
hashed_password = arg_root_password;
|
||||||
|
else {
|
||||||
|
_cleanup_free_ char *salt = NULL;
|
||||||
|
/* hashed_password points inside cd after crypt_r returns so cd has function scope. */
|
||||||
|
|
||||||
r = make_salt(&salt);
|
r = make_salt(&salt);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to get salt: %m");
|
return log_error_errno(r, "Failed to get salt: %m");
|
||||||
|
|
||||||
errno = 0;
|
errno = 0;
|
||||||
item.sp_pwdp = crypt_r(arg_root_password, salt, &cd);
|
hashed_password = crypt_r(arg_root_password, salt, &cd);
|
||||||
if (!item.sp_pwdp)
|
if (!hashed_password)
|
||||||
return log_error_errno(errno == 0 ? SYNTHETIC_ERRNO(EINVAL) : errno,
|
return log_error_errno(errno == 0 ? SYNTHETIC_ERRNO(EINVAL) : errno,
|
||||||
"Failed to encrypt password: %m");
|
"Failed to encrypt password: %m");
|
||||||
|
}
|
||||||
|
|
||||||
item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
|
r = write_root_shadow(etc_shadow, hashed_password);
|
||||||
|
|
||||||
r = write_root_shadow(etc_shadow, &item);
|
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
|
return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
|
||||||
|
|
||||||
|
@ -682,6 +805,27 @@ static int process_root_password(void) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int process_kernel_cmdline(void) {
|
||||||
|
const char *etc_kernel_cmdline;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
etc_kernel_cmdline = prefix_roota(arg_root, "/etc/kernel/cmdline");
|
||||||
|
if (laccess(etc_kernel_cmdline, F_OK) >= 0 && !arg_force)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!arg_kernel_cmdline)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
r = write_string_file(etc_kernel_cmdline, arg_kernel_cmdline,
|
||||||
|
WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
|
||||||
|
(arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to write %s: %m", etc_kernel_cmdline);
|
||||||
|
|
||||||
|
log_info("%s written.", etc_kernel_cmdline);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int help(void) {
|
static int help(void) {
|
||||||
_cleanup_free_ char *link = NULL;
|
_cleanup_free_ char *link = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
@ -701,8 +845,9 @@ static int help(void) {
|
||||||
" --timezone=TIMEZONE Set timezone\n"
|
" --timezone=TIMEZONE Set timezone\n"
|
||||||
" --hostname=NAME Set hostname\n"
|
" --hostname=NAME Set hostname\n"
|
||||||
" --machine-ID=ID Set machine ID\n"
|
" --machine-ID=ID Set machine ID\n"
|
||||||
" --root-password=PASSWORD Set root password\n"
|
" --root-password=PASSWORD Set root password from plaintext password\n"
|
||||||
" --root-password-file=FILE Set root password from file\n"
|
" --root-password-file=FILE Set root password from file\n"
|
||||||
|
" --root-password-hashed=HASHED_PASSWORD Set root password from hashed password\n"
|
||||||
" --prompt-locale Prompt the user for locale settings\n"
|
" --prompt-locale Prompt the user for locale settings\n"
|
||||||
" --prompt-keymap Prompt the user for keymap settings\n"
|
" --prompt-keymap Prompt the user for keymap settings\n"
|
||||||
" --prompt-timezone Prompt the user for timezone\n"
|
" --prompt-timezone Prompt the user for timezone\n"
|
||||||
|
@ -715,6 +860,8 @@ static int help(void) {
|
||||||
" --copy-root-password Copy root password from host\n"
|
" --copy-root-password Copy root password from host\n"
|
||||||
" --copy Copy locale, keymap, timezone, root password\n"
|
" --copy Copy locale, keymap, timezone, root password\n"
|
||||||
" --setup-machine-id Generate a new random machine ID\n"
|
" --setup-machine-id Generate a new random machine ID\n"
|
||||||
|
" --force Overwrite existing files\n"
|
||||||
|
" --delete-root-password Delete root password\n"
|
||||||
"\nSee the %s for details.\n"
|
"\nSee the %s for details.\n"
|
||||||
, program_invocation_short_name
|
, program_invocation_short_name
|
||||||
, link
|
, link
|
||||||
|
@ -736,6 +883,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
ARG_MACHINE_ID,
|
ARG_MACHINE_ID,
|
||||||
ARG_ROOT_PASSWORD,
|
ARG_ROOT_PASSWORD,
|
||||||
ARG_ROOT_PASSWORD_FILE,
|
ARG_ROOT_PASSWORD_FILE,
|
||||||
|
ARG_ROOT_PASSWORD_HASHED,
|
||||||
|
ARG_KERNEL_COMMAND_LINE,
|
||||||
ARG_PROMPT,
|
ARG_PROMPT,
|
||||||
ARG_PROMPT_LOCALE,
|
ARG_PROMPT_LOCALE,
|
||||||
ARG_PROMPT_KEYMAP,
|
ARG_PROMPT_KEYMAP,
|
||||||
|
@ -748,6 +897,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
ARG_COPY_TIMEZONE,
|
ARG_COPY_TIMEZONE,
|
||||||
ARG_COPY_ROOT_PASSWORD,
|
ARG_COPY_ROOT_PASSWORD,
|
||||||
ARG_SETUP_MACHINE_ID,
|
ARG_SETUP_MACHINE_ID,
|
||||||
|
ARG_FORCE,
|
||||||
|
ARG_DELETE_ROOT_PASSWORD,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct option options[] = {
|
static const struct option options[] = {
|
||||||
|
@ -762,6 +913,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
{ "machine-id", required_argument, NULL, ARG_MACHINE_ID },
|
{ "machine-id", required_argument, NULL, ARG_MACHINE_ID },
|
||||||
{ "root-password", required_argument, NULL, ARG_ROOT_PASSWORD },
|
{ "root-password", required_argument, NULL, ARG_ROOT_PASSWORD },
|
||||||
{ "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE },
|
{ "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE },
|
||||||
|
{ "root-password-hashed", required_argument, NULL, ARG_ROOT_PASSWORD_HASHED },
|
||||||
|
{ "kernel-command-line", required_argument, NULL, ARG_KERNEL_COMMAND_LINE },
|
||||||
{ "prompt", no_argument, NULL, ARG_PROMPT },
|
{ "prompt", no_argument, NULL, ARG_PROMPT },
|
||||||
{ "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
|
{ "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
|
||||||
{ "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP },
|
{ "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP },
|
||||||
|
@ -774,6 +927,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
{ "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE },
|
{ "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE },
|
||||||
{ "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD },
|
{ "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD },
|
||||||
{ "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID },
|
{ "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID },
|
||||||
|
{ "force", no_argument, NULL, ARG_FORCE },
|
||||||
|
{ "delete-root-password", no_argument, NULL, ARG_DELETE_ROOT_PASSWORD },
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -838,6 +993,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
r = free_and_strdup(&arg_root_password, optarg);
|
r = free_and_strdup(&arg_root_password, optarg);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
|
arg_root_password_is_hashed = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_ROOT_PASSWORD_FILE:
|
case ARG_ROOT_PASSWORD_FILE:
|
||||||
|
@ -847,6 +1004,15 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to read %s: %m", optarg);
|
return log_error_errno(r, "Failed to read %s: %m", optarg);
|
||||||
|
|
||||||
|
arg_root_password_is_hashed = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARG_ROOT_PASSWORD_HASHED:
|
||||||
|
r = free_and_strdup(&arg_root_password, optarg);
|
||||||
|
if (r < 0)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
arg_root_password_is_hashed = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ARG_HOSTNAME:
|
case ARG_HOSTNAME:
|
||||||
|
@ -868,6 +1034,13 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ARG_KERNEL_COMMAND_LINE:
|
||||||
|
r = free_and_strdup(&arg_kernel_cmdline, optarg);
|
||||||
|
if (r < 0)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case ARG_PROMPT:
|
case ARG_PROMPT:
|
||||||
arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
|
arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
|
||||||
break;
|
break;
|
||||||
|
@ -920,6 +1093,14 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ARG_FORCE:
|
||||||
|
arg_force = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARG_DELETE_ROOT_PASSWORD:
|
||||||
|
arg_delete_root_password = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
@ -935,6 +1116,10 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
if (arg_locale_messages && !locale_is_ok(arg_locale_messages))
|
if (arg_locale_messages && !locale_is_ok(arg_locale_messages))
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages);
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages);
|
||||||
|
|
||||||
|
if (arg_delete_root_password && (arg_copy_root_password || arg_root_password || arg_prompt_root_password))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||||
|
"--delete-root-password cannot be combined with other root password options");
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -980,6 +1165,10 @@ static int run(int argc, char *argv[]) {
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
r = process_kernel_cmdline();
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||||
|
|
||||||
|
#include "alloc-util.h"
|
||||||
|
#include "fd-util.h"
|
||||||
|
#include "fs-util.h"
|
||||||
|
#include "rm-rf.h"
|
||||||
|
#include "string-util.h"
|
||||||
|
#include "strv.h"
|
||||||
|
#include "tests.h"
|
||||||
|
#include "tmpfile-util.h"
|
||||||
|
#include "fuzz.h"
|
||||||
|
#include "xdg-autostart-service.h"
|
||||||
|
|
||||||
|
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||||
|
_cleanup_(unlink_tempfilep) char name[] = "/tmp/fuzz-xdg-desktop.XXXXXX";
|
||||||
|
_cleanup_close_ int fd = -1;
|
||||||
|
_cleanup_(xdg_autostart_service_freep) XdgAutostartService *service = NULL;
|
||||||
|
_cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL;
|
||||||
|
|
||||||
|
/* We don't want to fill the logs with messages about parse errors.
|
||||||
|
* Disable most logging if not running standalone */
|
||||||
|
if (!getenv("SYSTEMD_LOG_LEVEL"))
|
||||||
|
log_set_max_level(LOG_CRIT);
|
||||||
|
|
||||||
|
assert_se(mkdtemp_malloc("/tmp/fuzz-xdg-desktop-XXXXXX", &tmpdir) >= 0);
|
||||||
|
|
||||||
|
fd = mkostemp_safe(name);
|
||||||
|
assert_se(fd >= 0);
|
||||||
|
assert_se(write(fd, data, size) == (ssize_t) size);
|
||||||
|
|
||||||
|
assert_se(service = xdg_autostart_service_parse_desktop(name));
|
||||||
|
assert_se(service->name = strdup("fuzz-xdg-desktop.service"));
|
||||||
|
if (service)
|
||||||
|
(void) xdg_autostart_service_generate_unit(service, tmpdir);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -146,4 +146,10 @@ fuzzers += [
|
||||||
[['src/fuzz/fuzz-time-util.c'],
|
[['src/fuzz/fuzz-time-util.c'],
|
||||||
[libshared],
|
[libshared],
|
||||||
[]],
|
[]],
|
||||||
|
|
||||||
|
[['src/fuzz/fuzz-xdg-desktop.c',
|
||||||
|
'src/xdg-autostart-generator/xdg-autostart-service.h',
|
||||||
|
'src/xdg-autostart-generator/xdg-autostart-service.c'],
|
||||||
|
[],
|
||||||
|
[]],
|
||||||
]
|
]
|
||||||
|
|
|
@ -465,8 +465,17 @@ static bool validate_runtime_directory(pam_handle_t *handle, const char *path, u
|
||||||
assert(handle);
|
assert(handle);
|
||||||
assert(path);
|
assert(path);
|
||||||
|
|
||||||
/* Just some extra paranoia: let's not set $XDG_RUNTIME_DIR if the directory we'd set it to isn't actually set
|
/* Some extra paranoia: let's not set $XDG_RUNTIME_DIR if the directory we'd set it to isn't actually
|
||||||
* up properly for us. */
|
* set up properly for us. This is supposed to provide a careful safety net for supporting su/sudo
|
||||||
|
* type transitions: in that case the UID changes, but the session and thus the user owning it
|
||||||
|
* doesn't change. Since the $XDG_RUNTIME_DIR life-cycle is bound to the session's user being logged
|
||||||
|
* in at least once we should be particularly careful when setting the environment variable, since
|
||||||
|
* otherwise we might end up setting $XDG_RUNTIME_DIR to some directory owned by the wrong user. */
|
||||||
|
|
||||||
|
if (!path_is_absolute(path)) {
|
||||||
|
pam_syslog(handle, LOG_ERR, "Provided runtime directory '%s' is not absolute.", path);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
if (lstat(path, &st) < 0) {
|
if (lstat(path, &st) < 0) {
|
||||||
pam_syslog(handle, LOG_ERR, "Failed to stat() runtime directory '%s': %s", path, strerror_safe(errno));
|
pam_syslog(handle, LOG_ERR, "Failed to stat() runtime directory '%s': %s", path, strerror_safe(errno));
|
||||||
|
@ -622,6 +631,29 @@ static int apply_user_record_settings(pam_handle_t *handle, UserRecord *ur, bool
|
||||||
return PAM_SUCCESS;
|
return PAM_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int configure_runtime_directory(
|
||||||
|
pam_handle_t *handle,
|
||||||
|
UserRecord *ur,
|
||||||
|
const char *rt) {
|
||||||
|
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(handle);
|
||||||
|
assert(ur);
|
||||||
|
assert(rt);
|
||||||
|
|
||||||
|
if (!validate_runtime_directory(handle, rt, ur->uid))
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
|
||||||
|
r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
|
||||||
|
if (r != PAM_SUCCESS) {
|
||||||
|
pam_syslog(handle, LOG_ERR, "Failed to set runtime dir: %s", pam_strerror(handle, r));
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return export_legacy_dbus_address(handle, rt);
|
||||||
|
}
|
||||||
|
|
||||||
_public_ PAM_EXTERN int pam_sm_open_session(
|
_public_ PAM_EXTERN int pam_sm_open_session(
|
||||||
pam_handle_t *handle,
|
pam_handle_t *handle,
|
||||||
int flags,
|
int flags,
|
||||||
|
@ -677,15 +709,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||||
char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
|
char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
|
||||||
|
|
||||||
xsprintf(rt, "/run/user/"UID_FMT, ur->uid);
|
xsprintf(rt, "/run/user/"UID_FMT, ur->uid);
|
||||||
if (validate_runtime_directory(handle, rt, ur->uid)) {
|
r = configure_runtime_directory(handle, ur, rt);
|
||||||
r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
|
|
||||||
if (r != PAM_SUCCESS) {
|
|
||||||
pam_syslog(handle, LOG_ERR, "Failed to set runtime dir: %s", pam_strerror(handle, r));
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r = export_legacy_dbus_address(handle, rt);
|
|
||||||
if (r != PAM_SUCCESS)
|
if (r != PAM_SUCCESS)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
@ -875,19 +899,11 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
if (original_uid == ur->uid) {
|
if (original_uid == ur->uid) {
|
||||||
/* Don't set $XDG_RUNTIME_DIR if the user we now
|
/* Don't set $XDG_RUNTIME_DIR if the user we now authenticated for does not match the
|
||||||
* authenticated for does not match the original user
|
* original user of the session. We do this in order not to result in privileged apps
|
||||||
* of the session. We do this in order not to result
|
* clobbering the runtime directory unnecessarily. */
|
||||||
* in privileged apps clobbering the runtime directory
|
|
||||||
* unnecessarily. */
|
|
||||||
|
|
||||||
if (validate_runtime_directory(handle, runtime_path, ur->uid)) {
|
r = configure_runtime_directory(handle, ur, runtime_path);
|
||||||
r = update_environment(handle, "XDG_RUNTIME_DIR", runtime_path);
|
|
||||||
if (r != PAM_SUCCESS)
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = export_legacy_dbus_address(handle, runtime_path);
|
|
||||||
if (r != PAM_SUCCESS)
|
if (r != PAM_SUCCESS)
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link);
|
||||||
static Link *dhcp6_prefix_get(Manager *m, struct in6_addr *addr);
|
static Link *dhcp6_prefix_get(Manager *m, struct in6_addr *addr);
|
||||||
static int dhcp6_prefix_add(Manager *m, struct in6_addr *addr, Link *link);
|
static int dhcp6_prefix_add(Manager *m, struct in6_addr *addr, Link *link);
|
||||||
static int dhcp6_prefix_remove_all(Manager *m, Link *link);
|
static int dhcp6_prefix_remove_all(Manager *m, Link *link);
|
||||||
|
static bool dhcp6_link_has_dhcpv6_prefix(Link *link);
|
||||||
|
|
||||||
static bool dhcp6_get_prefix_delegation(Link *link) {
|
static bool dhcp6_get_prefix_delegation(Link *link) {
|
||||||
if (!link->network)
|
if (!link->network)
|
||||||
|
@ -35,6 +36,97 @@ static bool dhcp6_get_prefix_delegation(Link *link) {
|
||||||
RADV_PREFIX_DELEGATION_BOTH);
|
RADV_PREFIX_DELEGATION_BOTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool dhcp6_has_preferred_subnet_id(Link *link) {
|
||||||
|
if (!link->network)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return link->network->router_prefix_subnet_id >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dhcp6_get_preferred_delegated_prefix(
|
||||||
|
Manager* manager,
|
||||||
|
Link *link,
|
||||||
|
const struct in6_addr *pd_prefix,
|
||||||
|
uint8_t pd_prefix_len,
|
||||||
|
struct in6_addr *ret_addr) {
|
||||||
|
int r;
|
||||||
|
union in_addr_union pd_prefix_union = {
|
||||||
|
.in6 = *pd_prefix,
|
||||||
|
};
|
||||||
|
int64_t subnet_id = link->network->router_prefix_subnet_id;
|
||||||
|
|
||||||
|
assert(pd_prefix_len <= 64);
|
||||||
|
|
||||||
|
uint8_t prefix_bits = 64 - pd_prefix_len;
|
||||||
|
uint64_t n_prefixes = UINT64_C(1) << prefix_bits;
|
||||||
|
_cleanup_free_ char *assigned_buf = NULL;
|
||||||
|
|
||||||
|
/* We start off with the original PD prefix we have been assigned and
|
||||||
|
* iterate from there */
|
||||||
|
union in_addr_union prefix = {
|
||||||
|
.in6 = *pd_prefix,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (subnet_id >= 0) {
|
||||||
|
/* If the link has a preference for a particular subnet id try to allocate that */
|
||||||
|
if ((uint64_t)subnet_id >= n_prefixes)
|
||||||
|
return log_link_debug_errno(link,
|
||||||
|
SYNTHETIC_ERRNO(ERANGE),
|
||||||
|
"subnet id %" PRIi64 " is out of range. Only have %" PRIu64 " subnets.",
|
||||||
|
subnet_id,
|
||||||
|
n_prefixes);
|
||||||
|
|
||||||
|
r = in_addr_prefix_nth(AF_INET6, &prefix, 64, subnet_id);
|
||||||
|
if (r < 0)
|
||||||
|
return log_link_debug_errno(link,
|
||||||
|
r,
|
||||||
|
"subnet id %" PRIi64 " is out of range. Only have %" PRIu64 " subnets.",
|
||||||
|
subnet_id,
|
||||||
|
n_prefixes);
|
||||||
|
|
||||||
|
/* Verify that the prefix we did calculate fits in the pd prefix.
|
||||||
|
* This should not fail as we checked the prefix size beforehand */
|
||||||
|
assert_se(in_addr_prefix_covers(AF_INET6, &pd_prefix_union, pd_prefix_len, &prefix) > 0);
|
||||||
|
|
||||||
|
Link* assigned_link = dhcp6_prefix_get(manager, &prefix.in6);
|
||||||
|
|
||||||
|
(void) in_addr_to_string(AF_INET6, &prefix, &assigned_buf);
|
||||||
|
|
||||||
|
if (assigned_link && assigned_link != link)
|
||||||
|
return log_link_error_errno(link, SYNTHETIC_ERRNO(EAGAIN),
|
||||||
|
"The requested prefix %s is already assigned to another link: %s",
|
||||||
|
strnull(assigned_buf),
|
||||||
|
strnull(assigned_link->ifname));
|
||||||
|
|
||||||
|
*ret_addr = prefix.in6;
|
||||||
|
|
||||||
|
log_link_debug(link, "The requested prefix %s is available. Using it.",
|
||||||
|
strnull(assigned_buf));
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
for (uint64_t n = 0; n < n_prefixes; n++) {
|
||||||
|
/* if we do not have an allocation preference just iterate
|
||||||
|
* through the address space and return the first free prefix. */
|
||||||
|
Link* assigned_link = dhcp6_prefix_get(manager, &prefix.in6);
|
||||||
|
|
||||||
|
if (!assigned_link || assigned_link == link) {
|
||||||
|
*ret_addr = prefix.in6;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = in_addr_prefix_next(AF_INET6, &prefix, 64);
|
||||||
|
if (r < 0)
|
||||||
|
return log_link_error_errno(link,
|
||||||
|
r,
|
||||||
|
"Can't allocate another prefix. Out of address space?");
|
||||||
|
}
|
||||||
|
|
||||||
|
log_link_warning(link, "Couldn't find a suitable prefix. Ran out of address space.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return -ERANGE;
|
||||||
|
}
|
||||||
|
|
||||||
static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) {
|
static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) {
|
||||||
Manager *manager;
|
Manager *manager;
|
||||||
Link *l;
|
Link *l;
|
||||||
|
@ -165,24 +257,27 @@ int dhcp6_lease_pd_prefix_lost(sd_dhcp6_client *client, Link* link) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
|
static int dhcp6_pd_prefix_distribute(Link *dhcp6_link,
|
||||||
struct in6_addr *pd_prefix,
|
struct in6_addr *pd_prefix,
|
||||||
uint8_t pd_prefix_len,
|
uint8_t pd_prefix_len,
|
||||||
uint32_t lifetime_preferred,
|
uint32_t lifetime_preferred,
|
||||||
uint32_t lifetime_valid) {
|
uint32_t lifetime_valid,
|
||||||
|
bool assign_preferred_subnet_id) {
|
||||||
|
Iterator i;
|
||||||
Link *link;
|
Link *link;
|
||||||
Manager *manager = dhcp6_link->manager;
|
Manager *manager = dhcp6_link->manager;
|
||||||
union in_addr_union prefix;
|
union in_addr_union prefix = {
|
||||||
uint64_t n_prefixes, n_used = 0;
|
.in6 = *pd_prefix,
|
||||||
|
};
|
||||||
|
uint64_t n_prefixes;
|
||||||
_cleanup_free_ char *buf = NULL;
|
_cleanup_free_ char *buf = NULL;
|
||||||
_cleanup_free_ char *assigned_buf = NULL;
|
_cleanup_free_ char *assigned_buf = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
bool pool_depleted = false;
|
||||||
|
|
||||||
assert(manager);
|
assert(manager);
|
||||||
assert(pd_prefix_len <= 64);
|
assert(pd_prefix_len <= 64);
|
||||||
|
|
||||||
prefix.in6 = *pd_prefix;
|
|
||||||
|
|
||||||
r = in_addr_mask(AF_INET6, &prefix, pd_prefix_len);
|
r = in_addr_mask(AF_INET6, &prefix, pd_prefix_len);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
@ -193,15 +288,8 @@ static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
|
||||||
log_link_debug(dhcp6_link, "Assigning up to %" PRIu64 " prefixes from %s/%u",
|
log_link_debug(dhcp6_link, "Assigning up to %" PRIu64 " prefixes from %s/%u",
|
||||||
n_prefixes, strnull(buf), pd_prefix_len);
|
n_prefixes, strnull(buf), pd_prefix_len);
|
||||||
|
|
||||||
while (hashmap_iterate(manager->links, i, (void **)&link, NULL)) {
|
HASHMAP_FOREACH(link, manager->links, i) {
|
||||||
Link *assigned_link;
|
union in_addr_union assigned_prefix;
|
||||||
|
|
||||||
if (n_used == n_prefixes) {
|
|
||||||
log_link_debug(dhcp6_link, "Assigned %" PRIu64 "/%" PRIu64 " prefixes from %s/%u",
|
|
||||||
n_used, n_prefixes, strnull(buf), pd_prefix_len);
|
|
||||||
|
|
||||||
return -EAGAIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (link == dhcp6_link)
|
if (link == dhcp6_link)
|
||||||
continue;
|
continue;
|
||||||
|
@ -209,35 +297,42 @@ static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
|
||||||
if (!dhcp6_get_prefix_delegation(link))
|
if (!dhcp6_get_prefix_delegation(link))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
assigned_link = dhcp6_prefix_get(manager, &prefix.in6);
|
if (dhcp6_link_has_dhcpv6_prefix(link))
|
||||||
if (assigned_link && assigned_link != link)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
(void) in_addr_to_string(AF_INET6, &prefix, &assigned_buf);
|
if (assign_preferred_subnet_id != dhcp6_has_preferred_subnet_id(link))
|
||||||
r = dhcp6_pd_prefix_assign(link, &prefix.in6, 64,
|
continue;
|
||||||
|
|
||||||
|
r = dhcp6_get_preferred_delegated_prefix(manager, link, &prefix.in6, pd_prefix_len,
|
||||||
|
&assigned_prefix.in6);
|
||||||
|
|
||||||
|
if (assign_preferred_subnet_id && r == -EAGAIN) {
|
||||||
|
/* A link has a preferred subnet_id but that one is
|
||||||
|
* already taken by another link. Now all the remaining
|
||||||
|
* links will also not obtain a prefix. */
|
||||||
|
pool_depleted = true;
|
||||||
|
continue;
|
||||||
|
} else if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
(void) in_addr_to_string(AF_INET6, &assigned_prefix, &assigned_buf);
|
||||||
|
r = dhcp6_pd_prefix_assign(link, &assigned_prefix.in6, 64,
|
||||||
lifetime_preferred, lifetime_valid);
|
lifetime_preferred, lifetime_valid);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
log_link_error_errno(link, r, "Unable to %s prefix %s/64 from %s/%u for link: %m",
|
log_link_error_errno(link, r, "Unable to assign/update prefix %s/64 from %s/%u for link: %m",
|
||||||
assigned_link ? "update": "assign",
|
|
||||||
strnull(assigned_buf),
|
strnull(assigned_buf),
|
||||||
strnull(buf), pd_prefix_len);
|
strnull(buf), pd_prefix_len);
|
||||||
|
|
||||||
if (!assigned_link)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
} else
|
} else
|
||||||
log_link_debug(link, "Assigned prefix %" PRIu64 "/%" PRIu64 " %s/64 from %s/%u to link",
|
log_link_debug(link, "Assigned prefix %s/64 from %s/%u to link",
|
||||||
n_used + 1, n_prefixes,
|
|
||||||
strnull(assigned_buf),
|
strnull(assigned_buf),
|
||||||
strnull(buf), pd_prefix_len);
|
strnull(buf), pd_prefix_len);
|
||||||
|
|
||||||
n_used++;
|
|
||||||
|
|
||||||
r = in_addr_prefix_next(AF_INET6, &prefix, 64);
|
|
||||||
if (r < 0 && n_used < n_prefixes)
|
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If one of the link requests couldn't be fulfilled, signal that we
|
||||||
|
should try again with another prefix. */
|
||||||
|
if (pool_depleted)
|
||||||
|
return -EAGAIN;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +357,6 @@ static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) {
|
||||||
union in_addr_union pd_prefix;
|
union in_addr_union pd_prefix;
|
||||||
uint8_t pd_prefix_len;
|
uint8_t pd_prefix_len;
|
||||||
uint32_t lifetime_preferred, lifetime_valid;
|
uint32_t lifetime_preferred, lifetime_valid;
|
||||||
Iterator i = ITERATOR_FIRST;
|
|
||||||
|
|
||||||
r = sd_dhcp6_client_get_lease(client, &lease);
|
r = sd_dhcp6_client_get_lease(client, &lease);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
|
@ -314,15 +408,47 @@ static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) {
|
||||||
} else
|
} else
|
||||||
log_link_debug(link, "Not adding a blocking route since distributed prefix is /64");
|
log_link_debug(link, "Not adding a blocking route since distributed prefix is /64");
|
||||||
|
|
||||||
r = dhcp6_pd_prefix_distribute(link, &i, &pd_prefix.in6,
|
/* We are doing prefix allocation in two steps:
|
||||||
|
* 1. all those links that have a preferred subnet id will be assigned their subnet
|
||||||
|
* 2. all those links that remain will receive prefixes in sequential
|
||||||
|
* order. Prefixes that were previously already allocated to another
|
||||||
|
* link will be skipped.
|
||||||
|
|
||||||
|
* If a subnet id request couldn't be fullfilled the failure will be logged (as error)
|
||||||
|
* and no further attempts at obtaining a prefix will be made.
|
||||||
|
|
||||||
|
* The assignment has to be split in two phases since subnet id
|
||||||
|
* preferences should be honored. Meaning that any subnet id should be
|
||||||
|
* handed out to the requesting link and not to some link that didn't
|
||||||
|
* specify any preference. */
|
||||||
|
|
||||||
|
r = dhcp6_pd_prefix_distribute(link, &pd_prefix.in6,
|
||||||
pd_prefix_len,
|
pd_prefix_len,
|
||||||
lifetime_preferred,
|
lifetime_preferred,
|
||||||
lifetime_valid);
|
lifetime_valid,
|
||||||
|
true);
|
||||||
if (r < 0 && r != -EAGAIN)
|
if (r < 0 && r != -EAGAIN)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
if (r >= 0)
|
/* if r == -EAGAIN then the allocation failed because we ran
|
||||||
i = ITERATOR_FIRST;
|
* out of addresses for the preferred subnet id's. This doesn't
|
||||||
|
* mean we can't fullfill other prefix requests.
|
||||||
|
*
|
||||||
|
* Since we do not have dedicated lists of links that request
|
||||||
|
* specific subnet id's and those that accept any prefix we
|
||||||
|
* *must* reset the iterator to the start as otherwise some
|
||||||
|
* links might not get their requested prefix. */
|
||||||
|
|
||||||
|
r = dhcp6_pd_prefix_distribute(link, &pd_prefix.in6,
|
||||||
|
pd_prefix_len,
|
||||||
|
lifetime_preferred,
|
||||||
|
lifetime_valid,
|
||||||
|
false);
|
||||||
|
if (r < 0 && r != -EAGAIN)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* If the prefix distribution did return -EAGAIN we will try to
|
||||||
|
* fullfill those with the next available pd delegated prefix. */
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -878,3 +1004,17 @@ static int dhcp6_prefix_remove_all(Manager *m, Link *link) {
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool dhcp6_link_has_dhcpv6_prefix(Link *link) {
|
||||||
|
Iterator i;
|
||||||
|
Link *l;
|
||||||
|
|
||||||
|
assert(link);
|
||||||
|
assert(link->manager);
|
||||||
|
|
||||||
|
HASHMAP_FOREACH(l, link->manager->dhcp6_prefixes, i)
|
||||||
|
if (link == l)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -250,6 +250,7 @@ BridgeVLAN.PVID, config_parse_brvlan_pvid,
|
||||||
BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0
|
BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0
|
||||||
BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0
|
BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0
|
||||||
Network.IPv6PrefixDelegation, config_parse_router_prefix_delegation, 0, 0
|
Network.IPv6PrefixDelegation, config_parse_router_prefix_delegation, 0, 0
|
||||||
|
Network.IPv6PDSubnetId, config_parse_router_prefix_subnet_id, 0, 0
|
||||||
IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec)
|
IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec)
|
||||||
IPv6PrefixDelegation.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
|
IPv6PrefixDelegation.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
|
||||||
IPv6PrefixDelegation.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information)
|
IPv6PrefixDelegation.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information)
|
||||||
|
|
|
@ -415,6 +415,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
|
||||||
.dhcp_server_emit_router = true,
|
.dhcp_server_emit_router = true,
|
||||||
.dhcp_server_emit_timezone = true,
|
.dhcp_server_emit_timezone = true,
|
||||||
|
|
||||||
|
.router_prefix_subnet_id = -1,
|
||||||
.router_emit_dns = true,
|
.router_emit_dns = true,
|
||||||
.router_emit_domains = true,
|
.router_emit_domains = true,
|
||||||
|
|
||||||
|
|
|
@ -178,6 +178,7 @@ struct Network {
|
||||||
|
|
||||||
/* IPv6 prefix delegation support */
|
/* IPv6 prefix delegation support */
|
||||||
RADVPrefixDelegation router_prefix_delegation;
|
RADVPrefixDelegation router_prefix_delegation;
|
||||||
|
int64_t router_prefix_subnet_id;
|
||||||
usec_t router_lifetime_usec;
|
usec_t router_lifetime_usec;
|
||||||
uint8_t router_preference;
|
uint8_t router_preference;
|
||||||
bool router_managed;
|
bool router_managed;
|
||||||
|
|
|
@ -864,3 +864,40 @@ int config_parse_router_preference(const char *unit,
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int config_parse_router_prefix_subnet_id(const char *unit,
|
||||||
|
const char *filename,
|
||||||
|
unsigned line,
|
||||||
|
const char *section,
|
||||||
|
unsigned section_line,
|
||||||
|
const char *lvalue,
|
||||||
|
int ltype,
|
||||||
|
const char *rvalue,
|
||||||
|
void *data,
|
||||||
|
void *userdata) {
|
||||||
|
Network *network = userdata;
|
||||||
|
uint64_t t;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(filename);
|
||||||
|
assert(lvalue);
|
||||||
|
assert(rvalue);
|
||||||
|
assert(data);
|
||||||
|
|
||||||
|
if (isempty(rvalue) || streq(rvalue, "auto")) {
|
||||||
|
network->router_prefix_subnet_id = -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = safe_atoux64(rvalue, &t);
|
||||||
|
if (r < 0 || t > INT64_MAX) {
|
||||||
|
log_syntax(unit, LOG_ERR, filename, line, r,
|
||||||
|
"Subnet id '%s' is invalid, ignoring assignment.",
|
||||||
|
rvalue);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
network->router_prefix_subnet_id = (int64_t)t;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ RADVPrefixDelegation radv_prefix_delegation_from_string(const char *s) _pure_;
|
||||||
|
|
||||||
CONFIG_PARSER_PROTOTYPE(config_parse_router_prefix_delegation);
|
CONFIG_PARSER_PROTOTYPE(config_parse_router_prefix_delegation);
|
||||||
CONFIG_PARSER_PROTOTYPE(config_parse_router_preference);
|
CONFIG_PARSER_PROTOTYPE(config_parse_router_preference);
|
||||||
|
CONFIG_PARSER_PROTOTYPE(config_parse_router_prefix_subnet_id);
|
||||||
CONFIG_PARSER_PROTOTYPE(config_parse_prefix);
|
CONFIG_PARSER_PROTOTYPE(config_parse_prefix);
|
||||||
CONFIG_PARSER_PROTOTYPE(config_parse_prefix_flags);
|
CONFIG_PARSER_PROTOTYPE(config_parse_prefix_flags);
|
||||||
CONFIG_PARSER_PROTOTYPE(config_parse_prefix_lifetime);
|
CONFIG_PARSER_PROTOTYPE(config_parse_prefix_lifetime);
|
||||||
|
|
|
@ -345,28 +345,6 @@ static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int sync_rights(FILE *from, const char *to) {
|
|
||||||
struct stat st;
|
|
||||||
|
|
||||||
if (fstat(fileno(from), &st) < 0)
|
|
||||||
return -errno;
|
|
||||||
|
|
||||||
return chmod_and_chown_unsafe(to, st.st_mode & 07777, st.st_uid, st.st_gid);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int rename_and_apply_smack(const char *temp_path, const char *dest_path) {
|
|
||||||
int r = 0;
|
|
||||||
if (rename(temp_path, dest_path) < 0)
|
|
||||||
return -errno;
|
|
||||||
|
|
||||||
#ifdef SMACK_RUN_LABEL
|
|
||||||
r = mac_smack_apply(dest_path, SMACK_ATTR_ACCESS, SMACK_FLOOR_LABEL);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
#endif
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char* default_shell(uid_t uid) {
|
static const char* default_shell(uid_t uid) {
|
||||||
return uid == 0 ? "/bin/sh" : NOLOGIN;
|
return uid == 0 ? "/bin/sh" : NOLOGIN;
|
||||||
}
|
}
|
||||||
|
@ -389,7 +367,7 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
|
||||||
original = fopen(passwd_path, "re");
|
original = fopen(passwd_path, "re");
|
||||||
if (original) {
|
if (original) {
|
||||||
|
|
||||||
r = sync_rights(original, passwd_tmp);
|
r = sync_rights(fileno(original), fileno(passwd));
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
@ -491,7 +469,7 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
|
||||||
original = fopen(shadow_path, "re");
|
original = fopen(shadow_path, "re");
|
||||||
if (original) {
|
if (original) {
|
||||||
|
|
||||||
r = sync_rights(original, shadow_tmp);
|
r = sync_rights(fileno(original), fileno(shadow));
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
@ -588,7 +566,7 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
|
||||||
original = fopen(group_path, "re");
|
original = fopen(group_path, "re");
|
||||||
if (original) {
|
if (original) {
|
||||||
|
|
||||||
r = sync_rights(original, group_tmp);
|
r = sync_rights(fileno(original), fileno(group));
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
@ -687,7 +665,7 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
|
||||||
if (original) {
|
if (original) {
|
||||||
struct sgrp *sg;
|
struct sgrp *sg;
|
||||||
|
|
||||||
r = sync_rights(original, gshadow_tmp);
|
r = sync_rights(fileno(original), fileno(gshadow));
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
@ -794,14 +772,14 @@ static int write_files(void) {
|
||||||
|
|
||||||
/* And make the new files count */
|
/* And make the new files count */
|
||||||
if (group) {
|
if (group) {
|
||||||
r = rename_and_apply_smack(group_tmp, group_path);
|
r = rename_and_apply_smack_floor_label(group_tmp, group_path);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
group_tmp = mfree(group_tmp);
|
group_tmp = mfree(group_tmp);
|
||||||
}
|
}
|
||||||
if (gshadow) {
|
if (gshadow) {
|
||||||
r = rename_and_apply_smack(gshadow_tmp, gshadow_path);
|
r = rename_and_apply_smack_floor_label(gshadow_tmp, gshadow_path);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
@ -809,14 +787,14 @@ static int write_files(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passwd) {
|
if (passwd) {
|
||||||
r = rename_and_apply_smack(passwd_tmp, passwd_path);
|
r = rename_and_apply_smack_floor_label(passwd_tmp, passwd_path);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
passwd_tmp = mfree(passwd_tmp);
|
passwd_tmp = mfree(passwd_tmp);
|
||||||
}
|
}
|
||||||
if (shadow) {
|
if (shadow) {
|
||||||
r = rename_and_apply_smack(shadow_tmp, shadow_path);
|
r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
|
|
@ -849,7 +849,7 @@ static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_servic
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", path)) {
|
FOREACH_DIRENT(de, d, log_warning_errno(errno, "Failed to enumerate directory %s, ignoring: %m", path)) {
|
||||||
_cleanup_free_ char *name = NULL, *fpath = NULL;
|
_cleanup_free_ char *name = NULL, *fpath = NULL;
|
||||||
int a, b;
|
int a, b;
|
||||||
|
|
||||||
|
|
|
@ -1143,3 +1143,13 @@ tests += [
|
||||||
libshared],
|
libshared],
|
||||||
[threads]],
|
[threads]],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
tests += [
|
||||||
|
[['src/test/test-xdg-autostart.c',
|
||||||
|
'src/xdg-autostart-generator/xdg-autostart-service.c',
|
||||||
|
'src/xdg-autostart-generator/xdg-autostart-service.h',],
|
||||||
|
[],
|
||||||
|
[]],
|
||||||
|
]
|
||||||
|
|
|
@ -173,6 +173,45 @@ static void test_in_addr_prefix_next(void) {
|
||||||
test_in_addr_prefix_next_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", 120, NULL);
|
test_in_addr_prefix_next_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", 120, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_in_addr_prefix_nth_one(unsigned f, const char *before, unsigned pl, uint64_t nth, const char *after) {
|
||||||
|
union in_addr_union ubefore, uafter, t;
|
||||||
|
|
||||||
|
assert_se(in_addr_from_string(f, before, &ubefore) >= 0);
|
||||||
|
|
||||||
|
t = ubefore;
|
||||||
|
assert_se((in_addr_prefix_nth(f, &t, pl, nth) > 0) == !!after);
|
||||||
|
|
||||||
|
if (after) {
|
||||||
|
assert_se(in_addr_from_string(f, after, &uafter) >= 0);
|
||||||
|
assert_se(in_addr_equal(f, &t, &uafter) > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_in_addr_prefix_nth(void) {
|
||||||
|
log_info("/* %s */", __func__);
|
||||||
|
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 24, 0, "192.168.0.0");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 24, 1, "192.168.1.0");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 24, 4, "192.168.4.0");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 25, 1, "192.168.0.128");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET, "192.168.255.0", 25, 1, "192.168.255.128");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET, "192.168.255.0", 24, 0, "192.168.255.0");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET, "255.255.255.255", 32, 1, NULL);
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET, "255.255.255.255", 0, 1, NULL);
|
||||||
|
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET6, "4400::", 8, 1, "4500::");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET6, "4400::", 7, 1, "4600::");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET6, "4400::", 64, 1, "4400:0:0:1::");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET6, "4400::", 64, 2, "4400:0:0:2::");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET6, "4400::", 64, 0xbad, "4400:0:0:0bad::");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET6, "4400:0:0:ffff::", 64, 1, "4400:0:1::");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET6, "4400::", 56, ((uint64_t)1<<48) -1, "44ff:ffff:ffff:ff00::");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET6, "0000::", 8, 255, "ff00::");
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET6, "0000::", 8, 256, NULL);
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128, 1, NULL);
|
||||||
|
test_in_addr_prefix_nth_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 0, 1, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static void test_in_addr_to_string_one(int f, const char *addr) {
|
static void test_in_addr_to_string_one(int f, const char *addr) {
|
||||||
union in_addr_union ua;
|
union in_addr_union ua;
|
||||||
_cleanup_free_ char *r = NULL;
|
_cleanup_free_ char *r = NULL;
|
||||||
|
@ -691,6 +730,7 @@ int main(int argc, char *argv[]) {
|
||||||
test_in_addr_is_null();
|
test_in_addr_is_null();
|
||||||
test_in_addr_prefix_intersect();
|
test_in_addr_prefix_intersect();
|
||||||
test_in_addr_prefix_next();
|
test_in_addr_prefix_next();
|
||||||
|
test_in_addr_prefix_nth();
|
||||||
test_in_addr_to_string();
|
test_in_addr_to_string();
|
||||||
test_in_addr_ifindex_to_string();
|
test_in_addr_ifindex_to_string();
|
||||||
test_in_addr_ifindex_from_string_auto();
|
test_in_addr_ifindex_from_string_auto();
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||||
|
|
||||||
|
#include "alloc-util.h"
|
||||||
|
#include "fd-util.h"
|
||||||
|
#include "fs-util.h"
|
||||||
|
#include "string-util.h"
|
||||||
|
#include "strv.h"
|
||||||
|
#include "tests.h"
|
||||||
|
#include "tmpfile-util.h"
|
||||||
|
#include "xdg-autostart-service.h"
|
||||||
|
|
||||||
|
static void test_translate_name(void) {
|
||||||
|
_cleanup_free_ char *t;
|
||||||
|
|
||||||
|
assert_se(t = xdg_autostart_service_translate_name("a-b.blub.desktop"));
|
||||||
|
assert_se(streq(t, "app-a\\x2db.blub-autostart.service"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_xdg_format_exec_start_one(const char *exec, const char *expected) {
|
||||||
|
_cleanup_free_ char* out = NULL;
|
||||||
|
|
||||||
|
xdg_autostart_format_exec_start(exec, &out);
|
||||||
|
log_info("In: '%s', out: '%s', expected: '%s'", exec, out, expected);
|
||||||
|
assert_se(streq(out, expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_xdg_format_exec_start(void) {
|
||||||
|
test_xdg_format_exec_start_one("/bin/sleep 100", "/bin/sleep \"100\"");
|
||||||
|
|
||||||
|
/* All standardised % identifiers are stripped. */
|
||||||
|
test_xdg_format_exec_start_one("/bin/sleep %f \"%F\" %u %U %d %D\t%n %N %i %c %k %v %m", "/bin/sleep");
|
||||||
|
|
||||||
|
/* Unknown % identifier currently remain, but are escaped. */
|
||||||
|
test_xdg_format_exec_start_one("/bin/sleep %X \"%Y\"", "/bin/sleep \"%%X\" \"%%Y\"");
|
||||||
|
|
||||||
|
test_xdg_format_exec_start_one("/bin/sleep \";\\\"\"", "/bin/sleep \";\\\"\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* const xdg_desktop_file[] = {
|
||||||
|
"[Desktop Entry]\n"
|
||||||
|
"Exec\t =\t /bin/sleep 100\n" /* Whitespace Before/After = must be ignored */
|
||||||
|
"OnlyShowIn = A;B;\n"
|
||||||
|
"NotShowIn=C;;D\\\\\\;;E\n", /* "C", "", "D\;", "E" */
|
||||||
|
|
||||||
|
"[Desktop Entry]\n"
|
||||||
|
"Exec=a\n"
|
||||||
|
"Exec=b\n",
|
||||||
|
|
||||||
|
"[Desktop Entry]\n"
|
||||||
|
"Hidden=\t true\n",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void test_xdg_desktop_parse(unsigned i, const char *s) {
|
||||||
|
_cleanup_(unlink_tempfilep) char name[] = "/tmp/test-xdg-autostart-parser.XXXXXX";
|
||||||
|
_cleanup_fclose_ FILE *f = NULL;
|
||||||
|
_cleanup_(xdg_autostart_service_freep) XdgAutostartService *service = NULL;
|
||||||
|
|
||||||
|
log_info("== %s[%i] ==", __func__, i);
|
||||||
|
|
||||||
|
assert_se(fmkostemp_safe(name, "r+", &f) == 0);
|
||||||
|
assert_se(fwrite(s, strlen(s), 1, f) == 1);
|
||||||
|
rewind(f);
|
||||||
|
|
||||||
|
assert_se(service = xdg_autostart_service_parse_desktop(name));
|
||||||
|
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
assert_se(streq(service->exec_string, "/bin/sleep 100"));
|
||||||
|
assert_se(strv_equal(service->only_show_in, STRV_MAKE("A", "B")));
|
||||||
|
assert_se(strv_equal(service->not_show_in, STRV_MAKE("C", "", "D\\;", "E")));
|
||||||
|
assert_se(!service->hidden);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
/* The second entry is not permissible and will be ignored (and error logged). */
|
||||||
|
assert_se(streq(service->exec_string, "a"));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
assert_se(service->hidden);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
test_setup_logging(LOG_DEBUG);
|
||||||
|
|
||||||
|
test_translate_name();
|
||||||
|
test_xdg_format_exec_start();
|
||||||
|
|
||||||
|
for (i = 0; i < ELEMENTSOF(xdg_desktop_file); i++)
|
||||||
|
test_xdg_desktop_parse(i, xdg_desktop_file[i]);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||||
|
|
||||||
|
#include "main-func.h"
|
||||||
|
#include "strv.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This binary is intended to be run as an ExecCondition= in units generated
|
||||||
|
* by the xdg-autostart-generator. It does the appropriate checks against
|
||||||
|
* XDG_CURRENT_DESKTOP that are too advanced for simple ConditionEnvironment=
|
||||||
|
* matches.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int run(int argc, char *argv[]) {
|
||||||
|
_cleanup_strv_free_ char **only_show_in = NULL, **not_show_in = NULL, **desktops = NULL;
|
||||||
|
const char *xdg_current_desktop;
|
||||||
|
char **d;
|
||||||
|
|
||||||
|
if (argc != 3)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||||
|
"Wrong argument count. Expected the OnlyShowIn= and NotShowIn= sets, each colon separated.");
|
||||||
|
|
||||||
|
xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP");
|
||||||
|
if (xdg_current_desktop) {
|
||||||
|
desktops = strv_split(xdg_current_desktop, ":");
|
||||||
|
if (!desktops)
|
||||||
|
return log_oom();
|
||||||
|
}
|
||||||
|
|
||||||
|
only_show_in = strv_split(argv[1], ":");
|
||||||
|
not_show_in = strv_split(argv[2], ":");
|
||||||
|
if (!only_show_in || !not_show_in)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
/* Each desktop in XDG_CURRENT_DESKTOP needs to be matched in order. */
|
||||||
|
STRV_FOREACH(d, desktops) {
|
||||||
|
if (strv_contains(only_show_in, *d))
|
||||||
|
return 0;
|
||||||
|
if (strv_contains(not_show_in, *d))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* non-zero exit code when only_show_in has a proper value */
|
||||||
|
return !strv_isempty(only_show_in);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
|
|
@ -0,0 +1,116 @@
|
||||||
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "dirent-util.h"
|
||||||
|
#include "fd-util.h"
|
||||||
|
#include "generator.h"
|
||||||
|
#include "hashmap.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "main-func.h"
|
||||||
|
#include "nulstr-util.h"
|
||||||
|
#include "path-lookup.h"
|
||||||
|
#include "stat-util.h"
|
||||||
|
#include "string-util.h"
|
||||||
|
#include "strv.h"
|
||||||
|
#include "xdg-autostart-service.h"
|
||||||
|
|
||||||
|
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(xdgautostartservice_hash_ops, char, string_hash_func, string_compare_func, XdgAutostartService, xdg_autostart_service_free);
|
||||||
|
|
||||||
|
static int enumerate_xdg_autostart(Hashmap *all_services) {
|
||||||
|
_cleanup_strv_free_ char **autostart_dirs = NULL;
|
||||||
|
_cleanup_strv_free_ char **config_dirs = NULL;
|
||||||
|
_unused_ _cleanup_strv_free_ char **data_dirs = NULL;
|
||||||
|
_cleanup_free_ char *user_config_autostart_dir = NULL;
|
||||||
|
char **path;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
r = xdg_user_config_dir(&user_config_autostart_dir, "/autostart");
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
r = strv_extend(&autostart_dirs, user_config_autostart_dir);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = xdg_user_dirs(&config_dirs, &data_dirs);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
r = strv_extend_strv_concat(&autostart_dirs, config_dirs, "/autostart");
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
STRV_FOREACH(path, autostart_dirs) {
|
||||||
|
_cleanup_closedir_ DIR *d = NULL;
|
||||||
|
struct dirent *de;
|
||||||
|
|
||||||
|
d = opendir(*path);
|
||||||
|
if (!d) {
|
||||||
|
if (errno != ENOENT)
|
||||||
|
log_warning_errno(errno, "Opening %s failed, ignoring: %m", *path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FOREACH_DIRENT(de, d, log_warning_errno(errno, "Failed to enumerate directory %s, ignoring: %m", *path)) {
|
||||||
|
_cleanup_free_ char *fpath = NULL, *name = NULL;
|
||||||
|
_cleanup_(xdg_autostart_service_freep) XdgAutostartService *service = NULL;
|
||||||
|
struct stat st;
|
||||||
|
|
||||||
|
if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
|
||||||
|
log_warning_errno(errno, "stat() failed on %s/%s, ignoring: %m", *path, de->d_name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!S_ISREG(st.st_mode))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
name = xdg_autostart_service_translate_name(de->d_name);
|
||||||
|
if (!name)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
if (hashmap_contains(all_services, name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fpath = path_join(*path, de->d_name);
|
||||||
|
if (!fpath)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
service = xdg_autostart_service_parse_desktop(fpath);
|
||||||
|
if (!service)
|
||||||
|
return log_oom();
|
||||||
|
service->name = TAKE_PTR(name);
|
||||||
|
|
||||||
|
r = hashmap_put(all_services, service->name, service);
|
||||||
|
if (r < 0)
|
||||||
|
return log_oom();
|
||||||
|
TAKE_PTR(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int run(const char *dest, const char *dest_early, const char *dest_late) {
|
||||||
|
_cleanup_(hashmap_freep) Hashmap *all_services = NULL;
|
||||||
|
XdgAutostartService *service;
|
||||||
|
Iterator j;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert_se(dest_late);
|
||||||
|
|
||||||
|
all_services = hashmap_new(&xdgautostartservice_hash_ops);
|
||||||
|
if (!all_services)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
r = enumerate_xdg_autostart(all_services);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
HASHMAP_FOREACH(service, all_services, j)
|
||||||
|
(void) xdg_autostart_service_generate_unit(service, dest_late);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_MAIN_GENERATOR_FUNCTION(run);
|
|
@ -0,0 +1,618 @@
|
||||||
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "xdg-autostart-service.h"
|
||||||
|
|
||||||
|
#include "conf-parser.h"
|
||||||
|
#include "escape.h"
|
||||||
|
#include "unit-name.h"
|
||||||
|
#include "path-util.h"
|
||||||
|
#include "fd-util.h"
|
||||||
|
#include "generator.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "specifier.h"
|
||||||
|
#include "string-util.h"
|
||||||
|
#include "nulstr-util.h"
|
||||||
|
#include "strv.h"
|
||||||
|
|
||||||
|
XdgAutostartService* xdg_autostart_service_free(XdgAutostartService *s) {
|
||||||
|
if (!s)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
free(s->name);
|
||||||
|
free(s->path);
|
||||||
|
free(s->description);
|
||||||
|
|
||||||
|
free(s->type);
|
||||||
|
free(s->exec_string);
|
||||||
|
|
||||||
|
strv_free(s->only_show_in);
|
||||||
|
strv_free(s->not_show_in);
|
||||||
|
|
||||||
|
free(s->try_exec);
|
||||||
|
free(s->autostart_condition);
|
||||||
|
free(s->kde_autostart_condition);
|
||||||
|
|
||||||
|
free(s->gnome_autostart_phase);
|
||||||
|
|
||||||
|
return mfree(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *xdg_autostart_service_translate_name(const char *name) {
|
||||||
|
_cleanup_free_ char *c = NULL, *escaped = NULL;
|
||||||
|
char *res;
|
||||||
|
|
||||||
|
c = strdup(name);
|
||||||
|
if (!c)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
res = endswith(c, ".desktop");
|
||||||
|
if (res)
|
||||||
|
*res = '\0';
|
||||||
|
|
||||||
|
escaped = unit_name_escape(c);
|
||||||
|
if (!escaped)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return strjoin("app-", escaped, "-autostart.service");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xdg_config_parse_bool(
|
||||||
|
const char *unit,
|
||||||
|
const char *filename,
|
||||||
|
unsigned line,
|
||||||
|
const char *section,
|
||||||
|
unsigned section_line,
|
||||||
|
const char *lvalue,
|
||||||
|
int ltype,
|
||||||
|
const char *rvalue,
|
||||||
|
void *data,
|
||||||
|
void *userdata) {
|
||||||
|
|
||||||
|
bool *b = data;
|
||||||
|
const char *value;
|
||||||
|
|
||||||
|
assert(filename);
|
||||||
|
assert(lvalue);
|
||||||
|
assert(rvalue);
|
||||||
|
assert(data);
|
||||||
|
|
||||||
|
if (streq(rvalue, "true"))
|
||||||
|
*b = true;
|
||||||
|
else if (streq(rvalue, "false"))
|
||||||
|
*b = false;
|
||||||
|
else
|
||||||
|
return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Invalid value for boolean: %s", value);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unescapes the string in-place, returns non-zero status on error. */
|
||||||
|
static int xdg_unescape_string(
|
||||||
|
const char *unit,
|
||||||
|
const char *filename,
|
||||||
|
int line,
|
||||||
|
char *str) {
|
||||||
|
|
||||||
|
char *in;
|
||||||
|
char *out;
|
||||||
|
|
||||||
|
assert(str);
|
||||||
|
|
||||||
|
in = out = str;
|
||||||
|
|
||||||
|
for (; *in; in++, out++) {
|
||||||
|
if (*in == '\\') {
|
||||||
|
/* Move forward, and ensure it is a valid escape. */
|
||||||
|
in++;
|
||||||
|
|
||||||
|
switch (*in) {
|
||||||
|
case 's':
|
||||||
|
*out = ' ';
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
*out = '\n';
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
*out = '\t';
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
*out = '\r';
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
*out = '\\';
|
||||||
|
break;
|
||||||
|
case ';':
|
||||||
|
/* Technically only permitted for strv. */
|
||||||
|
*out = ';';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Undefined escape sequence \\%c.", *in);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = *in;
|
||||||
|
}
|
||||||
|
*out = '\0';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Note: We do not bother with unescaping the strings, hence the _raw postfix. */
|
||||||
|
static int xdg_config_parse_string(
|
||||||
|
const char *unit,
|
||||||
|
const char *filename,
|
||||||
|
unsigned line,
|
||||||
|
const char *section,
|
||||||
|
unsigned section_line,
|
||||||
|
const char *lvalue,
|
||||||
|
int ltype,
|
||||||
|
const char *rvalue,
|
||||||
|
void *data,
|
||||||
|
void *userdata) {
|
||||||
|
|
||||||
|
_cleanup_free_ char *res = NULL;
|
||||||
|
char **out = data;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(filename);
|
||||||
|
assert(lvalue);
|
||||||
|
assert(rvalue);
|
||||||
|
assert(data);
|
||||||
|
|
||||||
|
/* XDG does not allow duplicate definitions. */
|
||||||
|
if (*out) {
|
||||||
|
log_syntax(unit, LOG_ERR, filename, line, 0, "Key %s was defined multiple times, ignoring.", lvalue);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = strdup(rvalue);
|
||||||
|
if (!res)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
r = xdg_unescape_string(unit, filename, line, res);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
*out = TAKE_PTR(res);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xdg_config_parse_strv(
|
||||||
|
const char *unit,
|
||||||
|
const char *filename,
|
||||||
|
unsigned line,
|
||||||
|
const char *section,
|
||||||
|
unsigned section_line,
|
||||||
|
const char *lvalue,
|
||||||
|
int ltype,
|
||||||
|
const char *rvalue,
|
||||||
|
void *data,
|
||||||
|
void *userdata) {
|
||||||
|
|
||||||
|
char ***sv = data;
|
||||||
|
const char *start;
|
||||||
|
const char *end;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(filename);
|
||||||
|
assert(lvalue);
|
||||||
|
assert(rvalue);
|
||||||
|
assert(data);
|
||||||
|
|
||||||
|
/* XDG does not allow duplicate definitions. */
|
||||||
|
if (*sv) {
|
||||||
|
log_syntax(unit, LOG_ERR, filename, line, 0, "Key %s was defined multiple times, ignoring.", lvalue);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*sv = strv_new(NULL);
|
||||||
|
if (!*sv)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
/* We cannot use strv_split because it does not handle escaping correctly. */
|
||||||
|
start = rvalue;
|
||||||
|
|
||||||
|
for (end = start; *end; end++) {
|
||||||
|
if (*end == '\\') {
|
||||||
|
/* Move forward, and ensure it is a valid escape. */
|
||||||
|
end++;
|
||||||
|
if (strchr("sntr\\;", *end) == NULL) {
|
||||||
|
log_syntax(unit, LOG_ERR, filename, line, 0, "Undefined escape sequence \\%c.", *end);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*end == ';') {
|
||||||
|
_cleanup_free_ char *copy = NULL;
|
||||||
|
|
||||||
|
copy = strndup(start, end - start);
|
||||||
|
if (!copy)
|
||||||
|
return log_oom();
|
||||||
|
r = xdg_unescape_string(unit, filename, line, copy);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
r = strv_consume(sv, TAKE_PTR(copy));
|
||||||
|
if (r < 0)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Any trailing entry should be ignored if it is empty. */
|
||||||
|
if (end > start) {
|
||||||
|
r = strv_extend(sv, start);
|
||||||
|
if (r < 0)
|
||||||
|
return log_oom();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xdg_config_item_table_lookup(
|
||||||
|
const void *table,
|
||||||
|
const char *section,
|
||||||
|
const char *lvalue,
|
||||||
|
ConfigParserCallback *func,
|
||||||
|
int *ltype,
|
||||||
|
void **data,
|
||||||
|
void *userdata) {
|
||||||
|
|
||||||
|
assert(lvalue);
|
||||||
|
|
||||||
|
/* Ignore any keys with [] as those are translations. */
|
||||||
|
if (strchr(lvalue, '[')) {
|
||||||
|
*func = NULL;
|
||||||
|
*ltype = 0;
|
||||||
|
*data = NULL;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config_item_table_lookup(table, section, lvalue, func, ltype, data, userdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
XdgAutostartService *xdg_autostart_service_parse_desktop(const char *path) {
|
||||||
|
_cleanup_(xdg_autostart_service_freep) XdgAutostartService *service = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
service = new0(XdgAutostartService, 1);
|
||||||
|
if (!service)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
service->path = strdup(path);
|
||||||
|
if (!service->path)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
const ConfigTableItem items[] = {
|
||||||
|
{ "Desktop Entry", "Name", xdg_config_parse_string, 0, &service->description},
|
||||||
|
{ "Desktop Entry", "Exec", xdg_config_parse_string, 0, &service->exec_string},
|
||||||
|
{ "Desktop Entry", "TryExec", xdg_config_parse_string, 0, &service->try_exec},
|
||||||
|
{ "Desktop Entry", "Type", xdg_config_parse_string, 0, &service->type},
|
||||||
|
{ "Desktop Entry", "OnlyShowIn", xdg_config_parse_strv, 0, &service->only_show_in},
|
||||||
|
{ "Desktop Entry", "NotShowIn", xdg_config_parse_strv, 0, &service->not_show_in},
|
||||||
|
{ "Desktop Entry", "Hidden", xdg_config_parse_bool, 0, &service->hidden},
|
||||||
|
{ "Desktop Entry", "AutostartCondition", xdg_config_parse_string, 0, &service->autostart_condition},
|
||||||
|
{ "Desktop Entry", "X-KDE-autostart-condition", xdg_config_parse_string, 0, &service->kde_autostart_condition},
|
||||||
|
{ "Desktop Entry", "X-GNOME-Autostart-Phase", xdg_config_parse_string, 0, &service->gnome_autostart_phase},
|
||||||
|
{ "Desktop Entry", "X-systemd-skip", xdg_config_parse_bool, 0, &service->systemd_skip},
|
||||||
|
|
||||||
|
/* Common entries that we do not use currently. */
|
||||||
|
{ "Desktop Entry", "Categories", NULL, 0, NULL},
|
||||||
|
{ "Desktop Entry", "Comment", NULL, 0, NULL},
|
||||||
|
{ "Desktop Entry", "Encoding", NULL, 0, NULL},
|
||||||
|
{ "Desktop Entry", "GenericName", NULL, 0, NULL},
|
||||||
|
{ "Desktop Entry", "Icon", NULL, 0, NULL},
|
||||||
|
{ "Desktop Entry", "Keywords", NULL, 0, NULL},
|
||||||
|
{ "Desktop Entry", "NoDisplay", NULL, 0, NULL},
|
||||||
|
{ "Desktop Entry", "StartupNotify", NULL, 0, NULL},
|
||||||
|
{ "Desktop Entry", "Terminal", NULL, 0, NULL},
|
||||||
|
{ "Desktop Entry", "Version", NULL, 0, NULL},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
r = config_parse(NULL, service->path, NULL,
|
||||||
|
"Desktop Entry\0",
|
||||||
|
xdg_config_item_table_lookup, items,
|
||||||
|
CONFIG_PARSE_WARN, service);
|
||||||
|
/* If parsing failed, only hide the file so it will still mask others. */
|
||||||
|
if (r < 0) {
|
||||||
|
log_warning_errno(r, "Failed to parse %s, ignoring it", service->path);
|
||||||
|
service->hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TAKE_PTR(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
int xdg_autostart_format_exec_start(
|
||||||
|
const char *exec,
|
||||||
|
char **ret_exec_start) {
|
||||||
|
|
||||||
|
_cleanup_strv_free_ char **exec_split = NULL;
|
||||||
|
char *res;
|
||||||
|
size_t n, i;
|
||||||
|
bool first_arg;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unfortunately, there is a mismatch between systemd's idea of $PATH
|
||||||
|
* and XDGs. i.e. we need to ensure that we have an absolute path to
|
||||||
|
* support cases where $PATH has been modified from the default set.
|
||||||
|
*
|
||||||
|
* Note that this is only needed for development environments though;
|
||||||
|
* so while it is important, this should have no effect in production
|
||||||
|
* environments.
|
||||||
|
*
|
||||||
|
* To be compliant with the XDG specification, we also need to strip
|
||||||
|
* certain parameters and such. Doing so properly makes parsing the
|
||||||
|
* command line unavoidable.
|
||||||
|
*
|
||||||
|
* NOTE: Technically, XDG only specifies " as quotes, while this also
|
||||||
|
* accepts '.
|
||||||
|
*/
|
||||||
|
exec_split = strv_split_full(exec, WHITESPACE, SPLIT_QUOTES | SPLIT_RELAX);
|
||||||
|
if (!exec_split)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (strv_isempty(exec_split))
|
||||||
|
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Exec line is empty");
|
||||||
|
|
||||||
|
first_arg = true;
|
||||||
|
for (i = n = 0; exec_split[i]; i++) {
|
||||||
|
_cleanup_free_ char *c = NULL, *raw = NULL, *p = NULL, *escaped = NULL, *quoted = NULL;
|
||||||
|
|
||||||
|
r = cunescape(exec_split[i], 0, &c);
|
||||||
|
if (r < 0)
|
||||||
|
return log_debug_errno(r, "Failed to unescape '%s': %m", exec_split[i]);
|
||||||
|
|
||||||
|
if (first_arg) {
|
||||||
|
_cleanup_free_ char *executable = NULL;
|
||||||
|
|
||||||
|
/* This is the executable, find it in $PATH */
|
||||||
|
first_arg = false;
|
||||||
|
r = find_binary(c, &executable);
|
||||||
|
if (r < 0)
|
||||||
|
return log_info_errno(r, "Exec binary '%s' does not exist: %m", c);
|
||||||
|
|
||||||
|
escaped = cescape(executable);
|
||||||
|
if (!escaped)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
free(exec_split[n]);
|
||||||
|
exec_split[n++] = TAKE_PTR(escaped);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove any standardised XDG fields; we assume they never appear as
|
||||||
|
* part of another argument as that just does not make any sense as
|
||||||
|
* they can be empty (GLib will e.g. turn "%f" into an empty argument).
|
||||||
|
* Other implementations may handle this differently.
|
||||||
|
*/
|
||||||
|
if (STR_IN_SET(c,
|
||||||
|
"%f", "%F",
|
||||||
|
"%u", "%U",
|
||||||
|
"%d", "%D",
|
||||||
|
"%n", "%N",
|
||||||
|
"%i", /* Location of icon, could be implemented. */
|
||||||
|
"%c", /* Translated application name, could be implemented. */
|
||||||
|
"%k", /* Location of desktop file, could be implemented. */
|
||||||
|
"%v",
|
||||||
|
"%m"
|
||||||
|
))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* %% -> % and then % -> %% means that we correctly quote any %
|
||||||
|
* and also quote any left over (and invalid) % specifier from
|
||||||
|
* the desktop file.
|
||||||
|
*/
|
||||||
|
raw = strreplace(c, "%%", "%");
|
||||||
|
if (!raw)
|
||||||
|
return log_oom();
|
||||||
|
p = strreplace(raw, "%", "%%");
|
||||||
|
if (!p)
|
||||||
|
return log_oom();
|
||||||
|
escaped = cescape(p);
|
||||||
|
if (!escaped)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
quoted = strjoin("\"", escaped, "\"");
|
||||||
|
if (!quoted)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
free(exec_split[n]);
|
||||||
|
exec_split[n++] = TAKE_PTR(quoted);
|
||||||
|
}
|
||||||
|
for (; exec_split[n]; n++)
|
||||||
|
exec_split[n] = mfree(exec_split[n]);
|
||||||
|
|
||||||
|
res = strv_join(exec_split, " ");
|
||||||
|
if (!res)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
*ret_exec_start = res;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xdg_autostart_generate_desktop_condition(
|
||||||
|
FILE *f,
|
||||||
|
const char *test_binary,
|
||||||
|
const char *condition) {
|
||||||
|
|
||||||
|
int r;
|
||||||
|
|
||||||
|
/* Generate an ExecCondition for GNOME autostart condition */
|
||||||
|
if (!isempty(condition)) {
|
||||||
|
_cleanup_free_ char *gnome_autostart_condition_path = NULL, *e_autostart_condition = NULL;
|
||||||
|
|
||||||
|
r = find_binary(test_binary, &gnome_autostart_condition_path);
|
||||||
|
if (r < 0) {
|
||||||
|
log_full_errno(r == -ENOENT ? LOG_INFO : LOG_WARNING, r,
|
||||||
|
"%s not found: %m", test_binary);
|
||||||
|
fprintf(f, "# ExecCondition using %s skipped due to missing binary.\n", test_binary);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
e_autostart_condition = cescape(condition);
|
||||||
|
if (!e_autostart_condition)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
fprintf(f,
|
||||||
|
"ExecCondition=%s --condition \"%s\"\n",
|
||||||
|
gnome_autostart_condition_path,
|
||||||
|
e_autostart_condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int xdg_autostart_service_generate_unit(
|
||||||
|
XdgAutostartService *service,
|
||||||
|
const char *dest) {
|
||||||
|
|
||||||
|
_cleanup_free_ char *path_escaped = NULL, *exec_start = NULL, *unit = NULL;
|
||||||
|
_cleanup_fclose_ FILE *f = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(service);
|
||||||
|
|
||||||
|
/* Nothing to do for hidden services. */
|
||||||
|
if (service->hidden) {
|
||||||
|
log_info("Not generating service for XDG autostart %s, it is hidden.", service->name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (service->systemd_skip) {
|
||||||
|
log_info("Not generating service for XDG autostart %s, should be skipped by generator.", service->name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nothing to do if type is not Application. */
|
||||||
|
if (!streq_ptr(service->type, "Application")) {
|
||||||
|
log_info("Not generating service for XDG autostart %s, it is hidden.", service->name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!service->exec_string) {
|
||||||
|
log_warning("Not generating service for XDG autostart %s, it is has no Exec= line.", service->name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The TryExec key cannot be checked properly from the systemd unit,
|
||||||
|
* it is trivial to check using find_binary though.
|
||||||
|
*/
|
||||||
|
if (service->try_exec) {
|
||||||
|
r = find_binary(service->try_exec, NULL);
|
||||||
|
if (r < 0) {
|
||||||
|
log_full_errno(r == -ENOENT ? LOG_INFO : LOG_WARNING, r,
|
||||||
|
"Not generating service for XDG autostart %s, could not find TryExec= binary %s: %m",
|
||||||
|
service->name, service->try_exec);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = xdg_autostart_format_exec_start(service->exec_string, &exec_start);
|
||||||
|
if (r < 0) {
|
||||||
|
log_warning_errno(r,
|
||||||
|
"Not generating service for XDG autostart %s, error parsing Exec= line: %m",
|
||||||
|
service->name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (streq_ptr(service->gnome_autostart_phase, "EarlyInitialization")) {
|
||||||
|
log_info("Not generating service for XDG autostart %s, EarlyInitialization needs to be handled separately.",
|
||||||
|
service->name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
path_escaped = specifier_escape(service->path);
|
||||||
|
if (!path_escaped)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
unit = path_join(dest, service->name);
|
||||||
|
if (!unit)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
f = fopen(unit, "wxe");
|
||||||
|
if (!f)
|
||||||
|
return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
|
||||||
|
|
||||||
|
fprintf(f,
|
||||||
|
"# Automatically generated by systemd-xdg-autostart-generator\n\n"
|
||||||
|
"[Unit]\n"
|
||||||
|
"Documentation=man:systemd-xdg-autostart-generator(8)\n"
|
||||||
|
"SourcePath=%s\n"
|
||||||
|
"PartOf=graphical-session.target\n\n",
|
||||||
|
path_escaped);
|
||||||
|
|
||||||
|
if (service->description) {
|
||||||
|
_cleanup_free_ char *t = NULL;
|
||||||
|
|
||||||
|
t = specifier_escape(service->description);
|
||||||
|
if (!t)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
fprintf(f, "Description=%s\n", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only start after the session is ready.
|
||||||
|
* XXX: GNOME has an autostart order which we may want to support.
|
||||||
|
* It is not clear how this can be implemented reliably, which
|
||||||
|
* is why it is skipped for now. */
|
||||||
|
fprintf(f,
|
||||||
|
"After=graphical-session.target\n");
|
||||||
|
|
||||||
|
fprintf(f,
|
||||||
|
"\n[Service]\n"
|
||||||
|
"Type=simple\n"
|
||||||
|
"ExecStart=:%s\n"
|
||||||
|
"Restart=no\n"
|
||||||
|
"TimeoutSec=5s\n"
|
||||||
|
"Slice=app.slice\n",
|
||||||
|
exec_start);
|
||||||
|
|
||||||
|
/* Generate an ExecCondition to check $XDG_CURRENT_DESKTOP */
|
||||||
|
if (!strv_isempty(service->only_show_in) || !strv_isempty(service->not_show_in)) {
|
||||||
|
_cleanup_free_ char *only_show_in = NULL, *not_show_in = NULL, *e_only_show_in = NULL, *e_not_show_in = NULL;
|
||||||
|
|
||||||
|
only_show_in = strv_join(service->only_show_in, ":");
|
||||||
|
not_show_in = strv_join(service->not_show_in, ":");
|
||||||
|
if (!only_show_in || !not_show_in)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
e_only_show_in = cescape(only_show_in);
|
||||||
|
e_not_show_in = cescape(not_show_in);
|
||||||
|
if (!e_only_show_in || !e_not_show_in)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
/* Just assume the values are reasonably sane */
|
||||||
|
fprintf(f,
|
||||||
|
"ExecCondition=" ROOTLIBEXECDIR "/systemd-xdg-autostart-condition \"%s\" \"%s\"\n",
|
||||||
|
e_only_show_in,
|
||||||
|
e_not_show_in);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = xdg_autostart_generate_desktop_condition(f,
|
||||||
|
"gnome-systemd-autostart-condition",
|
||||||
|
service->autostart_condition);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = xdg_autostart_generate_desktop_condition(f,
|
||||||
|
"kde-systemd-start-condition",
|
||||||
|
service->kde_autostart_condition);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
(void) generator_add_symlink(dest, "xdg-desktop-autostart.target", "wants", service->name);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "macro.h"
|
||||||
|
|
||||||
|
typedef struct XdgAutostartService {
|
||||||
|
char *name;
|
||||||
|
char *path;
|
||||||
|
char *description; /* Name in XDG desktop file */
|
||||||
|
|
||||||
|
char *type; /* Purely as an assertion check */
|
||||||
|
char *exec_string;
|
||||||
|
|
||||||
|
char **only_show_in;
|
||||||
|
char **not_show_in;
|
||||||
|
|
||||||
|
char *try_exec;
|
||||||
|
char *autostart_condition; /* This is mostly GNOME specific */
|
||||||
|
char *kde_autostart_condition;
|
||||||
|
|
||||||
|
char *gnome_autostart_phase;
|
||||||
|
|
||||||
|
bool hidden;
|
||||||
|
bool systemd_skip;
|
||||||
|
|
||||||
|
} XdgAutostartService;
|
||||||
|
|
||||||
|
|
||||||
|
XdgAutostartService * xdg_autostart_service_free(XdgAutostartService *s);
|
||||||
|
DEFINE_TRIVIAL_CLEANUP_FUNC(XdgAutostartService*, xdg_autostart_service_free);
|
||||||
|
|
||||||
|
char *xdg_autostart_service_translate_name(const char *name);
|
||||||
|
int xdg_autostart_format_exec_start(const char *exec, char **ret_exec_start);
|
||||||
|
|
||||||
|
XdgAutostartService *xdg_autostart_service_parse_desktop(const char *path);
|
||||||
|
int xdg_autostart_service_generate_unit(XdgAutostartService *service, const char *dest);
|
|
@ -186,6 +186,7 @@ NTP=
|
||||||
DHCP=
|
DHCP=
|
||||||
Domains=
|
Domains=
|
||||||
IPv6PrefixDelegation=
|
IPv6PrefixDelegation=
|
||||||
|
IPv6PDSubnetId=
|
||||||
VLAN=
|
VLAN=
|
||||||
DHCPServer=
|
DHCPServer=
|
||||||
BindCarrier=
|
BindCarrier=
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=GNOME Settings Daemon's power plugin
|
||||||
|
Exec=/usr/bin/sleep %i %f "%F" "--test" ";\\\\!?"
|
||||||
|
OnlyShowIn=GNOME;
|
||||||
|
NoDisplay=true
|
||||||
|
X-GNOME-Autostart-Phase=Initialization
|
||||||
|
X-GNOME-Autostart-Notify=true
|
||||||
|
X-GNOME-AutoRestart=true
|
||||||
|
X-GNOME-HiddenUnderSystemd=true
|
|
@ -0,0 +1,10 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=GNOME Settings Daemon's power plugin
|
||||||
|
Exec=/usr/libexec/gsd-power
|
||||||
|
OnlyShowIn=GNOME;
|
||||||
|
NoDisplay=true
|
||||||
|
X-GNOME-Autostart-Phase=Initialization
|
||||||
|
X-GNOME-Autostart-Notify=true
|
||||||
|
X-GNOME-AutoRestart=true
|
||||||
|
X-GNOME-HiddenUnderSystemd=true
|
|
@ -0,0 +1,12 @@
|
||||||
|
Desktop Entry
|
||||||
|
Name=
|
||||||
|
Exec=
|
||||||
|
TryExec=
|
||||||
|
Type=
|
||||||
|
OnlyShowIn=
|
||||||
|
NotShowIn=
|
||||||
|
Hidden=
|
||||||
|
AutostartCondition=
|
||||||
|
X-KDE-autostart-condition=
|
||||||
|
X-GNOME-Autostart-Phase=
|
||||||
|
X-GNOME-HiddenUnderSystemd=
|
|
@ -20,6 +20,10 @@ units = [
|
||||||
'timers.target',
|
'timers.target',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if conf.get('ENABLE_XDG_AUTOSTART') == 1
|
||||||
|
units += [ 'xdg-desktop-autostart.target', ]
|
||||||
|
endif
|
||||||
|
|
||||||
foreach file : units
|
foreach file : units
|
||||||
install_data(file,
|
install_data(file,
|
||||||
install_dir : userunitdir)
|
install_dir : userunitdir)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Startup of XDG autostart applications
|
||||||
|
Documentation=man:systemd.special(7)
|
||||||
|
RefuseManualStart=yes
|
||||||
|
StopWhenUnneeded=yes
|
Loading…
Reference in New Issue