chiark / gitweb /
Merge branch 'python-systemd-reader'
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 1 Mar 2013 00:53:42 +0000 (19:53 -0500)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 1 Mar 2013 01:05:12 +0000 (20:05 -0500)
* python-systemd-reader:
  python-systemd: rename Journal to Reader
  build-sys: upload python documentation to freedesktop.org
  systemd-python: add Journal class for reading journal
  python: build html docs using sphinx
  journalct: also print Python code in --new-id
  python: utilize uuid.UUID in logging
  python: add systemd.id128 module
  ... and 34 other commits

In short: python module systemd.id128 is added, and existing
systemd.journal gains a new class systemd.journal.Reader, which can be
used to iterate over journal entries. Documentation is provided, and
accessible under e.g.
    pydoc3 systemd.journal.Reader
or
    firefox http://www.freedesktop.org/software/systemd/man/python-systemd/

51 files changed:
.gitignore
Makefile-man.am
Makefile.am
TODO
configure.ac
make-directive-index.py
make-man-rules.py
man/kernel-install.xml [new file with mode: 0644]
man/logind.conf.xml
man/systemctl.xml
man/systemd-activate.xml [new file with mode: 0644]
man/systemd-nspawn.xml
man/systemd-system.conf.xml
shell-completion/systemd-bash-completion.sh
src/activate/activate.c [new file with mode: 0644]
src/core/cgroup-attr.c
src/core/cgroup-attr.h
src/core/cgroup-semantics.c [new file with mode: 0644]
src/core/cgroup-semantics.h [new file with mode: 0644]
src/core/dbus-manager.c
src/core/dbus-unit.c
src/core/dbus-unit.h
src/core/execute.c
src/core/execute.h
src/core/job.c
src/core/load-fragment-gperf.gperf.m4
src/core/load-fragment.c
src/core/load-fragment.h
src/core/manager.c
src/core/manager.h
src/core/mount-setup.c
src/core/transaction.c
src/core/unit.c
src/core/unit.h
src/journal/journalctl.c
src/journal/sd-journal.c
src/kernel-install/kernel-install [new file with mode: 0644]
src/nspawn/nspawn.c
src/shared/log.c
src/shared/path-lookup.c
src/shared/socket-util.c
src/shared/socket-util.h
src/shared/strv.c
src/shared/strv.h
src/shared/util.c
src/shared/util.h
src/systemctl/systemctl.c
src/test/test-util.c
test/TEST-03-JOBS/test-jobs.sh
test/TEST-03-JOBS/test.sh
test/unstoppable.service [new file with mode: 0644]

index 9afbb10..7385107 100644 (file)
@@ -23,6 +23,7 @@
 /systemadm
 /systemctl
 /systemd
+/systemd-activate
 /systemd-ac-power
 /systemd-analyze
 /systemd-ask-password
index e4eb26c..d02fff0 100644 (file)
@@ -1,5 +1,5 @@
 # Do not edit. Generated by make-man-rules.py.
-# Regenerate with 'make update-man-list'.
+# Regenerate with 'make all update-man-list'.
 
 MANPAGES += \
        man/bootup.7 \
@@ -9,6 +9,7 @@ MANPAGES += \
        man/journalctl.1 \
        man/journald.conf.5 \
        man/kernel-command-line.7 \
+       man/kernel-install.8 \
        man/locale.conf.5 \
        man/localtime.5 \
        man/machine-id.5 \
@@ -43,6 +44,7 @@ MANPAGES += \
        man/shutdown.8 \
        man/sysctl.d.5 \
        man/systemctl.1 \
+       man/systemd-activate.8 \
        man/systemd-analyze.1 \
        man/systemd-ask-password-console.service.8 \
        man/systemd-ask-password.1 \
index f1c2ce0..790e501 100644 (file)
@@ -267,8 +267,7 @@ rootbin_PROGRAMS = \
        systemd-ask-password \
        systemd-tty-ask-password-agent \
        systemd-tmpfiles \
-       systemd-machine-id-setup \
-       systemd-analyze
+       systemd-machine-id-setup
 
 bin_PROGRAMS = \
        systemd-cgls \
@@ -276,7 +275,14 @@ bin_PROGRAMS = \
        systemd-stdio-bridge \
        systemd-nspawn \
        systemd-detect-virt \
-       systemd-delta
+       systemd-delta \
+       systemd-analyze
+
+bin_SCRIPTS = \
+       src/kernel-install/kernel-install
+
+EXTRA_DIST += \
+       src/kernel-install/kernel-install
 
 rootlibexec_PROGRAMS = \
        systemd \
@@ -513,7 +519,7 @@ man/index.html:
 
 NON_INDEX_XML_FILES = $(filter-out man/systemd.index.xml,$(XML_FILES))
 
