Merge pull request #30547 from poettering/uid0
add new "uid0" command as alternative multi-call interface for systemd-run, as sudo replacement
This commit is contained in:
@@ -132,6 +132,10 @@ Deprecations and removals:
|
||||
|
||||
Features:
|
||||
|
||||
* add a new ExecStart= flag that inserts the configured user's shell as first
|
||||
word in the comand line. (maybe use character '.'). Usecase: tool such as
|
||||
uid0 can use that to spawn the target user's default shell.
|
||||
|
||||
* varlink: figure out how to do docs for our varlink interfaces. Idea: install
|
||||
interface files augmented with docs in /usr/share/ somewhere. And have
|
||||
functionality in varlinkctl to merge interface info extracted from binaries
|
||||
@@ -140,14 +144,6 @@ Features:
|
||||
* introduce mntid_t, and make it 64bit, as apparently the kernel switched to
|
||||
64bit mount ids
|
||||
|
||||
* Add an alias to systemd-run maybe called "uid0" or so, which tries to mimic
|
||||
the sudo/su command lines to some level, but is backed by transient services,
|
||||
and proper security isolate/tty forwarding. This would then allow us to run
|
||||
systems with NNP turned on reasonably nicely. To make this extra nice and
|
||||
pretty, in ptyfwd rewrite every NL we pass through so that it resets the bg
|
||||
color to some reddish tone, and erase the whole coming line first, so that
|
||||
the background color indicates when operating with privileges and when not.
|
||||
|
||||
* use udev rule networkd ownership property to take ownership of network
|
||||
interfaces nspawn creates
|
||||
|
||||
|
||||
@@ -1257,6 +1257,7 @@ manpages = [
|
||||
''],
|
||||
['udev_new', '3', ['udev_ref', 'udev_unref'], ''],
|
||||
['udevadm', '8', [], ''],
|
||||
['uid0', '1', [], ''],
|
||||
['ukify', '1', [], 'ENABLE_UKIFY'],
|
||||
['user@.service',
|
||||
'5',
|
||||
|
||||
+34
-5
@@ -176,11 +176,14 @@
|
||||
<term><option>--slice-inherit</option></term>
|
||||
|
||||
<listitem><para>Make the new <filename>.service</filename> or <filename>.scope</filename> unit part
|
||||
of the inherited slice. This option can be combined with <option>--slice=</option>.</para>
|
||||
of the slice the <command>systemd-run</command> itself has been invoked in. This option may be
|
||||
combined with <option>--slice=</option>, in which case the slice specified via
|
||||
<option>--slice=</option> is placed within the slice the <command>systemd-run</command> command is
|
||||
invoked in.</para>
|
||||
|
||||
<para>An inherited slice is located within <command>systemd-run</command> slice. Example: if
|
||||
<command>systemd-run</command> slice is <filename>foo.slice</filename>, and the
|
||||
<option>--slice=</option> argument is <filename>bar</filename>, the unit will be placed under the
|
||||
<para>Example: consider <command>systemd-run</command> being invoked in the slice
|
||||
<filename>foo.slice</filename>, and the <option>--slice=</option> argument is
|
||||
<filename>bar</filename>. The unit will then be placed under
|
||||
<filename>foo-bar.slice</filename>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v246"/>
|
||||
@@ -485,6 +488,31 @@
|
||||
<xi:include href="version-info.xml" xpointer="v236"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--ignore-failure</option></term>
|
||||
|
||||
<listitem><para>By default, if the specified command fails the invoked unit will be marked failed
|
||||
(though possibly still unloaded, see <option>--collect=</option>, above), and this is reported in the
|
||||
logs. If this switch is specified this is suppressed and any non-success exit status/code of the
|
||||
command is treated as success.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--background=<replaceable>COLOR</replaceable></option></term>
|
||||
|
||||
<listitem><para>Change the terminal background color to the specified ANSI color as long as the
|
||||
session lasts. The color specified should be an ANSI X3.64 SGR background color, i.e. strings such as
|
||||
<literal>40</literal>, <literal>41</literal>, …, <literal>47</literal>, <literal>48;2;…</literal>,
|
||||
<literal>48;5;…</literal>. See <ulink
|
||||
url="https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters">ANSI
|
||||
Escape Code (Wikipedia)</ulink> for details.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="user-system-options.xml" xpointer="user" />
|
||||
<xi:include href="user-system-options.xml" xpointer="system" />
|
||||
<xi:include href="user-system-options.xml" xpointer="host" />
|
||||
@@ -677,7 +705,8 @@ $ systemd-run --user --wait -p SuccessExitStatus=SIGUSR1 --expand-environment=no
|
||||
<citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-mount</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
<citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>uid0</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
<?xml version='1.0'?>
|
||||
<!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-or-later -->
|
||||
|
||||
<refentry id="uid0"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>uid0</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>uid0</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>uid0</refname>
|
||||
<refpurpose>Elevate privileges</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>uid0</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="opt" rep="repeat">COMMAND</arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>uid0</command> may be used to temporarily and interactively acquire elavated or different
|
||||
privileges. It serves a similar purpose as <citerefentry
|
||||
project='man-pages'><refentrytitle>sudo</refentrytitle><manvolnum>8</manvolnum></citerefentry>, but
|
||||
operates differently in a couple of key areas:</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>No execution or security context credentials are inherited from the caller into the
|
||||
invoked commands, as they are invoked from a fresh, isolated service forked off the service
|
||||
manager.</para></listitem>
|
||||
|
||||
<listitem><para>Authentication takes place via <ulink
|
||||
url="https://www.freedesktop.org/wiki/Software/polkit">polkit</ulink>, thus isolating the
|
||||
authentication prompt from the terminal (if possible).</para></listitem>
|
||||
|
||||
<listitem><para>An independent pseudo-tty is allocated for the invoked command, detaching its lifecycle and
|
||||
isolating it for security.</para></listitem>
|
||||
|
||||
<listitem><para>No SetUID/SetGID file access bit functionality is used for the implementation.</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>Altogether this should provide a safer and more robust alternative to the <command>sudo</command>
|
||||
mechanism, in particular in OS environments where SetUID/SetGID support is not available (for example by
|
||||
setting the <varname>NoNewPrivileges=</varname> variable in
|
||||
<citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>).</para>
|
||||
|
||||
<para>Any session invoked via <command>uid0</command> will run through the
|
||||
<literal>systemd-uid0</literal> PAM stack.</para>
|
||||
|
||||
<para>Note that <command>uid0</command> is implemented as an alternative multi-call invocation of
|
||||
<citerefentry><refentrytitle>systemd-run</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
|
||||
<para>The following options are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--no-ask-password</option></term>
|
||||
|
||||
<listitem><para>Do not query the user for authentication for privileged operations.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--unit=</option></term>
|
||||
|
||||
<listitem><para>Use this unit name instead of an automatically generated one.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--property=</option></term>
|
||||
|
||||
<listitem><para>Sets a property on the service unit that is created. This option takes an assignment
|
||||
in the same format as
|
||||
<citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
|
||||
<command>set-property</command> command.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--description=</option></term>
|
||||
|
||||
<listitem><para>Provide a description for the service unit that is invoked. If not specified,
|
||||
the command itself will be used as a description. See <varname>Description=</varname> in
|
||||
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
|
||||
</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--slice=</option></term>
|
||||
|
||||
<listitem><para>Make the new <filename>.service</filename> unit part of the specified slice, instead
|
||||
of <filename>user.slice</filename>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--slice-inherit</option></term>
|
||||
|
||||
<listitem><para>Make the new <filename>.service</filename> unit part of the slice the
|
||||
<command>uid0</command> itself has been invoked in. This option may be combined with
|
||||
<option>--slice=</option>, in which case the slice specified via <option>--slice=</option> is placed
|
||||
within the slice the <command>uid0</command> command is invoked in.</para>
|
||||
|
||||
<para>Example: consider <command>uid0</command> being invoked in the slice
|
||||
<filename>foo.slice</filename>, and the <option>--slice=</option> argument is
|
||||
<filename>bar</filename>. The unit will then be placed under
|
||||
<filename>foo-bar.slice</filename>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--user=</option></term>
|
||||
<term><option>-u</option></term>
|
||||
<term><option>--group=</option></term>
|
||||
<term><option>-g</option></term>
|
||||
|
||||
<listitem><para>Switches to the specified user/group instead of root.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--nice=</option></term>
|
||||
|
||||
<listitem><para>Runs the invoked session with the specified nice level.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--chdir=</option></term>
|
||||
<term><option>-D</option></term>
|
||||
|
||||
<listitem><para>Runs the invoked session with the specified working directory. If not specified
|
||||
defaults to the client's current working directory if switching to the root user, or the target
|
||||
user's home directory otherwise.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--setenv=<replaceable>NAME</replaceable>[=<replaceable>VALUE</replaceable>]</option></term>
|
||||
|
||||
<listitem><para>Runs the invoked session with the specified environment variable set. This parameter
|
||||
may be used more than once to set multiple variables. When <literal>=</literal> and
|
||||
<replaceable>VALUE</replaceable> are omitted, the value of the variable with the same name in the
|
||||
invoking environment will be used.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--background=<replaceable>COLOR</replaceable></option></term>
|
||||
|
||||
<listitem><para>Change the terminal background color to the specified ANSI color as long as the
|
||||
session lasts. If not specified, the background will be tinted in a reddish tone when operating as
|
||||
root, and in a yellowish tone when operating under another UID, as reminder of the changed
|
||||
privileges. The color specified should be an ANSI X3.64 SGR background color, i.e. strings such as
|
||||
<literal>40</literal>, <literal>41</literal>, …, <literal>47</literal>, <literal>48;2;…</literal>,
|
||||
<literal>48;5;…</literal>. See <ulink
|
||||
url="https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters">ANSI
|
||||
Escape Code (Wikipedia)</ulink> for details. Set to an empty string to disable.</para>
|
||||
|
||||
<para>Example: <literal>--background=44</literal> for a blue background.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="user-system-options.xml" xpointer="machine" />
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
</variablelist>
|
||||
|
||||
<para>All command line arguments after the first non-option argument become part of the command line of
|
||||
the launched process. If no command line is specified an interactive shell is invoked. The shell to
|
||||
invoke may be controlled via <option>--setenv=SHELL=…</option> and currently defaults to the
|
||||
<emphasis>originating user's</emphasis> shell (i.e. not the target user's!) if operating locally, or
|
||||
<filename>/bin/sh</filename> when operating with <option>--machine=</option>.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Exit status</title>
|
||||
|
||||
<para>On success, 0 is returned. If <command>uid0</command> failed to start the session or the specified command fails, a
|
||||
non-zero return value will be returned.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-run</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>sudo</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
||||
@@ -70,7 +70,7 @@ if [ ! -f "$BUILDDIR"/build.ninja ]; then
|
||||
-D tests=unsafe
|
||||
-D slow-tests="${SLOW_TESTS:-false}"
|
||||
-D create-log-dirs=false
|
||||
-D pamconfdir=no
|
||||
-D pamconfdir=/usr/lib/pam.d/
|
||||
-D utmp=true
|
||||
-D hibernate=true
|
||||
-D ldconfig=true
|
||||
|
||||
@@ -458,6 +458,35 @@ int strv_env_assign(char ***l, const char *key, const char *value) {
|
||||
return strv_env_replace_consume(l, p);
|
||||
}
|
||||
|
||||
int strv_env_assignf(char ***l, const char *key, const char *valuef, ...) {
|
||||
int r;
|
||||
|
||||
assert(l);
|
||||
assert(key);
|
||||
|
||||
if (!env_name_is_valid(key))
|
||||
return -EINVAL;
|
||||
|
||||
if (!valuef) {
|
||||
strv_env_unset(*l, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *value = NULL;
|
||||
va_list ap;
|
||||
va_start(ap, valuef);
|
||||
r = vasprintf(&value, valuef, ap);
|
||||
va_end(ap);
|
||||
if (r < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
char *p = strjoin(key, "=", value);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
return strv_env_replace_consume(l, p);
|
||||
}
|
||||
|
||||
int _strv_env_assign_many(char ***l, ...) {
|
||||
va_list ap;
|
||||
int r;
|
||||
|
||||
@@ -49,6 +49,7 @@ int strv_env_replace_consume(char ***l, char *p); /* In place ... */
|
||||
int strv_env_replace_strdup(char ***l, const char *assignment);
|
||||
int strv_env_replace_strdup_passthrough(char ***l, const char *assignment);
|
||||
int strv_env_assign(char ***l, const char *key, const char *value);
|
||||
int strv_env_assignf(char ***l, const char *key, const char *valuef, ...) _printf_(3, 4);
|
||||
int _strv_env_assign_many(char ***l, ...) _sentinel_;
|
||||
#define strv_env_assign_many(l, ...) _strv_env_assign_many(l, __VA_ARGS__, NULL)
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "inotify-util.h"
|
||||
#include "io-util.h"
|
||||
#include "log.h"
|
||||
@@ -1551,3 +1552,218 @@ int set_terminal_cursor_position(int fd, unsigned int row, unsigned int column)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void termios_disable_echo(struct termios *termios) {
|
||||
assert(termios);
|
||||
|
||||
termios->c_lflag &= ~(ICANON|ECHO);
|
||||
termios->c_cc[VMIN] = 1;
|
||||
termios->c_cc[VTIME] = 0;
|
||||
}
|
||||
|
||||
typedef enum BackgroundColorState {
|
||||
BACKGROUND_TEXT,
|
||||
BACKGROUND_ESCAPE,
|
||||
BACKGROUND_BRACKET,
|
||||
BACKGROUND_FIRST_ONE,
|
||||
BACKGROUND_SECOND_ONE,
|
||||
BACKGROUND_SEMICOLON,
|
||||
BACKGROUND_R,
|
||||
BACKGROUND_G,
|
||||
BACKGROUND_B,
|
||||
BACKGROUND_RED,
|
||||
BACKGROUND_GREEN,
|
||||
BACKGROUND_BLUE,
|
||||
} BackgroundColorState;
|
||||
|
||||
typedef struct BackgroundColorContext {
|
||||
BackgroundColorState state;
|
||||
uint32_t red, green, blue;
|
||||
unsigned red_bits, green_bits, blue_bits;
|
||||
} BackgroundColorContext;
|
||||
|
||||
static int scan_background_color_response(
|
||||
BackgroundColorContext *context,
|
||||
const char *buf,
|
||||
size_t size) {
|
||||
|
||||
assert(context);
|
||||
assert(buf || size == 0);
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
char c = buf[i];
|
||||
|
||||
switch (context->state) {
|
||||
|
||||
case BACKGROUND_TEXT:
|
||||
context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
|
||||
break;
|
||||
|
||||
case BACKGROUND_ESCAPE:
|
||||
context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT;
|
||||
break;
|
||||
|
||||
case BACKGROUND_BRACKET:
|
||||
context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT;
|
||||
break;
|
||||
|
||||
case BACKGROUND_FIRST_ONE:
|
||||
context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT;
|
||||
break;
|
||||
|
||||
case BACKGROUND_SECOND_ONE:
|
||||
context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT;
|
||||
break;
|
||||
|
||||
case BACKGROUND_SEMICOLON:
|
||||
context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT;
|
||||
break;
|
||||
|
||||
case BACKGROUND_R:
|
||||
context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT;
|
||||
break;
|
||||
|
||||
case BACKGROUND_G:
|
||||
context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT;
|
||||
break;
|
||||
|
||||
case BACKGROUND_B:
|
||||
context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT;
|
||||
break;
|
||||
|
||||
case BACKGROUND_RED:
|
||||
if (c == '/')
|
||||
context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT;
|
||||
else {
|
||||
int d = unhexchar(c);
|
||||
if (d < 0 || context->red_bits >= sizeof(context->red)*8)
|
||||
context->state = BACKGROUND_TEXT;
|
||||
else {
|
||||
context->red = (context->red << 4) | d;
|
||||
context->red_bits += 4;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BACKGROUND_GREEN:
|
||||
if (c == '/')
|
||||
context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT;
|
||||
else {
|
||||
int d = unhexchar(c);
|
||||
if (d < 0 || context->green_bits >= sizeof(context->green)*8)
|
||||
context->state = BACKGROUND_TEXT;
|
||||
else {
|
||||
context->green = (context->green << 4) | d;
|
||||
context->green_bits += 4;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BACKGROUND_BLUE:
|
||||
if (c == '\x07') {
|
||||
if (context->blue_bits > 0)
|
||||
return 1; /* success! */
|
||||
|
||||
context->state = BACKGROUND_TEXT;
|
||||
} else {
|
||||
int d = unhexchar(c);
|
||||
if (d < 0 || context->blue_bits >= sizeof(context->blue)*8)
|
||||
context->state = BACKGROUND_TEXT;
|
||||
else {
|
||||
context->blue = (context->blue << 4) | d;
|
||||
context->blue_bits += 4;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Reset any colors we might have picked up */
|
||||
if (context->state == BACKGROUND_TEXT) {
|
||||
/* reset color */
|
||||
context->red = context->green = context->blue = 0;
|
||||
context->red_bits = context->green_bits = context->blue_bits = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0; /* all good, but not enough data yet */
|
||||
}
|
||||
|
||||
int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) {
|
||||
int r;
|
||||
|
||||
assert(ret_red);
|
||||
assert(ret_green);
|
||||
assert(ret_blue);
|
||||
|
||||
if (!colors_enabled())
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (isatty(STDOUT_FILENO) < 1 || isatty(STDIN_FILENO) < 1)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (streq_ptr(getenv("TERM"), "linux")) {
|
||||
/* Linux console is black */
|
||||
*ret_red = *ret_green = *ret_blue = 0.0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct termios old_termios;
|
||||
if (tcgetattr(STDIN_FILENO, &old_termios) < 0)
|
||||
return -errno;
|
||||
|
||||
struct termios new_termios = old_termios;
|
||||
termios_disable_echo(&new_termios);
|
||||
|
||||
if (tcsetattr(STDOUT_FILENO, TCSADRAIN, &new_termios) < 0)
|
||||
return -errno;
|
||||
|
||||
r = loop_write(STDOUT_FILENO, "\x1B]11;?\x07", SIZE_MAX);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
usec_t end = usec_add(now(CLOCK_MONOTONIC), 100 * USEC_PER_MSEC);
|
||||
char buf[256];
|
||||
size_t buf_full = 0;
|
||||
BackgroundColorContext context = {};
|
||||
|
||||
for (;;) {
|
||||
usec_t n = now(CLOCK_MONOTONIC);
|
||||
|
||||
if (n >= end) {
|
||||
r = -EOPNOTSUPP;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = fd_wait_for_event(STDIN_FILENO, POLLIN, usec_sub_unsigned(end, n));
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
ssize_t l;
|
||||
l = read(STDIN_FILENO, buf, sizeof(buf) - buf_full);
|
||||
if (l < 0) {
|
||||
r = -errno;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
buf_full += l;
|
||||
assert(buf_full <= sizeof(buf));
|
||||
|
||||
r = scan_background_color_response(&context, buf, buf_full);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
if (r > 0) {
|
||||
assert(context.red_bits > 0);
|
||||
*ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1);
|
||||
assert(context.green_bits > 0);
|
||||
*ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1);
|
||||
assert(context.blue_bits > 0);
|
||||
*ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1);
|
||||
r = 0;
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
||||
finish:
|
||||
(void) tcsetattr(STDOUT_FILENO, TCSADRAIN, &old_termios);
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <stdio.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/types.h>
|
||||
#include <termios.h>
|
||||
|
||||
#include "macro.h"
|
||||
#include "time-util.h"
|
||||
@@ -168,7 +169,6 @@ bool underline_enabled(void);
|
||||
bool dev_console_colors_enabled(void);
|
||||
|
||||
static inline bool colors_enabled(void) {
|
||||
|
||||
/* Returns true if colors are considered supported on our stdout. */
|
||||
return get_color_mode() != COLOR_OFF;
|
||||
}
|
||||
@@ -286,3 +286,7 @@ static inline const char* ansi_highlight_green_red(bool b) {
|
||||
|
||||
/* This assumes there is a 'tty' group */
|
||||
#define TTY_MODE 0620
|
||||
|
||||
void termios_disable_echo(struct termios *termios);
|
||||
|
||||
int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue);
|
||||
|
||||
+1
-34
@@ -11,6 +11,7 @@
|
||||
#include "blockdev-util.h"
|
||||
#include "build.h"
|
||||
#include "chase.h"
|
||||
#include "color-util.h"
|
||||
#include "conf-files.h"
|
||||
#include "efi-api.h"
|
||||
#include "env-util.h"
|
||||
@@ -1932,40 +1933,6 @@ static int event_log_map_components(EventLog *el) {
|
||||
return event_log_validate_fully_recognized(el);
|
||||
}
|
||||
|
||||
static void hsv_to_rgb(
|
||||
double h, double s, double v,
|
||||
uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b) {
|
||||
|
||||
double c, x, m, r, g, b;
|
||||
|
||||
assert(s >= 0 && s <= 100);
|
||||
assert(v >= 0 && v <= 100);
|
||||
assert(ret_r);
|
||||
assert(ret_g);
|
||||
assert(ret_b);
|
||||
|
||||
c = (s / 100.0) * (v / 100.0);
|
||||
x = c * (1 - fabs(fmod(h / 60.0, 2) - 1));
|
||||
m = (v / 100) - c;
|
||||
|
||||
if (h >= 0 && h < 60)
|
||||
r = c, g = x, b = 0.0;
|
||||
else if (h >= 60 && h < 120)
|
||||
r = x, g = c, b = 0.0;
|
||||
else if (h >= 120 && h < 180)
|
||||
r = 0.0, g = c, b = x;
|
||||
else if (h >= 180 && h < 240)
|
||||
r = 0.0, g = x, b = c;
|
||||
else if (h >= 240 && h < 300)
|
||||
r = x, g = 0.0, b = c;
|
||||
else
|
||||
r = c, g = 0.0, b = x;
|
||||
|
||||
*ret_r = (uint8_t) ((r + m) * 255);
|
||||
*ret_g = (uint8_t) ((g + m) * 255);
|
||||
*ret_b = (uint8_t) ((b + m) * 255);
|
||||
}
|
||||
|
||||
#define ANSI_TRUE_COLOR_MAX (7U + 3U + 1U + 3U + 1U + 3U + 2U)
|
||||
|
||||
static const char *ansi_true_color(uint8_t r, uint8_t g, uint8_t b, char ret[static ANSI_TRUE_COLOR_MAX]) {
|
||||
|
||||
@@ -7,3 +7,17 @@ executables += [
|
||||
'sources' : files('run.c'),
|
||||
},
|
||||
]
|
||||
|
||||
install_emptydir(bindir)
|
||||
|
||||
meson.add_install_script(sh, '-c',
|
||||
ln_s.format(bindir / 'systemd-run',
|
||||
bindir / 'uid0'))
|
||||
|
||||
custom_target(
|
||||
'systemd-uid0',
|
||||
input : 'systemd-uid0.in',
|
||||
output : 'systemd-uid0',
|
||||
command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
|
||||
install : pamconfdir != 'no',
|
||||
install_dir : pamconfdir)
|
||||
|
||||
+336
-6
@@ -17,6 +17,7 @@
|
||||
#include "bus-unit-util.h"
|
||||
#include "bus-wait-for-jobs.h"
|
||||
#include "calendarspec.h"
|
||||
#include "color-util.h"
|
||||
#include "env-util.h"
|
||||
#include "escape.h"
|
||||
#include "exit-status.h"
|
||||
@@ -73,6 +74,9 @@ static bool arg_aggressive_gc = false;
|
||||
static char *arg_working_directory = NULL;
|
||||
static bool arg_shell = false;
|
||||
static char **arg_cmdline = NULL;
|
||||
static char *arg_exec_path = NULL;
|
||||
static bool arg_ignore_failure = false;
|
||||
static char *arg_background = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_description, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_environment, strv_freep);
|
||||
@@ -82,6 +86,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_socket_property, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_timer_property, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_working_directory, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_cmdline, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_exec_path, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
@@ -104,7 +110,7 @@ static int help(void) {
|
||||
" -p --property=NAME=VALUE Set service or scope unit property\n"
|
||||
" --description=TEXT Description for unit\n"
|
||||
" --slice=SLICE Run in the specified slice\n"
|
||||
" --slice-inherit Inherit the slice\n"
|
||||
" --slice-inherit Inherit the slice from the caller\n"
|
||||
" --expand-environment=BOOL Control expansion of environment variables\n"
|
||||
" --no-block Do not wait until operation finished\n"
|
||||
" -r --remain-after-exit Leave service around until explicitly stopped\n"
|
||||
@@ -123,6 +129,8 @@ static int help(void) {
|
||||
" -q --quiet Suppress information messages during runtime\n"
|
||||
" -G --collect Unload unit after it ran, even when failed\n"
|
||||
" -S --shell Invoke a $SHELL interactively\n"
|
||||
" --ignore-failure Ignore the exit status of the invoked process\n"
|
||||
" --background=COLOR Set ANSI color for background\n"
|
||||
"\n%3$sPath options:%4$s\n"
|
||||
" --path-property=NAME=VALUE Set path unit property\n"
|
||||
"\n%3$sSocket options:%4$s\n"
|
||||
@@ -146,6 +154,40 @@ static int help(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int help_sudo_mode(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("uid0", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%s [OPTIONS...] COMMAND [ARGUMENTS...]\n"
|
||||
"\n%sElevate privileges interactively.%s\n\n"
|
||||
" -h --help Show this help\n"
|
||||
" -V --version Show package version\n"
|
||||
" --no-ask-password Do not prompt for password\n"
|
||||
" --machine=CONTAINER Operate on local container\n"
|
||||
" --unit=UNIT Run under the specified unit name\n"
|
||||
" --property=NAME=VALUE Set service or scope unit property\n"
|
||||
" --description=TEXT Description for unit\n"
|
||||
" --slice=SLICE Run in the specified slice\n"
|
||||
" --slice-inherit Inherit the slice\n"
|
||||
" -u --user=USER Run as system user\n"
|
||||
" -g --group=GROUP Run as system group\n"
|
||||
" --nice=NICE Nice level\n"
|
||||
" -D --chdir=PATH Set working directory\n"
|
||||
" --setenv=NAME[=VALUE] Set environment variable\n"
|
||||
" --background=COLOR Set ANSI color for background\n"
|
||||
"\nSee the %s for details.\n",
|
||||
program_invocation_short_name,
|
||||
ansi_highlight(),
|
||||
ansi_normal(),
|
||||
link);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_timer_property(const char *name, const char *val) {
|
||||
char *p;
|
||||
|
||||
@@ -162,6 +204,18 @@ static int add_timer_property(const char *name, const char *val) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char **make_login_shell_cmdline(const char *shell) {
|
||||
_cleanup_free_ char *argv0 = NULL;
|
||||
|
||||
assert(shell);
|
||||
|
||||
argv0 = strjoin("-", shell); /* The - is how shells determine if they shall be consider login shells */
|
||||
if (!argv0)
|
||||
return NULL;
|
||||
|
||||
return strv_new(argv0);
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
@@ -194,6 +248,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_WAIT,
|
||||
ARG_WORKING_DIRECTORY,
|
||||
ARG_SHELL,
|
||||
ARG_IGNORE_FAILURE,
|
||||
ARG_BACKGROUND,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@@ -239,6 +295,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY },
|
||||
{ "same-dir", no_argument, NULL, 'd' },
|
||||
{ "shell", no_argument, NULL, 'S' },
|
||||
{ "ignore-failure", no_argument, NULL, ARG_IGNORE_FAILURE },
|
||||
{ "background", no_argument, NULL, ARG_BACKGROUND },
|
||||
{},
|
||||
};
|
||||
|
||||
@@ -282,7 +340,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
case ARG_DESCRIPTION:
|
||||
r = free_and_strdup(&arg_description, optarg);
|
||||
r = free_and_strdup_warn(&arg_description, optarg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
@@ -524,6 +582,16 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
arg_shell = true;
|
||||
break;
|
||||
|
||||
case ARG_IGNORE_FAILURE:
|
||||
arg_ignore_failure = true;
|
||||
break;
|
||||
|
||||
case ARG_BACKGROUND:
|
||||
r = free_and_strdup_warn(&arg_background, optarg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
@@ -651,6 +719,259 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_NO_ASK_PASSWORD = 0x100,
|
||||
ARG_HOST,
|
||||
ARG_MACHINE,
|
||||
ARG_UNIT,
|
||||
ARG_PROPERTY,
|
||||
ARG_DESCRIPTION,
|
||||
ARG_SLICE,
|
||||
ARG_SLICE_INHERIT,
|
||||
ARG_NICE,
|
||||
ARG_SETENV,
|
||||
ARG_BACKGROUND,
|
||||
};
|
||||
|
||||
/* If invoked as "uid0" binary, let's expose a more sudo-like interface. We add various extensions
|
||||
* though (but limit the extension to long options). */
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, 'V' },
|
||||
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
|
||||
{ "machine", required_argument, NULL, ARG_MACHINE },
|
||||
{ "unit", required_argument, NULL, ARG_UNIT },
|
||||
{ "property", required_argument, NULL, ARG_PROPERTY },
|
||||
{ "description", required_argument, NULL, ARG_DESCRIPTION },
|
||||
{ "slice", required_argument, NULL, ARG_SLICE },
|
||||
{ "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT },
|
||||
{ "user", required_argument, NULL, 'u' },
|
||||
{ "group", required_argument, NULL, 'g' },
|
||||
{ "nice", required_argument, NULL, ARG_NICE },
|
||||
{ "chdir", required_argument, NULL, 'D' },
|
||||
{ "setenv", required_argument, NULL, ARG_SETENV },
|
||||
{ "background", required_argument, NULL, ARG_BACKGROUND },
|
||||
{},
|
||||
};
|
||||
|
||||
int r, c;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
/* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
|
||||
* that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
|
||||
optind = 0;
|
||||
while ((c = getopt_long(argc, argv, "+hVu:g:D:", options, NULL)) >= 0)
|
||||
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
return help_sudo_mode();
|
||||
|
||||
case 'V':
|
||||
return version();
|
||||
|
||||
case ARG_NO_ASK_PASSWORD:
|
||||
arg_ask_password = false;
|
||||
break;
|
||||
|
||||
case ARG_MACHINE:
|
||||
arg_transport = BUS_TRANSPORT_MACHINE;
|
||||
arg_host = optarg;
|
||||
break;
|
||||
|
||||
case ARG_UNIT:
|
||||
arg_unit = optarg;
|
||||
break;
|
||||
|
||||
case ARG_PROPERTY:
|
||||
if (strv_extend(&arg_property, optarg) < 0)
|
||||
return log_oom();
|
||||
|
||||
break;
|
||||
|
||||
case ARG_DESCRIPTION:
|
||||
r = free_and_strdup_warn(&arg_description, optarg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_SLICE:
|
||||
arg_slice = optarg;
|
||||
break;
|
||||
|
||||
case ARG_SLICE_INHERIT:
|
||||
arg_slice_inherit = true;
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
arg_exec_user = optarg;
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
arg_exec_group = optarg;
|
||||
break;
|
||||
|
||||
case ARG_NICE:
|
||||
r = parse_nice(optarg, &arg_nice);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse nice value: %s", optarg);
|
||||
|
||||
arg_nice_set = true;
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
r = parse_path_argument(optarg, true, &arg_working_directory);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_SETENV:
|
||||
r = strv_env_replace_strdup_passthrough(&arg_environment, optarg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case ARG_BACKGROUND:
|
||||
r = free_and_strdup_warn(&arg_background, optarg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
if (!arg_working_directory) {
|
||||
if (arg_exec_user) {
|
||||
/* When switching to a specific user, also switch to its home directory. */
|
||||
arg_working_directory = strdup("~");
|
||||
if (!arg_working_directory)
|
||||
return log_oom();
|
||||
} else {
|
||||
/* When switching to root without this being specified, then stay in the current directory */
|
||||
r = safe_getcwd(&arg_working_directory);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get current working directory: %m");
|
||||
}
|
||||
}
|
||||
|
||||
arg_service_type = "exec";
|
||||
arg_quiet = true;
|
||||
arg_wait = true;
|
||||
arg_aggressive_gc = true;
|
||||
|
||||
arg_stdio = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) ? ARG_STDIO_PTY : ARG_STDIO_DIRECT;
|
||||
arg_expand_environment = false;
|
||||
arg_send_sighup = true;
|
||||
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
if (argc > optind)
|
||||
l = strv_copy(argv + optind);
|
||||
else {
|
||||
const char *e;
|
||||
|
||||
e = strv_env_get(arg_environment, "SHELL");
|
||||
if (e)
|
||||
arg_exec_path = strdup(e);
|
||||
else {
|
||||
if (arg_transport == BUS_TRANSPORT_LOCAL) {
|
||||
r = get_shell(&arg_exec_path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine shell: %m");
|
||||
} else
|
||||
arg_exec_path = strdup("/bin/sh");
|
||||
}
|
||||
if (!arg_exec_path)
|
||||
return log_oom();
|
||||
|
||||
l = make_login_shell_cmdline(arg_exec_path);
|
||||
}
|
||||
if (!l)
|
||||
return log_oom();
|
||||
|
||||
strv_free_and_replace(arg_cmdline, l);
|
||||
|
||||
if (!arg_slice) {
|
||||
arg_slice = strdup("user.slice");
|
||||
if (!arg_slice)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
_cleanup_free_ char *un = NULL;
|
||||
un = getusername_malloc();
|
||||
if (!un)
|
||||
return log_oom();
|
||||
|
||||
/* Set a bunch of environment variables in a roughly sudo-compatible way */
|
||||
r = strv_env_assign(&arg_environment, "SUDO_USER", un);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set $SUDO_USER environment variable: %m");
|
||||
|
||||
r = strv_env_assignf(&arg_environment, "SUDO_UID", UID_FMT, getuid());
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set $SUDO_UID environment variable: %m");
|
||||
|
||||
r = strv_env_assignf(&arg_environment, "SUDO_GID", GID_FMT, getgid());
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set $SUDO_GID environment variable: %m");
|
||||
|
||||
if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_UID=" UID_FMT, getuid()) < 0)
|
||||
return log_oom();
|
||||
|
||||
if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_GID=" GID_FMT, getgid()) < 0)
|
||||
return log_oom();
|
||||
|
||||
if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_USER=%s", un) < 0)
|
||||
return log_oom();
|
||||
|
||||
if (strv_extend(&arg_property, "PAMName=systemd-uid0") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (!arg_background && arg_stdio == ARG_STDIO_PTY) {
|
||||
double red, green, blue;
|
||||
|
||||
r = get_default_background_color(&red, &green, &blue);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Unable to get terminal background color, not tinting background: %m");
|
||||
else {
|
||||
double h, s, v;
|
||||
|
||||
rgb_to_hsv(red, green, blue, &h, &s, &v);
|
||||
|
||||
if (!arg_exec_user || STR_IN_SET(arg_exec_user, "root", "0"))
|
||||
h = 0; /* red */
|
||||
else
|
||||
h = 60 /* yellow */;
|
||||
|
||||
if (v > 50) /* If the background is bright, then pull down saturation */
|
||||
s = 25;
|
||||
else /* otherwise pump it up */
|
||||
s = 75;
|
||||
|
||||
v = MAX(30, v); /* Make sure we don't hide the color in black */
|
||||
|
||||
uint8_t r8, g8, b8;
|
||||
hsv_to_rgb(h, s, v, &r8, &g8, &b8);
|
||||
|
||||
if (asprintf(&arg_background, "48;2;%u;%u;%u", r8, g8, b8) < 0)
|
||||
return log_oom();
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int transient_unit_set_properties(sd_bus_message *m, UnitType t, char **properties) {
|
||||
int r;
|
||||
|
||||
@@ -899,7 +1220,7 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(m, "s", arg_cmdline[0]);
|
||||
r = sd_bus_message_append(m, "s", arg_exec_path ?: arg_cmdline[0]);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
@@ -910,9 +1231,10 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
|
||||
if (use_ex_prop)
|
||||
r = sd_bus_message_append_strv(
|
||||
m,
|
||||
STRV_MAKE(arg_expand_environment > 0 ? NULL : "no-env-expand"));
|
||||
STRV_MAKE(arg_expand_environment > 0 ? NULL : "no-env-expand",
|
||||
arg_ignore_failure ? "ignore-failure" : NULL));
|
||||
else
|
||||
r = sd_bus_message_append(m, "b", false);
|
||||
r = sd_bus_message_append(m, "b", arg_ignore_failure);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
@@ -1432,6 +1754,9 @@ static int start_transient_service(sd_bus *bus) {
|
||||
|
||||
/* Make sure to process any TTY events before we process bus events */
|
||||
(void) pty_forward_set_priority(c.forward, SD_EVENT_PRIORITY_IMPORTANT);
|
||||
|
||||
if (!isempty(arg_background))
|
||||
(void) pty_forward_set_background_color(c.forward, arg_background);
|
||||
}
|
||||
|
||||
path = unit_dbus_path_from_name(service);
|
||||
@@ -1899,6 +2224,8 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) {
|
||||
}
|
||||
|
||||
static bool shall_make_executable_absolute(void) {
|
||||
if (arg_exec_path)
|
||||
return false;
|
||||
if (strv_isempty(arg_cmdline))
|
||||
return false;
|
||||
if (arg_transport != BUS_TRANSPORT_LOCAL)
|
||||
@@ -1919,7 +2246,10 @@ static int run(int argc, char* argv[]) {
|
||||
log_parse_environment();
|
||||
log_open();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (invoked_as(argv, "uid0"))
|
||||
r = parse_argv_sudo_mode(argc, argv);
|
||||
else
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# Used by uid0 sessions
|
||||
|
||||
{% if ENABLE_HOMED %}
|
||||
-account sufficient pam_systemd_home.so
|
||||
{% endif %}
|
||||
account required pam_unix.so
|
||||
|
||||
{% if HAVE_SELINUX %}
|
||||
session required pam_selinux.so close
|
||||
session required pam_selinux.so open
|
||||
{% endif %}
|
||||
session required pam_loginuid.so
|
||||
session optional pam_keyinit.so force revoke
|
||||
session required pam_namespace.so
|
||||
{% if ENABLE_HOMED %}
|
||||
-session optional pam_systemd_home.so
|
||||
{% endif %}
|
||||
session optional pam_umask.so silent
|
||||
session optional pam_systemd.so
|
||||
session required pam_unix.so
|
||||
@@ -443,9 +443,7 @@ int ask_password_tty(
|
||||
(void) loop_write(ttyfd, ANSI_NORMAL, SIZE_MAX);
|
||||
|
||||
new_termios = old_termios;
|
||||
new_termios.c_lflag &= ~(ICANON|ECHO);
|
||||
new_termios.c_cc[VMIN] = 1;
|
||||
new_termios.c_cc[VTIME] = 0;
|
||||
termios_disable_echo(&new_termios);
|
||||
|
||||
r = RET_NERRNO(tcsetattr(ttyfd, TCSADRAIN, &new_termios));
|
||||
if (r < 0)
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "color-util.h"
|
||||
#include "macro.h"
|
||||
|
||||
void rgb_to_hsv(double r, double g, double b,
|
||||
double *ret_h, double *ret_s, double *ret_v) {
|
||||
|
||||
assert(r >= 0 && r <= 1);
|
||||
assert(g >= 0 && g <= 1);
|
||||
assert(b >= 0 && b <= 1);
|
||||
assert(ret_h);
|
||||
assert(ret_s);
|
||||
assert(ret_v);
|
||||
|
||||
double max_color = fmax(r, fmax(g, b));
|
||||
double min_color = fmin(r, fmin(g, b));
|
||||
double delta = max_color - min_color;
|
||||
|
||||
*ret_v = max_color * 100.0;
|
||||
|
||||
if (max_color > 0)
|
||||
*ret_s = delta / max_color * 100.0;
|
||||
else {
|
||||
*ret_s = 0;
|
||||
*ret_h = NAN;
|
||||
return;
|
||||
}
|
||||
|
||||
if (delta > 0) {
|
||||
if (r >= max_color)
|
||||
*ret_h = 60 * fmod((g - b) / delta, 6);
|
||||
else if (g >= max_color)
|
||||
*ret_h = 60 * (((b - r) / delta) + 2);
|
||||
else if (b >= max_color)
|
||||
*ret_h = 60 * (((r - g) / delta) + 4);
|
||||
|
||||
*ret_h = fmod(*ret_h, 360);
|
||||
} else
|
||||
*ret_h = NAN;
|
||||
}
|
||||
|
||||
void hsv_to_rgb(double h, double s, double v,
|
||||
uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b) {
|
||||
|
||||
double c, x, m, r, g, b;
|
||||
|
||||
assert(s >= 0 && s <= 100);
|
||||
assert(v >= 0 && v <= 100);
|
||||
assert(ret_r);
|
||||
assert(ret_g);
|
||||
assert(ret_b);
|
||||
|
||||
h = fmod(h, 360);
|
||||
c = (s / 100.0) * (v / 100.0);
|
||||
x = c * (1 - fabs(fmod(h / 60.0, 2) - 1));
|
||||
m = (v / 100) - c;
|
||||
|
||||
if (h >= 0 && h < 60)
|
||||
r = c, g = x, b = 0.0;
|
||||
else if (h >= 60 && h < 120)
|
||||
r = x, g = c, b = 0.0;
|
||||
else if (h >= 120 && h < 180)
|
||||
r = 0.0, g = c, b = x;
|
||||
else if (h >= 180 && h < 240)
|
||||
r = 0.0, g = x, b = c;
|
||||
else if (h >= 240 && h < 300)
|
||||
r = x, g = 0.0, b = c;
|
||||
else
|
||||
r = c, g = 0.0, b = x;
|
||||
|
||||
*ret_r = (uint8_t) ((r + m) * 255);
|
||||
*ret_g = (uint8_t) ((g + m) * 255);
|
||||
*ret_b = (uint8_t) ((b + m) * 255);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
void rgb_to_hsv(double r, double g, double b,
|
||||
double *ret_h, double *ret_s, double *ret_v);
|
||||
|
||||
void hsv_to_rgb(
|
||||
double h, double s, double v,
|
||||
uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b);
|
||||
@@ -39,6 +39,7 @@ shared_sources = files(
|
||||
'chown-recursive.c',
|
||||
'clean-ipc.c',
|
||||
'clock-util.c',
|
||||
'color-util.c',
|
||||
'common-signal.c',
|
||||
'compare-operator.c',
|
||||
'condition.c',
|
||||
|
||||
+274
-5
@@ -18,15 +18,26 @@
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "extract-word.h"
|
||||
#include "fd-util.h"
|
||||
#include "io-util.h"
|
||||
#include "log.h"
|
||||
#include "macro.h"
|
||||
#include "ptyfwd.h"
|
||||
#include "stat-util.h"
|
||||
#include "strv.h"
|
||||
#include "terminal-util.h"
|
||||
#include "time-util.h"
|
||||
|
||||
typedef enum AnsiColorState {
|
||||
ANSI_COLOR_STATE_TEXT,
|
||||
ANSI_COLOR_STATE_ESC,
|
||||
ANSI_COLOR_STATE_CSI_SEQUENCE,
|
||||
ANSI_COLOR_STATE_NEWLINE,
|
||||
_ANSI_COLOR_STATE_MAX,
|
||||
_ANSI_COLOR_STATE_INVALID = -EINVAL,
|
||||
} AnsiColorState;
|
||||
|
||||
struct PTYForward {
|
||||
sd_event *event;
|
||||
|
||||
@@ -67,7 +78,8 @@ struct PTYForward {
|
||||
bool last_char_set:1;
|
||||
char last_char;
|
||||
|
||||
char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
|
||||
char in_buffer[LINE_MAX], *out_buffer;
|
||||
size_t out_buffer_size;
|
||||
size_t in_buffer_full, out_buffer_full;
|
||||
|
||||
usec_t escape_timestamp;
|
||||
@@ -75,6 +87,10 @@ struct PTYForward {
|
||||
|
||||
PTYForwardHandler handler;
|
||||
void *userdata;
|
||||
|
||||
char *background_color;
|
||||
AnsiColorState ansi_color_state;
|
||||
char *csi_sequence;
|
||||
};
|
||||
|
||||
#define ESCAPE_USEC (1*USEC_PER_SEC)
|
||||
@@ -115,6 +131,14 @@ static void pty_forward_disconnect(PTYForward *f) {
|
||||
}
|
||||
|
||||
f->saved_stdout = f->saved_stdin = false;
|
||||
|
||||
f->out_buffer = mfree(f->out_buffer);
|
||||
f->out_buffer_size = 0;
|
||||
f->out_buffer_full = 0;
|
||||
f->in_buffer_full = 0;
|
||||
|
||||
f->csi_sequence = mfree(f->csi_sequence);
|
||||
f->ansi_color_state = _ANSI_COLOR_STATE_INVALID;
|
||||
}
|
||||
|
||||
static int pty_forward_done(PTYForward *f, int rcode) {
|
||||
@@ -202,10 +226,243 @@ static bool drained(PTYForward *f) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static int shovel(PTYForward *f) {
|
||||
ssize_t k;
|
||||
static char *background_color_sequence(PTYForward *f) {
|
||||
assert(f);
|
||||
assert(f->background_color);
|
||||
|
||||
/* This sets the background color to the desired one, and erase the rest of the line with it */
|
||||
|
||||
return strjoin("\x1B[", f->background_color, "m", ANSI_ERASE_TO_END_OF_LINE);
|
||||
}
|
||||
|
||||
static int insert_string(PTYForward *f, size_t offset, const char *s) {
|
||||
assert(f);
|
||||
assert(offset <= f->out_buffer_full);
|
||||
assert(s);
|
||||
|
||||
size_t l = strlen(s);
|
||||
assert(l <= INT_MAX); /* Make sure we can still return this */
|
||||
|
||||
void *p = realloc(f->out_buffer, MAX(f->out_buffer_full + l, (size_t) LINE_MAX));
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
f->out_buffer = p;
|
||||
f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer);
|
||||
|
||||
memmove(f->out_buffer + offset + l, f->out_buffer + offset, f->out_buffer_full - offset);
|
||||
memcpy(f->out_buffer + offset, s, l);
|
||||
f->out_buffer_full += l;
|
||||
|
||||
return (int) l;
|
||||
}
|
||||
|
||||
static int insert_erase_newline(PTYForward *f, size_t offset) {
|
||||
_cleanup_free_ char *s = NULL;
|
||||
|
||||
assert(f);
|
||||
assert(f->background_color);
|
||||
|
||||
s = background_color_sequence(f);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
return insert_string(f, offset, s);
|
||||
}
|
||||
|
||||
static int is_csi_background_reset_sequence(const char *seq) {
|
||||
enum {
|
||||
COLOR_TOKEN_NO,
|
||||
COLOR_TOKEN_START,
|
||||
COLOR_TOKEN_8BIT,
|
||||
COLOR_TOKEN_24BIT_R,
|
||||
COLOR_TOKEN_24BIT_G,
|
||||
COLOR_TOKEN_24BIT_B,
|
||||
} token_state = COLOR_TOKEN_NO;
|
||||
|
||||
bool b = false;
|
||||
int r;
|
||||
|
||||
assert(seq);
|
||||
|
||||
/* This parses CSI "m" sequences, and determines if they reset the background color. If so returns
|
||||
* 1. This can then be used to insert another sequence that sets the color to the desired
|
||||
* replacement. */
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *token = NULL;
|
||||
|
||||
r = extract_first_word(&seq, &token, ";", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
switch (token_state) {
|
||||
|
||||
case COLOR_TOKEN_NO:
|
||||
|
||||
if (STR_IN_SET(token, "", "0", "00", "49"))
|
||||
b = true; /* These tokens set the background back to normal */
|
||||
else if (STR_IN_SET(token, "40", "41", "42", "43", "44", "45", "46", "47", "48"))
|
||||
b = false; /* And these tokens set them to something other than normal */
|
||||
|
||||
if (STR_IN_SET(token, "38", "48", "58"))
|
||||
token_state = COLOR_TOKEN_START; /* These tokens mean an 8bit or 24bit color will follow */
|
||||
break;
|
||||
|
||||
case COLOR_TOKEN_START:
|
||||
|
||||
if (STR_IN_SET(token, "5", "05"))
|
||||
token_state = COLOR_TOKEN_8BIT; /* 8bit color */
|
||||
else if (STR_IN_SET(token, "2", "02"))
|
||||
token_state = COLOR_TOKEN_24BIT_R; /* 24bit color */
|
||||
else
|
||||
token_state = COLOR_TOKEN_NO; /* something weird? */
|
||||
break;
|
||||
|
||||
case COLOR_TOKEN_24BIT_R:
|
||||
token_state = COLOR_TOKEN_24BIT_G;
|
||||
break;
|
||||
|
||||
case COLOR_TOKEN_24BIT_G:
|
||||
token_state = COLOR_TOKEN_24BIT_B;
|
||||
break;
|
||||
|
||||
case COLOR_TOKEN_8BIT:
|
||||
case COLOR_TOKEN_24BIT_B:
|
||||
token_state = COLOR_TOKEN_NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
static int insert_background_fix(PTYForward *f, size_t offset) {
|
||||
assert(f);
|
||||
assert(f->background_color);
|
||||
|
||||
if (!is_csi_background_reset_sequence(strempty(f->csi_sequence)))
|
||||
return 0;
|
||||
|
||||
_cleanup_free_ char *s = NULL;
|
||||
s = strjoin(";", f->background_color);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
return insert_string(f, offset, s);
|
||||
}
|
||||
|
||||
static int pty_forward_ansi_process(PTYForward *f, size_t offset) {
|
||||
int r;
|
||||
|
||||
assert(f);
|
||||
assert(offset <= f->out_buffer_full);
|
||||
|
||||
if (!f->background_color)
|
||||
return 0;
|
||||
|
||||
for (size_t i = offset; i < f->out_buffer_full; i++) {
|
||||
char c = f->out_buffer[i];
|
||||
|
||||
switch (f->ansi_color_state) {
|
||||
|
||||
case ANSI_COLOR_STATE_TEXT:
|
||||
if (c == '\n')
|
||||
f->ansi_color_state = ANSI_COLOR_STATE_NEWLINE;
|
||||
if (c == 0x1B) /* ESC */
|
||||
f->ansi_color_state = ANSI_COLOR_STATE_ESC;
|
||||
break;
|
||||
|
||||
case ANSI_COLOR_STATE_NEWLINE: {
|
||||
/* Immediately after a newline insert an ANSI sequence to erase the line with a background color */
|
||||
|
||||
r = insert_erase_newline(f, i);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
i += r;
|
||||
|
||||
f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
|
||||
break;
|
||||
}
|
||||
|
||||
case ANSI_COLOR_STATE_ESC: {
|
||||
|
||||
if (c == '[')
|
||||
f->ansi_color_state = ANSI_COLOR_STATE_CSI_SEQUENCE;
|
||||
else
|
||||
f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ANSI_COLOR_STATE_CSI_SEQUENCE: {
|
||||
|
||||
if (c >= 0x20 && c <= 0x3F) {
|
||||
/* If this is a "parameter" or "intermediary" byte (i.e. ranges 0x20…0x2F and
|
||||
* 0x30…0x3F) then we are still in the CSI sequence */
|
||||
|
||||
if (strlen_ptr(f->csi_sequence) >= 64) {
|
||||
/* Safety check: lets not accept unbounded CSI sequences */
|
||||
|
||||
f->csi_sequence = mfree(f->csi_sequence);
|
||||
f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
|
||||
} else if (!strextend(&f->csi_sequence, CHAR_TO_STR(c)))
|
||||
return -ENOMEM;
|
||||
} else {
|
||||
/* Otherwise, the CSI sequence is over */
|
||||
|
||||
if (c == 'm') {
|
||||
/* This is an "SGR" (Select Graphic Rendition) sequence. Patch in our background color. */
|
||||
r = insert_background_fix(f, i);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
i += r;
|
||||
}
|
||||
|
||||
f->csi_sequence = mfree(f->csi_sequence);
|
||||
f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int shovel(PTYForward *f) {
|
||||
ssize_t k;
|
||||
int r;
|
||||
|
||||
assert(f);
|
||||
|
||||
if (f->out_buffer_size == 0 && f->background_color) {
|
||||
/* Erase the first line when we start */
|
||||
f->out_buffer = background_color_sequence(f);
|
||||
if (!f->out_buffer)
|
||||
return pty_forward_done(f, log_oom());
|
||||
|
||||
f->out_buffer_full = strlen(f->out_buffer);
|
||||
f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer);
|
||||
}
|
||||
|
||||
if (f->out_buffer_size < LINE_MAX) {
|
||||
/* Make sure we always have room for at least one "line" */
|
||||
void *p = realloc(f->out_buffer, LINE_MAX);
|
||||
if (!p)
|
||||
return pty_forward_done(f, log_oom());
|
||||
|
||||
f->out_buffer = p;
|
||||
f->out_buffer_size = MALLOC_SIZEOF_SAFE(p);
|
||||
}
|
||||
|
||||
while ((f->stdin_readable && f->in_buffer_full <= 0) ||
|
||||
(f->master_writable && f->in_buffer_full > 0) ||
|
||||
@@ -267,9 +524,9 @@ static int shovel(PTYForward *f) {
|
||||
}
|
||||
}
|
||||
|
||||
if (f->master_readable && f->out_buffer_full < LINE_MAX) {
|
||||
if (f->master_readable && f->out_buffer_full < MIN(f->out_buffer_size, (size_t) LINE_MAX)) {
|
||||
|
||||
k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
|
||||
k = read(f->master, f->out_buffer + f->out_buffer_full, f->out_buffer_size - f->out_buffer_full);
|
||||
if (k < 0) {
|
||||
|
||||
/* Note that EIO on the master device might be caused by vhangup() or
|
||||
@@ -289,7 +546,12 @@ static int shovel(PTYForward *f) {
|
||||
}
|
||||
} else {
|
||||
f->read_from_master = true;
|
||||
size_t scan_index = f->out_buffer_full;
|
||||
f->out_buffer_full += (size_t) k;
|
||||
|
||||
r = pty_forward_ansi_process(f, scan_index);
|
||||
if (r < 0)
|
||||
return pty_forward_done(f, log_error_errno(r, "Failed to scan for ANSI sequences: %m"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,6 +817,7 @@ PTYForward *pty_forward_free(PTYForward *f) {
|
||||
if (!f)
|
||||
return NULL;
|
||||
pty_forward_disconnect(f);
|
||||
free(f->background_color);
|
||||
return mfree(f);
|
||||
}
|
||||
|
||||
@@ -689,3 +952,9 @@ int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pty_forward_set_background_color(PTYForward *f, const char *color) {
|
||||
assert(f);
|
||||
|
||||
return free_and_strdup(&f->background_color, color);
|
||||
}
|
||||
|
||||
@@ -39,4 +39,6 @@ int pty_forward_set_priority(PTYForward *f, int64_t priority);
|
||||
|
||||
int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height);
|
||||
|
||||
int pty_forward_set_background_color(PTYForward *f, const char *color);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free);
|
||||
|
||||
@@ -57,6 +57,7 @@ simple_tests += files(
|
||||
'test-cgroup.c',
|
||||
'test-chase.c',
|
||||
'test-clock.c',
|
||||
'test-color-util.c',
|
||||
'test-compare-operator.c',
|
||||
'test-condition.c',
|
||||
'test-conf-files.c',
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "color-util.h"
|
||||
#include "tests.h"
|
||||
|
||||
TEST(hsv_to_rgb) {
|
||||
uint8_t r, g, b;
|
||||
|
||||
hsv_to_rgb(0, 0, 0, &r, &g, &b);
|
||||
assert(r == 0 && g == 0 && b == 0);
|
||||
|
||||
hsv_to_rgb(60, 0, 0, &r, &g, &b);
|
||||
assert(r == 0 && g == 0 && b == 0);
|
||||
|
||||
hsv_to_rgb(0, 0, 100, &r, &g, &b);
|
||||
assert(r == 255 && g == 255 && b == 255);
|
||||
|
||||
hsv_to_rgb(0, 100, 100, &r, &g, &b);
|
||||
assert(r == 255 && g == 0 && b == 0);
|
||||
|
||||
hsv_to_rgb(120, 100, 100, &r, &g, &b);
|
||||
assert(r == 0 && g == 255 && b == 0);
|
||||
|
||||
hsv_to_rgb(240, 100, 100, &r, &g, &b);
|
||||
assert(r == 0 && g == 0 && b == 255);
|
||||
|
||||
hsv_to_rgb(311, 52, 62, &r, &g, &b);
|
||||
assert(r == 158 && g == 75 && b == 143);
|
||||
}
|
||||
|
||||
TEST(rgb_to_hsv) {
|
||||
|
||||
double h, s, v;
|
||||
rgb_to_hsv(0, 0, 0, &h, &s, &v);
|
||||
assert(s <= 0);
|
||||
assert(v <= 0);
|
||||
|
||||
rgb_to_hsv(1, 1, 1, &h, &s, &v);
|
||||
assert(s <= 0);
|
||||
assert(v >= 100);
|
||||
|
||||
rgb_to_hsv(1, 0, 0, &h, &s, &v);
|
||||
assert(h >= 359 || h <= 1);
|
||||
assert(s >= 100);
|
||||
assert(v >= 100);
|
||||
|
||||
rgb_to_hsv(0, 1, 0, &h, &s, &v);
|
||||
assert(h >= 119 && h <= 121);
|
||||
assert(s >= 100);
|
||||
assert(v >= 100);
|
||||
|
||||
rgb_to_hsv(0, 0, 1, &h, &s, &v);
|
||||
assert(h >= 239 && h <= 241);
|
||||
assert(s >= 100);
|
||||
assert(v >= 100);
|
||||
|
||||
rgb_to_hsv(0.5, 0.6, 0.7, &h, &s, &v);
|
||||
assert(h >= 209 && h <= 211);
|
||||
assert(s >= 28 && s <= 31);
|
||||
assert(v >= 69 && v <= 71);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||
@@ -133,6 +133,27 @@ TEST(strv_env_assign) {
|
||||
assert_se(streq(a[0], "a=A"));
|
||||
}
|
||||
|
||||
TEST(strv_env_assignf) {
|
||||
_cleanup_strv_free_ char **a = NULL;
|
||||
|
||||
assert_se(strv_env_assignf(&a, "a", "a") > 0);
|
||||
assert_se(strv_env_assignf(&a, "a", "%c", 'a') == 0);
|
||||
|
||||
assert_se(strv_env_assignf(&a, "c", "xxx%iyyy", 5) > 0);
|
||||
assert_se(strv_length(a) == 2);
|
||||
assert_se(strv_equal(a, STRV_MAKE("a=a", "c=xxx5yyy")));
|
||||
assert_se(strv_env_assignf(&a, "c", NULL) == 0);
|
||||
|
||||
assert_se(strv_env_assignf(&a, "b", "b") > 0);
|
||||
assert_se(strv_env_assignf(&a, "a", "A") == 0);
|
||||
assert_se(strv_env_assignf(&a, "b", NULL) == 0);
|
||||
|
||||
assert_se(strv_env_assignf(&a, "a=", "B") == -EINVAL);
|
||||
|
||||
assert_se(strv_length(a) == 1);
|
||||
assert_se(streq(a[0], "a=A"));
|
||||
}
|
||||
|
||||
TEST(strv_env_assign_many) {
|
||||
_cleanup_strv_free_ char **a = NULL;
|
||||
|
||||
|
||||
@@ -165,4 +165,15 @@ TEST(get_ctty) {
|
||||
log_notice("Not invoked with stdin == ctty, cutting get_ctty() test short");
|
||||
}
|
||||
|
||||
TEST(get_default_background_color) {
|
||||
double red, green, blue;
|
||||
int r;
|
||||
|
||||
r = get_default_background_color(&red, &green, &blue);
|
||||
if (r < 0)
|
||||
log_notice_errno(r, "Can't get terminal default background color: %m");
|
||||
else
|
||||
log_notice("R=%g G=%g B=%g", red, green, blue);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
|
||||
@@ -227,5 +227,9 @@ B=$(systemd-run -q --wait --pipe -p ProtectProc=invisible cat /proc/version)
|
||||
|
||||
assert_eq "$A" "$B"
|
||||
|
||||
# Check that invoking the tool under the uid0 alias name works
|
||||
uid0 ls /
|
||||
echo "$(uid0 echo foo)" = "foo"
|
||||
|
||||
umount /proc/version
|
||||
rm "$V"
|
||||
|
||||
Reference in New Issue
Block a user