-XML_GLOB = $(wildcard $(top_srcdir)/man/*.xml $(top_srcdir)/man/*.xml.in)
+XML_GLOB = $(wildcard $(top_srcdir)/man/*.xml $(top_srcdir)/man/*.xml.in $(top_builddir)/man/*.xml)
 update-man-list: make-man-rules.py $(XML_GLOB)
        $(AM_V_GEN)$(PYTHON) $^ > $(top_srcdir)/Makefile-man.tmp
        $(AM_V_at)mv $(top_srcdir)/Makefile-man.tmp $(top_srcdir)/Makefile-man.am
@@ -849,6 +855,8 @@ libsystemd_core_la_SOURCES = \
        src/core/tcpwrap.h \
        src/core/cgroup-attr.c \
        src/core/cgroup-attr.h \
+       src/core/cgroup-semantics.c \
+       src/core/cgroup-semantics.h \
        src/core/securebits.h \
        src/core/initreq.h \
        src/core/special.h \
@@ -1560,7 +1568,6 @@ libsystemd_daemon_la_CFLAGS = \
 
 libsystemd_daemon_la_LDFLAGS = \
        $(AM_LDFLAGS) \
-       -shared \
        -version-info $(LIBSYSTEMD_DAEMON_CURRENT):$(LIBSYSTEMD_DAEMON_REVISION):$(LIBSYSTEMD_DAEMON_AGE) \
        -Wl,--version-script=$(top_srcdir)/src/libsystemd-daemon/libsystemd-daemon.sym
 
@@ -2261,7 +2268,6 @@ libsystemd_id128_la_CFLAGS = \
 
 libsystemd_id128_la_LDFLAGS = \
        $(AM_LDFLAGS) \
-       -shared \
        -version-info $(LIBSYSTEMD_ID128_CURRENT):$(LIBSYSTEMD_ID128_REVISION):$(LIBSYSTEMD_ID128_AGE) \
        -Wl,--version-script=$(top_srcdir)/src/libsystemd-id128/libsystemd-id128.sym
 
@@ -2308,6 +2314,18 @@ EXTRA_DIST += \
        src/libsystemd-id128/libsystemd-id128.sym
 
 # ------------------------------------------------------------------------------
+
+rootlibexec_PROGRAMS += \
+       systemd-activate
+
+systemd_activate_SOURCES = \
+       src/activate/activate.c
+
+systemd_activate_LDADD = \
+       libsystemd-shared.la \
+       libsystemd-daemon.la
+
+# ------------------------------------------------------------------------------
 systemd_journald_SOURCES = \
        src/journal/journald.c \
        src/journal/journald-server.h
@@ -2452,7 +2470,6 @@ libsystemd_journal_la_CFLAGS = \
 
 libsystemd_journal_la_LDFLAGS = \
        $(AM_LDFLAGS) \
-       -shared \
        -version-info $(LIBSYSTEMD_JOURNAL_CURRENT):$(LIBSYSTEMD_JOURNAL_REVISION):$(LIBSYSTEMD_JOURNAL_AGE) \
        -Wl,--version-script=$(top_srcdir)/src/journal/libsystemd-journal.sym
 
@@ -2554,6 +2571,8 @@ libsystemd-journal-uninstall-hook:
 INSTALL_EXEC_HOOKS += libsystemd-journal-install-hook
 UNINSTALL_EXEC_HOOKS += libsystemd-journal-uninstall-hook
 
+# ------------------------------------------------------------------------------
+
 # Update catalog on installation. Do not bother if installing
 # in DESTDIR, since this is likely for packaging purposes.
 catalog-update-hook:
@@ -3254,7 +3273,6 @@ libsystemd_login_la_CFLAGS = \
 
 libsystemd_login_la_LDFLAGS = \
        $(AM_LDFLAGS) \
-       -shared \
        -version-info $(LIBSYSTEMD_LOGIN_CURRENT):$(LIBSYSTEMD_LOGIN_REVISION):$(LIBSYSTEMD_LOGIN_AGE) \
        -Wl,--version-script=$(top_srcdir)/src/login/libsystemd-login.sym
 
diff --git a/TODO b/TODO
index 4bcaea7..2366470 100644 (file)
--- a/TODO
+++ b/TODO
@@ -17,6 +17,7 @@ Bugfixes:
 * suppress log output on shutdown when "quiet" is used
 
 Fedora 19:
+
 * make anaconda write timeout=0 for encrypted devices
 
 * make sure pkexec works fine with pam_systemd works fine with audit=0
@@ -29,11 +30,7 @@ Fedora 19:
   that's sometimes kinda hard to parse for a human.
 
 * cgroup attrs:
-  - don't unconditionally filter out duplicate settings
-  - support high-level cgroup setting syntax in systemctl. Example: "systemctl set-cgroup-attr MemoryLimit 5K"
-  - support writte string mapping even for non-high-level settings
-  - add man page for systemctl commands
-  - make sure we work fine with multi-line strings
+  - update dbus interface docs in wiki
 
 * kernel cmdline switch to turn off predictable network interface names
 
@@ -52,6 +49,54 @@ Fedora 19:
 
 Features:
 
+* systemd-inhibit: refuse taking delay locks
+
+* journal-or-kmsg is currently broken? See reverted commit 4a01181e460686d8b4a543b1dfa7f77c9e3c5ab8.
+
+* remove any syslog support from log.c -- we probably can't do this before split-off udev is gone for good
+
+* convert /etc/bash_completion.d/ to /usr/share/bash-completion/
+  (systemd (and everything else) should install zero static data in /etc)
+
+* tmpfiles: when traversing the tree, check for bind mount points with nametohandle()
+
+* fedora: connect the timer units of a service to the service via Also= in [Install], and maybe introduce timers.target
+
+* fedora: F20: go timer units all the way, leave cron.daily for cron
+
+* add a tool that lists active timer units plus their next elapstion and the time the units ran last
+
+* man: document the very specific env the shutdown drop-in tools live in
+
+* shutdown logging: store to EFI var, and store to USB stick?
+
+* man: extend runlevel(8) to mention that runlevels suck, and are dead. Maybe  add runlevel(7) with a note about that too
+
+* systemctl: maybe add "systemctl add-wants" or so...
+
+* man: add a link to socket activation blog from systemd.socket(5)
+
+* systemctl status: show drop-in snippets for service files in addition to service file path themesevles.
+
+* man: add more examples to man pages
+
+* man: maybe sort directives in man pages, and take sections from --help and apply them to man too
+
+* add "# export SYSTEMD_PAGER=" to bash login
+
+* /usr/bin/service should actually show the new command line
+
+* fedora: suggest auto-restart on failure, but not on sucess and not on coredump. also, ask people to think about changing the start limit logic. Also point people to RestartPreventExitStatus=, SuccessExitStatus=
+
+* write UI tool that pops up emergency messages from the journal as notification
+
+* think about window-manager-run-as-user-service problem: exit 0 → activate shutdown.target; exit != 0 → restart service
+
+* msgcatalog determine default language of entries from the catalog
+  file name. i.e. foobar.de_DE.catalog would set the default entry
+  name for the entries to de_DE if they aren't explicitly suffixed
+  individually.
+
 * figure out what we do about hostnames/fqdn in hostnamectl/hostnamed
 
 * use "log level" rather than "log priority" everywhere
@@ -114,6 +159,7 @@ Features:
 * service: watchdog logic: for testing purposes allow ping, but do not require pong
 
 * journal:
+  - import and delete pstore filesystem content at startup
   - journald: also get thread ID from client, plus thread name
   - journal: when waiting for journal additions in the client always sleep at least 1s or so, in order to minimize wakeups
   - add API to close/reopen/get fd for journal client fd in libsystemd-journal.
@@ -266,6 +312,8 @@ Features:
     interfaces to the container
   - nspawn: maybe add a way to drop additional caps, in addition to add additional caps
   - nspawn: maybe explicitly reset loginuid?
+  - nspawn: move all containers to name=systemd:/containers/, and introduce externally visible names for containers
+  - nspawn: make it work for dwalsh and shared /usr containers -- tmpfs mounts as command line parameters, selinux exec context
 
 * cryptsetup:
   - cryptsetup-generator: warn if the password files are world-readable
@@ -301,8 +349,6 @@ Features:
 
 * explore multiple service instances per listening socket idea
 
-* testing tool for socket activation: some binary that listens on a socket and passes it on using the usual socket activation protocol to some server.
-
 * shutdown: don't read-only mount anything when running in container
 
 * MountFlags=shared acts as MountFlags=slave right now.
@@ -478,7 +524,11 @@ Features:
   - non-cgroup resource management
   - dynamic resource management with cgroups
   - refreshed, longer missions statement
-  - celendar time events
+  - calendar time events
+  - init=/bin/sh vs. "emergency" mode, vs. "rescue" mode, vs. "multi-user" mode, vs. "graphical" mode, and the debug shell
+  - how to create your own target
+  - instantiated apache, dovecot and so on
+  - hooking a script into various stages of shutdown/rearly booot
 
 * allow port=0 in .socket units
 
index 397ce82..ea9fafe 100644 (file)
@@ -277,7 +277,7 @@ AM_CONDITIONAL(HAVE_SELINUX, [test "$have_selinux" = "yes"])
 if test "x${have_selinux}" != xno ; then
         sushell=/sbin/sushell
 else
-        sushell=/bin/bash
+        sushell=/bin/sh
 fi
 AC_SUBST(sushell)
 
index 15bd9b9..c61383b 100755 (executable)
@@ -185,7 +185,10 @@ def _extract_directives(directive_groups, formatting, page):
                 stor[text].append((pagename, section))
                 if text not in formatting:
                     # use element as formatted display
-                    name.tail = ''
+                    if name.text[-1] in '= ':
+                        name.clear()
+                    else:
+                        name.tail = ''
                     name.text = text
                     formatting[text] = name
 
index 9c50c8d..5415984 100644 (file)
@@ -21,6 +21,7 @@ from __future__ import print_function
 import xml.etree.ElementTree as tree
 import collections
 import sys
+import os
 
 SECTION = '''\
 MANPAGES += \\
@@ -40,7 +41,7 @@ endif
 
 HEADER = '''\
 # Do not edit. Generated by make-man-rules.py.
-# Regenerate with 'make update-man-list'.
+# Regenerate with 'make all update-man-list'.
 
 '''
 
@@ -96,7 +97,8 @@ def make_makefile(rules, cleanfiles):
 
 if __name__ == '__main__':
     sources = set(sys.argv[1:])
+    basenames = [os.path.basename(source) for source in sources]
     spares = set([source for source in sources
-                  if source + '.in' in sources])
+                  if os.path.basename(source) + '.in' in basenames])
     rules = create_rules(*(sources - spares))
     print(make_makefile(rules, spares), end='')
diff --git a/man/kernel-install.xml b/man/kernel-install.xml
new file mode 100644 (file)
index 0000000..93dc0ae
--- /dev/null
@@ -0,0 +1,166 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+This file is part of systemd.
+
+Copyright 2013 Harald Hoyer
+
+systemd is free software; you can redistribute it and/or modify it
+under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+systemd is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with systemd; If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="kernel-install">
+
+  <refentryinfo>
+    <title>kernel-install</title>
+    <productname>systemd</productname>
+
+    <authorgroup>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Harald</firstname>
+        <surname>Hoyer</surname>
+        <email>harald@redhat.com</email>
+      </author>
+    </authorgroup>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>kernel-install</refentrytitle>
+    <manvolnum>8</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>kernel-install</refname>
+    <refpurpose>Add and remove kernel and initramfs images to and from /boot</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>kernel-install</command> <arg choice="req">COMMAND</arg> <arg choice="req">KERNEL VERSION</arg> <arg choice="req">KERNEL IMAGE</arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+    <para>
+      <command>kernel-install</command> is used to install and remove kernel and
+      initramfs images to and from <filename>/boot</filename>.
+    </para>
+
+    <para>kernel-install will execute the files located in the directory <filename>/usr/lib/kernel/install.d/</filename>
+    and the local administration directory <filename>/etc/kernel/install.d/</filename>.
+    All files are collectively sorted and executed in lexical order, regardless of the directories in
+    which they live. However, files with identical file names replace each other.
+    Files in <filename>/etc/kernel/install.d/</filename> take precedence over files with the same name
+    in <filename>/usr/lib/kernel/install.d/</filename>. This can be used to override a system-supplied
+    executables with a local file if needed; a symbolic link in <filename>/etc/kernel/install.d/</filename>
+    with the same name as an executable in <filename>/usr/lib/kernel/install.d/</filename>,
+    pointing to /dev/null, disables the executable entirely. Executables must have the
+    extension .install; other extensions are ignored.</para>
+
+  </refsect1>
+
+  <refsect1>
+    <title>Commands</title>
+    <para>The following commands are understood:</para>
+    <variablelist>
+      <varlistentry>
+        <term>add &lt;KERNEL VERSION> &lt;KERNEL IMAGE></term>
+        <listitem>
+          <para>calls every executable <filename>/usr/lib/kernel/install.d/*.install</filename>
+          and <filename>/etc/kernel/install.d/*.install</filename> with the arguments
+          "add &lt;KERNEL VERSION> <filename>/boot/&lt;MACHINE-ID>/&lt;KERNEL VERSION>/</filename>"</para>
+
+          <para>kernel-install copies &lt;KERNEL IMAGE> to
+          <filename>/boot/&lt;MACHINE-ID>/&lt;KERNEL VERSION>/linux</filename>.</para>
+
+          <para>kernel-install also creates a boot loader entry according to the boot loader specification
+          in <filename>/boot/loader/entries/&lt;OS-ID>-&lt;KERNEL VERSION>-&lt;MACHINE-ID>.conf</filename>.
+          If the file <filename>initrd</filename> is found next to the <filename>linux</filename> file,
+          the initrd will be added to the configuration.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>remove &lt;KERNEL VERSION> &lt;KERNEL IMAGE></term>
+        <listitem><para>calls every executable <filename>/usr/lib/kernel/install.d/*.install</filename>
+        and <filename>/etc/kernel/install.d/*.install</filename> with the arguments:
+        "remove &lt;KERNEL VERSION> <filename>/boot/&lt;MACHINE-ID>/&lt;KERNEL VERSION>/</filename>"
+        </para>
+        <para>kernel-install removes the entire directory <filename>/boot/&lt;MACHINE-ID>/&lt;KERNEL VERSION>/</filename>
+        and the file <filename>/boot/loader/entries/&lt;OS-ID>-&lt;KERNEL VERSION>-&lt;MACHINE-ID>.conf</filename></para>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+
+  </refsect1>
+
+  <refsect1>
+    <title>Exit status</title>
+    <para>If every executable returns with 0, 0 is returned, a non-zero failure code otherwise.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Files</title>
+    <variablelist>
+      <varlistentry>
+        <term>
+          <filename>/usr/lib/kernel/install.d/*.install</filename>
+          <filename>/etc/kernel/install.d/*.install</filename>
+        </term>
+          <listitem>
+            <para>Drop-in files, which are executed by kernel-install.</para>
+          </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <filename>/etc/kernel/cmdline</filename>
+          <filename>/proc/cmdline</filename>
+        </term>
+          <listitem>
+            <para>The contents of the file <filename>/etc/kernel/cmdline</filename> specifies the kernel command line to use.
+            If that file does not exist, <filename>/proc/cmdline</filename> is used.</para>
+          </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <filename>/etc/machine-id</filename>
+        </term>
+          <listitem>
+            <para>The contents of the file specifies the machine identifaction &lt;MACHINE-ID>.</para>
+          </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <filename>/etc/os-release</filename>
+        </term>
+          <listitem>
+            <para>The contents of the file specifies the the operating system id &lt;OS-ID>.</para>
+          </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+      <ulink url="http://www.freedesktop.org/wiki/Specifications/BootLoaderSpec">Boot loader specification</ulink>
+    </para>
+  </refsect1>
+
+</refentry>
index 96e5936..362a4f6 100644 (file)
 
                                 <listitem><para>These settings control
                                 the default control group hierarchies
-                                users logging in are added to. When
-                                logging in users will get private
-                                control groups in all hierarchies
-                                listed in
+                                users logging in are added to, in
+                                addition to the
+                                <literal>name=systemd</literal> named
+                                hierarchy. These settings take space
+                                separated lists of controller
+                                names. Pass the empty string to ensure
+                                that logind does not touch any
+                                hierarchies but systemd's own. When
+                                logging in user sessions will get
+                                private control groups in all
+                                hierarchies listed in
                                 <varname>Controllers=</varname> and be
                                 reset to the root control group in all
                                 hierarchies listed in
                                 defaults to the empty list,
                                 <varname>ResetControllers=</varname>
                                 defaults to
-                                <literal>cpu</literal>.</para></listitem>
+                                <literal>cpu</literal>. Note that for
+                                all controllers that are not listed in
+                                either <varname>Controllers=</varname>
+                                nor
+                                <varname>ResetControllers=</varname>
+                                newly created sessions will be part of
+                                the control groups of the system
+                                service that created the
+                                session.</para></listitem>
                         </varlistentry>
 
                         <varlistentry>
index 39229a0..5656564 100644 (file)
@@ -117,10 +117,11 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
         <term><option>--property=</option></term>
 
         <listitem>
-          <para>When showing unit/job/manager properties, limit
-          display to certain properties as specified as argument. If
-          not specified all set properties are shown. The argument
-          should be a comma-seperated list of property names, such as
+          <para>When showing unit/job/manager properties with the
+          <command>show</command> command, limit display to certain
+          properties as specified as argument. If not specified all
+          set properties are shown. The argument should be a
+          comma-seperated list of property names, such as
           <literal>MainPID</literal>. If specified more than once all
           properties with the specified names are shown.</para>
         </listitem>
@@ -206,7 +207,7 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
           sleep state. Any user may take these locks and privileged
           users may override these locks. If any locks are taken,
           shutdown and sleep state requests will normally fail
-          (regardless if privileged or not) and list of active locks
+          (regardless if privileged or not) and list of active locks
           is printed. However if <option>--ignore-inhibitors</option>
           is specified the locks are ignored and not printed, and the
           operation attempted anyway, possibly requiring additional
@@ -392,14 +393,22 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
         <term><option>--runtime</option></term>
 
         <listitem>
-          <para>When used with
-          <command>enable</command>/<command>disable</command>/<command>is-enabled</command>
+          <para>When used with <command>enable</command>,
+          <command>disable</command>, <command>is-enabled</command>
           (and related commands), make changes only temporarily, so
-          that they are dropped on the next reboot. This will have the
+          that they are lost on the next reboot. This will have the
           effect that changes are not made in subdirectories of
           <filename>/etc</filename> but in <filename>/run</filename>,
           with identical immediate effects, however, since the latter
           is lost on reboot, the changes are lost too.</para>
+
+          <para>Similar, when used with
+          <command>set-cgroup-attr</command>,
+          <command>unset-cgroup-attr</command>,
+          <command>set-cgroup</command> and
+          <command>unset-cgroup</command>, make changes only
+          temporarily, so that they are lost on the next
+          reboot.</para>
         </listitem>
       </varlistentry>
 
@@ -631,6 +640,98 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
           human-readable output.</para>
         </listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><command>get-cgroup-attr <replaceable>NAME</replaceable> <replaceable>ATTRIBUTE</replaceable>...</command></term>
+
+        <listitem>
+          <para>Retrieve the specified control group attributes of the
+          specified unit. Takes a unit name and one or more attribute
+          names such as <literal>cpu.shares</literal>. This will
+          output the current values of the specified attributes,
+          separated by new-lines. For attributes that take list of
+          items the output will be new-line separated, too. This
+          operation will always try to retrieve the data in question
+          from the kernel first, and if that is not available use the
+          configured values instead. Instead of low-level control
+          group attribute names high-level pretty names may be used,
+          as used for unit execution environment configuration, see
+          <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+          for details. For example, passing
+          <literal>memory.limit_in_bytes</literal> and
+          <literal>MemoryLimit</literal> is equivalent.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>set-cgroup-attr <replaceable>NAME</replaceable> <replaceable>ATTRIBUTE</replaceable> <replaceable>VALUE</replaceable>...</command></term>
+
+        <listitem>
+          <para>Set the specified control group attribute of the
+          specified unit to the specified value. Takes a unit
+          name and an attribute name such as
+          <literal>cpu.shares</literal>, plus one or more values
+          (multiple values may only be used for attributes that take
+          multiple values). This operation will immediately update the
+          kernel attribute for this unit and persistently store this
+          setting for later reboots (unless <option>--runtime</option>
+          is passed, in which case the setting is not saved
+          persistently and only valid until the next reboot.) Instead
+          of low-level control group attribute names high-level pretty
+          names may be used, as used for unit execution environment
+          configuration, see
+          <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+          for details. For example, passing
+          <literal>memory.limit_in_bytes</literal> and
+          <literal>MemoryLimit</literal> is equivalent. This operation
+          will implicitly create a control group for the unit in the
+          controller the attribute belongs to, if needed. For
+          attributes that take multiple values, this operation will
+          append the specified values to the previously set values
+          list (use <command>unset-cgroup-attr</command> to reset the
+          list explicitly). For attributes that take a single value
+          only the list will be reset implicitly.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>unset-cgroup-attr <replaceable>NAME</replaceable> <replaceable>ATTRIBUTE</replaceable>...</command></term>
+
+        <listitem><para>Unset the specified control group attributes
+        of the specified unit. Takes a unit name and one or more
+        attribut names such as <literal>cpu.shares</literal>. This
+        operation might or might not have an immediate effect on the
+        current kernel attribute value. This will remove any
+        persistently stored configuration values for this attribute
+        (as set with <command>set-cgroup-attr</command> before),
+        unless <option>--runtime</option> is passed, in which case the
+        configuration is reset only until the next reboot. Again,
+        high-level control group attributes may be used instead of the
+        low-level kernel ones. For attributes which take multiple
+        values, all currently set values are reset.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>set-cgroup <replaceable>NAME</replaceable> <replaceable>CGROUP</replaceable>...</command></term>
+        <term><command>unset-cgroup <replaceable>NAME</replaceable> <replaceable>CGROUP</replaceable>...</command></term>
+
+        <listitem><para>Add or remove a unit to/from a specific
+        control group hierarchy and/or control group path. Takes a
+        unit name, plus a control group specification in the syntax
+        <replaceable>CONTROLLER</replaceable>:<replaceable>PATH</replaceable>
+        or <replaceable>CONTROLLER</replaceable>. In the latter syntax
+        (where the path is ommitted) the default unit control group
+        path is implied. Examples: <literal>cpu</literal> or
+        <literal>cpu:/foo/bar</literal>. If a unit is removed from a
+        control group hierarchy all its processes will be moved to the
+        root group of the hierarchy and all control group attributes
+        will be reset. These operations are immediately reflected in
+        the kernel hierarchy, and stored persistently to disk (unless
+        <option>--runtime</option> is passed).</para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><command>help <replaceable>NAME</replaceable>...|<replaceable>PID</replaceable>...</command></term>
 
@@ -641,6 +742,7 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
           shown.</para>
         </listitem>
       </varlistentry>
+
       <varlistentry>
         <term><command>reset-failed [<replaceable>NAME</replaceable>...]</command></term>
 
diff --git a/man/systemd-activate.xml b/man/systemd-activate.xml
new file mode 100644 (file)
index 0000000..6949731
--- /dev/null
@@ -0,0 +1,171 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+This file is part of systemd.
+
+Copyright 2013 Zbigniew Jędrzejewski-Szmek
+
+systemd is free software; you can redistribute it and/or modify it
+under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+systemd is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with systemd; If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-journal-gatewayd.service">
+
+  <refentryinfo>
+    <title>systemd-activate</title>
+    <productname>systemd</productname>
+
+    <authorgroup>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Zbigniew</firstname>
+        <surname>Jędrzejewski-Szmek</surname>
+        <email>zbyszek@in.waw.pl</email>
+      </author>
+    </authorgroup>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd-activate</refentrytitle>
+    <manvolnum>8</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd-activate</refname>
+    <refpurpose>Test socket activation of daemons</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>/usr/lib/systemd/systemd-activate</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="plain"><replaceable>daemon</replaceable></arg>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><command>systemd-activate</command> can be used to
+    launch a socket activated daemon from the command-line for
+    testing purposes. It can also be used to launch single instances
+    of the daemon per connection (inetd-style).
+    </para>
+
+    <para>The daemon to launch and its options should be specifed
+    after options intended for <command>systemd-activate</command>.
+    </para>
+
+    <para>If the <option>-a</option> option is given, file descriptor
+    of the connection will be used as the standard input and output of
+    the launched process. Otherwise, standard input and output will be
+    inherited, and sockets will be passed through file descriptors 3
+    and higher. Sockets passed through <varname>$LISTEN_FDS</varname>
+    to <command>systemd-activate</command> will be passed through to
+    the dameon, in the original positions. Other sockets specified
+    with <option>--listen</option> will use consecutive descriptors.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+    <variablelist>
+      <varlistentry>
+        <term><option>--help</option></term>
+        <term><option>-h</option></term>
+
+        <listitem><para>Prints a short help
+        text and exits.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--version</option></term>
+
+        <listitem><para>Prints a short version
+        string and exits.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-l</option></term>
+        <term><option>--listen=<replaceable>address</replaceable></option></term>
+
+        <listitem><para>Listen on this <replaceable>address</replaceable>.
+        Takes a string like <literal>2000</literal> or
+        <literal>127.0.0.1:2001</literal>.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-a</option></term>
+        <term><option>--accept</option></term>
+
+        <listitem><para>Launch a separate instance of daemon per
+        connection and pass the connection socket as standard input
+        and standard output.</para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Environment variables</title>
+    <variablelist class='environment-variables'>
+      <varlistentry>
+        <term><varname>$LISTEN_FDS</varname></term>
+        <term><varname>$LISTEN_PID</varname></term>
+
+        <listitem><para>See
+        <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>$SYSTEMD_LOG_TARGET</varname></term>
+        <term><varname>$SYSTEMD_LOG_LEVEL</varname></term>
+        <term><varname>$SYSTEMD_LOG_COLOR</varname></term>
+        <term><varname>$SYSTEMD_LOG_LOCATION</varname></term>
+
+        <listitem><para>Same as in
+        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Example 1</title>
+
+    <programlisting>$ /usr/lib/systemd/systemd-activate -l 2000 -a cat</programlisting>
+
+    <para>This runs an echo server on port 2000.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Example 2</title>
+
+    <programlisting>$ /usr/lib/systemd/systemd-activate -l 19531 /usr/lib/systemd/systemd-journal-gatewayd</programlisting>
+
+    <para>This runs a socket activated instance of
+    <citerefentry><refentrytitle>systemd-journal-gatewayd</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>cat</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+    </para>
+  </refsect1>
+</refentry>
index 5cba40b..8adcd94 100644 (file)
                 <cmdsynopsis>
                         <command>systemd-nspawn</command>
                         <arg choice="opt" rep="repeat">OPTIONS</arg>
-                        <arg choice="opt">COMMAND</arg>
+                        <arg choice="opt"><replaceable>COMMAND</replaceable>
+                        <arg choice="opt" rep="repeat">ARGS</arg>
+                        </arg>
+                </cmdsynopsis>
+                <cmdsynopsis>
+                        <command>systemd-nspawn</command>
+                        <arg choice="plain">-b</arg>
+                        <arg choice="opt" rep="repeat">OPTIONS</arg>
                         <arg choice="opt" rep="repeat">ARGS</arg>
                 </cmdsynopsis>
         </refsynopsisdiv>
         <refsect1>
                 <title>Options</title>
 
-                <para>If no arguments are passed the container is set
-                up and a shell started in it, otherwise the passed
-                command and arguments are executed in it. The
-                following options are understood:</para>
+                <para>If option <option>-b</option> is specified, the
+                arguments are used as arguments for the init
+                binary. Otherwise, <replaceable>COMMAND</replaceable>
+                specifies the program to launch in the container, and
+                the remaining arguments are used as arguments for this
+                program. If <option>-b</option> is not used and no
+                arguments are specifed, a shell is launched in the
+                container.</para>
+
+                <para>The following options are understood:</para>
 
                 <variablelist>
                         <varlistentry>
                                 <listitem><para>Automatically search
                                 for an init binary and invoke it
                                 instead of a shell or a user supplied
-                                program.</para></listitem>
+                                program. If this option is used, arguments
+                                specified on the command line are used
+                                as arguments for the init binary.
+                                </para></listitem>
                         </varlistentry>
 
                         <varlistentry>
                                 <listitem><para>Equivalent to
                                 <option>--link-journal=guest</option>.</para></listitem>
                         </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--bind=</option></term>
+                                <term><option>--bind-ro=</option></term>
+
+                                <listitem><para>Bind mount a file or
+                                directory from the host into the
+                                container. Either takes a path
+                                argument -- in which case the
+                                specified path will be mounted from
+                                the host to the same path in the
+                                container --, or a colon-separated
+                                pair of paths -- in which case the
+                                first specified path is the source in
+                                the host, and the second path is the
+                                destination in the container. The
+                                <option>--bind-ro=</option> option
+                                creates read-only bind
+                                mount.</para></listitem>
+                        </varlistentry>
                 </variablelist>
 
         </refsect1>
index 12979d8..eaa770b 100644 (file)
                                 <term><varname>DefaultControllers=cpu</varname></term>
 
                                 <listitem><para>Configures in which
-                                cgroup controller hierarchies to
-                                create per-service cgroups
-                                automatically, in addition to the
-                                name=systemd named hierarchy. Defaults
-                                to 'cpu'. Takes a space separated list
-                                of controller names. Pass an empty
-                                string to ensure that systemd does not
-                                touch any hierarchies but its
-                                own.</para>
+                                control group hierarchies to create
+                                per-service cgroups automatically, in
+                                addition to the
+                                <literal>name=systemd</literal> named
+                                hierarchy. Defaults to
+                                <literal>cpu</literal>. Takes a space
+                                separated list of controller
+                                names. Pass the empty string to ensure
+                                that systemd does not touch any
+                                hierarchies but its own.</para>
 
                                 <para>Note that the default value of
                                 'cpu' will make realtime scheduling
index 1132b3c..7848baf 100644 (file)
@@ -334,6 +334,20 @@ _journalctl() {
         elif [[ $cur = *=* ]]; then
                 mapfile -t field_vals < <(journalctl -F "${prev%=}" 2>/dev/null)
                 COMPREPLY=( $(compgen -W '${field_vals[*]}' -- "${cur#=}") )
+        elif [[ $cur = /dev* ]]; then
+                compopt -o filenames
+                COMPREPLY=( $(compgen -f -- "${cur}") )
+        elif [[ $cur = /* ]]; then
+                # Append /dev/ to the list of completions, so that
+                # after typing /<TAB><TAB> the user sees /dev/ as one
+                # of the alternatives. Later on the rule above will
+                # take care of showing device files in /dev/.
+                mapfile -t field_vals < <(journalctl -F "_EXE" 2>/dev/null; echo '/dev/')
+                COMPREPLY=( $(compgen -W '${field_vals[*]}' -- "${cur}") )
+                if [[ "${COMPREPLY[@]}" = '/dev/' ]]; then
+                    compopt -o filenames
+                    COMPREPLY=( $(compgen -f -- "${cur}") )
+                fi
         elif [[ $prev = '=' ]]; then
                 mapfile -t field_vals < <(journalctl -F "${COMP_WORDS[COMP_CWORD-2]}" 2>/dev/null)
                 COMPREPLY=( $(compgen -W '${field_vals[*]}' -- "$cur") )
diff --git a/src/activate/activate.c b/src/activate/activate.c
new file mode 100644 (file)
index 0000000..7fcb0a9
--- /dev/null
@@ -0,0 +1,410 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <getopt.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "socket-util.h"
+#include "build.h"
+#include "log.h"
+#include "strv.h"
+#include "macro.h"
+
+static char** arg_listen = NULL;
+static bool arg_accept = false;
+static char** arg_args = NULL;
+
+static int add_epoll(int epoll_fd, int fd) {
+        int r;
+        struct epoll_event ev = {EPOLLIN};
+        ev.data.fd = fd;
+
+        assert(epoll_fd >= 0);
+        assert(fd >= 0);
+
+        r = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
+        if (r < 0)
+                log_error("Failed to add event on epoll fd:%d for fd:%d: %s",
+                          epoll_fd, fd, strerror(-r));
+        return r;
+}
+
+static int set_nocloexec(int fd) {
+        int flags;
+
+        flags = fcntl(fd, F_GETFD);
+        if (flags < 0) {
+                log_error("Querying flags for fd:%d: %m", fd);
+                return -errno;
+        }
+
+        if (!(flags & FD_CLOEXEC))
+                return 0;
+
+        if (fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC) < 0) {
+                log_error("Settings flags for fd:%d: %m", fd);
+                return -errno;
+        }
+
+        return 0;
+}
+
+static int print_socket(const char* desc, int fd) {
+        int r;
+        SocketAddress addr = {
+                .size = sizeof(union sockaddr_union),
+                .type = SOCK_STREAM,
+        };
+        int family;
+
+        r = getsockname(fd, &addr.sockaddr.sa, &addr.size);
+        if (r < 0) {
+                log_warning("Failed to query socket on fd:%d: %m", fd);
+                return 0;
+        }
+
+        family = socket_address_family(&addr);
+        switch(family) {
+        case AF_INET:
+        case AF_INET6: {
+                char* _cleanup_free_ a = NULL;
+                r = socket_address_print(&addr, &a);
+                if (r < 0)
+                        log_warning("socket_address_print(): %s", strerror(-r));
+                else
+                        log_info("%s %s address %s",
+                                 desc,
+                                 family == AF_INET ? "IP" : "IPv6",
+                                 a);
+                break;
+        }
+        default:
+                log_warning("Connection with unknown family %d", family);
+        }
+
+        return 0;
+}
+
+static int open_sockets(int *epoll_fd, bool accept) {
+        int n, fd;
+        int count = 0;
+        char **address;
+
+        n = sd_listen_fds(true);
+        if (n < 0) {
+                log_error("Failed to read listening file descriptors from environment: %s",
+                          strerror(-n));
+                return n;
+        }
+        log_info("Received %d descriptors", n);
+
+        for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
+                log_debug("Received descriptor fd:%d", fd);
+                print_socket("Listening on", fd);
+
+                if (!arg_accept) {
+                        int r = set_nocloexec(fd);
+                        if (r < 0)
+                                return r;
+                }
+
+                count ++;
+        }
+
+        STRV_FOREACH(address, arg_listen) {
+                log_info("Opening address %s", *address);
+
+                fd = make_socket_fd(*address, SOCK_STREAM | (arg_accept*SOCK_CLOEXEC));
+                if (fd < 0) {
+                        log_error("Failed to open '%s': %s", *address, strerror(-fd));
+                        return fd;
+                }
+
+                count ++;
+        }
+
+        *epoll_fd = epoll_create1(EPOLL_CLOEXEC);
+        if (*epoll_fd < 0) {
+                log_error("Failed to create epoll object: %m");
+                return -errno;
+        }
+
+        for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) {
+                int r = add_epoll(*epoll_fd, fd);
+                if (r < 0)
+                        return r;
+        }
+
+        return count;
+}
+
+static int launch(char* name, char **argv, char **environ, int fds) {
+        unsigned n_env = 0;
+        char* envp[7] = {NULL}; /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID */
+        static const char* tocopy[] = {"TERM=", "PATH=", "USER=", "HOME="};
+        char _cleanup_free_ *tmp = NULL;
+        unsigned i;
+
+        for (i = 0; i < ELEMENTSOF(tocopy); i++) {
+                envp[n_env] = strv_find_prefix(environ, tocopy[i]);
+                if (envp[n_env])
+                        n_env ++;
+        }
+
+        if ((asprintf((char**)(envp + n_env++), "LISTEN_FDS=%d", fds) < 0) ||
+            (asprintf((char**)(envp + n_env++), "LISTEN_PID=%d", getpid()) < 0))
+                return log_oom();
+
+        tmp = strv_join(argv, " ");
+        if (!tmp)
+                return log_oom();
+
+        log_info("Execing %s (%s)", name, tmp);
+        execvpe(name, argv, envp);
+        log_error("Failed to execp %s (%s): %m", name, tmp);
+        return -errno;
+}
+
+static int launch1(const char* child, char** argv, char **environ, int fd) {
+        pid_t parent_pid, child_pid;
+        int r;
+
+        char _cleanup_free_ *tmp = NULL;
+        tmp = strv_join(argv, " ");
+        if (!tmp)
+                return log_oom();
+
+        parent_pid = getpid();
+
+        child_pid = fork();
+        if (child_pid < 0) {
+                log_error("Failed to fork: %m");
+                return -errno;
+        }
+
+        /* In the child */
+        if (child_pid == 0) {
+                r = dup2(fd, STDIN_FILENO);
+                if (r < 0) {
+                        log_error("Failed to dup connection to stdin: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                r = dup2(fd, STDOUT_FILENO);
+                if (r < 0) {
+                        log_error("Failed to dup connection to stdout: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                r = close(fd);
+                if (r < 0) {
+                        log_error("Failed to close dupped connection: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                /* Make sure the child goes away when the parent dies */
+                if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
+                        _exit(EXIT_FAILURE);
+
+                /* Check whether our parent died before we were able
+                 * to set the death signal */
+                if (getppid() != parent_pid)
+                        _exit(EXIT_SUCCESS);
+
+                execvp(child, argv);
+                log_error("Failed to exec child %s: %m", child);
+                _exit(EXIT_FAILURE);
+        }
+
+        log_info("Spawned %s (%s) as PID %d", child, tmp, child_pid);
+
+        return 0;
+}
+
+static int do_accept(const char* name, char **argv, char **envp, int fd) {
+        SocketAddress addr = {
+                .size = sizeof(union sockaddr_union),
+                .type = SOCK_STREAM,
+        };
+        int fd2, r;
+
+        fd2 = accept(fd, &addr.sockaddr.sa, &addr.size);
+        if (fd2 < 0) {
+                log_error("Failed to accept connection on fd:%d: %m", fd);
+                return fd2;
+        }
+
+        print_socket("Connection from", fd2);
+
+        r = launch1(name, argv, envp, fd2);
+        return r;
+}
+
+/* SIGCHLD handler. */
+static void sigchld_hdl(int sig, siginfo_t *t, void *data)
+{
+        log_info("Child %d died with code %d", t->si_pid, t->si_status);
+       /* Wait for a dead child. */
+       waitpid(t->si_pid, NULL, 0);
+}
+
+static int install_chld_handler(void) {
+        int r;
+       struct sigaction act;
+        zero(act);
+        act.sa_flags = SA_SIGINFO;
+        act.sa_sigaction = sigchld_hdl;
+
+        r = sigaction(SIGCHLD, &act, 0);
+        if (r < 0)
+                log_error("Failed to install SIGCHLD handler: %m");
+        return r;
+}
+
+static int help(void) {
+        printf("%s [OPTIONS...]\n\n"
+               "Listen on sockets and launch child on connection.\n\n"
+               "Options:\n"
+               "  -l --listen=ADDR     Listen for raw connections at ADDR\n"
+               "  -a --accept          Spawn separate child for each connection\n"
+               "  -h --help            Show this help and exit\n"
+               "  --version            Print version string and exit\n"
+               "\n"
+               "Note: file descriptors from sd_listen_fds() will be passed through.\n"
+               , program_invocation_short_name
+               );
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_VERSION = 0x100,
+        };
+
+        static const struct option options[] = {
+                { "help",         no_argument,       NULL, 'h'           },
+                { "version",      no_argument,       NULL, ARG_VERSION   },
+                { "listen",       required_argument, NULL, 'l'           },
+                { "accept",       no_argument,       NULL, 'a'           },
+                { NULL,           0,                 NULL, 0             }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "+hl:sa", options, NULL)) >= 0)
+                switch(c) {
+                case 'h':
+                        help();
+                        return 0 /* done */;
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0 /* done */;
+
+                case 'l': {
+                        int r = strv_extend(&arg_listen, optarg);
+                        if (r < 0)
+                                return r;
+
+                        break;
+                }
+
+                case 'a':
+                        arg_accept = true;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+
+        if (optind == argc) {
+                log_error("Usage: %s [OPTION...] PROGRAM [OPTION...]",
+                          program_invocation_short_name);
+                return -EINVAL;
+        }
+
+        arg_args = argv + optind;
+
+        return 1 /* work to do */;
+}
+
+int main(int argc, char **argv, char **envp) {
+        int r, n;
+        int epoll_fd = -1;
+
+        log_set_max_level(LOG_DEBUG);
+        log_show_color(true);
+        log_parse_environment();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+        r = install_chld_handler();
+        if (r < 0)
+                return EXIT_FAILURE;
+
+        n = open_sockets(&epoll_fd, arg_accept);
+        if (n < 0)
+                return EXIT_FAILURE;
+
+        while (true) {
+                struct epoll_event event;
+
+                r = epoll_wait(epoll_fd, &event, 1, -1);
+                if (r < 0) {
+                        if (errno == EINTR)
+                                continue;
+
+                        log_error("epoll_wait() failed: %m");
+                        return EXIT_FAILURE;
+                }
+
+                log_info("Communication attempt on fd:%d", event.data.fd);
+                if (arg_accept) {
+                        r = do_accept(argv[optind], argv + optind, envp,
+                                      event.data.fd);
+                        if (r < 0)
+                                return EXIT_FAILURE;
+                } else
+                        break;
+        }
+
+        launch(argv[optind], argv + optind, envp, n);
+
+        return EXIT_SUCCESS;
+}
index 1373684..2ab4d46 100644 (file)
@@ -25,8 +25,8 @@
 #include "fileio.h"
 
 int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b) {
-        int r;
         _cleanup_free_ char *path = NULL, *v = NULL;
+        int r;
 
         assert(a);
 
@@ -34,8 +34,8 @@ int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b) {
         if (!b)
                 return 0;
 
-        if (a->map_callback) {
-                r = a->map_callback(a->controller, a->name, a->value, &v);
+        if (a->semantics && a->semantics->map_write) {
+                r = a->semantics->map_write(a->semantics, a->value, &v);
                 if (r < 0)
                         return r;
         }
@@ -66,6 +66,29 @@ int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b) {
         return r;
 }
 
+bool cgroup_attribute_matches(CGroupAttribute *a, const char *controller, const char *name) {
+        assert(a);
+
+        if (controller) {
+                if (streq(a->controller, controller) && (!name || streq(a->name, name)))
+                        return true;
+
+        } else if (!name)
+                return true;
+        else if (streq(a->name, name)) {
+                size_t x, y;
+                x = strlen(a->controller);
+                y = strlen(name);
+
+                if (y > x &&
+                    memcmp(a->controller, name, x) == 0 &&
+                    name[x] == '.')
+                        return true;
+        }
+
+        return false;
+}
+
 CGroupAttribute *cgroup_attribute_find_list(
                 CGroupAttribute *first,
                 const char *controller,
@@ -74,24 +97,9 @@ CGroupAttribute *cgroup_attribute_find_list(
 
         assert(name);
 
-        LIST_FOREACH(by_unit, a, first) {
-
-
-                if (controller) {
-                        if (streq(a->controller, controller) && streq(a->name, name))
-                                return a;
-
-                } else if (streq(a->name, name)) {
-                        size_t x, y;
-                        x = strlen(a->controller);
-                        y = strlen(name);
-
-                        if (y > x &&
-                            memcmp(a->controller, name, x) == 0 &&
-                            name[x] == '.')
-                                return a;
-                }
-        }
+        LIST_FOREACH(by_unit, a, first)
+                if (cgroup_attribute_matches(a, controller, name))
+                        return a;
 
         return NULL;
 }
@@ -114,3 +122,11 @@ void cgroup_attribute_free_list(CGroupAttribute *first) {
         LIST_FOREACH_SAFE(by_unit, a, n, first)
                 cgroup_attribute_free(a);
 }
+
+void cgroup_attribute_free_some(CGroupAttribute *first, const char *controller, const char *name) {
+        CGroupAttribute *a, *n;
+
+        LIST_FOREACH_SAFE(by_unit, a, n, first)
+                if (cgroup_attribute_matches(a, controller, name))
+                        cgroup_attribute_free(a);
+}
index 0f5b854..0b54298 100644 (file)
 
 typedef struct CGroupAttribute CGroupAttribute;
 
-typedef int (*CGroupAttributeMapCallback)(const char *controller, const char*name, const char *value, char **ret);
-
 #include "unit.h"
 #include "cgroup.h"
+#include "cgroup-semantics.h"
 
 struct CGroupAttribute {
         char *controller;
@@ -35,7 +34,7 @@ struct CGroupAttribute {
 
         Unit *unit;
 
-        CGroupAttributeMapCallback map_callback;
+        const CGroupSemantics *semantics;
 
         LIST_FIELDS(CGroupAttribute, by_unit);
 };
@@ -43,7 +42,9 @@ struct CGroupAttribute {
 int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b);
 int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b);
 
+bool cgroup_attribute_matches(CGroupAttribute *a, const char *controller, const char *name);
 CGroupAttribute *cgroup_attribute_find_list(CGroupAttribute *first, const char *controller, const char *name);
 
 void cgroup_attribute_free(CGroupAttribute *a);
 void cgroup_attribute_free_list(CGroupAttribute *first);
+void cgroup_attribute_free_some(CGroupAttribute *first, const char *controller, const char *name);
diff --git a/src/core/cgroup-semantics.c b/src/core/cgroup-semantics.c
new file mode 100644 (file)
index 0000000..82b02bb
--- /dev/null
@@ -0,0 +1,333 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "util.h"
+#include "strv.h"
+#include "path-util.h"
+#include "cgroup-util.h"
+
+#include "cgroup-semantics.h"
+
+static int parse_cpu_shares(const CGroupSemantics *s, const char *value, char **ret) {
+        unsigned long ul;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        if (safe_atolu(value, &ul) < 0 || ul < 1)
+                return -EINVAL;
+
+        if (asprintf(ret, "%lu", ul) < 0)
+                return -ENOMEM;
+
+        return 1;
+}
+
+static int parse_memory_limit(const CGroupSemantics *s, const char *value, char **ret) {
+        off_t sz;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        if (parse_bytes(value, &sz) < 0 || sz <= 0)
+                return -EINVAL;
+
+        if (asprintf(ret, "%llu", (unsigned long long) sz) < 0)
+                return -ENOMEM;
+
+        return 1;
+}
+
+static int parse_device(const CGroupSemantics *s, const char *value, char **ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        char *x;
+        unsigned k;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        l = strv_split_quoted(value);
+        if (!l)
+                return -ENOMEM;
+
+        k = strv_length(l);
+        if (k < 1 || k > 2)
+                return -EINVAL;
+
+        if (!streq(l[0], "*") && !path_startswith(l[0], "/dev"))
+                return -EINVAL;
+
+        if (!isempty(l[1]) && !in_charset(l[1], "rwm"))
+                return -EINVAL;
+
+        x = strdup(value);
+        if (!x)
+                return -ENOMEM;
+
+        *ret = x;
+        return 1;
+}
+
+static int parse_blkio_weight(const CGroupSemantics *s, const char *value, char **ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        unsigned long ul;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        l = strv_split_quoted(value);
+        if (!l)
+                return -ENOMEM;
+
+        if (strv_length(l) != 1)
+                return 0; /* Returning 0 will cause parse_blkio_weight_device() be tried instead */
+
+        if (safe_atolu(l[0], &ul) < 0 || ul < 10 || ul > 1000)
+                return -EINVAL;
+
+        if (asprintf(ret, "%lu", ul) < 0)
+                return -ENOMEM;
+
+        return 1;
+}
+
+static int parse_blkio_weight_device(const CGroupSemantics *s, const char *value, char **ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        unsigned long ul;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        l = strv_split_quoted(value);
+        if (!l)
+                return -ENOMEM;
+
+        if (strv_length(l) != 2)
+                return -EINVAL;
+
+        if (!path_startswith(l[0], "/dev"))
+                return -EINVAL;
+
+        if (safe_atolu(l[1], &ul) < 0 || ul < 10 || ul > 1000)
+                return -EINVAL;
+
+        if (asprintf(ret, "%s %lu", l[0], ul) < 0)
+                return -ENOMEM;
+
+        return 1;
+}
+
+static int parse_blkio_bandwidth(const CGroupSemantics *s, const char *value, char **ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        off_t bytes;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        l = strv_split_quoted(value);
+        if (!l)
+                return -ENOMEM;
+
+        if (strv_length(l) != 2)
+                return -EINVAL;
+
+        if (!path_startswith(l[0], "/dev")) {
+                return -EINVAL;
+        }
+
+        if (parse_bytes(l[1], &bytes) < 0 || bytes <= 0)
+                return -EINVAL;
+
+        if (asprintf(ret, "%s %llu", l[0], (unsigned long long) bytes) < 0)
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int map_device(const CGroupSemantics *s, const char *value, char **ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        unsigned k;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        l = strv_split_quoted(value);
+        if (!l)
+                return -ENOMEM;
+
+        k = strv_length(l);
+        if (k < 1 || k > 2)
+                return -EINVAL;
+
+        if (streq(l[0], "*")) {
+
+                if (asprintf(ret, "a *:*%s%s",
+                             isempty(l[1]) ? "" : " ", strempty(l[1])) < 0)
+                        return -ENOMEM;
+        } else {
+                struct stat st;
+
+                if (stat(l[0], &st) < 0) {
+                        log_warning("Couldn't stat device %s", l[0]);
+                        return -errno;
+                }
+
+                if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
+                        log_warning("%s is not a device.", l[0]);
+                        return -ENODEV;
+                }
+
+                if (asprintf(ret, "%c %u:%u%s%s",
+                             S_ISCHR(st.st_mode) ? 'c' : 'b',
+                             major(st.st_rdev), minor(st.st_rdev),
+                             isempty(l[1]) ? "" : " ", strempty(l[1])) < 0)
+                        return -ENOMEM;
+        }
+
+        return 0;
+}
+
+static int map_blkio(const CGroupSemantics *s, const char *value, char **ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        struct stat st;
+        dev_t d;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        l = strv_split_quoted(value);
+        if (!l)
+                return log_oom();
+
+        if (strv_length(l) != 2)
+                return -EINVAL;
+
+        if (stat(l[0], &st) < 0) {
+                log_warning("Couldn't stat device %s", l[0]);
+                return -errno;
+        }
+
+        if (S_ISBLK(st.st_mode))
+                d = st.st_rdev;
+        else if (major(st.st_dev) != 0) {
+                /* If this is not a device node then find the block
+                 * device this file is stored on */
+                d = st.st_dev;
+
+                /* If this is a partition, try to get the originating
+                 * block device */
+                block_get_whole_disk(d, &d);
+        } else {
+                log_warning("%s is not a block device and file system block device cannot be determined or is not local.", l[0]);
+                return -ENODEV;
+        }
+
+        if (asprintf(ret, "%u:%u %s", major(d), minor(d), l[1]) < 0)
+                return -ENOMEM;
+
+        return 0;
+}
+
+static const CGroupSemantics semantics[] = {
+        { "cpu",     "cpu.shares",                 "CPUShare",              false, parse_cpu_shares,          NULL,       NULL },
+        { "memory",  "memory.soft_limit_in_bytes", "MemorySoftLimit",       false, parse_memory_limit,        NULL,       NULL },
+        { "memory",  "memory.limit_in_bytes",      "MemoryLimit",           false, parse_memory_limit,        NULL,       NULL },
+        { "devices", "devices.allow",              "DeviceAllow",           true,  parse_device,              map_device, NULL },
+        { "devices", "devices.deny",               "DeviceDeny",            true,  parse_device,              map_device, NULL },
+        { "blkio",   "blkio.weight",               "BlockIOWeight",         false, parse_blkio_weight,        NULL,       NULL },
+        { "blkio",   "blkio.weight_device",        "BlockIOWeight",         true,  parse_blkio_weight_device, map_blkio,  NULL },
+        { "blkio",   "blkio.read_bps_device",      "BlockIOReadBandwidth",  true,  parse_blkio_bandwidth,     map_blkio,  NULL },
+        { "blkio",   "blkio.write_bps_device",     "BlockIOWriteBandwidth", true,  parse_blkio_bandwidth,     map_blkio,  NULL }
+};
+
+int cgroup_semantics_find(
+                const char *controller,
+                const char *name,
+                const char *value,
+                char **ret,
+                const CGroupSemantics **_s) {
+
+        _cleanup_free_ char *c = NULL;
+        unsigned i;
+        int r;
+
+        assert(name);
+        assert(_s);
+        assert(!value == !ret);
+
+        if (!controller) {
+                r = cg_controller_from_attr(name, &c);
+                if (r < 0)
+                        return r;
+
+                controller = c;
+        }
+
+        for (i = 0; i < ELEMENTSOF(semantics); i++) {
+                const CGroupSemantics *s = semantics + i;
+                bool matches_name, matches_pretty;
+
+                if (controller && s->controller && !streq(s->controller, controller))
+                        continue;
+
+                matches_name = s->name && streq(s->name, name);
+                matches_pretty = s->pretty && streq(s->pretty, name);
+
+                if (!matches_name && !matches_pretty)
+                        continue;
+
+                if (value) {
+                        if (matches_pretty && s->map_pretty) {
+
+                                r = s->map_pretty(s, value, ret);
+                                if (r < 0)
+                                        return r;
+
+                                if (r == 0)
+                                        continue;
+
+                        } else {
+                                char *x;
+
+                                x = strdup(value);
+                                if (!x)
+                                        return -ENOMEM;
+
+                                *ret = x;
+                        }
+                }
+
+                *_s = s;
+                return 1;
+        }
+
+        *ret = NULL;
+        *_s = NULL;
+        return 0;
+}
diff --git a/src/core/cgroup-semantics.h b/src/core/cgroup-semantics.h
new file mode 100644 (file)
index 0000000..4f848f4
--- /dev/null
@@ -0,0 +1,43 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2011 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct CGroupSemantics CGroupSemantics;
+
+struct CGroupSemantics {
+        const char *controller;
+        const char *name;
+        const char *pretty;
+
+        bool multiple;
+
+        /* This call is used for parsing the pretty value to the actual attribute value */
+        int (*map_pretty)(const CGroupSemantics *semantics, const char *value, char **ret);
+
+        /* Right before writing this attribute the attribute value is converted to a low-level value */
+        int (*map_write)(const CGroupSemantics *semantics, const char *value, char **ret);
+
+        /* If this attribute takes a list, this call can be used to reset the list to empty */
+        int (*reset)(const CGroupSemantics *semantics, const char *group);
+};
+
+int cgroup_semantics_find(const char *controller, const char *name, const char *value, char **ret, const CGroupSemantics **semantics);
index de23369..8f4bbc5 100644 (file)
         "  <method name=\"ResetFailedUnit\">\n"                         \
         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"                                                 \
-        "  <method name=\"GetUnitControlGroupAttributes\">\n"           \
+        "  <method name=\"SetUnitControlGroup\">\n"                     \
         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
-        "   <arg name=\"attributes\" type=\"as\" direction=\"in\"/>\n"  \
-        "   <arg name=\"values\" type=\"as\" direction=\"out\"/>\n"      \
+        "   <arg name=\"group\" type=\"s\" direction=\"in\"/>\n"        \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"                                                 \
-        "  <method name=\"SetUnitControlGroupAttributes\">\n"           \
+        "  <method name=\"UnsetUnitControlGroup\">\n"                   \
         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
-        "   <arg name=\"attributes\" type=\"a(sss)\" direction=\"in\"/>\n" \
+        "   <arg name=\"group\" type=\"s\" direction=\"in\"/>\n"        \
         "   <arg name=\"mode\" type=\"s\" direction=\"in\"\n/>"         \
         "  </method>\n"                                                 \
-        "  <method name=\"UnsetUnitControlGroupAttributes\">\n"         \
+        "  <method name=\"GetUnitControlGroupAttribute\">\n"            \
         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
-        "   <arg name=\"attributes\" type=\"a(ss)\" direction=\"in\"/>\n" \
-        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n"    \
+        "   <arg name=\"values\" type=\"as\" direction=\"out\"/>\n"     \
         "  </method>\n"                                                 \
-        "  <method name=\"SetUnitControlGroups\">\n"                    \
+        "  <method name=\"SetUnitControlGroupAttribute\">\n"            \
         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
-        "   <arg name=\"groups\" type=\"as\" direction=\"in\"/>\n"      \
-        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n"    \
+        "   <arg name=\"values\" type=\"as\" direction=\"in\"/>\n"      \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"\n/>"         \
         "  </method>\n"                                                 \
-        "  <method name=\"UnsetUnitControlGroups\">\n"                  \
+        "  <method name=\"UnsetUnitControlGroupAttributes\">\n"         \
         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
-        "   <arg name=\"groups\" type=\"as\" direction=\"in\"/>\n"      \
-        "   <arg name=\"mode\" type=\"s\" direction=\"in\"\n/>"         \
+        "   <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n"    \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"                                                 \
         "  <method name=\"GetJob\">\n"                                  \
         "   <arg name=\"id\" type=\"u\" direction=\"in\"/>\n"           \
@@ -874,7 +875,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 if (!reply)
                         goto oom;
 
-        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroups")) {
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroup")) {
                 const char *name;
                 Unit *u;
                 DBusMessageIter iter;
@@ -902,7 +903,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 if (!reply)
                         goto oom;
 
-        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroups")) {
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroup")) {
                 const char *name;
                 Unit *u;
                 DBusMessageIter iter;
@@ -930,7 +931,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 if (!reply)
                         goto oom;
 
-        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroupAttributes")) {
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroupAttribute")) {
                 const char *name;
                 Unit *u;
                 DBusMessageIter iter;
@@ -949,6 +950,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 }
 
                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start");
+
                 r = bus_unit_cgroup_attribute_set(u, &iter);
                 if (r < 0)
                         return bus_send_error_reply(connection, message, NULL, r);
@@ -957,7 +959,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 if (!reply)
                         goto oom;
 
-        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroupAttributes")) {
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroupAttribute")) {
                 const char *name;
                 Unit *u;
                 DBusMessageIter iter;
@@ -985,7 +987,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 if (!reply)
                         goto oom;
 
-        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitControlGroupAttributes")) {
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitControlGroupAttribute")) {
                 const char *name;
                 Unit *u;
                 DBusMessageIter iter;
@@ -1005,6 +1007,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 }
 
                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "status");
+
                 r = bus_unit_cgroup_attribute_get(u, &iter, &list);
                 if (r < 0)
                         return bus_send_error_reply(connection, message, NULL, r);
index 4f968c2..7c23e1e 100644 (file)
@@ -344,8 +344,8 @@ static int bus_unit_append_cgroup_attrs(DBusMessageIter *i, const char *property
                 char _cleanup_free_ *v = NULL;
                 bool success;
 
-                if (a->map_callback)
-                        a->map_callback(a->controller, a->name, a->value, &v);
+                if (a->semantics && a->semantics->map_write)
+                        a->semantics->map_write(a->semantics, a->value, &v);
 
                 success =
                         dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) &&
@@ -472,7 +472,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
                 if (!reply)
                         goto oom;
 
-        } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroups")) {
+        } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroup")) {
                 DBusMessageIter iter;
 
                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start");
@@ -488,7 +488,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
                 if (!reply)
                         goto oom;
 
-        } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroups")) {
+        } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroup")) {
                 DBusMessageIter iter;
 
                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop");
@@ -496,14 +496,14 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
                 if (!dbus_message_iter_init(message, &iter))
                         goto oom;
 
-                r = bus_unit_cgroup_set(u, &iter);
+                r = bus_unit_cgroup_unset(u, &iter);
                 if (r < 0)
                         return bus_send_error_reply(connection, message, NULL, r);
 
                 reply = dbus_message_new_method_return(message);
                 if (!reply)
                         goto oom;
-        } else if (streq_ptr(dbus_message_get_member(message), "GetControlGroupAttributes")) {
+        } else if (streq_ptr(dbus_message_get_member(message), "GetControlGroupAttribute")) {
                 DBusMessageIter iter;
                 _cleanup_strv_free_ char **list = NULL;
 
@@ -524,7 +524,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
                 if (bus_append_strv_iter(&iter, list) < 0)
                         goto oom;
 
-        } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroupAttributes")) {
+        } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroupAttribute")) {
                 DBusMessageIter iter;
 
                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start");
@@ -540,7 +540,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
                 if (!reply)
                         goto oom;
 
-        } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroupAttributes")) {
+        } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroupAttribute")) {
                 DBusMessageIter iter;
 
                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop");
@@ -897,17 +897,17 @@ oom:
         return DBUS_HANDLER_RESULT_NEED_MEMORY;
 }
 
-static int next_and_parse_mode(DBusMessageIter *iter, bool *runtime) {
+static int parse_mode(DBusMessageIter *iter, bool *runtime, bool next) {
         const char *mode;
+        int r;
 
         assert(iter);
         assert(runtime);
 
-        dbus_message_iter_next(iter);
-        if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
-                return -EINVAL;
+        r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &mode, next);
+        if (r < 0)
+                return r;
 
-        dbus_message_iter_get_basic(iter, &mode);
         if (streq(mode, "runtime"))
                 *runtime = true;
         else if (streq(mode, "persistent"))
@@ -919,10 +919,11 @@ static int next_and_parse_mode(DBusMessageIter *iter, bool *runtime) {
 }
 
 int bus_unit_cgroup_set(Unit *u, DBusMessageIter *iter) {
-        int r;
-        _cleanup_strv_free_ char **a = NULL;
-        char **name;
+        _cleanup_free_ char *controller = NULL, *old_path = NULL, *new_path = NULL, *contents = NULL;
+        const char *name;
+        CGroupBonding *b;
         bool runtime;
+        int r;
 
         assert(u);
         assert(iter);
@@ -930,60 +931,74 @@ int bus_unit_cgroup_set(Unit *u, DBusMessageIter *iter) {
         if (!unit_get_exec_context(u))
                 return -EINVAL;
 
-        r = bus_parse_strv_iter(iter, &a);
+        r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true);
         if (r < 0)
                 return r;
 
-        r = next_and_parse_mode(iter, &runtime);
+        r = parse_mode(iter, &runtime, false);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(name, a) {
-                _cleanup_free_ char *controller = NULL, *old_path = NULL, *new_path = NULL, *contents = NULL;
-                CGroupBonding *b;
-
-                r = cg_split_spec(*name, &controller, &new_path);
-                if (r < 0)
-                        return r;
+        r = cg_split_spec(name, &controller, &new_path);
+        if (r < 0)
+                return r;
 
-                b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
-                if (b) {
-                        old_path = strdup(b->path);
-                        if (!old_path)
-                                return -ENOMEM;
-                }
+        if (!new_path) {
+                new_path = unit_default_cgroup_path(u);
+                if (!new_path)
+                        return -ENOMEM;
+        }
 
-                r = unit_add_cgroup_from_text(u, *name, true, &b);
-                if (r < 0)
-                        return r;
+        if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER))
+                return -EINVAL;
 
-                if (r > 0) {
-                        /* Try to move things to the new place, and clean up the old place */
-                        cgroup_bonding_realize(b);
-                        cgroup_bonding_migrate(b, u->cgroup_bondings);
+        b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
+        if (b) {
+                if (streq(b->path, new_path))
+                        return 0;
 
-                        if (old_path)
-                                cg_trim(controller, old_path, true);
-                }
+                if (b->essential)
+                        return -EINVAL;
 
-                contents = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n"
-                                   "ControlGroup=", *name, "\n", NULL);
-                if (!contents)
+                old_path = strdup(b->path);
+                if (!old_path)
                         return -ENOMEM;
+        }
 
-                r = unit_write_drop_in(u, runtime, *name, contents);
-                if (r < 0)
-                        return r;
+        r = unit_add_cgroup_from_text(u, name, true, &b);
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                CGroupAttribute *a;
+
+                /* Try to move things to the new place, and clean up the old place */
+                cgroup_bonding_realize(b);
+                cgroup_bonding_migrate(b, u->cgroup_bondings);
+
+                if (old_path)
+                        cg_trim(controller, old_path, true);
+
+                /* Apply the attributes to the new group */
+                LIST_FOREACH(by_unit, a, u->cgroup_attributes)
+                        if (streq(a->controller, controller))
+                                cgroup_attribute_apply(a, b);
         }
 
-        return 0;
+        contents = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n"
+                           "ControlGroup=", name, "\n", NULL);
+        if (!contents)
+                return -ENOMEM;
+
+        return unit_write_drop_in(u, runtime, controller, contents);
 }
 
 int bus_unit_cgroup_unset(Unit *u, DBusMessageIter *iter) {
-        _cleanup_strv_free_ char **a = NULL;
-        char **name;
-        int r;
+        _cleanup_free_ char *controller = NULL, *path = NULL, *target = NULL;
+        const char *name;
+        CGroupAttribute *a, *n;
+        CGroupBonding *b;
         bool runtime;
+        int r;
 
         assert(u);
         assert(iter);
@@ -991,50 +1006,57 @@ int bus_unit_cgroup_unset(Unit *u, DBusMessageIter *iter) {
         if (!unit_get_exec_context(u))
                 return -EINVAL;
 
-        r = bus_parse_strv_iter(iter, &a);
+        r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true);
         if (r < 0)
                 return r;
 
-        r = next_and_parse_mode(iter, &runtime);
+        r = parse_mode(iter, &runtime, false);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(name, a) {
-                _cleanup_free_ char *controller = NULL, *path = NULL, *target = NULL;
-                CGroupBonding *b;
+        r = cg_split_spec(name, &controller, &path);
+        if (r < 0)
+                return r;
 
-                r = cg_split_spec(*name, &controller, &path);
-                if (r < 0)
-                        return r;
+        if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER))
+                return -EINVAL;
 
-                if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER))
-                        return -EINVAL;
+        b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
+        if (!b)
+                return -ENOENT;
 
-                unit_remove_drop_in(u, runtime, *name);
+        if (path && !path_equal(path, b->path))
+                return -ENOENT;
 
-                b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
-                if (!b)
-                        continue;
+        if (b->essential)
+                return -EINVAL;
 
-                if (path && !path_equal(path, b->path))
-                        continue;
+        unit_remove_drop_in(u, runtime, controller);
 
-                if (b->essential)
-                        return -EINVAL;
+        /* Try to migrate the old group away */
+        if (cg_get_by_pid(controller, 0, &target) >= 0)
+                cgroup_bonding_migrate_to(u->cgroup_bondings, target, false);
+
+        cgroup_bonding_free(b, true);
 
-                /* Try to migrate the old group away */
-                if (cg_get_by_pid(controller, 0, &target) >= 0)
-                        cgroup_bonding_migrate_to(u->cgroup_bondings, target, false);
+        /* Drop all attributes of this controller */
+        LIST_FOREACH_SAFE(by_unit, a, n, u->cgroup_attributes) {
+                if (!streq(a->controller, controller))
+                        continue;
 
-                cgroup_bonding_free(b, true);
+                unit_remove_drop_in(u, runtime, a->name);
+                cgroup_attribute_free(a);
         }
 
         return 0;
 }
 
 int bus_unit_cgroup_attribute_get(Unit *u, DBusMessageIter *iter, char ***_result) {
-        _cleanup_strv_free_ char **l = NULL, **result = NULL;
-        char **name;
+        _cleanup_free_ char *controller = NULL;
+        CGroupAttribute *a;
+        CGroupBonding *b;
+        const char *name;
+        char **l = NULL;
         int r;
 
         assert(u);
@@ -1044,63 +1066,99 @@ int bus_unit_cgroup_attribute_get(Unit *u, DBusMessageIter *iter, char ***_resul
         if (!unit_get_exec_context(u))
                 return -EINVAL;
 
-        r = bus_parse_strv_iter(iter, &l);
+        r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, false);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(name, l) {
-                _cleanup_free_ char *controller = NULL;
-                CGroupAttribute *a;
-                CGroupBonding *b;
+        r = cg_controller_from_attr(name, &controller);
+        if (r < 0)
+                return r;
+
+        /* First attempt, read the value from the kernel */
+        b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
+        if (b) {
+                _cleanup_free_ char *p = NULL, *v = NULL;
 
-                r = cg_controller_from_attr(*name, &controller);
+                r = cg_get_path(b->controller, b->path, name, &p);
                 if (r < 0)
                         return r;
 
-                /* First attempt, read the value from the kernel */
-                b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
-                if (b) {
-                        _cleanup_free_ char *p = NULL, *v = NULL;
+                r = read_full_file(p, &v, NULL);
+                if (r >= 0) {
+                        /* Split on new lines */
+                        l = strv_split_newlines(v);
+                        if (!l)
+                                return -ENOMEM;
 
-                        r = cg_get_path(b->controller, b->path, *name, &p);
-                        if (r < 0)
-                                return r;
+                        *_result = l;
+                        return 0;
 
-                        r = read_full_file(p, &v, NULL);
-                        if (r >= 0) {
-                                r = strv_extend(&result, v);
-                                if (r < 0)
-                                        return r;
-
-                                continue;
-                        } else if (r != -ENOENT)
-                                return r;
                 }
+        }
 
-                /* If that didn't work, read our cached value */
-                a = cgroup_attribute_find_list(u->cgroup_attributes, NULL, *name);
-                if (a) {
-                        r = strv_extend(&result, a->value);
-                        if (r < 0)
-                                return r;
+        /* If that didn't work, read our cached value */
+        LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
 
+                if (!cgroup_attribute_matches(a, controller, name))
                         continue;
-                }
 
-                return -ENOENT;
+                r = strv_extend(&l, a->value);
+                if (r < 0) {
+                        strv_free(l);
+                        return r;
+                }
         }
 
-        *_result = result;
-        result = NULL;
+        if (!l)
+                return -ENOENT;
 
+        *_result = l;
         return 0;
 }
 
+static int update_attribute_drop_in(Unit *u, bool runtime, const char *name) {
+        _cleanup_free_ char *buf = NULL;
+        CGroupAttribute *a;
+
+        assert(u);
+        assert(name);
+
+        LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
+                if (!cgroup_attribute_matches(a, NULL, name))
+                        continue;
+
+                if (!buf) {
+                        buf = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n"
+                                      "ControlGroupAttribute=", a->name, " ", a->value, "\n", NULL);
+
+                        if (!buf)
+                                return -ENOMEM;
+                } else {
+                        char *b;
+
+                        b = strjoin(buf,
+                                    "ControlGroupAttribute=", a->name, " ", a->value, "\n", NULL);
+
+                        if (!b)
+                                return -ENOMEM;
+
+                        free(buf);
+                        buf = b;
+                }
+        }
+
+        if (buf)
+                return unit_write_drop_in(u, runtime, name, buf);
+        else
+                return unit_remove_drop_in(u, runtime, name);
+}
+
 int bus_unit_cgroup_attribute_set(Unit *u, DBusMessageIter *iter) {
         _cleanup_strv_free_ char **l = NULL;
         int r;
         bool runtime = false;
-        char **name, **value;
+        char **value;
+        const char *name;
 
         assert(u);
         assert(iter);
@@ -1108,19 +1166,34 @@ int bus_unit_cgroup_attribute_set(Unit *u, DBusMessageIter *iter) {
         if (!unit_get_exec_context(u))
                 return -EINVAL;
 
-        r = bus_parse_strv_pairs_iter(iter, &l);
+        r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true);
+        if (r < 0)
+                return r;
+
+        r = bus_parse_strv_iter(iter, &l);
         if (r < 0)
                 return r;
 
-        r = next_and_parse_mode(iter, &runtime);
+        if (!dbus_message_iter_next(iter))
+                return -EINVAL;
+
+        r = parse_mode(iter, &runtime, false);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH_PAIR(name, value, l) {
-                _cleanup_free_ char *contents = NULL;
+        STRV_FOREACH(value, l) {
+                _cleanup_free_ char *v = NULL;
                 CGroupAttribute *a;
+                const CGroupSemantics *s;
+
+                r = cgroup_semantics_find(NULL, name, *value, &v, &s);
+                if (r < 0)
+                        return r;
+
+                if (s && !s->multiple && l[1])
+                        return -EINVAL;
 
-                r = unit_add_cgroup_attribute(u, NULL, *name, *value, NULL, &a);
+                r = unit_add_cgroup_attribute(u, s, NULL, name, v ? v : *value, &a);
                 if (r < 0)
                         return r;
 
@@ -1144,22 +1217,17 @@ int bus_unit_cgroup_attribute_set(Unit *u, DBusMessageIter *iter) {
                         cgroup_attribute_apply(a, u->cgroup_bondings);
                 }
 
-                contents = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n"
-                                   "ControlGroupAttribute=", *name, " ", *value, "\n", NULL);
-                if (!contents)
-                        return -ENOMEM;
-
-                r = unit_write_drop_in(u, runtime, *name, contents);
-                if (r < 0)
-                        return r;
         }
 
+        r = update_attribute_drop_in(u, runtime, name);
+        if (r < 0)
+                return r;
+
         return 0;
 }
 
 int bus_unit_cgroup_attribute_unset(Unit *u, DBusMessageIter *iter) {
-        _cleanup_strv_free_ char **l = NULL;
-        char **name;
+        const char *name;
         bool runtime;
         int r;
 
@@ -1169,23 +1237,16 @@ int bus_unit_cgroup_attribute_unset(Unit *u, DBusMessageIter *iter) {
         if (!unit_get_exec_context(u))
                 return -EINVAL;
 
-        r = bus_parse_strv_iter(iter, &l);
+        r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true);
         if (r < 0)
                 return r;
 
-        r = next_and_parse_mode(iter, &runtime);
+        r = parse_mode(iter, &runtime, false);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(name, l) {
-                CGroupAttribute *a;
-
-                a = cgroup_attribute_find_list(u->cgroup_attributes, NULL, *name);
-                if (a)
-                        cgroup_attribute_free(a);
-
-                unit_remove_drop_in(u, runtime, *name);
-        }
+        cgroup_attribute_free_some(u->cgroup_attributes, NULL, name);
+        update_attribute_drop_in(u, runtime, name);
 
         return 0;
 }
index c8903f8..34980b0 100644 (file)
         "  <property name=\"DefaultControlGroup\" type=\"s\" access=\"read\"/>\n" \
         "  <property name=\"ControlGroups\" type=\"as\" access=\"read\"/>\n" \
         "  <property name=\"ControlGroupAttributes\" type=\"a(sss)\" access=\"read\"/>\n" \
-        "  <method name=\"GetControlGroupAttributes\">\n"               \
-        "   <arg name=\"attributes\" type=\"as\" direction=\"in\"/>\n"  \
-        "   <arg name=\"values\" type=\"as\" direction=\"out\"/>\n"     \
-        "  </method>\n"                                                 \
-        "  <method name=\"SetControlGroupAttributes\">\n"               \
-        "   <arg name=\"attributes\" type=\"a(ss)\" direction=\"in\"/>\n" \
+        "  <method name=\"SetControlGroup\">\n"                         \
+        "   <arg name=\"group\" type=\"s\" direction=\"in\"/>\n"        \
         "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"                                                 \
-        "  <method name=\"UnsetControlGroupAttributes\">\n"             \
-        "   <arg name=\"attributes\" type=\"as\" direction=\"in\"/>\n"  \
+        "  <method name=\"UnsetControlGroup\">\n"                       \
+        "   <arg name=\"group\" type=\"s\" direction=\"in\"/>\n"        \
         "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"                                                 \
-        "  <method name=\"SetControlGroups\">\n"                        \
-        "   <arg name=\"groups\" type=\"as\" direction=\"in\"/>\n"      \
+        "  <method name=\"GetControlGroupAttribute\">\n"                \
+        "   <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n"    \
+        "   <arg name=\"values\" type=\"as\" direction=\"out\"/>\n"     \
+        "  </method>\n"                                                 \
+        "  <method name=\"SetControlGroupAttribute\">\n"                \
+        "   <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n"    \
+        "   <arg name=\"values\" type=\"as\" direction=\"in\"/>\n"      \
         "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"                                                 \
-        "  <method name=\"UnsetControlGroups\">\n"                      \
-        "   <arg name=\"groups\" type=\"as\" direction=\"in\"/>\n"      \
+        "  <method name=\"UnsetControlGroupAttribute\">\n"              \
+        "   <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n"    \
         "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"
 
index b28962a..92cf174 100644 (file)
@@ -165,6 +165,14 @@ void exec_context_tty_reset(const ExecContext *context) {
                 vt_disallocate(context->tty_path);
 }
 
+static bool is_terminal_output(ExecOutput o) {
+        return
+                o == EXEC_OUTPUT_TTY ||
+                o == EXEC_OUTPUT_SYSLOG_AND_CONSOLE ||
+                o == EXEC_OUTPUT_KMSG_AND_CONSOLE ||
+                o == EXEC_OUTPUT_JOURNAL_AND_CONSOLE;
+}
+
 static int open_null_as(int flags, int nfd) {
         int fd, r;
 
@@ -224,7 +232,7 @@ static int connect_logger_as(const ExecContext *context, ExecOutput output, cons
                 !!context->syslog_level_prefix,
                 output == EXEC_OUTPUT_SYSLOG || output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE,
                 output == EXEC_OUTPUT_KMSG || output == EXEC_OUTPUT_KMSG_AND_CONSOLE,
-                output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || output == EXEC_OUTPUT_KMSG_AND_CONSOLE || output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE);
+                is_terminal_output(output));
 
         if (fd != nfd) {
                 r = dup2(fd, nfd) < 0 ? -errno : nfd;
@@ -1710,6 +1718,37 @@ int exec_context_load_environment(const ExecContext *c, char ***l) {
         return 0;
 }
 
+static bool tty_may_match_dev_console(const char *tty) {
+        char *active = NULL, *console;
+        bool b;
+
+        if (startswith(tty, "/dev/"))
+                tty += 5;
+
+        /* trivial identity? */
+        if (streq(tty, "console"))
+                return true;
+
+        console = resolve_dev_console(&active);
+        /* if we could not resolve, assume it may */
+        if (!console)
+                return true;
+
+        /* "tty0" means the active VC, so it may be the same sometimes */
+        b = streq(console, tty) || (streq(console, "tty0") && tty_is_vc(tty));
+        free(active);
+
+        return b;
+}
+
+bool exec_context_may_touch_console(ExecContext *ec) {
+        return (ec->tty_reset || ec->tty_vhangup || ec->tty_vt_disallocate ||
+                is_terminal_input(ec->std_input) ||
+                is_terminal_output(ec->std_output) ||
+                is_terminal_output(ec->std_error)) &&
+               tty_may_match_dev_console(tty_path(ec));
+}
+
 static void strv_fprintf(FILE *f, char **l) {
         char **g;
 
index 2bcd2e1..001eb0e 100644 (file)
@@ -198,6 +198,8 @@ void exec_context_tty_reset(const ExecContext *context);
 
 int exec_context_load_environment(const ExecContext *c, char ***l);
 
+bool exec_context_may_touch_console(ExecContext *c);
+
 void exec_status_start(ExecStatus *s, pid_t pid);
 void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status);
 void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix);
index 990607f..90de550 100644 (file)
@@ -206,6 +206,7 @@ Job* job_install(Job *j) {
                                                "Merged into running job, re-running: %s/%s as %u",
                                                uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id);
                                 uj->state = JOB_WAITING;
+                                uj->manager->n_running_jobs--;
                                 return uj;
                         }
                 }
@@ -483,7 +484,7 @@ static void job_change_type(Job *j, JobType newtype) {
 int job_run_and_invalidate(Job *j) {
         int r;
         uint32_t id;
-        Manager *m;
+        Manager *m = j->manager;
 
         assert(j);
         assert(j->installed);
@@ -500,6 +501,7 @@ int job_run_and_invalidate(Job *j) {
                 return -EAGAIN;
 
         j->state = JOB_RUNNING;
+        m->n_running_jobs++;
         job_add_to_dbus_queue(j);
 
         /* While we execute this operation the job might go away (for
@@ -508,7 +510,6 @@ int job_run_and_invalidate(Job *j) {
          * store the id here, so that we can verify the job is still
          * valid. */
         id = j->id;
-        m = j->manager;
 
         switch (j->type) {
 
@@ -558,9 +559,10 @@ int job_run_and_invalidate(Job *j) {
                         r = job_finish_and_invalidate(j, JOB_DONE, true);
                 else if (r == -ENOEXEC)
                         r = job_finish_and_invalidate(j, JOB_SKIPPED, true);
-                else if (r == -EAGAIN)
+                else if (r == -EAGAIN) {
                         j->state = JOB_WAITING;
-                else if (r < 0)
+                        m->n_running_jobs--;
+                } else if (r < 0)
                         r = job_finish_and_invalidate(j, JOB_FAILED, true);
         }
 
@@ -642,20 +644,20 @@ static void job_print_status_message(Unit *u, JobType t, JobResult result) {
 
                 case JOB_DONE:
                         if (u->condition_result)
-                                unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON "  OK  " ANSI_HIGHLIGHT_OFF, format, unit_description(u));
+                                unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON "  OK  " ANSI_HIGHLIGHT_OFF, format);
                         break;
 
                 case JOB_FAILED:
-                        unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON "FAILED" ANSI_HIGHLIGHT_OFF, format, unit_description(u));
-                        unit_status_printf(u, NULL, "See 'systemctl status %s' for details.", u->id);
+                        unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON "FAILED" ANSI_HIGHLIGHT_OFF, format);
+                        manager_status_printf(u->manager, false, NULL, "See 'systemctl status %s' for details.", u->id);
                         break;
 
                 case JOB_DEPENDENCY:
-                        unit_status_printf(u, ANSI_HIGHLIGHT_YELLOW_ON "DEPEND" ANSI_HIGHLIGHT_OFF, format, unit_description(u));
+                        unit_status_printf(u, ANSI_HIGHLIGHT_YELLOW_ON "DEPEND" ANSI_HIGHLIGHT_OFF, format);
                         break;
 
                 case JOB_TIMEOUT:
-                        unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, format, unit_description(u));
+                        unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, format);
                         break;
 
                 default:
@@ -671,12 +673,12 @@ static void job_print_status_message(Unit *u, JobType t, JobResult result) {
                 switch (result) {
 
                 case JOB_TIMEOUT:
-                        unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, format, unit_description(u));
+                        unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, format);
                         break;
 
                 case JOB_DONE:
                 case JOB_FAILED:
-                        unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON "  OK  " ANSI_HIGHLIGHT_OFF, format, unit_description(u));
+                        unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON "  OK  " ANSI_HIGHLIGHT_OFF, format);
                         break;
 
                 default:
@@ -689,7 +691,7 @@ static void job_print_status_message(Unit *u, JobType t, JobResult result) {
                  * Most likely a DEPEND warning from a requisiting unit will
                  * occur next and it's nice to see what was requisited. */
                 if (result == JOB_SKIPPED)
-                        unit_status_printf(u, ANSI_HIGHLIGHT_ON " INFO " ANSI_HIGHLIGHT_OFF, "%s is not active.", unit_description(u));
+                        unit_status_printf(u, ANSI_HIGHLIGHT_ON " INFO " ANSI_HIGHLIGHT_OFF, "%s is not active.");
         }
 }
 
@@ -760,6 +762,9 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive) {
 
         j->result = result;
 
+        if (j->state == JOB_RUNNING)
+                j->manager->n_running_jobs--;
+
         log_debug_unit(u->id, "Job %s/%s finished, result=%s",
                        u->id, job_type_to_string(t), job_result_to_string(result));
 
index 0b6a5cc..22c1761 100644 (file)
@@ -68,14 +68,14 @@ $1.LimitRTPRIO,                  config_parse_limit,                 RLIMIT_RTPR
 $1.LimitRTTIME,                  config_parse_limit,                 RLIMIT_RTTIME,                 offsetof($1, exec_context.rlimit)
 $1.ControlGroup,                 config_parse_unit_cgroup,           0,                             0
 $1.ControlGroupAttribute,        config_parse_unit_cgroup_attr,      0,                             0
-$1.CPUShares,                    config_parse_unit_cpu_shares,       0,                             0
-$1.MemoryLimit,                  config_parse_unit_memory_limit,     0,                             0
-$1.MemorySoftLimit,              config_parse_unit_memory_limit,     0,                             0
-$1.DeviceAllow,                  config_parse_unit_device_allow,     0,                             0
-$1.DeviceDeny,                   config_parse_unit_device_allow,     0,                             0
-$1.BlockIOWeight,                config_parse_unit_blkio_weight,     0,                             0
-$1.BlockIOReadBandwidth,         config_parse_unit_blkio_bandwidth,  0,                             0
-$1.BlockIOWriteBandwidth,        config_parse_unit_blkio_bandwidth,  0,                             0
+$1.CPUShares,                    config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.MemoryLimit,                  config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.MemorySoftLimit,              config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.DeviceAllow,                  config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.DeviceDeny,                   config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.BlockIOWeight,                config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.BlockIOReadBandwidth,         config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.BlockIOWriteBandwidth,        config_parse_unit_cgroup_attr_pretty, 0,                           0
 $1.ReadWriteDirectories,         config_parse_path_strv,             0,                             offsetof($1, exec_context.read_write_dirs)
 $1.ReadOnlyDirectories,          config_parse_path_strv,             0,                             offsetof($1, exec_context.read_only_dirs)
 $1.InaccessibleDirectories,      config_parse_path_strv,             0,                             offsetof($1, exec_context.inaccessible_dirs)
index 6e333aa..d79e1d9 100644 (file)
@@ -1769,7 +1769,9 @@ int config_parse_unit_cgroup_attr(
                 void *userdata) {
 
         Unit *u = data;
-        _cleanup_strv_free_ char **l = NULL;
+        size_t a, b;
+        _cleanup_free_ char *n = NULL, *v = NULL;
+        const CGroupSemantics *s;
         int r;
 
         assert(filename);
@@ -1784,160 +1786,24 @@ int config_parse_unit_cgroup_attr(
                 return 0;
         }
 
-        l = strv_split_quoted(rvalue);
-        if (!l)
-                return log_oom();
-
-        if (strv_length(l) != 2) {
+        a = strcspn(rvalue, WHITESPACE);
+        b = strspn(rvalue + a, WHITESPACE);
+        if (a <= 0 || b <= 0) {
                 log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue);
                 return 0;
         }
 
-        r = unit_add_cgroup_attribute(u, NULL, l[0], l[1], NULL, NULL);
-        if (r < 0) {
-                log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        return 0;
-}
-
-int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
-        Unit *u = data;
-        int r;
-        unsigned long ul;
-        _cleanup_free_ char *t = NULL;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        if (safe_atolu(rvalue, &ul) < 0 || ul < 1) {
-                log_error("[%s:%u] Failed to parse CPU shares value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (asprintf(&t, "%lu", ul) < 0)
-                return log_oom();
-
-        r = unit_add_cgroup_attribute(u, "cpu", "cpu.shares", t, NULL, NULL);
-        if (r < 0) {
-                log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        return 0;
-}
-
-int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
-        Unit *u = data;
-        int r;
-        off_t sz;
-        _cleanup_free_ char *t = NULL;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        if (parse_bytes(rvalue, &sz) < 0 || sz <= 0) {
-                log_error("[%s:%u] Failed to parse memory limit value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (asprintf(&t, "%llu", (unsigned long long) sz) < 0)
+        n = strndup(rvalue, a);
+        if (!n)
                 return log_oom();
 
-        r = unit_add_cgroup_attribute(u,
-                                      "memory",
-                                      streq(lvalue, "MemorySoftLimit") ? "memory.soft_limit_in_bytes" : "memory.limit_in_bytes",
-                                      t, NULL, NULL);
+        r = cgroup_semantics_find(NULL, n, rvalue + a + b, &v, &s);
         if (r < 0) {
-                log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        return 0;
-}
-
-static int device_map(const char *controller, const char *name, const char *value, char **ret) {
-        _cleanup_strv_free_ char **l = NULL;
-
-        assert(controller);
-        assert(name);
-        assert(value);
-        assert(ret);
-
-        l = strv_split_quoted(value);
-        if (!l)
-                return -ENOMEM;
-
-        assert(strv_length(l) >= 1);
-
-        if (streq(l[0], "*")) {
-
-                if (asprintf(ret, "a *:*%s%s",
-                             isempty(l[1]) ? "" : " ", strempty(l[1])) < 0)
-                        return -ENOMEM;
-        } else {
-                struct stat st;
-
-                if (stat(l[0], &st) < 0) {
-                        log_warning("Couldn't stat device %s", l[0]);
-                        return -errno;
-                }
-
-                if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
-                        log_warning("%s is not a device.", l[0]);
-                        return -ENODEV;
-                }
-
-                if (asprintf(ret, "%c %u:%u%s%s",
-                             S_ISCHR(st.st_mode) ? 'c' : 'b',
-                             major(st.st_rdev), minor(st.st_rdev),
-                             isempty(l[1]) ? "" : " ", strempty(l[1])) < 0)
-                        return -ENOMEM;
-        }
-
-        return 0;
-}
-
-int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
-        Unit *u = data;
-        _cleanup_strv_free_ char **l = NULL;
-        int r;
-        unsigned k;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        l = strv_split_quoted(rvalue);
-        if (!l)
-                return log_oom();
-
-        k = strv_length(l);
-        if (k < 1 || k > 2) {
-                log_error("[%s:%u] Failed to parse device value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (!streq(l[0], "*") && !path_startswith(l[0], "/dev")) {
-                log_error("[%s:%u] Device node path not absolute, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (!isempty(l[1]) && !in_charset(l[1], "rwm")) {
-                log_error("[%s:%u] Device access string invalid, ignoring: %s", filename, line, rvalue);
+                log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue);
                 return 0;
         }
 
-        r = unit_add_cgroup_attribute(u, "devices",
-                                      streq(lvalue, "DeviceAllow") ? "devices.allow" : "devices.deny",
-                                      rvalue, device_map, NULL);
-
+        r = unit_add_cgroup_attribute(u, s, NULL, n, v ? v : rvalue + a + b, NULL);
         if (r < 0) {
                 log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
                 return 0;
@@ -1946,148 +1812,36 @@ int config_parse_unit_device_allow(const char *filename, unsigned line, const ch
         return 0;
 }
 
-static int blkio_map(const char *controller, const char *name, const char *value, char **ret) {
-        struct stat st;
-        _cleanup_strv_free_ char **l = NULL;
-        dev_t d;
-
-        assert(controller);
-        assert(name);
-        assert(value);
-        assert(ret);
-
-        l = strv_split_quoted(value);
-        if (!l)
-                return log_oom();
-
-        assert(strv_length(l) == 2);
-
-        if (stat(l[0], &st) < 0) {
-                log_warning("Couldn't stat device %s", l[0]);
-                return -errno;
-        }
-
-        if (S_ISBLK(st.st_mode))
-                d = st.st_rdev;
-        else if (major(st.st_dev) != 0) {
-                /* If this is not a device node then find the block
-                 * device this file is stored on */
-                d = st.st_dev;
-
-                /* If this is a partition, try to get the originating
-                 * block device */
-                block_get_whole_disk(d, &d);
-        } else {
-                log_warning("%s is not a block device and file system block device cannot be determined or is not local.", l[0]);
-                return -ENODEV;
-        }
-
-        if (asprintf(ret, "%u:%u %s", major(d), minor(d), l[1]) < 0)
-                return -ENOMEM;
-
-        return 0;
-}
+int config_parse_unit_cgroup_attr_pretty(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
 
-int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
         Unit *u = data;
+        _cleanup_free_ char *v = NULL;
+        const CGroupSemantics *s;
         int r;
-        unsigned long ul;
-        const char *device = NULL, *weight;
-        unsigned k;
-        _cleanup_free_ char *t = NULL;
-        _cleanup_strv_free_ char **l = NULL;
 
         assert(filename);
         assert(lvalue);
         assert(rvalue);
         assert(data);
 
-        l = strv_split_quoted(rvalue);
-        if (!l)
-                return log_oom();
-
-        k = strv_length(l);
-        if (k < 1 || k > 2) {
-                log_error("[%s:%u] Failed to parse weight value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (k == 1)
-                weight = l[0];
-        else {
-                device = l[0];
-                weight = l[1];
-        }
-
-        if (device && !path_is_absolute(device)) {
-                log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (safe_atolu(weight, &ul) < 0 || ul < 10 || ul > 1000) {
-                log_error("[%s:%u] Failed to parse block IO weight value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (device)
-                r = asprintf(&t, "%s %lu", device, ul);
-        else
-                r = asprintf(&t, "%lu", ul);
-        if (r < 0)
-                return log_oom();
-
-        if (device)
-                r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight_device", t, blkio_map, NULL);
-        else
-                r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight", t, NULL, NULL);
+        r = cgroup_semantics_find(NULL, lvalue, rvalue, &v, &s);
         if (r < 0) {
-                log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        return 0;
-}
-
-int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
-        Unit *u = data;
-        int r;
-        off_t bytes;
-        unsigned k;
-        _cleanup_free_ char *t = NULL;
-        _cleanup_strv_free_ char **l = NULL;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        l = strv_split_quoted(rvalue);
-        if (!l)
-                return log_oom();
-
-        k = strv_length(l);
-        if (k != 2) {
-                log_error("[%s:%u] Failed to parse bandwidth value, ignoring: %s", filename, line, rvalue);
+                log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue);
                 return 0;
-        }
-
-        if (!path_is_absolute(l[0])) {
-                log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue);
+        } else if (r == 0) {
+                log_error("[%s:%u] Unknown or unsupported cgroup attribute %s, ignoring: %s", filename, line, lvalue, rvalue);
                 return 0;
         }
 
-        if (parse_bytes(l[1], &bytes) < 0 || bytes <= 0) {
-                log_error("[%s:%u] Failed to parse block IO bandwidth value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        r = asprintf(&t, "%s %llu", l[0], (unsigned long long) bytes);
-        if (r < 0)
-                return log_oom();
-
-        r = unit_add_cgroup_attribute(u, "blkio",
-                                      streq(lvalue, "BlockIOReadBandwidth") ? "blkio.read_bps_device" : "blkio.write_bps_device",
-                                      t, blkio_map, NULL);
+        r = unit_add_cgroup_attribute(u, s, NULL, NULL, v, NULL);
         if (r < 0) {
                 log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
                 return 0;
index 421b4c3..dfb2ef0 100644 (file)
@@ -75,11 +75,7 @@ int config_parse_kill_mode(const char *filename, unsigned line, const char *sect
 int config_parse_notify_access(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_start_limit_action(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_unit_cgroup_attr(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
-int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
-int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
-int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
-int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
-int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_cgroup_attr_pretty(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_unit_requires_mounts_for(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_syscall_filter(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_environ(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
index a578813..ec12a75 100644 (file)
 /* As soon as 5s passed since a unit was added to our GC queue, make sure to run a gc sweep */
 #define GC_QUEUE_USEC_MAX (10*USEC_PER_SEC)
 
+/* Initial delay and the interval for printing status messages about running jobs */
+#define JOBS_IN_PROGRESS_WAIT_SEC 5
+#define JOBS_IN_PROGRESS_PERIOD_SEC 1
+#define JOBS_IN_PROGRESS_PERIOD_DIVISOR 3
+
 /* Where clients shall send notification messages to */
 #define NOTIFY_SOCKET "@/org/freedesktop/systemd1/notify"
 
@@ -140,6 +145,146 @@ static int manager_setup_notify(Manager *m) {
         return 0;
 }
 
+static int manager_jobs_in_progress_mod_timer(Manager *m) {
+        struct itimerspec its;
+
+        zero(its);
+
+        its.it_value.tv_sec = JOBS_IN_PROGRESS_WAIT_SEC;
+        its.it_interval.tv_sec = JOBS_IN_PROGRESS_PERIOD_SEC;
+
+        if (timerfd_settime(m->jobs_in_progress_watch.fd, 0, &its, NULL) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static int manager_watch_jobs_in_progress(Manager *m) {
+        struct epoll_event ev;
+        int r;
+
+        assert(m);
+
+        if (m->jobs_in_progress_watch.type != WATCH_INVALID)
+                return 0;
+
+        m->jobs_in_progress_watch.type = WATCH_JOBS_IN_PROGRESS;
+        m->jobs_in_progress_watch.fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC);
+        if (m->jobs_in_progress_watch.fd < 0) {
+                log_error("Failed to create timerfd: %m");
+                r = -errno;
+                goto err;
+        }
+
+        r = manager_jobs_in_progress_mod_timer(m);
+        if (r < 0) {
+                log_error("Failed to set up timer for jobs progress watch: %s", strerror(-r));
+                goto err;
+        }
+
+        zero(ev);
+        ev.events = EPOLLIN;
+        ev.data.ptr = &m->jobs_in_progress_watch;
+
+        if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->jobs_in_progress_watch.fd, &ev) < 0) {
+                log_error("Failed to add jobs progress timer fd to epoll: %m");
+                r = -errno;
+                goto err;
+        }
+
+        log_debug("Set up jobs progress timerfd.");
+
+        return 0;
+
+err:
+        if (m->jobs_in_progress_watch.fd >= 0)
+                close_nointr_nofail(m->jobs_in_progress_watch.fd);
+        watch_init(&m->jobs_in_progress_watch);
+        return r;
+}
+
+static void manager_unwatch_jobs_in_progress(Manager *m) {
+        if (m->jobs_in_progress_watch.type != WATCH_JOBS_IN_PROGRESS)
+                return;
+
+        assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, m->jobs_in_progress_watch.fd, NULL) >= 0);
+        close_nointr_nofail(m->jobs_in_progress_watch.fd);
+        watch_init(&m->jobs_in_progress_watch);
+        m->jobs_in_progress_iteration = 0;
+
+        log_debug("Closed jobs progress timerfd.");
+}
+
+#define CYLON_BUFFER_EXTRA (2*strlen(ANSI_RED_ON) + strlen(ANSI_HIGHLIGHT_RED_ON) + 2*strlen(ANSI_HIGHLIGHT_OFF))
+static void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned pos) {
+        char *p = buffer;
+
+        assert(buflen >= CYLON_BUFFER_EXTRA + width + 1);
+        assert(pos <= width+1); /* 0 or width+1 mean that the center light is behind the corner */
+
+        if (pos > 1) {
+                if (pos > 2) {
+                        memset(p, ' ', pos-2);
+                        p += pos-2;
+                }
+                memcpy(p, ANSI_RED_ON, strlen(ANSI_RED_ON));
+                p += strlen(ANSI_RED_ON);
+                *p++ = '*';
+        }
+
+        if (pos > 0 && pos <= width) {
+                memcpy(p, ANSI_HIGHLIGHT_RED_ON, strlen(ANSI_HIGHLIGHT_RED_ON));
+                p += strlen(ANSI_HIGHLIGHT_RED_ON);
+                *p++ = '*';
+        }
+
+        memcpy(p, ANSI_HIGHLIGHT_OFF, strlen(ANSI_HIGHLIGHT_OFF));
+        p += strlen(ANSI_HIGHLIGHT_OFF);
+
+        if (pos < width) {
+                memcpy(p, ANSI_RED_ON, strlen(ANSI_RED_ON));
+                p += strlen(ANSI_RED_ON);
+                *p++ = '*';
+                if (pos < width-1) {
+                        memset(p, ' ', width-1-pos);
+                        p += width-1-pos;
+                }
+                memcpy(p, ANSI_HIGHLIGHT_OFF, strlen(ANSI_HIGHLIGHT_OFF));
+                p += strlen(ANSI_HIGHLIGHT_OFF);
+        }
+        *p = 0;
+}
+
+static void manager_print_jobs_in_progress(Manager *m) {
+        Iterator i;
+        Job *j;
+        char *job_of_n = NULL;
+        unsigned counter = 0, print_nr;
+        char cylon[6 + CYLON_BUFFER_EXTRA + 1];
+        unsigned cylon_pos;
+
+        print_nr = (m->jobs_in_progress_iteration / JOBS_IN_PROGRESS_PERIOD_DIVISOR) % m->n_running_jobs;
+
+        HASHMAP_FOREACH(j, m->jobs, i)
+                if (j->state == JOB_RUNNING && counter++ == print_nr)
+                        break;
+
+        cylon_pos = m->jobs_in_progress_iteration % 14;
+        if (cylon_pos >= 8)
+                cylon_pos = 14 - cylon_pos;
+        draw_cylon(cylon, sizeof(cylon), 6, cylon_pos);
+
+        if (m->n_running_jobs > 1)
+                if (asprintf(&job_of_n, "(%u of %u) ", counter, m->n_running_jobs) < 0)
+                        job_of_n = NULL;
+
+        manager_status_printf(m, true, cylon, "%sA %s job is running for %s",
+                              strempty(job_of_n), job_type_to_string(j->type), unit_description(j->unit));
+        free(job_of_n);
+
+        m->jobs_in_progress_iteration++;
+}
+
 static int manager_setup_time_change(Manager *m) {
         struct epoll_event ev;
         struct itimerspec its;
@@ -324,6 +469,7 @@ int manager_new(SystemdRunningAs running_as, Manager **_m) {
         watch_init(&m->swap_watch);
         watch_init(&m->udev_watch);
         watch_init(&m->time_change_watch);
+        watch_init(&m->jobs_in_progress_watch);
 
         m->epoll_fd = m->dev_autofs_fd = -1;
         m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */
@@ -564,6 +710,8 @@ void manager_free(Manager *m) {
                 close_nointr_nofail(m->notify_watch.fd);
         if (m->time_change_watch.fd >= 0)
                 close_nointr_nofail(m->time_change_watch.fd);
+        if (m->jobs_in_progress_watch.fd >= 0)
+                close_nointr_nofail(m->jobs_in_progress_watch.fd);
 
         free(m->notify_socket);
 
@@ -988,6 +1136,10 @@ unsigned manager_dispatch_run_queue(Manager *m) {
         }
 
         m->dispatching_run_queue = false;
+
+        if (hashmap_size(m->jobs) > 0)
+                manager_watch_jobs_in_progress(m);
+
         return n;
 }
 
@@ -1527,6 +1679,16 @@ static int process_event(Manager *m, struct epoll_event *ev) {
                 break;
         }
 
+        case WATCH_JOBS_IN_PROGRESS: {
+                uint64_t v;
+
+                /* not interested in the data */
+                read(w->fd, &v, sizeof(v));
+
+                manager_print_jobs_in_progress(m);
+                break;
+        }
+
         default:
                 log_error("event type=%i", w->type);
                 assert_not_reached("Unknown epoll event type.");
@@ -2152,7 +2314,7 @@ finish:
         return r;
 }
 
-bool manager_is_booting_or_shutting_down(Manager *m) {
+static bool manager_is_booting_or_shutting_down(Manager *m) {
         Unit *u;
 
         assert(m);
@@ -2199,8 +2361,10 @@ void manager_check_finished(Manager *m) {
 
         assert(m);
 
-        if (hashmap_size(m->jobs) > 0)
+        if (hashmap_size(m->jobs) > 0) {
+                manager_jobs_in_progress_mod_timer(m);
                 return;
+        }
 
         /* Notify Type=idle units that we are done now */
         close_pipe(m->idle_pipe);
@@ -2208,6 +2372,8 @@ void manager_check_finished(Manager *m) {
         /* Turn off confirm spawn now */
         m->confirm_spawn = false;
 
+        manager_unwatch_jobs_in_progress(m);
+
         if (dual_timestamp_is_set(&m->finish_timestamp))
                 return;
 
@@ -2479,7 +2645,7 @@ void manager_set_show_status(Manager *m, bool b) {
                 unlink("/run/systemd/show-status");
 }
 
-bool manager_get_show_status(Manager *m) {
+static bool manager_get_show_status(Manager *m) {
         assert(m);
 
         if (m->running_as != SYSTEMD_SYSTEM)
@@ -2494,6 +2660,25 @@ bool manager_get_show_status(Manager *m) {
         return plymouth_running();
 }
 
+void manager_status_printf(Manager *m, bool ephemeral, const char *status, const char *format, ...) {
+        va_list ap;
+
+        if (!manager_get_show_status(m))
+                return;
+
+        /* XXX We should totally drop the check for ephemeral here
+         * and thus effectively make 'Type=idle' pointless. */
+        if (ephemeral && m->n_on_console > 0)
+                return;
+
+        if (!manager_is_booting_or_shutting_down(m))
+                return;
+
+        va_start(ap, format);
+        status_vprintf(status, true, ephemeral, format, ap);
+        va_end(ap);
+}
+
 void watch_init(Watch *w) {
         assert(w);
 
index cc4edf8..c486a16 100644 (file)
@@ -61,7 +61,8 @@ enum WatchType {
         WATCH_UDEV,
         WATCH_DBUS_WATCH,
         WATCH_DBUS_TIMEOUT,
-        WATCH_TIME_CHANGE
+        WATCH_TIME_CHANGE,
+        WATCH_JOBS_IN_PROGRESS
 };
 
 struct Watch {
@@ -127,6 +128,7 @@ struct Manager {
         Watch notify_watch;
         Watch signal_watch;
         Watch time_change_watch;
+        Watch jobs_in_progress_watch;
 
         int epoll_fd;
 
@@ -225,6 +227,11 @@ struct Manager {
         unsigned n_installed_jobs;
         unsigned n_failed_jobs;
 
+        /* Jobs in progress watching */
+        unsigned n_running_jobs;
+        unsigned n_on_console;
+        unsigned jobs_in_progress_iteration;
+
         /* Type=idle pipes */
         int idle_pipe[2];
 
@@ -276,8 +283,6 @@ int manager_distribute_fds(Manager *m, FDSet *fds);
 
 int manager_reload(Manager *m);
 
-bool manager_is_booting_or_shutting_down(Manager *m);
-
 void manager_reset_failed(Manager *m);
 
 void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success);
@@ -293,6 +298,6 @@ void manager_undo_generators(Manager *m);
 void manager_recheck_journal(Manager *m);
 
 void manager_set_show_status(Manager *m, bool b);
-bool manager_get_show_status(Manager *m);
+void manager_status_printf(Manager *m, bool ephemeral, const char *status, const char *format, ...);
 
 void watch_init(Watch *w);
index e7e2736..dab3601 100644 (file)
@@ -77,10 +77,6 @@ static const MountPoint mount_table[] = {
           NULL,       MNT_FATAL|MNT_IN_CONTAINER },
         { "securityfs", "/sys/kernel/security",      "securityfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
           NULL,       MNT_NONE },
-#ifdef ENABLE_EFI
-        { "efivarfs",   "/sys/firmware/efi/efivars", "efivarfs",   NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
-          is_efi_boot, MNT_NONE },
-#endif
         { "tmpfs",      "/dev/shm",                  "tmpfs",      "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME,
           NULL,       MNT_FATAL|MNT_IN_CONTAINER },
         { "devpts",     "/dev/pts",                  "devpts",     "mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC,
@@ -91,6 +87,12 @@ static const MountPoint mount_table[] = {
           NULL,       MNT_IN_CONTAINER },
         { "cgroup",     "/sys/fs/cgroup/systemd",    "cgroup",     "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV,
           NULL,       MNT_IN_CONTAINER },
+        { "pstore",     "/sys/fs/pstore",            "pstore",     NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
+          NULL,       MNT_NONE },
+#ifdef ENABLE_EFI
+        { "efivarfs",   "/sys/firmware/efi/efivars", "efivarfs",   NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
+          is_efi_boot, MNT_NONE },
+#endif
 };
 
 /* These are API file systems that might be mounted by other software,
index 0329366..4a8d90e 100644 (file)
@@ -396,8 +396,8 @@ static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsi
                                        "Job %s/%s deleted to break ordering cycle starting with %s/%s",
                                        delete->unit->id, job_type_to_string(delete->type),
                                        j->unit->id, job_type_to_string(j->type));
-                        status_printf(ANSI_HIGHLIGHT_RED_ON " SKIP " ANSI_HIGHLIGHT_OFF, true,
-                                      "Ordering cycle found, skipping %s", unit_description(delete->unit));
+                        unit_status_printf(delete->unit, ANSI_HIGHLIGHT_RED_ON " SKIP " ANSI_HIGHLIGHT_OFF,
+                                           "Ordering cycle found, skipping %s");
                         transaction_delete_unit(tr, delete->unit);
                         return -EAGAIN;
                 }
index 3a88996..e2c06ae 100644 (file)
@@ -749,15 +749,13 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
                                 prefix, b->controller, b->path);
 
                 LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
-                        char *v = NULL;
+                        _cleanup_free_ char *v = NULL;
 
-                        if (a->map_callback)
-                                a->map_callback(a->controller, a->name, a->value, &v);
+                        if (a->semantics && a->semantics->map_write)
+                                a->semantics->map_write(a->semantics, a->value, &v);
 
                         fprintf(f, "%s\tControlGroupAttribute: %s %s \"%s\"\n",
                                 prefix, a->controller, a->name, v ? v : a->value);
-
-                        free(v);
                 }
 
                 if (UNIT_VTABLE(u)->dump)
@@ -996,7 +994,7 @@ static void unit_status_print_starting_stopping(Unit *u, JobType t) {
         if (!format)
                 return;
 
-        unit_status_printf(u, "", format, unit_description(u));
+        unit_status_printf(u, "", format);
 }
 
 #pragma GCC diagnostic push
@@ -1322,6 +1320,7 @@ void unit_trigger_on_failure(Unit *u) {
 }
 
 void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) {
+        Manager *m;
         bool unexpected;
 
         assert(u);
@@ -1334,7 +1333,9 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
          * behavior here. For example: if a mount point is remounted
          * this function will be called too! */
 
-        if (u->manager->n_reloading <= 0) {
+        m = u->manager;
+
+        if (m->n_reloading <= 0) {
                 dual_timestamp ts;
 
                 dual_timestamp_get(&ts);
@@ -1356,6 +1357,21 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
         if (UNIT_IS_INACTIVE_OR_FAILED(ns))
                 cgroup_bonding_trim_list(u->cgroup_bondings, true);
 
+        if (UNIT_IS_INACTIVE_OR_FAILED(os) != UNIT_IS_INACTIVE_OR_FAILED(ns)) {
+                ExecContext *ec = unit_get_exec_context(u);
+                if (ec && exec_context_may_touch_console(ec)) {
+                        /* XXX The counter may get out of sync if the admin edits
+                         * TTY-related unit file properties and issues a daemon-reload
+                         * while the unit is active. No big deal though, because
+                         * it influences only the printing of boot/shutdown
+                         * status messages. */
+                        if (UNIT_IS_INACTIVE_OR_FAILED(ns))
+                                m->n_on_console--;
+                        else
+                                m->n_on_console++;
+                }
+        }
+
         if (u->job) {
                 unexpected = false;
 
@@ -1422,7 +1438,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
         } else
                 unexpected = true;
 
-        if (u->manager->n_reloading <= 0) {
+        if (m->n_reloading <= 0) {
 
                 /* If this state change happened without being
                  * requested by a job, then let's retroactively start
@@ -1458,18 +1474,18 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
                         /* The bus just might have become available,
                          * hence try to connect to it, if we aren't
                          * yet connected. */
-                        bus_init(u->manager, true);
+                        bus_init(m, true);
 
                 if (u->type == UNIT_SERVICE &&
                     !UNIT_IS_ACTIVE_OR_RELOADING(os) &&
-                    u->manager->n_reloading <= 0) {
+                    m->n_reloading <= 0) {
                         /* Write audit record if we have just finished starting up */
-                        manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, true);
+                        manager_send_unit_audit(m, u, AUDIT_SERVICE_START, true);
                         u->in_audit = true;
                 }
 
                 if (!UNIT_IS_ACTIVE_OR_RELOADING(os))
-                        manager_send_unit_plymouth(u->manager, u);
+                        manager_send_unit_plymouth(m, u);
 
         } else {
 
@@ -1479,25 +1495,25 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
                 if (u->type == UNIT_SERVICE &&
                     UNIT_IS_INACTIVE_OR_FAILED(ns) &&
                     !UNIT_IS_INACTIVE_OR_FAILED(os) &&
-                    u->manager->n_reloading <= 0) {
+                    m->n_reloading <= 0) {
 
                         /* Hmm, if there was no start record written
                          * write it now, so that we always have a nice
                          * pair */
                         if (!u->in_audit) {
-                                manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE);
+                                manager_send_unit_audit(m, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE);
 
                                 if (ns == UNIT_INACTIVE)
-                                        manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, true);
+                                        manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, true);
                         } else
                                 /* Write audit record if we have just finished shutting down */
-                                manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE);
+                                manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE);
 
                         u->in_audit = false;
                 }
         }
 
-        manager_recheck_journal(u->manager);
+        manager_recheck_journal(m);
 
         /* Maybe we finished startup and are now ready for being
          * stopped because unneeded? */
@@ -1900,30 +1916,12 @@ finish:
 }
 
 int set_unit_path(const char *p) {
-        char *cwd, *c;
-        int r;
+        _cleanup_free_ char *c = NULL;
 
         /* This is mostly for debug purposes */
-
-        if (path_is_absolute(p)) {
-                if (!(c = strdup(p)))
-                        return -ENOMEM;
-        } else {
-                if (!(cwd = get_current_dir_name()))
-                        return -errno;
-
-                r = asprintf(&c, "%s/%s", cwd, p);
-                free(cwd);
-
-                if (r < 0)
-                        return -ENOMEM;
-        }
-
-        if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0) {
-                r = -errno;
-                free(c);
-                return r;
-        }
+        c = path_make_absolute_cwd(p);
+        if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0)
+                return -errno;
 
         return 0;
 }
@@ -2109,7 +2107,7 @@ static int unit_add_one_default_cgroup(Unit *u, const char *controller) {
         if (r < 0)
                 goto fail;
 
-        return 0;
+        return 1;
 
 fail:
         free(b->path);
@@ -2153,10 +2151,10 @@ CGroupBonding* unit_get_default_cgroup(Unit *u) {
 
 int unit_add_cgroup_attribute(
                 Unit *u,
+                const CGroupSemantics *semantics,
                 const char *controller,
                 const char *name,
                 const char *value,
-                CGroupAttributeMapCallback map_callback,
                 CGroupAttribute **ret) {
 
         _cleanup_free_ char *c = NULL;
@@ -2164,48 +2162,67 @@ int unit_add_cgroup_attribute(
         int r;
 
         assert(u);
-        assert(name);
         assert(value);
 
+        if (semantics) {
+                /* Semantics always take precedence */
+                if (semantics->name)
+                        name = semantics->name;
+
+                if (semantics->controller)
+                        controller = semantics->controller;
+        }
+
+        if (!name)
+                return -EINVAL;
+
         if (!controller) {
                 r = cg_controller_from_attr(name, &c);
                 if (r < 0)
                         return -EINVAL;
 
                 controller = c;
-        } else {
-                if (!filename_is_safe(name))
-                        return -EINVAL;
-
-                if (!filename_is_safe(controller))
-                        return -EINVAL;
         }
 
         if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER))
                 return -EINVAL;
 
+        if (!filename_is_safe(name))
+                return -EINVAL;
+
+        if (!filename_is_safe(controller))
+                return -EINVAL;
+
+        /* Check if this attribute already exists. Note that we will
+         * explicitly check for the value here too, as there are
+         * attributes which accept multiple values. */
         a = cgroup_attribute_find_list(u->cgroup_attributes, controller, name);
         if (a) {
-                char *v;
-
                 if (streq(value, a->value)) {
+                        /* Exactly the same value is always OK, let's ignore this */
                         if (ret)
                                 *ret = a;
 
                         return 0;
                 }
 
-                v = strdup(value);
-                if (!v)
-                        return -ENOMEM;
+                if (semantics && !semantics->multiple) {
+                        char *v;
 
-                free(a->value);
-                a->value = v;
+                        /* If this is a single-item entry, we can
+                         * simply patch the existing attribute */
 
-                if (ret)
-                        *ret = a;
+                        v = strdup(value);
+                        if (!v)
+                                return -ENOMEM;
+
+                        free(a->value);
+                        a->value = v;
 
-                return 1;
+                        if (ret)
+                                *ret = a;
+                        return 1;
+                }
         }
 
         a = new0(CGroupAttribute, 1);
@@ -2226,11 +2243,10 @@ int unit_add_cgroup_attribute(
                 free(a->name);
                 free(a->value);
                 free(a);
-
                 return -ENOMEM;
         }
 
-        a->map_callback = map_callback;
+        a->semantics = semantics;
         a->unit = u;
 
         LIST_PREPEND(CGroupAttribute, by_unit, u->cgroup_attributes, a);
@@ -2537,21 +2553,8 @@ int unit_coldplug(Unit *u) {
         return 0;
 }
 
-void unit_status_printf(Unit *u, const char *status, const char *format, ...) {
-        va_list ap;
-
-        assert(u);
-        assert(format);
-
-        if (!manager_get_show_status(u->manager))
-                return;
-
-        if (!manager_is_booting_or_shutting_down(u->manager))
-                return;
-
-        va_start(ap, format);
-        status_vprintf(status, true, format, ap);
-        va_end(ap);
+void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) {
+        manager_status_printf(u->manager, false, status, unit_status_msg_format, unit_description(u));
 }
 
 bool unit_need_daemon_reload(Unit *u) {
@@ -2763,52 +2766,77 @@ ExecContext *unit_get_exec_context(Unit *u) {
         return (ExecContext*) ((uint8_t*) u + offset);
 }
 
-int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data) {
-        _cleanup_free_ char *p = NULL, *q = NULL;
+static int drop_in_file(Unit *u, bool runtime, const char *name, char **_p, char **_q) {
+        char *p, *q;
+        int r;
+
         assert(u);
+        assert(name);
+        assert(_p);
+        assert(_q);
 
-        if (u->manager->running_as != SYSTEMD_SYSTEM)
+        if (u->manager->running_as == SYSTEMD_USER && runtime)
                 return -ENOTSUP;
 
         if (!filename_is_safe(name))
                 return -EINVAL;
 
-        p = strjoin(runtime ? "/run/systemd/system/" : "/etc/systemd/system/", u->id, ".d", NULL);
+        if (u->manager->running_as == SYSTEMD_USER) {
+                _cleanup_free_ char *c = NULL;
+
+                r = user_config_home(&c);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -ENOENT;
+
+                p = strjoin(c, "/", u->id, ".d", NULL);
+        } else  if (runtime)
+                p = strjoin("/run/systemd/system/", u->id, ".d", NULL);
+        else
+                p = strjoin("/etc/systemd/system/", u->id, ".d", NULL);
         if (!p)
                 return -ENOMEM;
 
         q = strjoin(p, "/50-", name, ".conf", NULL);
-        if (!q)
+        if (!q) {
+                free(p);
                 return -ENOMEM;
+        }
 
-        mkdir_p(p, 0755);
-        return write_one_line_file_atomic_label(q, data);
+        *_p = p;
+        *_q = q;
+        return 0;
 }
 
-int unit_remove_drop_in(Unit *u, bool runtime, const char *name) {
+int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data) {
         _cleanup_free_ char *p = NULL, *q = NULL;
+        int r;
 
         assert(u);
 
-        if (u->manager->running_as != SYSTEMD_SYSTEM)
-                return -ENOTSUP;
+        r = drop_in_file(u, runtime, name, &p, &q);
+        if (r < 0)
+                return r;
 
-        if (!filename_is_safe(name))
-                return -EINVAL;
+        mkdir_p(p, 0755);
+        return write_one_line_file_atomic_label(q, data);
+}
 
-        p = strjoin(runtime ? "/run/systemd/system/" : "/etc/systemd/system/", u->id, ".d", NULL);
-        if (!p)
-                return -ENOMEM;
+int unit_remove_drop_in(Unit *u, bool runtime, const char *name) {
+        _cleanup_free_ char *p = NULL, *q = NULL;
+        int r;
 
-        q = strjoin(p, "/50-", name, ".conf", NULL);
-        if (!q)
-                return -ENOMEM;
+        assert(u);
 
+        r = drop_in_file(u, runtime, name, &p, &q);
         if (unlink(q) < 0)
-                return -errno;
+                r = -errno;
+        else
+                r = 0;
 
         rmdir(p);
-        return 0;
+        return r;
 }
 
 int unit_kill_context(
index c902103..9029d62 100644 (file)
@@ -40,6 +40,7 @@ typedef struct UnitStatusMessageFormats UnitStatusMessageFormats;
 #include "condition.h"
 #include "install.h"
 #include "unit-name.h"
+#include "cgroup-semantics.h"
 
 enum UnitActiveState {
         UNIT_ACTIVE,
@@ -445,7 +446,7 @@ int unit_add_cgroup(Unit *u, CGroupBonding *b);
 int unit_add_cgroup_from_text(Unit *u, const char *name, bool overwrite, CGroupBonding **ret);
 int unit_add_default_cgroups(Unit *u);
 CGroupBonding* unit_get_default_cgroup(Unit *u);
-int unit_add_cgroup_attribute(Unit *u, const char *controller, const char *name, const char *value, CGroupAttributeMapCallback map_callback, CGroupAttribute **ret);
+int unit_add_cgroup_attribute(Unit *u, const CGroupSemantics *semantics, const char *controller, const char *name, const char *value, CGroupAttribute **ret);
 
 int unit_choose_id(Unit *u, const char *name);
 int unit_set_description(Unit *u, const char *description);
@@ -519,7 +520,7 @@ int unit_add_node_link(Unit *u, const char *what, bool wants);
 
 int unit_coldplug(Unit *u);
 
-void unit_status_printf(Unit *u, const char *status, const char *format, ...);
+void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format);
 
 bool unit_need_daemon_reload(Unit *u);
 
index 0de159b..9084509 100644 (file)
@@ -433,7 +433,7 @@ static int parse_argv(int argc, char *argv[]) {
         if (arg_follow && !arg_no_tail && arg_lines < 0)
                 arg_lines = 10;
 
-        if (arg_since_set && arg_until_set && arg_since_set > arg_until_set) {
+        if (arg_since_set && arg_until_set && arg_since > arg_until) {
                 log_error("--since= must be before --until=.");
                 return -EINVAL;
         }
@@ -1079,6 +1079,8 @@ int main(int argc, char *argv[]) {
                                         log_error("Failed to determine timestamp: %s", strerror(-r));
                                         goto finish;
                                 }
+                                if (usec > arg_until)
+                                        goto finish;
                         }
 
                         if (!arg_merge) {
index 8904560..1912354 100644 (file)
@@ -1321,10 +1321,12 @@ static int add_directory(sd_journal *j, const char *prefix, const char *dirname)
         assert(prefix);
         assert(dirname);
 
+        log_debug("Considering %s/%s.", prefix, dirname);
+
         if ((j->flags & SD_JOURNAL_LOCAL_ONLY) &&
             (sd_id128_from_string(dirname, &id) < 0 ||
              sd_id128_get_machine(&mid) < 0 ||
-             !sd_id128_equal(id, mid)))
+             !(sd_id128_equal(id, mid) || path_startswith(prefix, "/run"))))
             return 0;
 
         path = strjoin(prefix, "/", dirname, NULL);
diff --git a/src/kernel-install/kernel-install b/src/kernel-install/kernel-install
new file mode 100644 (file)
index 0000000..16c06e0
--- /dev/null
@@ -0,0 +1,152 @@
+#!/bin/bash
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+#
+# This file is part of systemd.
+#
+# Copyright 2013 Harald Hoyer
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+
+export LC_COLLATE=C
+
+COMMAND="$1"
+KERNEL_VERSION="$2"
+KERNEL_IMAGE="$3"
+
+[[ -f /etc/os-release ]] && . /etc/os-release
+if ! [[ $ID ]]; then
+    echo "Can't determine the name of your distribution. Please create /etc/os-release." >&2
+    echo "See man:os-release(5)" >&2
+    exit 1
+fi
+
+[[ -f /etc/machine-id ]] && read MACHINE_ID < /etc/machine-id
+if ! [[ $MACHINE_ID ]]; then
+    echo "Can't determine your machine id. Please create /etc/machine-id!" >&2
+    echo "See man:machine-id(5)" >&2
+    exit 1
+fi
+
+if [[ -f /etc/kernel/cmdline ]]; then
+    readarray -t BOOT_OPTIONS < /etc/kernel/cmdline
+fi
+
+if ! [[ "${BOOT_OPTIONS[@]}" ]]; then
+    readarray -t BOOT_OPTIONS < /proc/cmdline
+fi
+
+if ! [[ $BOOT_OPTIONS ]]; then
+    echo "Can't determine the kernel command line parameters." >&2
+    echo "Please specify the kernel command line in /etc/kernel/cmdline!" >&2
+    exit 1
+fi
+
+usage()
+{
+    {
+        echo "Usage:"
+        echo "        $0 add <kernel-version> <kernel-image>"
+        echo "        $0 remove <kernel-version> <kernel-image>"
+    } >&2
+}
+
+if ! ( [[ $COMMAND ]] && [[ $KERNEL_VERSION ]] && [[ $KERNEL_IMAGE ]] ); then
+    usage
+    exit 1
+fi
+
+BOOT_DIR="/${MACHINE_ID}/${KERNEL_VERSION}"
+BOOT_DIR_ABS="/boot${BOOT_DIR}"
+LOADER_ENTRY="/boot/loader/entries/${MACHINE_ID}-${KERNEL_VERSION}.conf"
+ret=0
+
+dropindirs_sort()
+{
+    suffix=$1; shift
+    readarray -t files< <(
+        for d in "$@"; do
+            for i in "${d}/"*${suffix}; do
+                [[ -e $i ]] && echo ${i##*/}
+            done
+        done | sort -Vu
+    )
+
+    for f in "${files[@]}"; do
+        for d in "$@"; do
+            if [[ -e "$d/$f" ]]; then
+                echo "$d/$f"
+                continue 2
+            fi
+        done
+    done
+}
+
+readarray -t PLUGINS < <(
+    dropindirs_sort ".install" \
+        "/etc/kernel/install.d" \
+        "/usr/lib/kernel/install.d"
+)
+
+case "$COMMAND" in
+    add)
+        mkdir -p "$BOOT_DIR_ABS" || exit 1
+
+        for f in "${PLUGINS[@]}"; do
+            [[ -x $f ]] && "$f" add "$KERNEL_VERSION" "$BOOT_DIR_ABS"
+            ((ret+=$?))
+        done
+
+        if ! cp --preserve "$KERNEL_IMAGE" "$BOOT_DIR_ABS"/linux; then
+            echo "Can't copy '$KERNEL_IMAGE to '$BOOT_DIR_ABS/linux'!" >&2
+        fi
+
+        [[ -d /boot/loader/entries ]] || mkdir -p /boot/loader/entries
+
+        {
+            echo "title      $PRETTY_NAME"
+            echo "version    $KERNEL_VERSION"
+            echo "machine-id $MACHINE_ID"
+            echo "options    ${BOOT_OPTIONS[@]}"
+            echo "linux      $BOOT_DIR/linux"
+            [[ -f "${BOOT_DIR_ABS}"/initrd ]] && \
+                echo "initrd     $BOOT_DIR/initrd"
+            :
+        } > $LOADER_ENTRY
+
+        ((ret+=$?))
+
+        if ! [[ -f $LOADER_ENTRY ]]; then
+            echo "Could not create '$LOADER_ENTRY'!" >&2
+        fi
+        ;;
+
+    remove)
+        for f in "${PLUGINS[@]}"; do
+            [[ -x $f ]] && "$f" remove "$KERNEL_VERSION" "$BOOT_DIR_ABS"
+            ((ret+=$?))
+        done
+
+        rm -fr "$BOOT_DIR_ABS"
+        rm -f "$LOADER_ENTRY"
+        ;;
+
+    *)
+        usage
+        ret=1;;
+esac
+
+((ret+=$?))
+
+exit $ret
index b8962e9..8b57421 100644 (file)
@@ -101,23 +101,30 @@ static uint64_t arg_retain =
         (1ULL << CAP_SYS_BOOT) |
         (1ULL << CAP_AUDIT_WRITE) |
         (1ULL << CAP_AUDIT_CONTROL);
+static char **arg_bind = NULL;
+static char **arg_bind_ro = NULL;
 
 static int help(void) {
 
         printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n"
                "Spawn a minimal namespace container for debugging, testing and building.\n\n"
-               "  -h --help               Show this help\n"
-               "  --version               Print version string\n"
-               "  -D --directory=NAME     Root directory for the container\n"
-               "  -b --boot               Boot up full system (i.e. invoke init)\n"
-               "  -u --user=USER          Run the command under specified user or uid\n"
-               "  -C --controllers=LIST   Put the container in specified comma-separated cgroup hierarchies\n"
-               "     --uuid=UUID          Set a specific machine UUID for the container\n"
-               "     --private-network    Disable network in container\n"
-               "     --read-only          Mount the root directory read-only\n"
-               "     --capability=CAP     In addition to the default, retain specified capability\n"
-               "     --link-journal=MODE  Link up guest journal, one of no, auto, guest, host\n"
-               "  -j                      Equivalent to --link-journal=host\n",
+               "  -h --help                Show this help\n"
+               "  --version                Print version string\n"
+               "  -D --directory=NAME      Root directory for the container\n"
+               "  -b --boot                Boot up full system (i.e. invoke init)\n"
+               "  -u --user=USER           Run the command under specified user or uid\n"
+               "  -C --controllers=LIST    Put the container in specified comma-separated\n"
+               "                           cgroup hierarchies\n"
+               "     --uuid=UUID           Set a specific machine UUID for the container\n"
+               "     --private-network     Disable network in container\n"
+               "     --read-only           Mount the root directory read-only\n"
+               "     --capability=CAP      In addition to the default, retain specified\n"
+               "                           capability\n"
+               "     --link-journal=MODE   Link up guest journal, one of no, auto, guest, host\n"
+               "  -j                       Equivalent to --link-journal=host\n"
+               "     --bind=PATH[:PATH]    Bind mount a file or directory from the host into\n"
+               "                           the container\n"
+               "     --bind-ro=PATH[:PATH] Similar, but creates a read-only bind mount\n",
                program_invocation_short_name);
 
         return 0;
@@ -131,7 +138,9 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_UUID,
                 ARG_READ_ONLY,
                 ARG_CAPABILITY,
-                ARG_LINK_JOURNAL
+                ARG_LINK_JOURNAL,
+                ARG_BIND,
+                ARG_BIND_RO
         };
 
         static const struct option options[] = {
@@ -146,6 +155,8 @@ static int parse_argv(int argc, char *argv[]) {
                 { "read-only",       no_argument,       NULL, ARG_READ_ONLY       },
                 { "capability",      required_argument, NULL, ARG_CAPABILITY      },
                 { "link-journal",    required_argument, NULL, ARG_LINK_JOURNAL    },
+                { "bind",            required_argument, NULL, ARG_BIND            },
+                { "bind-ro",         required_argument, NULL, ARG_BIND_RO         },
                 { NULL,              0,                 NULL, 0                   }
         };
 
@@ -258,6 +269,43 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case ARG_BIND:
+                case ARG_BIND_RO: {
+                        _cleanup_free_ char *a = NULL, *b = NULL;
+                        char *e;
+                        char ***x;
+                        int r;
+
+                        x = c == ARG_BIND ? &arg_bind : &arg_bind_ro;
+
+                        e = strchr(optarg, ':');
+                        if (e) {
+                                a = strndup(optarg, e - optarg);
+                                b = strdup(e + 1);
+                        } else {
+                                a = strdup(optarg);
+                                b = strdup(optarg);
+                        }
+
+                        if (!a || !b)
+                                return log_oom();
+
+                        if (!path_is_absolute(a) || !path_is_absolute(b)) {
+                                log_error("Invalid bind mount specification: %s", optarg);
+                                return -EINVAL;
+                        }
+
+                        r = strv_extend(x, a);
+                        if (r < 0)
+                                return r;
+
+                        r = strv_extend(x, b);
+                        if (r < 0)
+                                return r;
+
+                        break;
+                }
+
                 case '?':
                         return -EINVAL;
 
@@ -303,14 +351,9 @@ static int mount_all(const char *dest) {
                 char _cleanup_free_ *where = NULL;
                 int t;
 
-                if (asprintf(&where, "%s/%s", dest, mount_table[k].where) < 0) {
-                        log_oom();
-
-                        if (r == 0)
-                                r = -ENOMEM;
-
-                        break;
-                }
+                where = strjoin(dest, "/", mount_table[k].where, NULL);
+                if (!where)
+                        return log_oom();
 
                 t = path_is_mount_point(where, true);
                 if (t < 0) {
@@ -326,7 +369,7 @@ static int mount_all(const char *dest) {
                 if (mount_table[k].what && t > 0)
                         continue;
 
-                mkdir_p_label(where, 0755);
+                mkdir_p(where, 0755);
 
                 if (mount(mount_table[k].what,
                           where,
@@ -345,6 +388,32 @@ static int mount_all(const char *dest) {
         return r;
 }
 
+static int mount_binds(const char *dest, char **l, unsigned long flags) {
+        char **x, **y;
+
+        STRV_FOREACH_PAIR(x, y, l) {
+                _cleanup_free_ char *where = NULL;
+
+                where = strjoin(dest, "/", *y, NULL);
+                if (!where)
+                        return log_oom();
+
+                mkdir_p_label(where, 0755);
+
+                if (mount(*x, where, "bind", MS_BIND, NULL) < 0) {
+                        log_error("mount(%s) failed: %m", where);
+                        return -errno;
+                }
+
+                if (flags && mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|flags, NULL) < 0) {
+                        log_error("mount(%s) failed: %m", where);
+                        return -errno;
+                }
+        }
+
+        return 0;
+}
+
 static int setup_timezone(const char *dest) {
         _cleanup_free_ char *where = NULL, *p = NULL, *q = NULL, *check = NULL, *what = NULL;
         char *z, *y;
@@ -1227,7 +1296,7 @@ int main(int argc, char *argv[]) {
                         const char *home = NULL;
                         uid_t uid = (uid_t) -1;
                         gid_t gid = (gid_t) -1;
-                        unsigned n_env = 0;
+                        unsigned n_env = 2;
                         const char *envp[] = {
                                 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                                 "container=systemd-nspawn", /* LXC sets container=lxc, so follow the scheme here */
@@ -1241,8 +1310,9 @@ int main(int argc, char *argv[]) {
                                 NULL
                         };
 
-                        envp[2] = strv_find_prefix(environ, "TERM=");
-                        n_env = 3;
+                        envp[n_env] = strv_find_prefix(environ, "TERM=");
+                        if (envp[n_env])
+                                n_env ++;
 
                         close_nointr_nofail(pipefd[1]);
                         fd_wait_for_event(pipefd[0], POLLHUP, -1);
@@ -1346,6 +1416,12 @@ int main(int argc, char *argv[]) {
                         if (setup_journal(arg_directory) < 0)
                                 goto child_fail;
 
+                        if (mount_binds(arg_directory, arg_bind, 0) < 0)
+                                goto child_fail;
+
+                        if (mount_binds(arg_directory, arg_bind_ro, MS_RDONLY) < 0)
+                                goto child_fail;
+
                         if (chdir(arg_directory) < 0) {
                                 log_error("chdir(%s) failed: %m", arg_directory);
                                 goto child_fail;
index ff2dd45..293c261 100644 (file)
@@ -541,11 +541,11 @@ static int log_dispatch(
 
                         k = write_to_journal(level, file, line, func,
                                              object_name, object, buffer);
-                        if (k <= 0) {
-                                if (k < 0 && k != -EAGAIN)
+                        if (k < 0) {
+                                if (k != -EAGAIN)
                                         log_close_journal();
                                 log_open_kmsg();
-                        } else
+                        } else if (k > 0)
                                 r++;
                 }
 
@@ -554,11 +554,11 @@ static int log_dispatch(
 
                         k = write_to_syslog(level, file, line, func,
                                             object_name, object, buffer);
-                        if (k <= 0) {
-                                if (k < 0 && k != -EAGAIN)
+                        if (k < 0) {
+                                if (k != -EAGAIN)
                                         log_close_syslog();
                                 log_open_kmsg();
-                        } else
+                        } else if (k > 0)
                                 r++;
                 }
 
@@ -571,11 +571,10 @@ static int log_dispatch(
 
                         k = write_to_kmsg(level, file, line, func,
                                           object_name, object, buffer);
-                        if (k <= 0) {
-                                if (k < 0 && k != -EAGAIN)
-                                        log_close_kmsg();
+                        if (k < 0) {
+                                log_close_kmsg();
                                 log_open_console();
-                        } else
+                        } else if (k > 0)
                                 r++;
                 }
 
index fa4995c..ffdc536 100644 (file)
@@ -41,21 +41,26 @@ DEFINE_STRING_TABLE_LOOKUP(systemd_running_as, SystemdRunningAs);
 
 int user_config_home(char **config_home) {
         const char *e;
+        char *r;
 
         e = getenv("XDG_CONFIG_HOME");
         if (e) {
-                if (asprintf(config_home, "%s/systemd/user", e) < 0)
+                r = strappend(e, "/systemd/user");
+                if (!r)
                         return -ENOMEM;
 
+                *config_home = r;
                 return 1;
         } else {
                 const char *home;
 
                 home = getenv("HOME");
                 if (home) {
-                        if (asprintf(config_home, "%s/.config/systemd/user", home) < 0)
+                        r = strappend(home, "/.config/systemd/user");
+                        if (!r)
                                 return -ENOMEM;
 
+                        *config_home = r;
                         return 1;
                 }
         }
index 6c94d69..f6ddea3 100644 (file)
@@ -565,6 +565,45 @@ bool socket_address_matches_fd(const SocketAddress *a, int fd) {
         return false;
 }
 
+int make_socket_fd(const char* address, int flags) {
+        SocketAddress a;
+        int fd, r;
+        char _cleanup_free_ *p = NULL;
+
+        r = socket_address_parse(&a, address);
+        if (r < 0) {
+                log_error("failed to parse socket: %s", strerror(-r));
+                return r;
+        }
+
+        fd = socket(socket_address_family(&a), flags, 0);
+        if (fd < 0) {
+                log_error("socket(): %m");
+                return -errno;
+        }
+
+        r = socket_address_print(&a, &p);
+        if (r < 0) {
+                log_error("socket_address_print(): %s", strerror(-r));
+                return r;
+        }
+        log_info("Listening on %s", p);
+
+        r = bind(fd, &a.sockaddr.sa, a.size);
+        if (r < 0) {
+                log_error("bind to %s: %m", address);
+                return -errno;
+        }
+
+        r = listen(fd, SOMAXCONN);
+        if (r < 0) {
+                log_error("listen on %s: %m", address);
+                return -errno;
+        }
+
+        return fd;
+}
+
 static const char* const netlink_family_table[] = {
         [NETLINK_ROUTE] = "route",
         [NETLINK_FIREWALL] = "firewall",
index 771765d..3383834 100644 (file)
@@ -88,6 +88,8 @@ bool socket_address_is_netlink(const SocketAddress *a, const char *s);
 
 bool socket_address_matches_fd(const SocketAddress *a, int fd);
 
+int make_socket_fd(const char* address, int flags);
+
 bool socket_address_equal(const SocketAddress *a, const SocketAddress *b);
 
 bool socket_address_needs_mount(const SocketAddress *a, const char *prefix);
index ec25755..60c4762 100644 (file)
@@ -305,6 +305,31 @@ char **strv_split_quoted(const char *s) {
         return r;
 }
 
+char **strv_split_newlines(const char *s) {
+        char **l;
+        unsigned n;
+
+        assert(s);
+
+        /* Special version of strv_split() that splits on newlines and
+         * suppresses an empty string at the end */
+
+        l = strv_split(s, NEWLINE);
+        if (!l)
+                return NULL;
+
+        n = strv_length(l);
+        if (n <= 0)
+                return l;
+
+        if (isempty(l[n-1])) {
+                free(l[n-1]);
+                l[n-1] = NULL;
+        }
+
+        return l;
+}
+
 char *strv_join(char **l, const char *separator) {
         char *r, *e;
         char **s;
index b3802a7..623f102 100644 (file)
@@ -58,6 +58,7 @@ static inline bool strv_isempty(char **l) {
 
 char **strv_split(const char *s, const char *separator) _malloc_;
 char **strv_split_quoted(const char *s) _malloc_;
+char **strv_split_newlines(const char *s) _malloc_;
 
 char *strv_join(char **l, const char *separator) _malloc_;
 
index f5adedc..e643cd3 100644 (file)
@@ -2859,12 +2859,13 @@ cpu_set_t* cpu_set_malloc(unsigned *ncpus) {
         }
 }
 
-int status_vprintf(const char *status, bool ellipse, const char *format, va_list ap) {
+int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) {
         static const char status_indent[] = "         "; /* "[" STATUS "] " */
         _cleanup_free_ char *s = NULL;
         _cleanup_close_ int fd = -1;
-        struct iovec iovec[5];
+        struct iovec iovec[6];
         int n = 0;
+        static bool prev_ephemeral;
 
         assert(format);
 
@@ -2902,6 +2903,10 @@ int status_vprintf(const char *status, bool ellipse, const char *format, va_list
 
         zero(iovec);
 
+        if (prev_ephemeral)
+                IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE);
+        prev_ephemeral = ephemeral;
+
         if (status) {
                 if (!isempty(status)) {
                         IOVEC_SET_STRING(iovec[n++], "[");
@@ -2912,7 +2917,8 @@ int status_vprintf(const char *status, bool ellipse, const char *format, va_list
         }
 
         IOVEC_SET_STRING(iovec[n++], s);
-        IOVEC_SET_STRING(iovec[n++], "\n");
+        if (!ephemeral)
+                IOVEC_SET_STRING(iovec[n++], "\n");
 
         if (writev(fd, iovec, n) < 0)
                 return -errno;
@@ -2920,14 +2926,14 @@ int status_vprintf(const char *status, bool ellipse, const char *format, va_list
         return 0;
 }
 
-int status_printf(const char *status, bool ellipse, const char *format, ...) {
+int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) {
         va_list ap;
         int r;
 
         assert(format);
 
         va_start(ap, format);
-        r = status_vprintf(status, ellipse, format, ap);
+        r = status_vprintf(status, ellipse, ephemeral, format, ap);
         va_end(ap);
 
         return r;
@@ -3503,6 +3509,29 @@ int vtnr_from_tty(const char *tty) {
         return i;
 }
 
+char *resolve_dev_console(char **active) {
+        char *tty;
+
+        /* Resolve where /dev/console is pointing to, if /sys is actually ours
+         * (i.e. not read-only-mounted which is a sign for container setups) */
+
+        if (path_is_read_only_fs("/sys") > 0)
+                return NULL;
+
+        if (read_one_line_file("/sys/class/tty/console/active", active) < 0)
+                return NULL;
+
+        /* If multiple log outputs are configured the last one is what
+         * /dev/console points to */
+        tty = strrchr(*active, ' ');
+        if (tty)
+                tty++;
+        else
+                tty = *active;
+
+        return tty;
+}
+
 bool tty_is_vc_resolve(const char *tty) {
         char *active = NULL;
         bool b;
@@ -3512,19 +3541,11 @@ bool tty_is_vc_resolve(const char *tty) {
         if (startswith(tty, "/dev/"))
                 tty += 5;
 
-        /* Resolve where /dev/console is pointing to, if /sys is
-         * actually ours (i.e. not read-only-mounted which is a sign
-         * for container setups) */
-        if (streq(tty, "console") && path_is_read_only_fs("/sys") <= 0)
-                if (read_one_line_file("/sys/class/tty/console/active", &active) >= 0) {
-                        /* If multiple log outputs are configured the
-                         * last one is what /dev/console points to */
-                        tty = strrchr(active, ' ');
-                        if (tty)
-                                tty++;
-                        else
-                                tty = active;
-                }
+        if (streq(tty, "console")) {
+                tty = resolve_dev_console(&active);
+                if (!tty)
+                        return false;
+        }
 
         b = tty_is_vc(tty);
         free(active);
index 19cc36a..b5ad1ff 100644 (file)
@@ -55,10 +55,12 @@ union dirent_storage {
 #define FORMAT_BYTES_MAX 8
 
 #define ANSI_HIGHLIGHT_ON "\x1B[1;39m"
+#define ANSI_RED_ON "\x1B[31m"
 #define ANSI_HIGHLIGHT_RED_ON "\x1B[1;31m"
 #define ANSI_HIGHLIGHT_GREEN_ON "\x1B[1;32m"
 #define ANSI_HIGHLIGHT_YELLOW_ON "\x1B[1;33m"
 #define ANSI_HIGHLIGHT_OFF "\x1B[0m"
+#define ANSI_ERASE_TO_END_OF_LINE "\x1B[K"
 
 size_t page_size(void);
 #define PAGE_ALIGN(l) ALIGN_TO((l), page_size())
@@ -354,8 +356,8 @@ int pipe_eof(int fd);
 
 cpu_set_t* cpu_set_malloc(unsigned *ncpus);
 
-int status_vprintf(const char *status, bool ellipse, const char *format, va_list ap);
-int status_printf(const char *status, bool ellipse, const char *format, ...);
+int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap);
+int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...);
 int status_welcome(void);
 
 int fd_columns(int fd);
@@ -388,6 +390,7 @@ DIR *xopendirat(int dirfd, const char *name, int flags);
 
 char *fstab_node_to_udev_node(const char *p);
 
+char *resolve_dev_console(char **active);
 bool tty_is_vc(const char *tty);
 bool tty_is_vc_resolve(const char *tty);
 bool tty_is_console(const char *tty);
index 8174376..c67c6c9 100644 (file)
@@ -1045,7 +1045,7 @@ static bool need_daemon_reload(DBusConnection *bus, const char *unit) {
         if (!n)
                 return log_oom();
 
-        r = bus_method_call_with_reply (
+        r = bus_method_call_with_reply(
                         bus,
                         "org.freedesktop.systemd1",
                         "/org/freedesktop/systemd1",
@@ -1963,53 +1963,41 @@ static int kill_unit(DBusConnection *bus, char **args) {
 }
 
 static int set_cgroup(DBusConnection *bus, char **args) {
-        _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
-        DBusError error;
-        const char *method;
-        DBusMessageIter iter;
-        int r;
         _cleanup_free_ char *n = NULL;
-        const char *runtime;
+        const char *method, *runtime;
+        char **argument;
+        int r;
 
         assert(bus);
         assert(args);
 
-        dbus_error_init(&error);
-
         method =
-                streq(args[0], "set-cgroup")  ? "SetUnitControlGroups" :
-                streq(args[0], "unset-group") ? "UnsetUnitControlGroups"
-                                              : "UnsetUnitControlGroupAttributes";
+                streq(args[0], "set-cgroup")   ? "SetUnitControlGroup" :
+                streq(args[0], "unset-cgroup") ? "UnsetUnitControlGroup"
+                                               : "UnsetUnitControlGroupAttribute";
+
+        runtime = arg_runtime ? "runtime" : "persistent";
 
         n = unit_name_mangle(args[1]);
         if (!n)
                 return log_oom();
 
-        m = dbus_message_new_method_call(
-                        "org.freedesktop.systemd1",
-                        "/org/freedesktop/systemd1",
-                        "org.freedesktop.systemd1.Manager",
-                        method);
-        if (!m)
-                return log_oom();
-
-        dbus_message_iter_init_append(m, &iter);
-        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &n))
-                return log_oom();
-
-        r = bus_append_strv_iter(&iter, args + 2);
-        if (r < 0)
-                return log_oom();
+        STRV_FOREACH(argument, args + 2) {
 
-        runtime = arg_runtime ? "runtime" : "persistent";
-        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &runtime))
-                return log_oom();
-
-        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
-        if (!reply) {
-                log_error("Failed to issue method call: %s", bus_error_message(&error));
-                dbus_error_free(&error);
-                return -EIO;
+                r = bus_method_call_with_reply(
+                                bus,
+                                "org.freedesktop.systemd1",
+                                "/org/freedesktop/systemd1",
+                                "org.freedesktop.systemd1.Manager",
+                                method,
+                                NULL,
+                                NULL,
+                                DBUS_TYPE_STRING, &n,
+                                DBUS_TYPE_STRING, argument,
+                                DBUS_TYPE_STRING, &runtime,
+                                DBUS_TYPE_INVALID);
+                if (r < 0)
+                        return r;
         }
 
         return 0;
@@ -2018,20 +2006,17 @@ static int set_cgroup(DBusConnection *bus, char **args) {
 static int set_cgroup_attr(DBusConnection *bus, char **args) {
         _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
         DBusError error;
-        DBusMessageIter iter, sub, sub2;
-        char **x, **y;
+        DBusMessageIter iter;
         _cleanup_free_ char *n = NULL;
         const char *runtime;
+        int r;
 
         assert(bus);
         assert(args);
 
         dbus_error_init(&error);
 
-        if (strv_length(args) % 2 != 0) {
-                log_error("Expecting an uneven number of arguments!");
-                return -EINVAL;
-        }
+        runtime = arg_runtime ? "runtime" : "persistent";
 
         n = unit_name_mangle(args[1]);
         if (!n)
@@ -2041,26 +2026,20 @@ static int set_cgroup_attr(DBusConnection *bus, char **args) {
                         "org.freedesktop.systemd1",
                         "/org/freedesktop/systemd1",
                         "org.freedesktop.systemd1.Manager",
-                        "SetUnitControlGroupAttributes");
+                        "SetUnitControlGroupAttribute");
         if (!m)
                 return log_oom();
 
         dbus_message_iter_init_append(m, &iter);
         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &n) ||
-            !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ss)", &sub))
+            !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &args[2]))
                 return log_oom();
 
-        STRV_FOREACH_PAIR(x, y, args + 2) {
-                if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) ||
-                    !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, x) ||
-                    !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, y) ||
-                    !dbus_message_iter_close_container(&sub, &sub2))
-                        return log_oom();
-        }
+        r = bus_append_strv_iter(&iter, args + 3);
+        if (r < 0)
+                return log_oom();
 
-        runtime = arg_runtime ? "runtime" : "persistent";
-        if (!dbus_message_iter_close_container(&iter, &sub) ||
-            !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &runtime))
+        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &runtime))
                 return log_oom();
 
         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
@@ -2075,57 +2054,49 @@ static int set_cgroup_attr(DBusConnection *bus, char **args) {
 
 static int get_cgroup_attr(DBusConnection *bus, char **args) {
         _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
-        DBusError error;
-        DBusMessageIter iter;
-        int r;
         _cleanup_free_ char *n = NULL;
-        _cleanup_strv_free_ char **list = NULL;
-        char **a;
+        char **argument;
+        int r;
 
         assert(bus);
         assert(args);
 
-        dbus_error_init(&error);
-
         n = unit_name_mangle(args[1]);
         if (!n)
                 return log_oom();
 
-        m = dbus_message_new_method_call(
-                        "org.freedesktop.systemd1",
-                        "/org/freedesktop/systemd1",
-                        "org.freedesktop.systemd1.Manager",
-                        "GetUnitControlGroupAttributes");
-        if (!m)
-                return log_oom();
+        STRV_FOREACH(argument, args + 2) {
+                _cleanup_strv_free_ char **list = NULL;
+                DBusMessageIter iter;
+                char **a;
 
-        dbus_message_iter_init_append(m, &iter);
-        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &n))
-                return log_oom();
-
-        r = bus_append_strv_iter(&iter, args + 2);
-        if (r < 0)
-                return log_oom();
-
-        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
-        if (!reply) {
-                log_error("Failed to issue method call: %s", bus_error_message(&error));
-                dbus_error_free(&error);
-                return -EIO;
-        }
+                r = bus_method_call_with_reply(
+                                bus,
+                                "org.freedesktop.systemd1",
+                                "/org/freedesktop/systemd1",
+                                "org.freedesktop.systemd1.Manager",
+                                "GetUnitControlGroupAttribute",
+                                &reply,
+                                NULL,
+                                DBUS_TYPE_STRING, &n,
+                                DBUS_TYPE_STRING, argument,
+                                DBUS_TYPE_INVALID);
+                if (r < 0)
+                        return r;
 
-        dbus_message_iter_init(reply, &iter);
-        r = bus_parse_strv_iter(&iter, &list);
-        if (r < 0) {
-                log_error("Failed to parse value list.");
-                return r;
-        }
+                dbus_message_iter_init(reply, &iter);
+                r = bus_parse_strv_iter(&iter, &list);
+                if (r < 0) {
+                        log_error("Failed to parse value list.");
+                        return r;
+                }
 
-        STRV_FOREACH(a, list) {
-                if (endswith(*a, "\n"))
-                        fputs(*a, stdout);
-                else
-                        puts(*a);
+                STRV_FOREACH(a, list) {
+                        if (endswith(*a, "\n"))
+                                fputs(*a, stdout);
+                        else
+                                puts(*a);
+                }
         }
 
         return 0;
@@ -3985,16 +3956,15 @@ static int enable_unit(DBusConnection *bus, char **args) {
         }
 
         if (carries_install_info == 0)
-                log_warning(
-"The unit files have no [Install] section. They are not meant to be enabled\n"
-"using systemctl.\n"
-"Possible reasons for having this kind of units are:\n"
-"1) A unit may be statically enabled by being symlinked from another unit's\n"
-"   .wants/ or .requires/ directory.\n"
-"2) A unit's purpose may be to act as a helper for some other unit which has\n"
-"   a requirement dependency on it.\n"
-"3) A unit may be started when needed via activation (socket, path, timer,\n"
-"   D-Bus, udev, scripted systemctl call, ...).\n");
+                log_warning("The unit files have no [Install] section. They are not meant to be enabled\n"
+                            "using systemctl.\n"
+                            "Possible reasons for having this kind of units are:\n"
+                            "1) A unit may be statically enabled by being symlinked from another unit's\n"
+                            "   .wants/ or .requires/ directory.\n"
+                            "2) A unit's purpose may be to act as a helper for some other unit which has\n"
+                            "   a requirement dependency on it.\n"
+                            "3) A unit may be started when needed via activation (socket, path, timer,\n"
+                            "   D-Bus, udev, scripted systemctl call, ...).\n");
 
 finish:
         unit_file_changes_free(changes, n_changes);
@@ -5124,11 +5094,11 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError
                 { "condreload",            MORE,  2, start_unit        }, /* For compatibility with ALTLinux */
                 { "condrestart",           MORE,  2, start_unit        }, /* For compatibility with RH */
                 { "isolate",               EQUAL, 2, start_unit        },
-                { "set-cgroup",            MORE,  2, set_cgroup        },
-                { "unset-cgroup",          MORE,  2, set_cgroup        },
-                { "get-cgroup-attr",       MORE,  2, get_cgroup_attr   },
-                { "set-cgroup-attr",       MORE,  2, set_cgroup_attr   },
-                { "unset-cgroup-attr",     MORE,  2, set_cgroup        },
+                { "set-cgroup",            MORE,  3, set_cgroup        },
+                { "unset-cgroup",          MORE,  3, set_cgroup        },
+                { "get-cgroup-attr",       MORE,  3, get_cgroup_attr   },
+                { "set-cgroup-attr",       MORE,  4, set_cgroup_attr   },
+                { "unset-cgroup-attr",     MORE,  3, set_cgroup        },
                 { "kill",                  MORE,  2, kill_unit         },
                 { "is-active",             MORE,  2, check_unit_active },
                 { "check",                 MORE,  2, check_unit_active },
index e4dea09..cb0af4f 100644 (file)
@@ -129,17 +129,107 @@ static void test_safe_atod(void) {
         assert_se(r == -EINVAL);
 }
 
+static void test_strstrip(void) {
+       char *r;
+       char input[] = "   hello, waldo.   ";
+
+       r = strstrip(input);
+       assert_se(streq(r, "hello, waldo."));
+
+}
+
+static void test_delete_chars(void) {
+       char *r;
+       char input[] = "   hello, waldo.   abc";
+
+       r = delete_chars(input, WHITESPACE);
+       assert_se(streq(r, "hello,waldo.abc"));
+}
+
+static void test_in_charset(void) {
+      assert_se(in_charset("dddaaabbbcccc", "abcd"));
+      assert_se(!in_charset("dddaaabbbcccc", "abc f"));
+}
+
+static void test_hexchar(void) {
+        assert_se(hexchar(0xa) == 'a');
+        assert_se(hexchar(0x0) == '0');
+}
+
+static void test_unhexchar(void) {
+        assert_se(unhexchar('a') == 0xA);
+        assert_se(unhexchar('A') == 0xA);
+        assert_se(unhexchar('0') == 0x0);
+}
+
+static void test_octchar(void) {
+        assert_se(octchar(00) == '0');
+        assert_se(octchar(07) == '7');
+}
+
+static void test_unoctchar(void) {
+        assert_se(unoctchar('0') == 00);
+        assert_se(unoctchar('7') == 07);
+}
+
+static void test_decchar(void) {
+        assert_se(decchar(0) == '0');
+        assert_se(decchar(9) == '9');
+}
+
+static void test_undecchar(void) {
+        assert_se(undecchar('0') == 0);
+        assert_se(undecchar('9') == 9);
+}
+
+static void test_foreach_word(void) {
+        char *w, *state;
+        size_t l;
+        int i = 0;
+        const char test[] = "test abc d\te   f   ";
+        const char * const expected[] = {
+                "test",
+                "abc",
+                "d",
+                "e",
+                "f",
+                "",
+                NULL
+        };
+
+        FOREACH_WORD(w, l, test, state) {
+                assert_se(strneq(expected[i++], w, l));
+        }
+}
+
 static void test_foreach_word_quoted(void) {
         char *w, *state;
         size_t l;
-        const char test[] = "test a b c 'd' e '' '' hhh '' ''";
+        int i = 0;
+        const char test[] = "test a b c 'd' e '' '' hhh '' '' \"a b c\"";
+        const char * const expected[] = {
+                "test",
+                "a",
+                "b",
+                "c",
+                "d",
+                "e",
+                "",
+                "",
+                "hhh",
+                "",
+                "",
+                "a b c",
+                NULL
+        };
+
         printf("<%s>\n", test);
         FOREACH_WORD_QUOTED(w, l, test, state) {
-                char *t;
+                _cleanup_free_ char *t = NULL;
 
                 assert_se(t = strndup(w, l));
+                assert_se(strneq(expected[i++], w, l));
                 printf("<%s>\n", t);
-                free(t);
         }
 }
 
@@ -175,12 +265,22 @@ int main(int argc, char *argv[]) {
         test_streq_ptr();
         test_first_word();
         test_parse_boolean();
-        test_default_term_for_tty();
         test_parse_pid();
         test_parse_uid();
         test_safe_atolli();
         test_safe_atod();
+        test_strstrip();
+        test_delete_chars();
+        test_in_charset();
+        test_hexchar();
+        test_unhexchar();
+        test_octchar();
+        test_unoctchar();
+        test_decchar();
+        test_undecchar();
+        test_foreach_word();
         test_foreach_word_quoted();
+        test_default_term_for_tty();
         test_memdup_multiply();
 
         return 0;
index 98746c5..12b38af 100755 (executable)
@@ -24,5 +24,18 @@ grep 'hello\.service' /root/list-jobs.txt && exit 1
 
 # TODO: add more job queueing/merging tests here.
 
+# Test for irreversible jobs
+systemctl start unstoppable.service || exit 1
+
+# This is expected to fail with 'job cancelled'
+systemctl stop unstoppable.service && exit 1
+# But this should succeed
+systemctl stop --irreversible unstoppable.service || exit 1
+
+# We're going to shutdown soon. Let's see if it succeeds when
+# there's an active service that tries to be unstoppable.
+# Shutdown of the container/VM will hang if not.
+systemctl start unstoppable.service || exit 1
+
 touch /testok
 exit 0
index bd211f3..6eaba72 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/bash
 # -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
 # ex: ts=8 sw=4 sts=4 et filetype=sh
-TEST_DESCRIPTION="Job merging"
+TEST_DESCRIPTION="Job-related tests"
 
 KVERSION=${KVERSION-$(uname -r)}
 KERNEL_VER=$(uname -r)
@@ -138,7 +138,8 @@ Type=oneshot
 EOF
 
         # copy the units used by this test
-        cp $TEST_BASE_DIR/{hello.service,sleep.service,hello-after-sleep.target} $initdir/etc/systemd/system
+        cp $TEST_BASE_DIR/{hello.service,sleep.service,hello-after-sleep.target,unstoppable.service} \
+            $initdir/etc/systemd/system
         cp test-jobs.sh $initdir/
 
         mkdir -p $initdir/etc/systemd/system/testsuite.target.wants
diff --git a/test/unstoppable.service b/test/unstoppable.service
new file mode 100644 (file)
index 0000000..24fb0a2
--- /dev/null
@@ -0,0 +1,5 @@
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/bin/echo 'I'm unstoppable!'
+ExecStop=/bin/systemctl start --no-block unstoppable.service