Compare commits

...

37 Commits

Author SHA1 Message Date
Lennart Poettering e1da60e430
Merge pull request #17324 from keszybz/resolvectl-compat-output
resolvectl compat output
2020-10-22 14:57:41 +02:00
Hans Ulrich Niedermann e1e8c60af7 test-env-util: Verify that \r is disallowed in env var values
This adds tests to make sure that basic/env-util considers environment
variables containing \r characters invalid, and that it removes such
variables during environment cleanup in strv_env_clean*().

test-env-util has not verified this behaviour before.

As \r characters can be used to hide information, disallowing them
helps with systemd's security barrier role, even when the \r
character comes as part of a DOS style (\r\n) line ending.

Prompted-by: https://github.com/systemd/systemd/issues/17378
2020-10-22 14:57:02 +02:00
Lennart Poettering d975310342
Merge pull request #16632 from keszybz/test-path-yet-again
Tighten handling of spawned services in tests that may fail
2020-10-22 14:56:46 +02:00
Lennart Poettering 44f88e7050
Merge pull request #17415 from keszybz/logind-resolved-docs
A bunch of updates to logind and resolved man pages
2020-10-22 13:44:17 +02:00
Zbigniew Jędrzejewski-Szmek 7d1e1afe28 resolvectl: wrap the extended status string too 2020-10-22 13:20:40 +02:00
Zbigniew Jędrzejewski-Szmek f08a64c5e1 resolvect: use wrapping for various lists
dump_list() is used for DNS servers, DNS domains, fallback DNS servers.
2020-10-22 13:20:40 +02:00
Zbigniew Jędrzejewski-Szmek b0e3d79989 format-table: add TABLE_STRV_WRAPPED
The idea is that we have strvs like list of server names or addresses, where
the majority of strings is rather short, but some are long and there can
potentially be many strings. So formattting them either all on one line or all
in separate lines leads to output that is either hard to read or uses way too
many rows. We want to wrap them, but relying on the pager to do the wrapping is
not nice. Normal text has a lot of redundancy, so when the pager wraps a line
in the middle of a word the read can understand what is going on without any
trouble. But for a high-density zero-redundancy text like an IP address it is
much nicer to wrap between words. This also makes c&p easier.

This adds a variant of TABLE_STRV which is wrapped on output (with line breaks
inserted between different strv entries).

The change table_print() is quite ugly. A second pass is added to re-calculate
column widths. Since column size is now "soft", i.e. it can adjust based on
available columns, we need to two passes:
- first we figure out how much space we want
- in the second pass we figure out what the actual wrapped columns
  widths will be.

To avoid unnessary work, the second pass is only done when we actually have
wrappable fields.

A test is added in test-format-table.
2020-10-22 13:20:40 +02:00
Zbigniew Jędrzejewski-Szmek 16e925c6bb test-path: relax test in "ci" and "release" modes 2020-10-22 13:16:26 +02:00
Zbigniew Jędrzejewski-Szmek 4eb0c875f8 tests: add helper function to autodetect CI environments
Sadly there is no standarized way to check if we're running in some
CI environment. So let's try to gather the heuristics in one helper function.
2020-10-22 13:16:26 +02:00
Zbigniew Jędrzejewski-Szmek 31619e2f61 resolved: add trailing newlines in generated file
Fixup for b3ffa2b5f3.
2020-10-22 11:55:35 +02:00
Zbigniew Jędrzejewski-Szmek 9c0780af5d resolved.conf: add empty DNSStubListenerExtra= 2020-10-22 11:55:35 +02:00
Zbigniew Jędrzejewski-Szmek bace688394 man: document differences between nss-resolve and nss-dns
https://bugzilla.redhat.com/show_bug.cgi?id=1889012
https://serverfault.com/questions/626612/dns-just-started-resolving-my-server-prod-addresses-to-127-0-53-53
https://serverfault.com/questions/649352/what-are-the-security-implications-of-the-allow-dns-suffix-appending-to-unquali
2020-10-22 11:55:35 +02:00
Zbigniew Jędrzejewski-Szmek 2ce493672c man/org.freedesktop.resolve1: briefly document a bunch of methods and properties
Fixes #13799.
2020-10-22 11:55:35 +02:00
Zbigniew Jędrzejewski-Szmek 9e1804b24c man/systemd-resolved: reword the description of query a bit
The phrase "routing domains" is used to mean both route-only domains and search
domains. Route-only domains are always called like that, and not just "route domains".

Some paragraphs are reordered to describe synthetisized records first, then
LLMNR, then various ways quries are routed.

Fixes #8928, hopefully.
2020-10-22 11:55:35 +02:00
Lennart Poettering 349bbd8331
Merge pull request #13589 from williamvds/systemctl-edit-copy
systemctl: show original contents in comment when editing unit
2020-10-22 11:19:26 +02:00
Kairui Song edb8c98446 pstore: don't enable crash_kexec_post_notifiers by default
commit f00c36641a enabled
crash_kexec_post_notifiers by default regardless of whether pstore
is enabled or not.

The original intention to enabled this option by default is that
it only affects kernel post-panic behavior, so should have no harm.
But this is not true if the user wants a reliable kdump.

crash_kexec_post_notifiers is known to cause problem with kdump,
and it's documented in kernel. It's not easy to fix the problem
because of how kdump works. Kdump expects the crashed kernel to
jump to an pre-loaded crash kernel, so doing any extra job before
the jump will increase the risk.

It depends on the user to choose between having a reliable kdump or
some other post-panic debug mechanic.

So it's better to keep this config untouched by default, or it may put
kdump at higher risk of failing silently. User should enable it by
uncommenting the config line manually if pstore is always needed.

Also add a inline comment inform user about the potential issue.

Thanks to Dave Young for finding out this issue.

Fixes #16661

Signed-off-by: Kairui Song <kasong@redhat.com>
2020-10-22 11:17:39 +02:00
Lennart Poettering dbb74162b2
Merge pull request #17412 from mrc0mmand/ghworkflow-buildtest-improvements
ci: GH 'build test' improvements
2020-10-22 11:16:12 +02:00
Zbigniew Jędrzejewski-Szmek b8ee3493a5 meson: convert developer_mode boolean to an enum
I initially changed this to add a third state. But even with two values having
an explicit name instead of just 0/1 is mode descriptive.
2020-10-22 11:05:17 +02:00
Zbigniew Jędrzejewski-Szmek 4cce6f2524 test-path: start infinite sleep instead of a short command
The test sometimes fails, e.g. in bionic-s390x ci. I think it might be because
the service binary exits before we get a chance to notice that it is running:
13:59:31 --- Listing only the last 100 lines from a long log. ---
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 4639845)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 4539608)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 4439376)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 4338946)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 4238702)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 4138424)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 4038116)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 3937835)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 3837553)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 3737250)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 3636934)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 3536622)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 3436318)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 3336021)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 3235730)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 3135468)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 3035158)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 2934855)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 2834541)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 2732511)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 2632255)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 2532014)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 2431746)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 2331438)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 2231213)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 2130952)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 2030663)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 1930428)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 1830172)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 1729906)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 1629652)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 1529368)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 1429110)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 1328852)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 1228593)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 1128320)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 1028083)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 927824)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 827564)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 724935)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 624664)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 524411)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 424124)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 323853)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 223585)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 120356)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: 18053)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 line 293: path-unit.path: state = running; result = success (left: -82385)
13:59:31 line 293: path-mycustomunit.service: state = exited; result = success
13:59:31 Test timeout when testing path-unit.path

It seems test/test-path/path-service.service wasn't actually used for anything.
2020-10-22 11:05:17 +02:00
Zbigniew Jędrzejewski-Szmek 28c48f4d78 tests: replace the few remaining Type=simple with Type=exec
Except for the places where we explicitly want to test Type=simple,
we should use Type=exec.
2020-10-22 11:05:17 +02:00
Zbigniew Jędrzejewski-Szmek 67b2edb21f xdg-autostart-generator: use Type=exec
We check that the binary exists before writing the service file, but
let's also not consider the service started until the fork has happened.
This is still relatively new stuff, so we're can change the implementation
details like this.
2020-10-22 11:05:17 +02:00
Zbigniew Jędrzejewski-Szmek 333d102c64 test-path: use Type=exec
In general, Type=exec is superior to Type=simple. Let's not assume that
the service is started before it was really started.
2020-10-22 11:05:17 +02:00
Zbigniew Jędrzejewski-Szmek fcb7138ca7 test-path: do not fail the test if we fail to start a service because of cgroup setup
The test was failing because it couldn't start the service:

path-modified.service: state = failed; result = exit-code
path-modified.path: state = waiting; result = success
path-modified.service: state = failed; result = exit-code
path-modified.path: state = waiting; result = success
path-modified.service: state = failed; result = exit-code
path-modified.path: state = waiting; result = success
path-modified.service: state = failed; result = exit-code
path-modified.path: state = waiting; result = success
path-modified.service: state = failed; result = exit-code
path-modified.path: state = waiting; result = success
path-modified.service: state = failed; result = exit-code
Failed to connect to system bus: No such file or directory
-.slice: Failed to enable/disable controllers on cgroup /system.slice/kojid.service, ignoring: Permission denied
path-modified.service: Failed to create cgroup /system.slice/kojid.service/path-modified.service: Permission denied
path-modified.service: Failed to attach to cgroup /system.slice/kojid.service/path-modified.service: No such file or directory
path-modified.service: Failed at step CGROUP spawning /bin/true: No such file or directory
path-modified.service: Main process exited, code=exited, status=219/CGROUP
path-modified.service: Failed with result 'exit-code'.
Test timeout when testing path-modified.path

In fact any of the services that we try to start may fail, especially
considering that we're doing some rogue cgroup operations. See
https://github.com/systemd/systemd/pull/16603#issuecomment-679133641.
2020-10-22 11:05:17 +02:00
Zbigniew Jędrzejewski-Szmek 75edb0b0d6 test-path: more debugging information
Just to make it easier to grok what happens when test-path fails.
Change printf→log_info so that output is interleaved and not split in two
independent parts in log files.
2020-10-22 11:05:17 +02:00
williamvds 85c5d313b5
systemctl: show original contents when editing unit
A comment indicates the start of the new contents of the override file,
and another indicates that lines following it will be discarded once
editing is finished.
The contents of the unit file and drop-ins are listed out after this
last marker.

Adds WRITE_STRING_FILE_TRUNCATE to set O_TRUNC when opening a file.

Thanks to cgzones for providing the required SELinux function calls.

Co-authored-by: Christian Göttsche <cgzones@googlemail.com>
2020-10-21 21:20:01 +01:00
williamvds 6797a74f78
Add WRITE_STRING_FILE_TRUNCATE to set O_TRUNC 2020-10-21 17:18:25 +01:00
williamvds 82443be506
Add strv_prepend
Inserts a copy of the value at the head of the list.
2020-10-21 17:14:37 +01:00
Frantisek Sumsal 5310fc624e ci: add the libfido2 dependency for better coverage 2020-10-21 16:28:22 +02:00
Frantisek Sumsal 0273f6e7f2 ci: build with clang-11 as well
The LLVM nightly repositories now have a separate branch for clang-11,
so let's build with it as well.
2020-10-21 16:24:01 +02:00
Frantisek Sumsal f1150eddf6 ci: bump the 'build test' image to Ubuntu Focal 2020-10-21 16:23:59 +02:00
Zbigniew Jędrzejewski-Szmek 6f8ca84c9b format-table: reduce scope of iterator variables 2020-10-20 19:58:37 +02:00
Zbigniew Jędrzejewski-Szmek fe37e5a5d1 resolvectl: use compat status string instead of a field-by-field table
The status string is modeled after our --version output: +enabled -disabled equals=more-info

For example:
     Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=allow-downgrade/supported
2020-10-20 19:58:37 +02:00
Zbigniew Jędrzejewski-Szmek af781878d5 resolvectl: sort domain/nta output
dns list shall not be sorted.
2020-10-20 19:58:37 +02:00
Zbigniew Jędrzejewski-Szmek 80b8c3d7fd resolvectl: add the usual typedef for struct link_info/global_info
Also move the struct defintions up in preparation for further changes.
2020-10-20 19:58:37 +02:00
Zbigniew Jędrzejewski-Szmek 2c91906e25 man: add example of negative trust anchor file
Fixes #17226.
2020-10-20 19:58:37 +02:00
Zbigniew Jędrzejewski-Szmek 7c50230378 resolvectl: break nta/domain/dns listings with newlines
We would print the whole string as a single super-long line. Let's nicely
break the text into lines that fit on the screen.

$ COLUMNS=70 build/resolvectl --no-pager nta
Global: home local intranet 23.172.in-addr.arpa lan
        18.172.in-addr.arpa 16.172.in-addr.arpa 19.172.in-addr.arpa
        25.172.in-addr.arpa 21.172.in-addr.arpa d.f.ip6.arpa
        20.172.in-addr.arpa 30.172.in-addr.arpa 17.172.in-addr.arpa
        internal 168.192.in-addr.arpa 28.172.in-addr.arpa
        22.172.in-addr.arpa 24.172.in-addr.arpa 26.172.in-addr.arpa
        corp 10.in-addr.arpa private 29.172.in-addr.arpa test
        27.172.in-addr.arpa 31.172.in-addr.arpa
Link 2 (hub0):
Link 4 (enp0s31f6):
Link 5 (wlp4s0):
Link 7 (virbr0): adsfasdfasdfasd.com 21.172.in-addr.arpa lan j b
        a.com home d.f.ip6.arpa b.com local 16.172.in-addr.arpa
        19.172.in-addr.arpa 18.172.in-addr.arpa 25.172.in-addr.arpa
        20.172.in-addr.arpa k i h 23.172.in-addr.arpa
        168.192.in-addr.arpa d g intranet 17.172.in-addr.arpa c e.com
        30.172.in-addr.arpa a f d.com e internal
Link 8 (virbr0-nic):
Link 9 (vnet0):
Link 10 (vb-rawhide):
Link 15 (wwp0s20f0u2i12):
2020-10-20 19:58:37 +02:00
Zbigniew Jędrzejewski-Szmek 60de93e744 tree-wide: update web link to logind description
https://www.freedesktop.org/wiki/Software/systemd/multiseat/ says that it
is obsoleted by sd-login(3), so it doesn't make much sense to link to the former.
2020-10-18 11:34:29 +02:00
45 changed files with 1140 additions and 601 deletions

View File

@ -13,13 +13,14 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-18.04 runs-on: ubuntu-20.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
env: env:
- { COMPILER: "gcc", COMPILER_VERSION: "10" } - { COMPILER: "gcc", COMPILER_VERSION: "10" }
- { COMPILER: "clang", COMPILER_VERSION: "10" } - { COMPILER: "clang", COMPILER_VERSION: "10" }
- { COMPILER: "clang", COMPILER_VERSION: "11" }
env: ${{ matrix.env }} env: ${{ matrix.env }}
steps: steps:
- name: Repository checkout - name: Repository checkout

View File

@ -19,7 +19,6 @@ PACKAGES=(
expect expect
fdisk fdisk
gettext gettext
iptables-dev
iputils-ping iputils-ping
isc-dhcp-client isc-dhcp-client
itstool itstool
@ -28,6 +27,7 @@ PACKAGES=(
libcap-dev libcap-dev
libcurl4-gnutls-dev libcurl4-gnutls-dev
libfdisk-dev libfdisk-dev
libfido2-dev
libgpg-error-dev libgpg-error-dev
liblz4-dev liblz4-dev
liblzma-dev liblzma-dev
@ -38,6 +38,7 @@ PACKAGES=(
libqrencode-dev libqrencode-dev
libssl-dev libssl-dev
libxkbcommon-dev libxkbcommon-dev
libxtables-dev
libzstd-dev libzstd-dev
mount mount
net-tools net-tools

3
TODO
View File

@ -813,9 +813,6 @@ Features:
* systemctl: if some operation fails, show log output? * systemctl: if some operation fails, show log output?
* systemctl edit: use equivalent of cat() to insert existing config as a comment, prepended with #.
Upon editor exit, lines with one # are removed, lines with two # are left with one #, etc.
* exponential backoff in timesyncd when we cannot reach a server * exponential backoff in timesyncd when we cannot reach a server
* timesyncd: add ugly bus calls to set NTP servers per-interface, for usage by NM * timesyncd: add ugly bus calls to set NTP servers per-interface, for usage by NM

View File

@ -138,7 +138,17 @@
and follow the same overriding rules. They are text files with the and follow the same overriding rules. They are text files with the
<filename>.negative</filename> suffix. Empty lines and lines whose first character is <filename>.negative</filename> suffix. Empty lines and lines whose first character is
<literal>;</literal> are ignored. Each line specifies one domain name which is the root of a DNS <literal>;</literal> are ignored. Each line specifies one domain name which is the root of a DNS
subtree where validation shall be disabled.</para> subtree where validation shall be disabled. For example:</para>
<programlisting># Reverse IPv4 mappings
10.in-addr.arpa
16.172.in-addr.arpa
168.192.in-addr.arpa
...
# Some custom domains
prod
stag
</programlisting>
<para>Negative trust anchors are useful to support private DNS <para>Negative trust anchors are useful to support private DNS
subtrees that are not referenced from the Internet DNS hierarchy, subtrees that are not referenced from the Internet DNS hierarchy,

View File

@ -207,7 +207,7 @@ if dbus_docs.length() > 0
'@INPUT@'], '@INPUT@'],
input : dbus_docs) input : dbus_docs)
if conf.get('DEVELOPER_MODE') == 1 if conf.get('BUILD_MODE') == 'BUILD_MODE_DEVELOPER'
test('dbus-docs-fresh', test('dbus-docs-fresh',
update_dbus_docs_py, update_dbus_docs_py,
args : ['--build-dir=@0@'.format(project_build_root), args : ['--build-dir=@0@'.format(project_build_root),

View File

@ -156,12 +156,6 @@ node /org/freedesktop/resolve1 {
}; };
</programlisting> </programlisting>
<!--method SetLinkDNSEx is not documented!-->
<!--method SetLinkDefaultRoute is not documented!-->
<!--method SetLinkDNSOverTLS is not documented!-->
<!--method RegisterService is not documented!--> <!--method RegisterService is not documented!-->
<!--method UnregisterService is not documented!--> <!--method UnregisterService is not documented!-->
@ -170,28 +164,8 @@ node /org/freedesktop/resolve1 {
<!--method ResetServerFeatures is not documented!--> <!--method ResetServerFeatures is not documented!-->
<!--property LLMNR is not documented!-->
<!--property MulticastDNS is not documented!-->
<!--property DNSOverTLS is not documented!-->
<!--property DNSEx is not documented!-->
<!--property FallbackDNS is not documented!-->
<!--property FallbackDNSEx is not documented!-->
<!--property CurrentDNSServer is not documented!-->
<!--property CurrentDNSServerEx is not documented!-->
<!--property DNSSEC is not documented!-->
<!--property DNSSECNegativeTrustAnchors is not documented!--> <!--property DNSSECNegativeTrustAnchors is not documented!-->
<!--property DNSStubListener is not documented!-->
<!--Autogenerated cross-references for systemd.directives, do not edit--> <!--Autogenerated cross-references for systemd.directives, do not edit-->
<variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.resolve1.Manager"/> <variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.resolve1.Manager"/>
@ -281,26 +255,28 @@ node /org/freedesktop/resolve1 {
<refsect2> <refsect2>
<title>Methods</title> <title>Methods</title>
<para><function>ResolveHostname()</function> takes a hostname and resolves it to one or more IP addresses. <para><function>ResolveHostname()</function> takes a hostname and resolves it to one or more IP
As parameters it takes the Linux network interface index to execute the query on, or 0 if it may be addresses. As parameters it takes the Linux network interface index to execute the query on, or 0 if
done on any suitable interface. The <varname>name</varname> parameter specifies the hostname to it may be done on any suitable interface. The <varname>name</varname> parameter specifies the hostname
resolve. Note that if required, IDNA conversion is applied to this name unless it is resolved via LLMNR or MulticastDNS. The <varname>family</varname> parameter to resolve. Note that if required, IDNA conversion is applied to this name unless it is resolved via
limits the results to a specific address family. It may be <constant>AF_INET</constant>, LLMNR or MulticastDNS. The <varname>family</varname> parameter limits the results to a specific address
<constant>AF_INET6</constant> or <constant>AF_UNSPEC</constant>. If <constant>AF_UNSPEC</constant> is specified (recommended), both kinds are retrieved, subject family. It may be <constant>AF_INET</constant>, <constant>AF_INET6</constant> or
to local network configuration (i.e. if no local, routable IPv6 address is found, no IPv6 address is <constant>AF_UNSPEC</constant>. If <constant>AF_UNSPEC</constant> is specified (recommended), both
retrieved; and similarly for IPv4). A 64-bit <varname>flags</varname> field may be used to alter the kinds are retrieved, subject to local network configuration (i.e. if no local, routable IPv6 address is
behaviour of the resolver operation (see below). The method returns an array of address records. Each found, no IPv6 address is retrieved; and similarly for IPv4). A 64-bit <varname>flags</varname> field
address record consists of the interface index the address belongs to, an address family as well as a may be used to alter the behaviour of the resolver operation (see below). The method returns an array
byte array with the actual IP address data (which either has 4 or 16 elements, depending on the address of address records. Each address record consists of the interface index the address belongs to, an
family). The returned address family will be one of <constant>AF_INET</constant> or address family as well as a byte array with the actual IP address data (which either has 4 or 16
<constant>AF_INET6</constant>. For IPv6, the returned address interface index should be used to elements, depending on the address family). The returned address family will be one of
initialize the .sin6_scope_id field of a <structname>struct sockaddr_in6</structname> instance to permit <constant>AF_INET</constant> or <constant>AF_INET6</constant>. For IPv6, the returned address interface
support for resolution to link-local IP addresses. The address array is followed by the canonical name index should be used to initialize the .sin6_scope_id field of a
of the host, which may or may not be identical to the resolved hostname. Finally, a 64-bit <structname>struct sockaddr_in6</structname> instance to permit support for resolution to link-local IP
<varname>flags</varname> field is returned that is defined similarly to the <varname>flags</varname> addresses. The address array is followed by the canonical name of the host, which may or may not be
field that was passed in, but contains information about the resolved data (see below). If the hostname identical to the resolved hostname. Finally, a 64-bit <varname>flags</varname> field is returned that
passed in is an IPv4 or IPv6 address formatted as string, it is parsed, and the result is returned. In is defined similarly to the <varname>flags</varname> field that was passed in, but contains information
this case, no network communication is done.</para> about the resolved data (see below). If the hostname passed in is an IPv4 or IPv6 address formatted as
string, it is parsed, and the result is returned. In this case, no network communication is
done.</para>
<para><function>ResolveAddress()</function> executes the reverse operation: it takes an IP address and <para><function>ResolveAddress()</function> executes the reverse operation: it takes an IP address and
acquires one or more hostnames for it. As parameters it takes the interface index to execute the query acquires one or more hostnames for it. As parameters it takes the interface index to execute the query
@ -387,15 +363,19 @@ node /org/freedesktop/resolve1 {
<constant>AF_INET6</constant>), followed by a 4-byte or 16-byte array with the raw address data. This <constant>AF_INET6</constant>), followed by a 4-byte or 16-byte array with the raw address data. This
method is a one-step shortcut for retrieving the Link object for a network interface using method is a one-step shortcut for retrieving the Link object for a network interface using
<function>GetLink()</function> (see above) and then invoking the <function>SetDNS()</function> method <function>GetLink()</function> (see above) and then invoking the <function>SetDNS()</function> method
(see below) on it. (see below) on it.</para>
<para><function>SetLinkDNSEx()</function> is similar to <function>SetLinkDNS()</function>, but allows
an IP port (instead of the default 53) and DNS name to be specified for each DNS server. The server
name is used for Server Name Indication (SNI), which is useful when DNS-over-TLS is
used. C.f. <varname>DNS=</varname> in
<citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para> </para>
<para>Network management software integrating with <filename>systemd-resolved</filename> should <para><function>SetLinkDefaultRoute()</function> specifies whether the link shall be used as the
call this method (and the five below) after the interface appeared in the kernel (and thus after a default route for name queries. See the description of name routing in
network interface index has been assigned), but before the network interfaces is activated <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
(<constant>IFF_UP</constant> set) so that all settings take effect during the full time the network for details.</para>
interface is up. It is safe to alter settings while the interface is up, however. Use
<function>RevertLink()</function> (described below) to reset all per-interface settings.</para>
<para>The <function>SetLinkDomains()</function> method sets the search and routing domains to use on a <para>The <function>SetLinkDomains()</function> method sets the search and routing domains to use on a
specific network interface for DNS look-ups. It takes a network interface index and an array of domains, specific network interface for DNS look-ups. It takes a network interface index and an array of domains,
@ -432,8 +412,22 @@ node /org/freedesktop/resolve1 {
Negative Trust Anchors (NTAs) for a specific network interface. It takes a network interface index and a Negative Trust Anchors (NTAs) for a specific network interface. It takes a network interface index and a
list of domains as arguments.</para> list of domains as arguments.</para>
<para>The <function>RevertLink()</function> method may be used to revert all per-link settings done with <para>The <function>SetLinkDNSOverTLS()</function> method enables or disables DNS-over-TLS.
the six methods described above to the defaults again.</para> C.f. <varname>DNSOverTLS=</varname> in
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for details.</para>
<para>Network management software integrating with <filename>systemd-resolved</filename> should call
<function>SetLinkDNS()</function> or <function>SetLinkDNSEx()</function>,
<function>SetLinkDefaultRoute()</function>, <function>SetLinkDomains()</function> and others after the
interface appeared in the kernel (and thus after a network interface index has been assigned), but
before the network interfaces is activated (<constant>IFF_UP</constant> set) so that all settings take
effect during the full time the network interface is up. It is safe to alter settings while the
interface is up, however. Use <function>RevertLink()</function> (described below) to reset all
per-interface settings.</para>
<para>The <function>RevertLink()</function> method may be used to revert all per-link settings
described above to the defaults.</para>
<refsect3> <refsect3>
<title>The Flags Parameter</title> <title>The Flags Parameter</title>
@ -458,11 +452,11 @@ node /org/freedesktop/resolve1 {
classic unicast DNS, LLMNR via IPv4/UDP and IPv6/UDP respectively, as well as MulticastDNS via classic unicast DNS, LLMNR via IPv4/UDP and IPv6/UDP respectively, as well as MulticastDNS via
IPv4/UDP and IPv6/UDP. If all of these five bits are off on input (which is strongly recommended) the IPv4/UDP and IPv6/UDP. If all of these five bits are off on input (which is strongly recommended) the
look-up will be done via all suitable protocols for the specific look-up. Note that these flags look-up will be done via all suitable protocols for the specific look-up. Note that these flags
operate as filter only, but cannot force a look-up to be done via a protocol. Specifically, <filename>systemd-resolved</filename> operate as filter only, but cannot force a look-up to be done via a protocol. Specifically,
will only route look-ups within the .local TLD to MulticastDNS (plus some reverse look-up address <filename>systemd-resolved</filename> will only route look-ups within the .local TLD to MulticastDNS
domains), and single-label names to LLMNR (plus some reverse address lookup domains). It will route (plus some reverse look-up address domains), and single-label names to LLMNR (plus some reverse
neither of these to Unicast DNS servers. Also, it will do LLMNR and Multicast DNS only on interfaces address lookup domains). It will route neither of these to Unicast DNS servers. Also, it will do
suitable for multicast.</para> LLMNR and Multicast DNS only on interfaces suitable for multicast.</para>
<para>On output, these five flags indicate which protocol was used to execute the operation, and hence <para>On output, these five flags indicate which protocol was used to execute the operation, and hence
where the data was found.</para> where the data was found.</para>
@ -498,34 +492,50 @@ node /org/freedesktop/resolve1 {
the data is "rightfully" unauthenticated (which includes cases where the underlying protocol or server the data is "rightfully" unauthenticated (which includes cases where the underlying protocol or server
does not support authenticating data).</para> does not support authenticating data).</para>
</refsect3> </refsect3>
</refsect2> </refsect2>
<refsect2> <refsect2>
<title>Properties</title> <title>Properties</title>
<para>The <varname>LLMNR</varname> and <varname>MulticastDNS</varname> properties report whether LLMNR
and MulticastDNS are (globally) enabled. Each may be one of <literal>yes</literal>,
<literal>no</literal>, and <literal>resolve</literal>. See <function>SetLinkLLMNR()</function>
and <function>SetLinkMulticastDNS()</function> above.</para>
<para><varname>LLMNRHostname</varname> contains the hostname currently exposed on the network via <para><varname>LLMNRHostname</varname> contains the hostname currently exposed on the network via
LLMNR. It usually follows the system hostname as may be queried via LLMNR. It usually follows the system hostname as may be queried via
<citerefentry project="man-pages"><refentrytitle>gethostname</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry project="man-pages"><refentrytitle>gethostname</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
but may differ if a conflict is detected on the network.</para> but may differ if a conflict is detected on the network.</para>
<para><varname>DNS</varname> contains an array of all DNS servers currently used by <para><varname>DNS</varname> and <varname>DNSEx</varname> contain arrays of all DNS servers currently
<filename>systemd-resolved</filename>. It contains similar information as the DNS server data written to used by <filename>systemd-resolved</filename>. <varname>DNS</varname> contains information similar to
/run/systemd/resolve/resolv.conf. Each structure in the array consists of a numeric network interface the DNS server data in <filename>/run/systemd/resolve/resolv.conf</filename>. Each structure in the
index, an address family, and a byte array containing the DNS server address (either 4 bytes in length array consists of a numeric network interface index, an address family, and a byte array containing the
for IPv4 or 16 bytes in lengths for IPv6). The array contains DNS servers configured system-wide, DNS server address (either 4 bytes in length for IPv4 or 16 bytes in lengths for IPv6).
including those possibly read from a foreign <filename>/etc/resolv.conf</filename> or the <varname>DNSEx</varname> is similar, but additionally contains the IP port and server name (used for
<varname>DNS=</varname> setting in <filename>/etc/systemd/resolved.conf</filename>, as well as Server Name Indication, SNI). Both arrays contain DNS servers configured system-wide, including those
per-interface DNS server information either retrieved from possibly read from a foreign <filename>/etc/resolv.conf</filename> or the <varname>DNS=</varname>
setting in <filename>/etc/systemd/resolved.conf</filename>, as well as per-interface DNS server
information either retrieved from
<citerefentry><refentrytitle>systemd-networkd</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-networkd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
or configured by external software via <function>SetLinkDNS()</function> (see above). The network or configured by external software via <function>SetLinkDNS()</function> or
interface index will be 0 for the system-wide configured services and non-zero for the per-link <function>SetLinkDNSEx()</function> (see above). The network interface index will be 0 for the
servers.</para> system-wide configured services and non-zero for the per-link servers.</para>
<para>Similarly, the <varname>Domains</varname> property contains an array of all search and <para><varname>FallbackDNS</varname> and <varname>FallbackDNSEx</varname> contain arrays of all DNS
routing domains currently used by <filename>systemd-resolved</filename>. Each entry consists of a network interface index (again, 0 servers configured as fallback servers, if any, using the same format as <varname>DNS</varname> and
encodes system-wide entries), the actual domain name, and whether the entry is used only for routing <varname>DNSEx</varname> described above. See the description of <varname>FallbackDNS=</varname> in
(true) or for both routing and searching (false).</para> <citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
the description of when those servers are used.</para>
<para><varname>CurrentDNSServer</varname> and <varname>CurrentDNSServerEx</varname> specify the server
that is currently used for query resolution, in the same format as a single entry in the
<varname>DNS</varname> and <varname>DNSEx</varname> arrays described above.</para>
<para>Similarly, the <varname>Domains</varname> property contains an array of all search and routing
domains currently used by <filename>systemd-resolved</filename>. Each entry consists of a network
interface index (again, 0 encodes system-wide entries), the actual domain name, and whether the entry
is used only for routing (true) or for both routing and searching (false).</para>
<para>The <varname>TransactionStatistics</varname> property contains information about the number of <para>The <varname>TransactionStatistics</varname> property contains information about the number of
transactions <filename>systemd-resolved</filename> has processed. It contains a pair of unsigned 64-bit counters, the first transactions <filename>systemd-resolved</filename> has processed. It contains a pair of unsigned 64-bit counters, the first
@ -542,6 +552,13 @@ node /org/freedesktop/resolve1 {
cache misses. The latter counters may be reset using <function>ResetStatistics()</function> (see cache misses. The latter counters may be reset using <function>ResetStatistics()</function> (see
above).</para> above).</para>
<para>The <varname>DNSSEC</varname> property specifies current status of DNSSEC validation. It is one
of <literal>yes</literal> (validation is enforced), <literal>no</literal> (no validation is done),
<literal>allow-downgrade</literal> (validation is done if the current DNS server supports it). See the
description of <varname>DNSSEC=</varname> in
<citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para>
<para>The <varname>DNSSECStatistics</varname> property contains information about the DNSSEC <para>The <varname>DNSSECStatistics</varname> property contains information about the DNSSEC
validations executed so far. It contains four 64-bit counters: the number of secure, insecure, bogus, validations executed so far. It contains four 64-bit counters: the number of secure, insecure, bogus,
and indeterminate DNSSEC validations so far. The counters are increased for each validated RRset, and and indeterminate DNSSEC validations so far. The counters are increased for each validated RRset, and
@ -559,12 +576,20 @@ node /org/freedesktop/resolve1 {
DNSSEC is supported by DNS servers until it verifies that this is not the case. Thus, the reported DNSSEC is supported by DNS servers until it verifies that this is not the case. Thus, the reported
value may initially be true, until the first transactions are executed.</para> value may initially be true, until the first transactions are executed.</para>
<para>The <varname>DNSOverTLS</varname> boolean property reports whether DNS-over-TLS is enabled.
</para>
<para>The <varname>ResolvConfMode</varname> property exposes how <filename>/etc/resolv.conf</filename> <para>The <varname>ResolvConfMode</varname> property exposes how <filename>/etc/resolv.conf</filename>
is managed on the host. Currently, the values <literal>uplink</literal>, <literal>stub</literal>, is managed on the host. Currently, the values <literal>uplink</literal>, <literal>stub</literal>,
<literal>static</literal> (these three correspond to the three different files <literal>static</literal> (these three correspond to the three different files
<filename>systemd-resolved.service</filename> provides), <literal>foreign</literal> (the file is <filename>systemd-resolved.service</filename> provides), <literal>foreign</literal> (the file is
managed by admin or another service, <filename>systemd-resolved.service</filename> just consumes it), managed by admin or another service, <filename>systemd-resolved.service</filename> just consumes it),
<literal>missing</literal> (<filename>/etc/resolv.conf</filename> is missing).</para> <literal>missing</literal> (<filename>/etc/resolv.conf</filename> is missing).</para>
<para>The <varname>DNSStubListener</varname> property reports whether the stub listener on port 53 is
enabled. Possible values are <literal>yes</literal> (enabled), <literal>no</literal> (disabled),
<literal>udp</literal> (only the UDP listener is enabled), and <literal>tcp</literal> (only the TCP
listener is enabled).</para>
</refsect2> </refsect2>
</refsect1> </refsect1>
@ -619,40 +644,6 @@ node /org/freedesktop/resolve1/link/_1 {
}; };
</programlisting> </programlisting>
<!--method SetDNSEx is not documented!-->
<!--method SetDomains is not documented!-->
<!--method SetDefaultRoute is not documented!-->
<!--method SetLLMNR is not documented!-->
<!--method SetMulticastDNS is not documented!-->
<!--method SetDNSOverTLS is not documented!-->
<!--method SetDNSSEC is not documented!-->
<!--method SetDNSSECNegativeTrustAnchors is not documented!-->
<!--method Revert is not documented!-->
<!--property DNSEx is not documented!-->
<!--property CurrentDNSServer is not documented!-->
<!--property CurrentDNSServerEx is not documented!-->
<!--property DefaultRoute is not documented!-->
<!--property LLMNR is not documented!-->
<!--property MulticastDNS is not documented!-->
<!--property DNSOverTLS is not documented!-->
<!--property DNSSEC is not documented!-->
<!--property DNSSECNegativeTrustAnchors is not documented!--> <!--property DNSSECNegativeTrustAnchors is not documented!-->
<!--Autogenerated cross-references for systemd.directives, do not edit--> <!--Autogenerated cross-references for systemd.directives, do not edit-->
@ -721,8 +712,13 @@ node /org/freedesktop/resolve1/link/_1 {
<function>SetLinkDNS()</function> on the Manager object, the main difference being that the later <function>SetLinkDNS()</function> on the Manager object, the main difference being that the later
expects an interface index to be specified. Invoking the methods on the Manager interface has the expects an interface index to be specified. Invoking the methods on the Manager interface has the
benefit of reducing roundtrips, as it is not necessary to first request the Link object path via benefit of reducing roundtrips, as it is not necessary to first request the Link object path via
<function>GetLink()</function> before invoking the methods. For further details on these methods see <function>GetLink()</function> before invoking the methods. The same relationship holds for
the <interfacename>Manager</interfacename> documentation above.</para> <function>SetDNSEx()</function>, <function>SetDomains()</function>,
<function>SetDefaultRoute()</function>, <function>SetLLMNR()</function>,
<function>SetMulticastDNS()</function>, <function>SetDNSOverTLS()</function>,
<function>SetDNSSEC()</function>, <function>SetDNSSECNegativeTrustAnchors()</function>, and
<function>Revert()</function>. For further details on these methods see the
<interfacename>Manager</interfacename> documentation above.</para>
</refsect2> </refsect2>
<refsect2> <refsect2>
@ -744,8 +740,12 @@ node /org/freedesktop/resolve1/link/_1 {
assumed available until it is detected that the configured server does not actually support it. Thus, assumed available until it is detected that the configured server does not actually support it. Thus,
this property may initially report that DNSSEC is supported on an interface.</para> this property may initially report that DNSSEC is supported on an interface.</para>
<para><varname>DefaultRoute</varname> exposes a boolean field that indicates whether the interface will
be used as default route for name queries. See <function>SetLinkDefaultRoute()</function> above.</para>
<para>The other properties reflect the state of the various configuration settings for the link which <para>The other properties reflect the state of the various configuration settings for the link which
may be set with the various methods calls such as SetDNS() or SetLLMNR().</para> may be set with the various methods calls such as <function>SetDNS()</function> or
<function>SetLLMNR()</function>.</para>
</refsect2> </refsect2>
</refsect1> </refsect1>

View File

@ -87,17 +87,17 @@
<refsect1> <refsect1>
<title>Synthetic Records</title> <title>Synthetic Records</title>
<para><command>systemd-resolved</command> synthesizes DNS resource records (RRs) for the following <para><command>systemd-resolved</command> synthetizes DNS resource records (RRs) for the following
cases:</para> cases:</para>
<itemizedlist> <itemizedlist>
<listitem><para>The local, configured hostname is resolved to all locally configured IP addresses <listitem><para>The local, configured hostname is resolved to all locally configured IP addresses
ordered by their scope, or — if none are configured — the IPv4 address 127.0.0.2 (which is on the local ordered by their scope, or — if none are configured — the IPv4 address 127.0.0.2 (which is on the local
loopback) and the IPv6 address ::1 (which is the local host).</para></listitem> loopback interface) and the IPv6 address ::1 (which is the local host).</para></listitem>
<listitem><para>The hostnames <literal>localhost</literal> and <literal>localhost.localdomain</literal> <listitem><para>The hostnames <literal>localhost</literal> and <literal>localhost.localdomain</literal>
(as well as any hostname ending in <literal>.localhost</literal> or as well as any hostname ending in <literal>.localhost</literal> or
<literal>.localhost.localdomain</literal>) are resolved to the IP addresses 127.0.0.1 and ::1. <literal>.localhost.localdomain</literal> are resolved to the IP addresses 127.0.0.1 and ::1.
</para></listitem> </para></listitem>
<listitem><para>The hostname <literal>_gateway</literal> is resolved to all current default routing <listitem><para>The hostname <literal>_gateway</literal> is resolved to all current default routing
@ -119,104 +119,162 @@
according to the following rules:</para> according to the following rules:</para>
<itemizedlist> <itemizedlist>
<listitem><para>Names for which synthetic records are generated (as listed in the previous section) are <listitem><para>Names for which synthetic records are generated (the local hostname,
never routed to the network and a reply is sent immediately. In particular this means that lookups for <literal>localhost</literal> and <literal>localdomain</literal>, local gateway, as listed in the
<literal>localhost</literal> are never routed to the network.</para></listitem> previous section) and addresses configured in <filename>/etc/hosts</filename> are never routed to the
network and a reply is sent immediately.</para></listitem>
<listitem><para>Single-label names are routed to all local interfaces capable of IP multicasting, where <listitem><para>Single-label names are resolved using LLMNR on all local interfaces where LLMNR is
LLMNR is not disabled, using the LLMNR protocol. Lookups for IPv4 addresses are only sent via LLMNR on enabled. Lookups for IPv4 addresses are only sent via LLMNR on IPv4, and lookups for IPv6 addresses are
IPv4, and lookups for IPv6 addresses are only sent via LLMNR on IPv6. Lookups for the locally only sent via LLMNR on IPv6. Note that lookups for single-label synthetized names are not routed to
configured hostname and the <literal>_gateway</literal> hostname are never routed to LLMNR. LLMNR, MulticastDNS or unicast DNS.</para></listitem>
</para></listitem>
<listitem><para>Multi-label names with the domain suffix <literal>.local</literal> are routed to all <listitem><para>Queries for the address records (A and AAAA) of single-label non-synthetized names are
local interfaces capable of IP multicasting, where MulticastDNS is not disabled, using the MulticastDNS resolved via unicast DNS using search domains. For any interface which defines search domains, such
protocol. As with LLMNR, IPv4 address lookups are sent via IPv4 and IPv6 address lookups are sent via look-ups are routed to that interface, suffixed with each of the search domains defined on that
IPv6.</para></listitem> interface in turn. When global search domains are defined, such look-ups are routed to all interfaces,
suffixed by each of the global search domains in turn. Additionally, lookup of single-label names via
unicast DNS may be enabled with the <varname>ResolveUnicastSingleLabel=yes</varname> setting. The
details of which servers are queried and how the final reply is chosen are described below. Note that
this means that address queries for single-label names are never sent out to remote DNS servers by
default, and resoulution is only possible if search domains are defined.</para></listitem>
<listitem><para>Resolution of address records (A and AAAA) via unicast DNS (i.e. not LLMNR or <listitem><para>Multi-label names with the domain suffix <literal>.local</literal> are resolved using
MulticastDNS) for non-synthesized single-label names is allowed for non-top-level domains. This means MulticastDNS on all local interfaces where MulticastDNS is enabled. As with LLMNR, IPv4 address lookups
that such records can be resolved when search domains are defined. For any interface which defines are sent via IPv4 and IPv6 address lookups are sent via IPv6.</para></listitem>
search domains, such look-ups are routed to that interface, suffixed with each of the search domains
defined on that interface in turn. When global search domains are defined, such look-ups are routed to
all interfaces, suffixed by each of the global search domains in turn. Additionally, lookup of
single-label names via unicast DNS may be enabled with the
<varname>ResolveUnicastSingleLabel=yes</varname> setting. The details of which servers are queried and
how the final reply is chosen are described below. Note that this means that address queries for
single-label names are never sent out to remote DNS servers by default, and if no search domains are
defined, resolution will fail.</para></listitem>
<listitem><para>Other multi-label names are routed to all local interfaces that have a DNS server <listitem><para>Queries for multi-label names are routed via unicast DNS on local interfaces that have
configured, plus the globally configured DNS servers if there are any. Note that by default, lookups for a DNS server configured, plus the globally configured DNS servers if there are any. Which interfaces
domains with the <literal>.local</literal> suffix are not routed to DNS servers, unless the domain is are used is determined by the routing logic based on search and route-only domains, described below.
specified explicitly as routing or search domain for the DNS server and interface. This means that on Note that by default, lookups for domains with the <literal>.local</literal> suffix are not routed to
networks where the <literal>.local</literal> domain is defined in a site-specific DNS server, explicit DNS servers, unless the domain is specified explicitly as routing or search domain for the DNS server
search or routing domains need to be configured to make lookups within this DNS domain work. Note that and interface. This means that on networks where the <literal>.local</literal> domain is defined in a
these days, it's generally recommended to avoid defining <literal>.local</literal> in a DNS server, as site-specific DNS server, explicit search or routing domains need to be configured to make lookups work
<ulink url="https://tools.ietf.org/html/rfc6762">RFC6762</ulink> reserves this domain for exclusive within this DNS domain. Note that these days, it's generally recommended to avoid defining
<literal>.local</literal> in a DNS server, as <ulink
url="https://tools.ietf.org/html/rfc6762">RFC6762</ulink> reserves this domain for exclusive
MulticastDNS use.</para></listitem> MulticastDNS use.</para></listitem>
<listitem><para>Address lookups are routed similarly to multi-label names, with the exception that <listitem><para>Address lookups (reverse lookups) are routed similarly to multi-label names, with the
addresses from the link-local address range are never routed to unicast DNS and are only resolved using exception that addresses from the link-local address range are never routed to unicast DNS and are only
LLMNR and MulticastDNS (when enabled).</para></listitem> resolved using LLMNR and MulticastDNS (when enabled).</para></listitem>
</itemizedlist> </itemizedlist>
<para>If lookups are routed to multiple interfaces, the first successful response is returned (thus <para>If lookups are routed to multiple interfaces, the first successful response is returned (thus
effectively merging the lookup zones on all matching interfaces). If the lookup failed on all interfaces, effectively merging the lookup zones on all matching interfaces). If the lookup failed on all interfaces,
the last failing response is returned.</para> the last failing response is returned.</para>
<para>Routing of lookups may be influenced by configuring per-interface domain names and other <para>Routing of lookups is determined by the per-interface routing domains (search and route-only) and
settings. See global search domains. See
<citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry> and <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry> and
<citerefentry><refentrytitle>resolvectl</refentrytitle><manvolnum>1</manvolnum></citerefentry> for <citerefentry><refentrytitle>resolvectl</refentrytitle><manvolnum>1</manvolnum></citerefentry> for a
details. The following query routing logic applies for unicast DNS traffic:</para> description how those settings are set dynamically and the discussion of <varname>Domains=</varname> in
<citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> for a
description of globally configured DNS settings.</para>
<para>The following query routing logic applies for unicast DNS traffic:</para>
<itemizedlist> <itemizedlist>
<listitem><para>If a name to look up matches (that is: is equal to or has as suffix) any of the <listitem><para>If a name to look up matches (that is: is equal to or has as suffix) any of the
configured search or route-only domains of any link (see configured routing domains (search or route-only) of any link, or the globally configured DNS settings,
<citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>), "best matching" routing domain is determined: the matching one with the most labels. The query is then
or the globally configured DNS settings (see the discussion of <varname>Domains=</varname> in sent to all DNS servers of any links or the globally configured DNS servers associated with this "best
<citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>), matching" routing domain. (Note that more than one link might have this same "best matching" routing
"best matching" search/route-only domain is determined: the matching one with the most labels. The domain configured, in which case the query is sent to all of them in parallel).</para>
query is then sent to all DNS servers of any links or the globally configured DNS servers associated
with this "best matching" search/route-only domain. (Note that more than one link might have this same
"best matching" search/route-only domain configured, in which case the query is sent to all of them in
parallel).</para>
<para>In case of single-label names, when search domains are defined, the same logic applies, except <para>In case of single-label names, when search domains are defined, the same logic applies, except
that the name is first suffixed by the search domain.</para></listitem> that the name is first suffixed by each of the search domains in turn. Note that this search logic
doesn't apply to any names with at least one dot. Also see the discussion about compatiblity with
the traditional glibc resolver below.</para></listitem>
<listitem><para>If a query does not match any configured search/route-only domain (neither per-link nor <listitem><para>If a query does not match any configured routing domain (either per-link or global), it
global), it is sent to all DNS servers that are configured on links with the "DNS default route" option is sent to all DNS servers that are configured on links with the <varname>DefaultRoute=</varname>
set, as well as the globally configured DNS server.</para></listitem> option set, as well as the globally configured DNS server.</para></listitem>
<listitem><para>If there is no link configured as "DNS default route" and no global DNS server <listitem><para>If there is no link configured as <varname>DefaultRoute=</varname> and no global DNS
configured, the compiled-in fallback DNS server is used.</para></listitem> server configured, one of the compiled-in fallback DNS servers is used.</para></listitem>
<listitem><para>Otherwise the query is failed as no suitable DNS servers could be determined. <listitem><para>Otherwise the unicast DNS query fails, as no suitable DNS servers can be determined.
</para></listitem> </para></listitem>
</itemizedlist> </itemizedlist>
<para>The "DNS default route" option is a boolean setting configurable with <command>resolvectl</command> <para>The <varname>DefaultRoute=</varname> option is a boolean setting configurable with
or in <filename>.network</filename> files. If not set, it is implicitly determined based on the <command>resolvectl</command> or in <filename>.network</filename> files. If not set, it is implicitly
configured DNS domains for a link: if there's any route-only domain (not matching <literal>~.</literal>) determined based on the configured DNS domains for a link: if there's a route-only domain other than
it defaults to false, otherwise to true.</para> <literal>~.</literal>, it defaults to false, otherwise to true.</para>
<para>Effectively this means: in order to support single-label non-synthetized names, define appropriate <para>Effectively this means: in order to support single-label non-synthetized names, define appropriate
search domains. In order to preferably route all DNS queries not explicitly matched by search/route-only search domains. In order to preferably route all DNS queries not explicitly matched by routing domain
domain configuration to a specific link, configure a <literal>~.</literal> route-only domain on it. This configuration to a specific link, configure a <literal>~.</literal> route-only domain on it. This will
will ensure that other links will not be considered for these queries (unless they too carry such a ensure that other links will not be considered for these queries (unless they too carry such a routing
route-only domain). In order to route all such DNS queries to a specific link only if no other link domain). In order to route all such DNS queries to a specific link only if no other link is preferred,
is preferable, set the "DNS default route" option for the link to true and do not configure a set the <varname>DefaultRoute=</varname> option for the link to true and do not configure a
<literal>~.</literal> route-only domain on it. Finally, in order to ensure that a specific link never <literal>~.</literal> route-only domain on it. Finally, in order to ensure that a specific link never
receives any DNS traffic not matching any of its configured search/route-only domains, set the "DNS receives any DNS traffic not matching any of its configured routing domains, set the
default route" option for it to false.</para> <varname>DefaultRoute=</varname> option for it to false.</para>
<para>See <para>See
<citerefentry><refentrytitle>org.freedesktop.resolve1</refentrytitle><manvolnum>5</manvolnum></citerefentry> <citerefentry><refentrytitle>org.freedesktop.resolve1</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for information about the D-Bus APIs <filename>systemd-resolved</filename> provides.</para> for information about the D-Bus APIs <filename>systemd-resolved</filename> provides.</para>
</refsect1> </refsect1>
<refsect1>
<title>Compatibility with the traditional glibc stub resolver</title>
<para>This section provides a short summary of differences in the stub resolver implemented by
<citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry> together
with <command>systemd-resolved</command> and the tranditional stub resolver implemented in
<citerefentry><refentrytitle>nss-dns</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<itemizedlist>
<listitem><para>Some names are always resolved internally (see Synthetic Records above). Traditionally
they would be resolved by <filename>nss-files</filename>, and only if provided in
<filename>/etc/hosts</filename>.</para></listitem>
<listitem><para>Single-label names are not resolved for A and AAAA records using unicast DNS (unless
overriden with <varname>ResolveUnicastSingleLabel=</varname>, see
<citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
This is similar to the <option>no-tld-query</option> option being set in
<citerefentry><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para></listitem>
<listitem><para>Search domains are not used for <emphasis>suffixing</emphasis> of multi-label names.
(Search domains are nevertheless used for lookup <emphasis>routing</emphasis>, for names that were
originally specified as single-label or multi-label.) Any name with at least one dot is always
interpreted as a FQDN. <filename>nss-dns</filename> would resolve names both as relative (using search
domains) and absolute FQDN names. Some names would be resolved as relative first, and after that query
has failed, as absolute, while other names would be resolved in opposite order. The
<varname>ndots</varname> option in <filename>/etc/resolv.conf</filename> was used to control how many
dots the name needs to have to be resolved as relative first. This stub resolver does not implement
this at all: multi-label names are only resolved as FQDNs. (There are currently more than 1500
top-level domain names defined, and new ones are added regularly, often using "attractive" names that
are also likely to be used locally. Not looking up multi-label names in this fashion avoids fragility
in both directions: a valid global name could be obscured by a local name, and resolution of a relative
local name could suddenly break when a new top-level domain is created, or when a new subdomain of a
top-level domain in registered. Resolving any given name as either relative or absolute avoids this
ambiguity.)</para></listitem>
<listitem><para>This resolver has a notion of the special <literal>.local</literal> domain used for
MulticastDNS, and will not route queries with that suffix to unicast DNS servers unless explicitly
configured, see above. Also, reverse lookups for link-local addresses are not sent to unicast DNS
servers.</para></listitem>
<listitem><para>This resolver reads and caches <filename>/etc/hosts</filename> internally. (In other
words, <filename>nss-resolve</filename> replaces <filename>nss-files</filename> in addition to
<filename>nss-dns</filename>). Entries in <filename>/etc/hosts</filename> have highest priority.</para>
</listitem>
<listitem><para>This resolver also implements LLMNR and MulticastDNS in addition to the classic unicast
DNS protocol, and will resolve single-label names using LLMNR (when enabled) and names ending in
<literal>.local</literal> using MulticastDNS (when enabled).</para></listitem>
<listitem><para>Environment variables <varname>$LOCALDOMAIN</varname> and
<varname>$RES_OPTIONS</varname> described in
<citerefentry><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> are not
supported currently.</para></listitem>
</itemizedlist>
</refsect1>
<refsect1> <refsect1>
<title><filename>/etc/resolv.conf</filename></title> <title><filename>/etc/resolv.conf</filename></title>
@ -303,7 +361,6 @@
synchronous way.</para></listitem> synchronous way.</para></listitem>
</varlistentry> </varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>
<refsect1> <refsect1>

View File

@ -38,8 +38,8 @@ relative_source_path = run_command('realpath',
project_source_root).stdout().strip() project_source_root).stdout().strip()
conf.set_quoted('RELATIVE_SOURCE_PATH', relative_source_path) conf.set_quoted('RELATIVE_SOURCE_PATH', relative_source_path)
conf.set10('DEVELOPER_MODE', get_option('mode') == 'developer', conf.set('BUILD_MODE', 'BUILD_MODE_' + get_option('mode').to_upper(),
description : 'enable additional checks only suitable in development') description : 'tailor build to development or release builds')
want_ossfuzz = get_option('oss-fuzz') want_ossfuzz = get_option('oss-fuzz')
want_libfuzzer = get_option('llvm-fuzz') want_libfuzzer = get_option('llvm-fuzz')

View File

@ -5,7 +5,7 @@ option('version-tag', type : 'string',
description : 'override the git version string') description : 'override the git version string')
option('mode', type : 'combo', choices : ['developer', 'release'], option('mode', type : 'combo', choices : ['developer', 'release'],
description : 'enable additional checks suitable for systemd development') description : 'autoenable features suitable for systemd development/release builds')
option('split-usr', type : 'combo', choices : ['auto', 'true', 'false'], option('split-usr', type : 'combo', choices : ['auto', 'true', 'false'],
description : '''/bin, /sbin aren't symlinks into /usr''') description : '''/bin, /sbin aren't symlinks into /usr''')

View File

@ -161,3 +161,8 @@
_IDN_FEATURE_ " " \ _IDN_FEATURE_ " " \
_PCRE2_FEATURE_ " " \ _PCRE2_FEATURE_ " " \
_CGROUP_HIERARCHY_ _CGROUP_HIERARCHY_
enum {
BUILD_MODE_DEVELOPER,
BUILD_MODE_RELEASE,
};

View File

@ -252,7 +252,8 @@ int write_string_file_ts(
/* We manually build our own version of fopen(..., "we") that works without O_CREAT and with O_NOFOLLOW if needed. */ /* We manually build our own version of fopen(..., "we") that works without O_CREAT and with O_NOFOLLOW if needed. */
fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY | fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY |
(FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) | (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) |
(FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0), (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) |
(FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0),
(FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666)); (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666));
if (fd < 0) { if (fd < 0) {
r = -errno; r = -errno;

View File

@ -16,14 +16,15 @@
typedef enum { typedef enum {
WRITE_STRING_FILE_CREATE = 1 << 0, WRITE_STRING_FILE_CREATE = 1 << 0,
WRITE_STRING_FILE_ATOMIC = 1 << 1, WRITE_STRING_FILE_TRUNCATE = 1 << 1,
WRITE_STRING_FILE_AVOID_NEWLINE = 1 << 2, WRITE_STRING_FILE_ATOMIC = 1 << 2,
WRITE_STRING_FILE_VERIFY_ON_FAILURE = 1 << 3, WRITE_STRING_FILE_AVOID_NEWLINE = 1 << 3,
WRITE_STRING_FILE_SYNC = 1 << 4, WRITE_STRING_FILE_VERIFY_ON_FAILURE = 1 << 4,
WRITE_STRING_FILE_DISABLE_BUFFER = 1 << 5, WRITE_STRING_FILE_SYNC = 1 << 5,
WRITE_STRING_FILE_NOFOLLOW = 1 << 6, WRITE_STRING_FILE_DISABLE_BUFFER = 1 << 6,
WRITE_STRING_FILE_MKDIR_0755 = 1 << 7, WRITE_STRING_FILE_NOFOLLOW = 1 << 7,
WRITE_STRING_FILE_MODE_0600 = 1 << 8, WRITE_STRING_FILE_MKDIR_0755 = 1 << 8,
WRITE_STRING_FILE_MODE_0600 = 1 << 9,
/* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one
more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file() more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file()

View File

@ -650,4 +650,8 @@ static inline int __coverity_check_and_return__(int condition) {
_copy; \ _copy; \
}) })
static inline size_t size_add(size_t x, size_t y) {
return y >= SIZE_MAX - x ? SIZE_MAX : x + y;
}
#include "log.h" #include "log.h"

View File

@ -27,7 +27,7 @@
#ifdef CAP_LAST_CAP #ifdef CAP_LAST_CAP
# if CAP_LAST_CAP > SYSTEMD_CAP_LAST_CAP # if CAP_LAST_CAP > SYSTEMD_CAP_LAST_CAP
# if DEVELOPER_MODE && defined(TEST_CAPABILITY_C) # if BUILD_MODE == BUILD_MODE_DEVELOPER && defined(TEST_CAPABILITY_C)
# warning "The capability list here is outdated" # warning "The capability list here is outdated"
# endif # endif
# else # else

View File

@ -33,6 +33,12 @@ static inline bool streq_ptr(const char *a, const char *b) {
return strcmp_ptr(a, b) == 0; return strcmp_ptr(a, b) == 0;
} }
static inline char* strstr_ptr(const char *haystack, const char *needle) {
if (!haystack || !needle)
return NULL;
return strstr(haystack, needle);
}
static inline const char* strempty(const char *s) { static inline const char* strempty(const char *s) {
return s ?: ""; return s ?: "";
} }
@ -53,6 +59,10 @@ static inline const char* true_false(bool b) {
return b ? "true" : "false"; return b ? "true" : "false";
} }
static inline const char* plus_minus(bool b) {
return b ? "+" : "-";
}
static inline const char* one_zero(bool b) { static inline const char* one_zero(bool b) {
return b ? "1" : "0"; return b ? "1" : "0";
} }

View File

@ -537,6 +537,19 @@ int strv_consume_prepend(char ***l, char *value) {
return r; return r;
} }
int strv_prepend(char ***l, const char *value) {
char *v;
if (!value)
return 0;
v = strdup(value);
if (!v)
return -ENOMEM;
return strv_consume_prepend(l, v);
}
int strv_extend(char ***l, const char *value) { int strv_extend(char ***l, const char *value) {
char *v; char *v;

View File

@ -34,6 +34,7 @@ size_t strv_length(char * const *l) _pure_;
int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates); int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates);
int strv_extend_strv_concat(char ***a, char * const *b, const char *suffix); int strv_extend_strv_concat(char ***a, char * const *b, const char *suffix);
int strv_prepend(char ***l, const char *value);
int strv_extend(char ***l, const char *value); int strv_extend(char ***l, const char *value);
int strv_extendf(char ***l, const char *format, ...) _printf_(2,0); int strv_extendf(char ***l, const char *format, ...) _printf_(2,0);
int strv_extend_front(char ***l, const char *value); int strv_extend_front(char ***l, const char *value);

View File

@ -87,7 +87,7 @@ struct ExecStatus {
dual_timestamp exit_timestamp; dual_timestamp exit_timestamp;
pid_t pid; pid_t pid;
int code; /* as in siginfo_t::si_code */ int code; /* as in siginfo_t::si_code */
int status; /* as in sigingo_t::si_status */ int status; /* as in siginfo_t::si_status */
}; };
/* Stores information about commands we execute. Covers both configuration settings as well as runtime data. */ /* Stores information about commands we execute. Covers both configuration settings as well as runtime data. */

View File

@ -35,6 +35,7 @@
#include "string-table.h" #include "string-table.h"
#include "strv.h" #include "strv.h"
#include "terminal-util.h" #include "terminal-util.h"
#include "utf8.h"
#include "verbs.h" #include "verbs.h"
static int arg_family = AF_UNSPEC; static int arg_family = AF_UNSPEC;
@ -1296,24 +1297,46 @@ static int map_link_domains(sd_bus *bus, const char *member, sd_bus_message *m,
if (r < 0) if (r < 0)
return r; return r;
strv_sort(*l);
return 0; return 0;
} }
static int status_print_strv_ifindex(int ifindex, const char *ifname, char **p) { static int status_print_strv_ifindex(int ifindex, const char *ifname, char **p) {
const unsigned indent = strlen("Global: "); /* Use the same indentation everywhere to make things nice */
int pos1, pos2;
if (ifname)
printf("%s%nLink %i (%s)%n%s:", ansi_highlight(), &pos1, ifindex, ifname, &pos2, ansi_normal());
else
printf("%s%nGlobal%n%s:", ansi_highlight(), &pos1, &pos2, ansi_normal());
size_t cols = columns(), position = pos2 - pos1 + 2;
char **i; char **i;
printf("%sLink %i (%s)%s:", STRV_FOREACH(i, p) {
ansi_highlight(), ifindex, ifname, ansi_normal()); size_t our_len = utf8_console_width(*i); /* This returns -1 on invalid utf-8 (which shouldn't happen).
* If that happens, we'll just print one item per line. */
STRV_FOREACH(i, p) if (position <= indent || size_add(size_add(position, 1), our_len) < cols) {
printf(" %s", *i); printf(" %s", *i);
position = size_add(size_add(position, 1), our_len);
} else {
printf("\n%*s%s", indent, "", *i);
position = size_add(our_len, indent);
}
}
printf("\n"); printf("\n");
return 0; return 0;
} }
struct link_info { static int status_print_strv_global(char **p) {
return status_print_strv_ifindex(0, NULL, p);
}
typedef struct LinkInfo {
uint64_t scopes_mask; uint64_t scopes_mask;
const char *llmnr; const char *llmnr;
const char *mdns; const char *mdns;
@ -1327,9 +1350,26 @@ struct link_info {
char **ntas; char **ntas;
bool dnssec_supported; bool dnssec_supported;
bool default_route; bool default_route;
}; } LinkInfo;
static void link_info_clear(struct link_info *p) { typedef struct GlobalInfo {
char *current_dns;
char *current_dns_ex;
char **dns;
char **dns_ex;
char **fallback_dns;
char **fallback_dns_ex;
char **domains;
char **ntas;
const char *llmnr;
const char *mdns;
const char *dns_over_tls;
const char *dnssec;
const char *resolv_conf_mode;
bool dnssec_supported;
} GlobalInfo;
static void link_info_clear(LinkInfo *p) {
free(p->current_dns); free(p->current_dns);
free(p->current_dns_ex); free(p->current_dns_ex);
strv_free(p->dns); strv_free(p->dns);
@ -1338,6 +1378,17 @@ static void link_info_clear(struct link_info *p) {
strv_free(p->ntas); strv_free(p->ntas);
} }
static void global_info_clear(GlobalInfo *p) {
free(p->current_dns);
free(p->current_dns_ex);
strv_free(p->dns);
strv_free(p->dns_ex);
strv_free(p->fallback_dns);
strv_free(p->fallback_dns_ex);
strv_free(p->domains);
strv_free(p->ntas);
}
static int dump_list(Table *table, const char *prefix, char * const *l) { static int dump_list(Table *table, const char *prefix, char * const *l) {
int r; int r;
@ -1346,33 +1397,88 @@ static int dump_list(Table *table, const char *prefix, char * const *l) {
r = table_add_many(table, r = table_add_many(table,
TABLE_STRING, prefix, TABLE_STRING, prefix,
TABLE_STRV, l); TABLE_STRV_WRAPPED, l);
if (r < 0) if (r < 0)
return table_log_add_error(r); return table_log_add_error(r);
return 0; return 0;
} }
static int strv_extend_extended_bool(char ***strv, const char *name, const char *value) {
int r;
if (value) {
r = parse_boolean(value);
if (r >= 0)
return strv_extendf(strv, "%s%s", plus_minus(r), name);
}
return strv_extendf(strv, "%s=%s", name, value ?: "???");
}
static char** link_protocol_status(const LinkInfo *info) {
_cleanup_strv_free_ char **s = NULL;
if (strv_extendf(&s, "%sDefaultRoute", plus_minus(info->default_route)) < 0)
return NULL;
if (strv_extend_extended_bool(&s, "LLMNR", info->llmnr) < 0)
return NULL;
if (strv_extend_extended_bool(&s, "mDNS", info->mdns) < 0)
return NULL;
if (strv_extend_extended_bool(&s, "DNSOverTLS", info->dns_over_tls) < 0)
return NULL;
if (strv_extendf(&s, "DNSSEC=%s/%s",
info->dnssec ?: "???",
info->dnssec_supported ? "supported" : "unsupported") < 0)
return NULL;
return TAKE_PTR(s);
}
static char** global_protocol_status(const GlobalInfo *info) {
_cleanup_strv_free_ char **s = NULL;
if (strv_extend_extended_bool(&s, "LLMNR", info->llmnr) < 0)
return NULL;
if (strv_extend_extended_bool(&s, "mDNS", info->mdns) < 0)
return NULL;
if (strv_extend_extended_bool(&s, "DNSOverTLS", info->dns_over_tls) < 0)
return NULL;
if (strv_extendf(&s, "DNSSEC=%s/%s",
info->dnssec ?: "???",
info->dnssec_supported ? "supported" : "unsupported") < 0)
return NULL;
return TAKE_PTR(s);
}
static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode mode, bool *empty_line) { static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode mode, bool *empty_line) {
static const struct bus_properties_map property_map[] = { static const struct bus_properties_map property_map[] = {
{ "ScopesMask", "t", NULL, offsetof(struct link_info, scopes_mask) }, { "ScopesMask", "t", NULL, offsetof(LinkInfo, scopes_mask) },
{ "DNS", "a(iay)", map_link_dns_servers, offsetof(struct link_info, dns) }, { "DNS", "a(iay)", map_link_dns_servers, offsetof(LinkInfo, dns) },
{ "DNSEx", "a(iayqs)", map_link_dns_servers_ex, offsetof(struct link_info, dns_ex) }, { "DNSEx", "a(iayqs)", map_link_dns_servers_ex, offsetof(LinkInfo, dns_ex) },
{ "CurrentDNSServer", "(iay)", map_link_current_dns_server, offsetof(struct link_info, current_dns) }, { "CurrentDNSServer", "(iay)", map_link_current_dns_server, offsetof(LinkInfo, current_dns) },
{ "CurrentDNSServerEx", "(iayqs)", map_link_current_dns_server_ex, offsetof(struct link_info, current_dns_ex) }, { "CurrentDNSServerEx", "(iayqs)", map_link_current_dns_server_ex, offsetof(LinkInfo, current_dns_ex) },
{ "Domains", "a(sb)", map_link_domains, offsetof(struct link_info, domains) }, { "Domains", "a(sb)", map_link_domains, offsetof(LinkInfo, domains) },
{ "DefaultRoute", "b", NULL, offsetof(struct link_info, default_route) }, { "DefaultRoute", "b", NULL, offsetof(LinkInfo, default_route) },
{ "LLMNR", "s", NULL, offsetof(struct link_info, llmnr) }, { "LLMNR", "s", NULL, offsetof(LinkInfo, llmnr) },
{ "MulticastDNS", "s", NULL, offsetof(struct link_info, mdns) }, { "MulticastDNS", "s", NULL, offsetof(LinkInfo, mdns) },
{ "DNSOverTLS", "s", NULL, offsetof(struct link_info, dns_over_tls) }, { "DNSOverTLS", "s", NULL, offsetof(LinkInfo, dns_over_tls) },
{ "DNSSEC", "s", NULL, offsetof(struct link_info, dnssec) }, { "DNSSEC", "s", NULL, offsetof(LinkInfo, dnssec) },
{ "DNSSECNegativeTrustAnchors", "as", NULL, offsetof(struct link_info, ntas) }, { "DNSSECNegativeTrustAnchors", "as", bus_map_strv_sort, offsetof(LinkInfo, ntas) },
{ "DNSSECSupported", "b", NULL, offsetof(struct link_info, dnssec_supported) }, { "DNSSECSupported", "b", NULL, offsetof(LinkInfo, dnssec_supported) },
{} {}
}; };
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_(link_info_clear) struct link_info link_info = {}; _cleanup_(link_info_clear) LinkInfo link_info = {};
_cleanup_(table_unrefp) Table *table = NULL; _cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *p = NULL; _cleanup_free_ char *p = NULL;
char ifi[DECIMAL_STR_MAX(int)], ifname[IF_NAMESIZE + 1] = ""; char ifi[DECIMAL_STR_MAX(int)], ifname[IF_NAMESIZE + 1] = "";
@ -1496,19 +1602,13 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode
if (r < 0) if (r < 0)
return table_log_add_error(r); return table_log_add_error(r);
_cleanup_strv_free_ char **pstatus = link_protocol_status(&link_info);
if (!pstatus)
return log_oom();
r = table_add_many(table, r = table_add_many(table,
TABLE_STRING, "DefaultRoute setting:", TABLE_STRING, "Protocols:",
TABLE_BOOLEAN, link_info.default_route, TABLE_STRV_WRAPPED, pstatus);
TABLE_STRING, "LLMNR setting:",
TABLE_STRING, strna(link_info.llmnr),
TABLE_STRING, "MulticastDNS setting:",
TABLE_STRING, strna(link_info.mdns),
TABLE_STRING, "DNSOverTLS setting:",
TABLE_STRING, strna(link_info.dns_over_tls),
TABLE_STRING, "DNSSEC setting:",
TABLE_STRING, strna(link_info.dnssec),
TABLE_STRING, "DNSSEC supported:",
TABLE_BOOLEAN, link_info.dnssec_supported);
if (r < 0) if (r < 0)
return table_log_add_error(r); return table_log_add_error(r);
@ -1631,71 +1731,32 @@ static int map_global_domains(sd_bus *bus, const char *member, sd_bus_message *m
if (r < 0) if (r < 0)
return r; return r;
return 0; strv_sort(*l);
}
static int status_print_strv_global(char **p) {
char **i;
printf("%sGlobal%s:", ansi_highlight(), ansi_normal());
STRV_FOREACH(i, p)
printf(" %s", *i);
printf("\n");
return 0; return 0;
} }
struct global_info {
char *current_dns;
char *current_dns_ex;
char **dns;
char **dns_ex;
char **fallback_dns;
char **fallback_dns_ex;
char **domains;
char **ntas;
const char *llmnr;
const char *mdns;
const char *dns_over_tls;
const char *dnssec;
const char *resolv_conf_mode;
bool dnssec_supported;
};
static void global_info_clear(struct global_info *p) {
free(p->current_dns);
free(p->current_dns_ex);
strv_free(p->dns);
strv_free(p->dns_ex);
strv_free(p->fallback_dns);
strv_free(p->fallback_dns_ex);
strv_free(p->domains);
strv_free(p->ntas);
}
static int status_global(sd_bus *bus, StatusMode mode, bool *empty_line) { static int status_global(sd_bus *bus, StatusMode mode, bool *empty_line) {
static const struct bus_properties_map property_map[] = { static const struct bus_properties_map property_map[] = {
{ "DNS", "a(iiay)", map_global_dns_servers, offsetof(struct global_info, dns) }, { "DNS", "a(iiay)", map_global_dns_servers, offsetof(GlobalInfo, dns) },
{ "DNSEx", "a(iiayqs)", map_global_dns_servers_ex, offsetof(struct global_info, dns_ex) }, { "DNSEx", "a(iiayqs)", map_global_dns_servers_ex, offsetof(GlobalInfo, dns_ex) },
{ "FallbackDNS", "a(iiay)", map_global_dns_servers, offsetof(struct global_info, fallback_dns) }, { "FallbackDNS", "a(iiay)", map_global_dns_servers, offsetof(GlobalInfo, fallback_dns) },
{ "FallbackDNSEx", "a(iiayqs)", map_global_dns_servers_ex, offsetof(struct global_info, fallback_dns_ex) }, { "FallbackDNSEx", "a(iiayqs)", map_global_dns_servers_ex, offsetof(GlobalInfo, fallback_dns_ex) },
{ "CurrentDNSServer", "(iiay)", map_global_current_dns_server, offsetof(struct global_info, current_dns) }, { "CurrentDNSServer", "(iiay)", map_global_current_dns_server, offsetof(GlobalInfo, current_dns) },
{ "CurrentDNSServerEx", "(iiayqs)", map_global_current_dns_server_ex, offsetof(struct global_info, current_dns_ex) }, { "CurrentDNSServerEx", "(iiayqs)", map_global_current_dns_server_ex, offsetof(GlobalInfo, current_dns_ex) },
{ "Domains", "a(isb)", map_global_domains, offsetof(struct global_info, domains) }, { "Domains", "a(isb)", map_global_domains, offsetof(GlobalInfo, domains) },
{ "DNSSECNegativeTrustAnchors", "as", NULL, offsetof(struct global_info, ntas) }, { "DNSSECNegativeTrustAnchors", "as", bus_map_strv_sort, offsetof(GlobalInfo, ntas) },
{ "LLMNR", "s", NULL, offsetof(struct global_info, llmnr) }, { "LLMNR", "s", NULL, offsetof(GlobalInfo, llmnr) },
{ "MulticastDNS", "s", NULL, offsetof(struct global_info, mdns) }, { "MulticastDNS", "s", NULL, offsetof(GlobalInfo, mdns) },
{ "DNSOverTLS", "s", NULL, offsetof(struct global_info, dns_over_tls) }, { "DNSOverTLS", "s", NULL, offsetof(GlobalInfo, dns_over_tls) },
{ "DNSSEC", "s", NULL, offsetof(struct global_info, dnssec) }, { "DNSSEC", "s", NULL, offsetof(GlobalInfo, dnssec) },
{ "DNSSECSupported", "b", NULL, offsetof(struct global_info, dnssec_supported) }, { "DNSSECSupported", "b", NULL, offsetof(GlobalInfo, dnssec_supported) },
{ "ResolvConfMode", "s", NULL, offsetof(struct global_info, resolv_conf_mode) }, { "ResolvConfMode", "s", NULL, offsetof(GlobalInfo, resolv_conf_mode) },
{} {}
}; };
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_(global_info_clear) struct global_info global_info = {}; _cleanup_(global_info_clear) GlobalInfo global_info = {};
_cleanup_(table_unrefp) Table *table = NULL; _cleanup_(table_unrefp) Table *table = NULL;
int r; int r;
@ -1760,18 +1821,14 @@ static int status_global(sd_bus *bus, StatusMode mode, bool *empty_line) {
table_set_header(table, false); table_set_header(table, false);
_cleanup_strv_free_ char **pstatus = global_protocol_status(&global_info);
if (!pstatus)
return log_oom();
r = table_add_many(table, r = table_add_many(table,
TABLE_STRING, "LLMNR setting:", TABLE_STRING, "Protocols:",
TABLE_SET_ALIGN_PERCENT, 100, TABLE_SET_ALIGN_PERCENT, 100,
TABLE_STRING, strna(global_info.llmnr), TABLE_STRV_WRAPPED, pstatus);
TABLE_STRING, "MulticastDNS setting:",
TABLE_STRING, strna(global_info.mdns),
TABLE_STRING, "DNSOverTLS setting:",
TABLE_STRING, strna(global_info.dns_over_tls),
TABLE_STRING, "DNSSEC setting:",
TABLE_STRING, strna(global_info.dnssec),
TABLE_STRING, "DNSSEC supported:",
TABLE_BOOLEAN, global_info.dnssec_supported);
if (r < 0) if (r < 0)
return table_log_add_error(r); return table_log_add_error(r);

View File

@ -273,7 +273,7 @@ static int write_uplink_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSe
} }
if (ordered_set_isempty(domains)) if (ordered_set_isempty(domains))
fputs("search .", f); /* Make sure that if the local hostname is chosen as fqdn this does not fputs("search .\n", f); /* Make sure that if the local hostname is chosen as fqdn this does not
* imply a search domain */ * imply a search domain */
else else
write_resolv_conf_search(domains, f); write_resolv_conf_search(domains, f);
@ -302,7 +302,7 @@ static int write_stub_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet
"options edns0 trust-ad\n", f); "options edns0 trust-ad\n", f);
if (ordered_set_isempty(domains)) if (ordered_set_isempty(domains))
fputs("search .", f); /* Make sure that if the local hostname is chosen as fqdn this does not fputs("search .\n", f); /* Make sure that if the local hostname is chosen as fqdn this does not
* imply a search domain */ * imply a search domain */
else else
write_resolv_conf_search(domains, f); write_resolv_conf_search(domains, f);

View File

@ -25,5 +25,6 @@
#LLMNR=@DEFAULT_LLMNR_MODE@ #LLMNR=@DEFAULT_LLMNR_MODE@
#Cache=yes #Cache=yes
#DNSStubListener=yes #DNSStubListener=yes
#DNSStubListenerExtra=
#ReadEtcHosts=yes #ReadEtcHosts=yes
#ResolveUnicastSingleLabel=no #ResolveUnicastSingleLabel=no

View File

@ -25,6 +25,23 @@ int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_err
return 0; return 0;
} }
int bus_map_strv_sort(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
_cleanup_strv_free_ char **l = NULL;
char ***p = userdata;
int r;
r = bus_message_read_strv_extend(m, &l);
if (r < 0)
return r;
r = strv_extend_strv(p, l, false);
if (r < 0)
return r;
strv_sort(*p);
return 0;
}
static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, unsigned flags, sd_bus_error *error, void *userdata) { static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, unsigned flags, sd_bus_error *error, void *userdata) {
char type; char type;
int r; int r;

View File

@ -18,6 +18,7 @@ enum {
}; };
int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata); int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata);
int bus_map_strv_sort(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata);
int bus_message_map_all_properties(sd_bus_message *m, const struct bus_properties_map *map, unsigned flags, sd_bus_error *error, void *userdata); int bus_message_map_all_properties(sd_bus_message *m, const struct bus_properties_map *map, unsigned flags, sd_bus_error *error, void *userdata);
int bus_map_all_properties(sd_bus *bus, const char *destination, const char *path, const struct bus_properties_map *map, int bus_map_all_properties(sd_bus *bus, const char *destination, const char *path, const struct bus_properties_map *map,

View File

@ -66,6 +66,7 @@ typedef struct TableData {
size_t minimum_width; /* minimum width for the column */ size_t minimum_width; /* minimum width for the column */
size_t maximum_width; /* maximum width for the column */ size_t maximum_width; /* maximum width for the column */
size_t formatted_for_width; /* the width we tried to format for */
unsigned weight; /* the horizontal weight for this column, in case the table is expanded/compressed */ unsigned weight; /* the horizontal weight for this column, in case the table is expanded/compressed */
unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */ unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */ unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
@ -164,7 +165,6 @@ Table *table_new_raw(size_t n_columns) {
Table *table_new_internal(const char *first_header, ...) { Table *table_new_internal(const char *first_header, ...) {
_cleanup_(table_unrefp) Table *t = NULL; _cleanup_(table_unrefp) Table *t = NULL;
size_t n_columns = 1; size_t n_columns = 1;
const char *h;
va_list ap; va_list ap;
int r; int r;
@ -172,8 +172,7 @@ Table *table_new_internal(const char *first_header, ...) {
va_start(ap, first_header); va_start(ap, first_header);
for (;;) { for (;;) {
h = va_arg(ap, const char*); if (!va_arg(ap, const char*))
if (!h)
break; break;
n_columns++; n_columns++;
@ -185,7 +184,7 @@ Table *table_new_internal(const char *first_header, ...) {
return NULL; return NULL;
va_start(ap, first_header); va_start(ap, first_header);
for (h = first_header; h; h = va_arg(ap, const char*)) { for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
TableCell *cell; TableCell *cell;
r = table_add_cell(t, &cell, TABLE_STRING, h); r = table_add_cell(t, &cell, TABLE_STRING, h);
@ -213,7 +212,7 @@ static TableData *table_data_free(TableData *d) {
free(d->formatted); free(d->formatted);
free(d->url); free(d->url);
if (d->type == TABLE_STRV) if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
strv_free(d->strv); strv_free(d->strv);
return mfree(d); return mfree(d);
@ -223,12 +222,10 @@ DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
Table *table_unref(Table *t) { Table *table_unref(Table *t) {
size_t i;
if (!t) if (!t)
return NULL; return NULL;
for (i = 0; i < t->n_cells; i++) for (size_t i = 0; i < t->n_cells; i++)
table_data_unref(t->data[i]); table_data_unref(t->data[i]);
free(t->data); free(t->data);
@ -252,6 +249,7 @@ static size_t table_data_size(TableDataType type, const void *data) {
return strlen(data) + 1; return strlen(data) + 1;
case TABLE_STRV: case TABLE_STRV:
case TABLE_STRV_WRAPPED:
return sizeof(char **); return sizeof(char **);
case TABLE_BOOLEAN: case TABLE_BOOLEAN:
@ -376,7 +374,7 @@ static TableData *table_data_new(
d->align_percent = align_percent; d->align_percent = align_percent;
d->ellipsize_percent = ellipsize_percent; d->ellipsize_percent = ellipsize_percent;
if (type == TABLE_STRV) { if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
d->strv = strv_copy(data); d->strv = strv_copy(data);
if (!d->strv) if (!d->strv)
return NULL; return NULL;
@ -817,6 +815,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
break; break;
case TABLE_STRV: case TABLE_STRV:
case TABLE_STRV_WRAPPED:
data = va_arg(ap, char * const *); data = va_arg(ap, char * const *);
break; break;
@ -1047,11 +1046,9 @@ int table_set_empty_string(Table *t, const char *empty) {
} }
int table_set_display_all(Table *t) { int table_set_display_all(Table *t) {
size_t allocated;
assert(t); assert(t);
allocated = t->n_display_map; size_t allocated = t->n_display_map;
if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, allocated))) if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, allocated)))
return -ENOMEM; return -ENOMEM;
@ -1124,7 +1121,6 @@ int table_set_sort(Table *t, size_t first_column, ...) {
} }
int table_hide_column_from_display(Table *t, size_t column) { int table_hide_column_from_display(Table *t, size_t column) {
size_t allocated, cur = 0;
int r; int r;
assert(t); assert(t);
@ -1137,7 +1133,7 @@ int table_hide_column_from_display(Table *t, size_t column) {
return r; return r;
} }
allocated = t->n_display_map; size_t allocated = t->n_display_map, cur = 0;
for (size_t i = 0; i < allocated; i++) { for (size_t i = 0; i < allocated; i++) {
if (t->display_map[i] == column) if (t->display_map[i] == column)
@ -1169,6 +1165,7 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
return path_compare(a->string, b->string); return path_compare(a->string, b->string);
case TABLE_STRV: case TABLE_STRV:
case TABLE_STRV_WRAPPED:
return strv_compare(a->strv, b->strv); return strv_compare(a->strv, b->strv);
case TABLE_BOOLEAN: case TABLE_BOOLEAN:
@ -1247,7 +1244,6 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
} }
static int table_data_compare(const size_t *a, const size_t *b, Table *t) { static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
size_t i;
int r; int r;
assert(t); assert(t);
@ -1262,7 +1258,7 @@ static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
return 1; return 1;
/* Order other lines by the sorting map */ /* Order other lines by the sorting map */
for (i = 0; i < t->n_sort_map; i++) { for (size_t i = 0; i < t->n_sort_map; i++) {
TableData *d, *dd; TableData *d, *dd;
d = t->data[*a + t->sort_map[i]]; d = t->data[*a + t->sort_map[i]];
@ -1277,10 +1273,46 @@ static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
return CMP(*a, *b); return CMP(*a, *b);
} }
static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing) { static char* format_strv_width(char **strv, size_t column_width) {
_cleanup_fclose_ FILE *f = NULL;
size_t sz = 0;
_cleanup_free_ char *buf = NULL;
f = open_memstream_unlocked(&buf, &sz);
if (!f)
return NULL;
size_t position = 0;
char **p;
STRV_FOREACH(p, strv) {
size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
* If that happens, we'll just print one item per line. */
if (position == 0) {
fputs(*p, f);
position = our_len;
} else if (size_add(size_add(position, 1), our_len) <= column_width) {
fprintf(f, " %s", *p);
position = size_add(size_add(position, 1), our_len);
} else {
fprintf(f, "\n%s", *p);
position = our_len;
}
}
if (fflush_and_check(f) < 0)
return NULL;
f = safe_fclose(f);
return TAKE_PTR(buf);
}
static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
assert(d); assert(d);
if (d->formatted) if (d->formatted &&
/* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
(d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
return d->formatted; return d->formatted;
switch (d->type) { switch (d->type) {
@ -1290,13 +1322,12 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
case TABLE_STRING: case TABLE_STRING:
case TABLE_PATH: case TABLE_PATH:
if (d->uppercase && !avoid_uppercasing) { if (d->uppercase && !avoid_uppercasing) {
char *p, *q;
d->formatted = new(char, strlen(d->string) + 1); d->formatted = new(char, strlen(d->string) + 1);
if (!d->formatted) if (!d->formatted)
return NULL; return NULL;
for (p = d->string, q = d->formatted; *p; p++, q++) char *q = d->formatted;
for (char *p = d->string; *p; p++, q++)
*q = (char) toupper((unsigned char) *p); *q = (char) toupper((unsigned char) *p);
*q = 0; *q = 0;
@ -1305,17 +1336,28 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
return d->string; return d->string;
case TABLE_STRV: { case TABLE_STRV:
char *p;
if (strv_isempty(d->strv)) if (strv_isempty(d->strv))
return strempty(t->empty_string); return strempty(t->empty_string);
p = strv_join(d->strv, "\n"); d->formatted = strv_join(d->strv, "\n");
if (!p) if (!d->formatted)
return NULL;
break;
case TABLE_STRV_WRAPPED: {
if (strv_isempty(d->strv))
return strempty(t->empty_string);
char *buf = format_strv_width(d->strv, column_width);
if (!buf)
return NULL; return NULL;
d->formatted = p; free_and_replace(d->formatted, buf);
d->formatted_for_width = column_width;
if (have_soft)
*have_soft = true;
break; break;
} }
@ -1632,16 +1674,19 @@ static int console_width_height(
static int table_data_requested_width_height( static int table_data_requested_width_height(
Table *table, Table *table,
TableData *d, TableData *d,
size_t available_width,
size_t *ret_width, size_t *ret_width,
size_t *ret_height) { size_t *ret_height,
bool *have_soft) {
_cleanup_free_ char *truncated = NULL; _cleanup_free_ char *truncated = NULL;
bool truncation_applied = false; bool truncation_applied = false;
size_t width, height; size_t width, height;
const char *t; const char *t;
int r; int r;
bool soft = false;
t = table_data_format(table, d, false); t = table_data_format(table, d, false, available_width, &soft);
if (!t) if (!t)
return -ENOMEM; return -ENOMEM;
@ -1669,6 +1714,8 @@ static int table_data_requested_width_height(
*ret_width = width; *ret_width = width;
if (ret_height) if (ret_height)
*ret_height = height; *ret_height = height;
if (have_soft && soft)
*have_soft = true;
return truncation_applied; return truncation_applied;
} }
@ -1678,7 +1725,6 @@ static char *align_string_mem(const char *str, const char *url, size_t new_lengt
_cleanup_free_ char *clickable = NULL; _cleanup_free_ char *clickable = NULL;
const char *p; const char *p;
char *ret; char *ret;
size_t i;
int r; int r;
/* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */ /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
@ -1723,10 +1769,10 @@ static char *align_string_mem(const char *str, const char *url, size_t new_lengt
if (!ret) if (!ret)
return NULL; return NULL;
for (i = 0; i < lspace; i++) for (size_t i = 0; i < lspace; i++)
ret[i] = ' '; ret[i] = ' ';
memcpy(ret + lspace, clickable ?: str, clickable_length); memcpy(ret + lspace, clickable ?: str, clickable_length);
for (i = lspace + clickable_length; i < space + clickable_length; i++) for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
ret[i] = ' '; ret[i] = ' ';
ret[space + clickable_length] = 0; ret[space + clickable_length] = 0;
@ -1740,7 +1786,7 @@ static bool table_data_isempty(TableData *d) {
return true; return true;
/* Let's also consider an empty strv as truly empty. */ /* Let's also consider an empty strv as truly empty. */
if (d->type == TABLE_STRV) if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
return strv_isempty(d->strv); return strv_isempty(d->strv);
/* Note that an empty string we do not consider empty here! */ /* Note that an empty string we do not consider empty here! */
@ -1771,8 +1817,8 @@ static const char* table_data_rgap_color(TableData *d) {
int table_print(Table *t, FILE *f) { int table_print(Table *t, FILE *f) {
size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width, size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
*width; *width = NULL;
_cleanup_free_ size_t *sorted = NULL; _cleanup_free_ size_t *sorted = NULL;
uint64_t *column_weight, weight_sum; uint64_t *column_weight, weight_sum;
int r; int r;
@ -1795,7 +1841,7 @@ int table_print(Table *t, FILE *f) {
if (!sorted) if (!sorted)
return -ENOMEM; return -ENOMEM;
for (i = 0; i < n_rows; i++) for (size_t i = 0; i < n_rows; i++)
sorted[i] = i * t->n_columns; sorted[i] = i * t->n_columns;
typesafe_qsort_r(sorted, n_rows, table_data_compare, t); typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
@ -1811,30 +1857,37 @@ int table_print(Table *t, FILE *f) {
minimum_width = newa(size_t, display_columns); minimum_width = newa(size_t, display_columns);
maximum_width = newa(size_t, display_columns); maximum_width = newa(size_t, display_columns);
requested_width = newa(size_t, display_columns); requested_width = newa(size_t, display_columns);
width = newa(size_t, display_columns);
column_weight = newa0(uint64_t, display_columns); column_weight = newa0(uint64_t, display_columns);
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
minimum_width[j] = 1; minimum_width[j] = 1;
maximum_width[j] = (size_t) -1; maximum_width[j] = (size_t) -1;
requested_width[j] = (size_t) -1;
} }
for (unsigned pass = 0; pass < 2; pass++) {
/* First pass: determine column sizes */ /* First pass: determine column sizes */
for (i = t->header ? 0 : 1; i < n_rows; i++) {
for (size_t j = 0; j < display_columns; j++)
requested_width[j] = (size_t) -1;
bool any_soft = false;
for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
TableData **row; TableData **row;
/* Note that we don't care about ordering at this time, as we just want to determine column sizes, /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
* hence we don't care for sorted[] during the first pass. */ * hence we don't care for sorted[] during the first pass. */
row = t->data + i * t->n_columns; row = t->data + i * t->n_columns;
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
TableData *d; TableData *d;
size_t req_width, req_height; size_t req_width, req_height;
assert_se(d = row[t->display_map ? t->display_map[j] : j]); assert_se(d = row[t->display_map ? t->display_map[j] : j]);
r = table_data_requested_width_height(t, d, &req_width, &req_height); r = table_data_requested_width_height(t, d,
width ? width[j] : SIZE_MAX,
&req_width, &req_height, &any_soft);
if (r < 0) if (r < 0)
return r; return r;
if (r > 0) { /* Truncated because too many lines? */ if (r > 0) { /* Truncated because too many lines? */
@ -1846,7 +1899,9 @@ int table_print(Table *t, FILE *f) {
* ellipsis. Hence, let's figure out the last line, and account for its * ellipsis. Hence, let's figure out the last line, and account for its
* length plus ellipsis. */ * length plus ellipsis. */
field = table_data_format(t, d, false); field = table_data_format(t, d, false,
width ? width[j] : SIZE_MAX,
&any_soft);
if (!field) if (!field)
return -ENOMEM; return -ENOMEM;
@ -1885,7 +1940,7 @@ int table_print(Table *t, FILE *f) {
/* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */ /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
weight_sum = 0; weight_sum = 0;
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
weight_sum += column_weight[j]; weight_sum += column_weight[j];
table_minimum_width += minimum_width[j]; table_minimum_width += minimum_width[j];
@ -1901,7 +1956,8 @@ int table_print(Table *t, FILE *f) {
/* Calculate effective table width */ /* Calculate effective table width */
if (t->width != 0 && t->width != (size_t) -1) if (t->width != 0 && t->width != (size_t) -1)
table_effective_width = t->width; table_effective_width = t->width;
else if (t->width == 0 || pager_have() || !isatty(STDOUT_FILENO)) else if (t->width == 0 ||
((pass > 0 || !any_soft) && (pager_have() || !isatty(STDOUT_FILENO))))
table_effective_width = table_requested_width; table_effective_width = table_requested_width;
else else
table_effective_width = MIN(table_requested_width, columns()); table_effective_width = MIN(table_requested_width, columns());
@ -1912,6 +1968,9 @@ int table_print(Table *t, FILE *f) {
if (table_effective_width < table_minimum_width) if (table_effective_width < table_minimum_width)
table_effective_width = table_minimum_width; table_effective_width = table_minimum_width;
if (!width)
width = newa(size_t, display_columns);
if (table_effective_width >= table_requested_width) { if (table_effective_width >= table_requested_width) {
size_t extra; size_t extra;
@ -1920,7 +1979,7 @@ int table_print(Table *t, FILE *f) {
extra = table_effective_width - table_requested_width; extra = table_effective_width - table_requested_width;
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
size_t delta; size_t delta;
if (weight_sum == 0) if (weight_sum == 0)
@ -1947,6 +2006,7 @@ int table_print(Table *t, FILE *f) {
weight_sum -= column_weight[j]; weight_sum -= column_weight[j];
} }
break; /* Every column should be happy, no need to repeat calculations. */
} else { } else {
/* We need to compress the table, columns can't get what they asked for. We first provide each column /* We need to compress the table, columns can't get what they asked for. We first provide each column
* with the minimum they need, and then distribute anything left. */ * with the minimum they need, and then distribute anything left. */
@ -1955,13 +2015,13 @@ int table_print(Table *t, FILE *f) {
extra = table_effective_width - table_minimum_width; extra = table_effective_width - table_minimum_width;
for (j = 0; j < display_columns; j++) for (size_t j = 0; j < display_columns; j++)
width[j] = (size_t) -1; width[j] = (size_t) -1;
for (;;) { for (;;) {
bool restart = false; bool restart = false;
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
size_t delta, w; size_t delta, w;
/* Did this column already get something assigned? If so, let's skip to the next */ /* Did this column already get something assigned? If so, let's skip to the next */
@ -2006,10 +2066,16 @@ int table_print(Table *t, FILE *f) {
if (!restart) if (!restart)
finalize = true; finalize = true;
} }
if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
* let's try to reformat them with the new widths. Otherwise, let's
* move on. */
break;
}
} }
/* Second pass: show output */ /* Second pass: show output */
for (i = t->header ? 0 : 1; i < n_rows; i++) { for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
size_t n_subline = 0; size_t n_subline = 0;
bool more_sublines; bool more_sublines;
TableData **row; TableData **row;
@ -2023,7 +2089,7 @@ int table_print(Table *t, FILE *f) {
const char *gap_color = NULL; const char *gap_color = NULL;
more_sublines = false; more_sublines = false;
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
_cleanup_free_ char *buffer = NULL, *extracted = NULL; _cleanup_free_ char *buffer = NULL, *extracted = NULL;
bool lines_truncated = false; bool lines_truncated = false;
const char *field, *color = NULL; const char *field, *color = NULL;
@ -2032,7 +2098,7 @@ int table_print(Table *t, FILE *f) {
assert_se(d = row[t->display_map ? t->display_map[j] : j]); assert_se(d = row[t->display_map ? t->display_map[j] : j]);
field = table_data_format(t, d, false); field = table_data_format(t, d, false, width[j], NULL);
if (!field) if (!field)
return -ENOMEM; return -ENOMEM;
@ -2247,6 +2313,7 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
return json_variant_new_string(ret, d->string); return json_variant_new_string(ret, d->string);
case TABLE_STRV: case TABLE_STRV:
case TABLE_STRV_WRAPPED:
return json_variant_new_array_strv(ret, d->strv); return json_variant_new_array_strv(ret, d->strv);
case TABLE_BOOLEAN: case TABLE_BOOLEAN:
@ -2332,17 +2399,15 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
} }
static char* string_to_json_field_name(const char *f) { static char* string_to_json_field_name(const char *f) {
char *c, *x;
/* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
* field name can be hence this is a bit vague and black magic. Right now we only convert spaces to * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to
* underscores and leave everything as is. */ * underscores and leave everything as is. */
c = strdup(f); char *c = strdup(f);
if (!c) if (!c)
return NULL; return NULL;
for (x = c; *x; x++) for (char *x = c; *x; x++)
if (isspace(*x)) if (isspace(*x))
*x = '_'; *x = '_';
@ -2352,7 +2417,7 @@ static char* string_to_json_field_name(const char *f) {
int table_to_json(Table *t, JsonVariant **ret) { int table_to_json(Table *t, JsonVariant **ret) {
JsonVariant **rows = NULL, **elements = NULL; JsonVariant **rows = NULL, **elements = NULL;
_cleanup_free_ size_t *sorted = NULL; _cleanup_free_ size_t *sorted = NULL;
size_t n_rows, i, j, display_columns; size_t n_rows, display_columns;
int r; int r;
assert(t); assert(t);
@ -2372,7 +2437,7 @@ int table_to_json(Table *t, JsonVariant **ret) {
goto finish; goto finish;
} }
for (i = 0; i < n_rows; i++) for (size_t i = 0; i < n_rows; i++)
sorted[i] = i * t->n_columns; sorted[i] = i * t->n_columns;
typesafe_qsort_r(sorted, n_rows, table_data_compare, t); typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
@ -2390,7 +2455,7 @@ int table_to_json(Table *t, JsonVariant **ret) {
goto finish; goto finish;
} }
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
_cleanup_free_ char *mangled = NULL; _cleanup_free_ char *mangled = NULL;
const char *formatted; const char *formatted;
TableData *d; TableData *d;
@ -2398,7 +2463,7 @@ int table_to_json(Table *t, JsonVariant **ret) {
assert_se(d = t->data[t->display_map ? t->display_map[j] : j]); assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
/* Field names must be strings, hence format whatever we got here as a string first */ /* Field names must be strings, hence format whatever we got here as a string first */
formatted = table_data_format(t, d, true); formatted = table_data_format(t, d, true, SIZE_MAX, NULL);
if (!formatted) { if (!formatted) {
r = -ENOMEM; r = -ENOMEM;
goto finish; goto finish;
@ -2422,7 +2487,7 @@ int table_to_json(Table *t, JsonVariant **ret) {
goto finish; goto finish;
} }
for (i = 1; i < n_rows; i++) { for (size_t i = 1; i < n_rows; i++) {
TableData **row; TableData **row;
if (sorted) if (sorted)
@ -2430,7 +2495,7 @@ int table_to_json(Table *t, JsonVariant **ret) {
else else
row = t->data + i * t->n_columns; row = t->data + i * t->n_columns;
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
TableData *d; TableData *d;
size_t k; size_t k;

View File

@ -12,6 +12,7 @@ typedef enum TableDataType {
TABLE_EMPTY, TABLE_EMPTY,
TABLE_STRING, TABLE_STRING,
TABLE_STRV, TABLE_STRV,
TABLE_STRV_WRAPPED,
TABLE_PATH, TABLE_PATH,
TABLE_BOOLEAN, TABLE_BOOLEAN,
TABLE_TIMESTAMP, TABLE_TIMESTAMP,

View File

@ -301,3 +301,43 @@ int enter_cgroup_subroot(char **ret_cgroup) {
int enter_cgroup_root(char **ret_cgroup) { int enter_cgroup_root(char **ret_cgroup) {
return enter_cgroup(ret_cgroup, false); return enter_cgroup(ret_cgroup, false);
} }
const char *ci_environment(void) {
/* We return a string because we might want to provide multiple bits of information later on: not
* just the general CI environment type, but also whether we're sanitizing or not, etc. The caller is
* expected to use strstr on the returned value. */
static const char *ans = POINTER_MAX;
const char *p;
int r;
if (ans != POINTER_MAX)
return ans;
/* We allow specifying the environment with $CITYPE. Nobody uses this so far, but we are ready. */
p = getenv("CITYPE");
if (!isempty(p))
return (ans = p);
if (getenv_bool("TRAVIS") > 0)
return (ans = "travis");
if (getenv_bool("SEMAPHORE") > 0)
return (ans = "semaphore");
if (getenv_bool("GITHUB_ACTIONS") > 0)
return (ans = "github-actions");
if (getenv("AUTOPKGTEST_ARTIFACTS") || getenv("AUTOPKGTEST_TMP"))
return (ans = "autopkgtest");
FOREACH_STRING(p, "CI", "CONTINOUS_INTEGRATION") {
/* Those vars are booleans according to Semaphore and Travis docs:
* https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
* https://docs.semaphoreci.com/ci-cd-environment/environment-variables/#ci
*/
r = getenv_bool(p);
if (r > 0)
return (ans = "unknown"); /* Some other unknown thing */
if (r == 0)
return (ans = NULL);
}
return (ans = NULL);
}

View File

@ -40,3 +40,6 @@ bool can_memlock(void);
} else { \ } else { \
printf("systemd not booted skipping '%s'\n", #x); \ printf("systemd not booted skipping '%s'\n", #x); \
} }
/* Provide a convenient way to check if we're running in CI. */
const char *ci_environment(void);

View File

@ -2,6 +2,8 @@
#include "bus-error.h" #include "bus-error.h"
#include "copy.h" #include "copy.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h" #include "fs-util.h"
#include "mkdir.h" #include "mkdir.h"
#include "pager.h" #include "pager.h"
@ -17,6 +19,9 @@
#include "terminal-util.h" #include "terminal-util.h"
#include "tmpfile-util.h" #include "tmpfile-util.h"
#define EDIT_MARKER_START "### Anything between here and the comment below will become the new contents of the file"
#define EDIT_MARKER_END "### Lines below this comment will be discarded"
int cat(int argc, char *argv[], void *userdata) { int cat(int argc, char *argv[], void *userdata) {
_cleanup_(hashmap_freep) Hashmap *cached_name_map = NULL, *cached_id_map = NULL; _cleanup_(hashmap_freep) Hashmap *cached_name_map = NULL, *cached_id_map = NULL;
_cleanup_(lookup_paths_free) LookupPaths lp = {}; _cleanup_(lookup_paths_free) LookupPaths lp = {};
@ -106,12 +111,11 @@ int cat(int argc, char *argv[], void *userdata) {
return rc; return rc;
} }
static int create_edit_temp_file(const char *new_path, const char *original_path, char **ret_tmp_fn) { static int create_edit_temp_file(const char *new_path, const char *original_path, char ** const original_unit_paths, char **ret_tmp_fn) {
_cleanup_free_ char *t = NULL; _cleanup_free_ char *t = NULL;
int r; int r;
assert(new_path); assert(new_path);
assert(original_path);
assert(ret_tmp_fn); assert(ret_tmp_fn);
r = tempfn_random(new_path, NULL, &t); r = tempfn_random(new_path, NULL, &t);
@ -122,26 +126,79 @@ static int create_edit_temp_file(const char *new_path, const char *original_path
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path); return log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path);
r = mac_selinux_create_file_prepare(original_path, S_IFREG); if (original_path) {
r = mac_selinux_create_file_prepare(new_path, S_IFREG);
if (r < 0) if (r < 0)
return r; return r;
r = copy_file(original_path, t, 0, 0644, 0, 0, COPY_REFLINK); r = copy_file(original_path, t, 0, 0644, 0, 0, COPY_REFLINK);
if (r == -ENOENT) { if (r == -ENOENT) {
r = touch(t); r = touch(t);
mac_selinux_create_file_clear(); mac_selinux_create_file_clear();
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t); return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t);
} else { } else {
mac_selinux_create_file_clear(); mac_selinux_create_file_clear();
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", new_path); return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", new_path);
} }
} else if (original_unit_paths) {
_cleanup_free_ char *new_contents = NULL;
_cleanup_fclose_ FILE *f = NULL;
char **path;
size_t size;
r = mac_selinux_create_file_prepare(new_path, S_IFREG);
if (r < 0)
return r;
f = fopen(t, "we");
mac_selinux_create_file_clear();
if (!f)
return log_error_errno(errno, "Failed to open \"%s\": %m", t);
r = fchmod(fileno(f), 0644);
if (r < 0)
return log_error_errno(errno, "Failed to change mode of \"%s\": %m", t);
r = read_full_file(new_path, &new_contents, &size);
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to read \"%s\": %m", new_path);
fprintf(f,
"### Editing %s\n"
EDIT_MARKER_START
"\n\n%s%s\n"
EDIT_MARKER_END,
new_path,
strempty(new_contents),
new_contents && endswith(new_contents, "\n") ? "" : "\n");
/* Add a comment with the contents of the original unit files */
STRV_FOREACH(path, original_unit_paths) {
_cleanup_free_ char *contents = NULL;
/* Skip the file that's being edited */
if (path_equal(*path, new_path))
continue;
r = read_full_file(*path, &contents, &size);
if (r < 0)
return log_error_errno(r, "Failed to read \"%s\": %m", *path);
fprintf(f, "\n\n### %s", *path);
if (!isempty(contents)) {
contents = strreplace(strstrip(contents), "\n", "\n# ");
if (!contents)
return log_oom();
fprintf(f, "\n# %s", contents);
}
}
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t);
}
*ret_tmp_fn = TAKE_PTR(t); *ret_tmp_fn = TAKE_PTR(t);
@ -185,6 +242,7 @@ static int unit_file_create_new(
const LookupPaths *paths, const LookupPaths *paths,
const char *unit_name, const char *unit_name,
const char *suffix, const char *suffix,
char ** const original_unit_paths,
char **ret_new_path, char **ret_new_path,
char **ret_tmp_path) { char **ret_tmp_path) {
@ -201,7 +259,7 @@ static int unit_file_create_new(
if (r < 0) if (r < 0)
return r; return r;
r = create_edit_temp_file(new_path, new_path, &tmp_path); r = create_edit_temp_file(new_path, NULL, original_unit_paths, &tmp_path);
if (r < 0) if (r < 0)
return r; return r;
@ -240,7 +298,7 @@ static int unit_file_create_copy(
return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "%s skipped.", unit_name); return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "%s skipped.", unit_name);
} }
r = create_edit_temp_file(new_path, fragment_path, &tmp_path); r = create_edit_temp_file(new_path, fragment_path, NULL, &tmp_path);
if (r < 0) if (r < 0)
return r; return r;
@ -332,9 +390,10 @@ static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) {
STRV_FOREACH(name, names) { STRV_FOREACH(name, names) {
_cleanup_free_ char *path = NULL, *new_path = NULL, *tmp_path = NULL, *tmp_name = NULL; _cleanup_free_ char *path = NULL, *new_path = NULL, *tmp_path = NULL, *tmp_name = NULL;
_cleanup_strv_free_ char **unit_paths = NULL;
const char *unit_name; const char *unit_name;
r = unit_find_paths(bus, *name, &lp, false, &cached_name_map, &cached_id_map, &path, NULL); r = unit_find_paths(bus, *name, &lp, false, &cached_name_map, &cached_id_map, &path, &unit_paths);
if (r == -EKEYREJECTED) { if (r == -EKEYREJECTED) {
/* If loading of the unit failed server side complete, then the server won't tell us /* If loading of the unit failed server side complete, then the server won't tell us
* the unit file path. In that case, find the file client side. */ * the unit file path. In that case, find the file client side. */
@ -361,7 +420,7 @@ static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) {
unit_name = *name; unit_name = *name;
r = unit_file_create_new(&lp, unit_name, r = unit_file_create_new(&lp, unit_name,
arg_full ? NULL : ".d/override.conf", arg_full ? NULL : ".d/override.conf",
&new_path, &tmp_path); NULL, &new_path, &tmp_path);
} else { } else {
assert(path); assert(path);
@ -384,8 +443,13 @@ static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) {
if (arg_full) if (arg_full)
r = unit_file_create_copy(&lp, unit_name, path, &new_path, &tmp_path); r = unit_file_create_copy(&lp, unit_name, path, &new_path, &tmp_path);
else else {
r = unit_file_create_new(&lp, unit_name, ".d/override.conf", &new_path, &tmp_path); r = strv_prepend(&unit_paths, path);
if (r < 0)
return log_oom();
r = unit_file_create_new(&lp, unit_name, ".d/override.conf", unit_paths, &new_path, &tmp_path);
}
} }
if (r < 0) if (r < 0)
return r; return r;
@ -400,6 +464,40 @@ static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) {
return 0; return 0;
} }
static int trim_edit_markers(const char *path) {
_cleanup_free_ char *contents = NULL;
char *contents_start = NULL;
const char *contents_end = NULL;
size_t size;
int r;
/* Trim out the lines between the two markers */
r = read_full_file(path, &contents, &size);
if (r < 0)
return log_error_errno(r, "Failed to read temporary file \"%s\": %m", path);
contents_start = strstr(contents, EDIT_MARKER_START);
if (contents_start)
contents_start += strlen(EDIT_MARKER_START);
else
contents_start = contents;
contents_end = strstr(contents_start, EDIT_MARKER_END);
if (contents_end)
strshorten(contents_start, contents_end - contents_start);
contents_start = strstrip(contents_start);
/* Write new contents if the trimming actually changed anything */
if (strlen(contents) != size) {
r = write_string_file(path, contents_start, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE);
if (r < 0)
return log_error_errno(r, "Failed to modify temporary file \"%s\": %m", path);
}
return 0;
}
int edit(int argc, char *argv[], void *userdata) { int edit(int argc, char *argv[], void *userdata) {
_cleanup_(lookup_paths_free) LookupPaths lp = {}; _cleanup_(lookup_paths_free) LookupPaths lp = {};
_cleanup_strv_free_ char **names = NULL; _cleanup_strv_free_ char **names = NULL;
@ -452,6 +550,10 @@ int edit(int argc, char *argv[], void *userdata) {
STRV_FOREACH_PAIR(original, tmp, paths) { STRV_FOREACH_PAIR(original, tmp, paths) {
/* If the temporary file is empty we ignore it. This allows the user to cancel the /* If the temporary file is empty we ignore it. This allows the user to cancel the
* modification. */ * modification. */
r = trim_edit_markers(*tmp);
if (r < 0)
continue;
if (null_or_empty_path(*tmp)) { if (null_or_empty_path(*tmp)) {
log_warning("Editing \"%s\" canceled: temporary file is empty.", *original); log_warning("Editing \"%s\" canceled: temporary file is empty.", *original);
continue; continue;

View File

@ -264,6 +264,7 @@ static void test_env_clean(void) {
"xyz=xyz\n", "xyz=xyz\n",
"another=one", "another=one",
"another=final one", "another=final one",
"CRLF=\r\n",
"BASH_FUNC_foo%%=() { echo foo\n}"); "BASH_FUNC_foo%%=() { echo foo\n}");
assert_se(e); assert_se(e);
assert_se(!strv_env_is_valid(e)); assert_se(!strv_env_is_valid(e));
@ -306,6 +307,8 @@ static void test_env_value_is_valid(void) {
assert_se(env_value_is_valid("printf \"\\x1b]0;<mock-chroot>\\x07<mock-chroot>\"")); assert_se(env_value_is_valid("printf \"\\x1b]0;<mock-chroot>\\x07<mock-chroot>\""));
assert_se(env_value_is_valid("tab\tcharacter")); assert_se(env_value_is_valid("tab\tcharacter"));
assert_se(env_value_is_valid("new\nline")); assert_se(env_value_is_valid("new\nline"));
assert_se(!env_value_is_valid("Show this?\rNope. Show that!"));
assert_se(!env_value_is_valid("new DOS\r\nline"));
} }
static void test_env_assignment_is_valid(void) { static void test_env_assignment_is_valid(void) {

View File

@ -36,11 +36,6 @@ static int cld_dumped_to_killed(int code) {
return code == CLD_DUMPED ? CLD_KILLED : code; return code == CLD_DUMPED ? CLD_KILLED : code;
} }
_unused_ static bool is_run_on_travis_ci(void) {
/* https://docs.travis-ci.com/user/environment-variables#default-environment-variables */
return streq_ptr(getenv("TRAVIS"), "true");
}
static void wait_for_service_finish(Manager *m, Unit *unit) { static void wait_for_service_finish(Manager *m, Unit *unit) {
Service *service = NULL; Service *service = NULL;
usec_t ts; usec_t ts;
@ -897,7 +892,7 @@ int main(int argc, char *argv[]) {
test_setup_logging(LOG_DEBUG); test_setup_logging(LOG_DEBUG);
#if HAS_FEATURE_ADDRESS_SANITIZER #if HAS_FEATURE_ADDRESS_SANITIZER
if (is_run_on_travis_ci()) { if (strstr_ptr(ci_environment(), "travis")) {
log_notice("Running on TravisCI under ASan, skipping, see https://github.com/systemd/systemd/issues/10696"); log_notice("Running on TravisCI under ASan, skipping, see https://github.com/systemd/systemd/issues/10696");
return EXIT_TEST_SKIP; return EXIT_TEST_SKIP;
} }

View File

@ -12,6 +12,8 @@ static void test_issue_9549(void) {
_cleanup_(table_unrefp) Table *table = NULL; _cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL; _cleanup_free_ char *formatted = NULL;
log_info("/* %s */", __func__);
assert_se(table = table_new("name", "type", "ro", "usage", "created", "modified")); assert_se(table = table_new("name", "type", "ro", "usage", "created", "modified"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(3), 100) >= 0); assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(3), 100) >= 0);
assert_se(table_add_many(table, assert_se(table_add_many(table,
@ -36,6 +38,8 @@ static void test_multiline(void) {
_cleanup_(table_unrefp) Table *table = NULL; _cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL; _cleanup_free_ char *formatted = NULL;
log_info("/* %s */", __func__);
assert_se(table = table_new("foo", "bar")); assert_se(table = table_new("foo", "bar"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0); assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0);
@ -148,6 +152,8 @@ static void test_strv(void) {
_cleanup_(table_unrefp) Table *table = NULL; _cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL; _cleanup_free_ char *formatted = NULL;
log_info("/* %s */", __func__);
assert_se(table = table_new("foo", "bar")); assert_se(table = table_new("foo", "bar"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0); assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0);
@ -256,8 +262,111 @@ static void test_strv(void) {
formatted = mfree(formatted); formatted = mfree(formatted);
} }
int main(int argc, char *argv[]) { static void test_strv_wrapped(void) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL;
log_info("/* %s */", __func__);
assert_se(table = table_new("foo", "bar"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0);
assert_se(table_add_many(table,
TABLE_STRV_WRAPPED, STRV_MAKE("three", "different", "lines"),
TABLE_STRV_WRAPPED, STRV_MAKE("two", "lines")) >= 0);
table_set_cell_height_max(table, 1);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different lines two lines\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, 2);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different lines two lines\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, 3);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different lines two lines\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, (size_t) -1);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different lines two lines\n"));
formatted = mfree(formatted);
assert_se(table_add_many(table,
TABLE_STRING, "short",
TABLE_STRV_WRAPPED, STRV_MAKE("a", "pair")) >= 0);
assert_se(table_add_many(table,
TABLE_STRV_WRAPPED, STRV_MAKE("short2"),
TABLE_STRV_WRAPPED, STRV_MAKE("a", "eight", "line", "ćęłł",
"___5___", "___6___", "___7___", "___8___")) >= 0);
table_set_cell_height_max(table, 1);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different… two lines\n"
"short a pair\n"
"short2 a eight line ćęłł…\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, 2);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different two lines\n"
"lines \n"
"short a pair\n"
"short2 a eight line ćęłł\n"
" ___5___ ___6___…\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, 3);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different two lines\n"
"lines \n"
"short a pair\n"
"short2 a eight line ćęłł\n"
" ___5___ ___6___\n"
" ___7___ ___8___\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, (size_t) -1);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different two lines\n"
"lines \n"
"short a pair\n"
"short2 a eight line ćęłł\n"
" ___5___ ___6___\n"
" ___7___ ___8___\n"));
formatted = mfree(formatted);
}
int main(int argc, char *argv[]) {
_cleanup_(table_unrefp) Table *t = NULL; _cleanup_(table_unrefp) Table *t = NULL;
_cleanup_free_ char *formatted = NULL; _cleanup_free_ char *formatted = NULL;
@ -399,6 +508,7 @@ int main(int argc, char *argv[]) {
test_issue_9549(); test_issue_9549();
test_multiline(); test_multiline();
test_strv(); test_strv();
test_strv_wrapped();
return 0; return 0;
} }

View File

@ -1,7 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */ /* SPDX-License-Identifier: LGPL-2.1+ */
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
@ -78,32 +77,52 @@ static Service *service_for_path(Manager *m, Path *path, const char *service_nam
return SERVICE(service_unit); return SERVICE(service_unit);
} }
static void check_states(Manager *m, Path *path, Service *service, PathState path_state, ServiceState service_state) { static int _check_states(unsigned line,
Manager *m, Path *path, Service *service, PathState path_state, ServiceState service_state) {
assert_se(m); assert_se(m);
assert_se(service); assert_se(service);
usec_t end = now(CLOCK_MONOTONIC) + 30 * USEC_PER_SEC; usec_t end = now(CLOCK_MONOTONIC) + 30 * USEC_PER_SEC;
while (path->result != PATH_SUCCESS || service->result != SERVICE_SUCCESS || while (path->state != path_state || service->state != service_state ||
path->state != path_state || service->state != service_state) { path->result != PATH_SUCCESS || service->result != SERVICE_SUCCESS) {
assert_se(sd_event_run(m->event, 100 * USEC_PER_MSEC) >= 0); assert_se(sd_event_run(m->event, 100 * USEC_PER_MSEC) >= 0);
printf("%s: state = %s; result = %s \n", usec_t n = now(CLOCK_MONOTONIC);
log_info("line %u: %s: state = %s; result = %s (left: %" PRIi64 ")",
line,
UNIT(path)->id, UNIT(path)->id,
path_state_to_string(path->state), path_state_to_string(path->state),
path_result_to_string(path->result)); path_result_to_string(path->result),
printf("%s: state = %s; result = %s \n", end - n);
log_info("line %u: %s: state = %s; result = %s",
line,
UNIT(service)->id, UNIT(service)->id,
service_state_to_string(service->state), service_state_to_string(service->state),
service_result_to_string(service->result)); service_result_to_string(service->result));
if (now(CLOCK_MONOTONIC) >= end) { if (service->state == SERVICE_FAILED &&
service->main_exec_status.status == EXIT_CGROUP &&
!ci_environment())
/* On a general purpose system we may fail to start the service for reasons which are
* not under our control: permission limits, resource exhaustion, etc. Let's skip the
* test in those cases. On developer machines we require proper setup. */
return log_notice_errno(SYNTHETIC_ERRNO(ECANCELED),
"Failed to start service %s, aborting test: %s/%s",
UNIT(service)->id,
service_state_to_string(service->state),
service_result_to_string(service->result));
if (n >= end) {
log_error("Test timeout when testing %s", UNIT(path)->id); log_error("Test timeout when testing %s", UNIT(path)->id);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
} }
return 0;
} }
#define check_states(...) _check_states(__LINE__, __VA_ARGS__)
static void test_path_exists(Manager *m) { static void test_path_exists(Manager *m) {
const char *test_path = "/tmp/test-path_exists"; const char *test_path = "/tmp/test-path_exists";
@ -119,18 +138,22 @@ static void test_path_exists(Manager *m) {
service = service_for_path(m, path, NULL); service = service_for_path(m, path, NULL);
assert_se(unit_start(unit) >= 0); assert_se(unit_start(unit) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(touch(test_path) >= 0); assert_se(touch(test_path) >= 0);
check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* Service restarts if file still exists */ /* Service restarts if file still exists */
assert_se(unit_stop(UNIT(service)) >= 0); assert_se(unit_stop(UNIT(service)) >= 0);
check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(unit_stop(UNIT(service)) >= 0); assert_se(unit_stop(UNIT(service)) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0); assert_se(unit_stop(unit) >= 0);
} }
@ -149,18 +172,22 @@ static void test_path_existsglob(Manager *m) {
service = service_for_path(m, path, NULL); service = service_for_path(m, path, NULL);
assert_se(unit_start(unit) >= 0); assert_se(unit_start(unit) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(touch(test_path) >= 0); assert_se(touch(test_path) >= 0);
check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* Service restarts if file still exists */ /* Service restarts if file still exists */
assert_se(unit_stop(UNIT(service)) >= 0); assert_se(unit_stop(UNIT(service)) >= 0);
check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(unit_stop(UNIT(service)) >= 0); assert_se(unit_stop(UNIT(service)) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0); assert_se(unit_stop(unit) >= 0);
} }
@ -180,23 +207,28 @@ static void test_path_changed(Manager *m) {
service = service_for_path(m, path, NULL); service = service_for_path(m, path, NULL);
assert_se(unit_start(unit) >= 0); assert_se(unit_start(unit) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(touch(test_path) >= 0); assert_se(touch(test_path) >= 0);
check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* Service does not restart if file still exists */ /* Service does not restart if file still exists */
assert_se(unit_stop(UNIT(service)) >= 0); assert_se(unit_stop(UNIT(service)) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
f = fopen(test_path, "w"); f = fopen(test_path, "w");
assert_se(f); assert_se(f);
fclose(f); fclose(f);
check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(unit_stop(UNIT(service)) >= 0); assert_se(unit_stop(UNIT(service)) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
(void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL);
assert_se(unit_stop(unit) >= 0); assert_se(unit_stop(unit) >= 0);
@ -217,23 +249,28 @@ static void test_path_modified(Manager *m) {
service = service_for_path(m, path, NULL); service = service_for_path(m, path, NULL);
assert_se(unit_start(unit) >= 0); assert_se(unit_start(unit) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(touch(test_path) >= 0); assert_se(touch(test_path) >= 0);
check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* Service does not restart if file still exists */ /* Service does not restart if file still exists */
assert_se(unit_stop(UNIT(service)) >= 0); assert_se(unit_stop(UNIT(service)) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
f = fopen(test_path, "w"); f = fopen(test_path, "w");
assert_se(f); assert_se(f);
fputs("test", f); fputs("test", f);
check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(unit_stop(UNIT(service)) >= 0); assert_se(unit_stop(UNIT(service)) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
(void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL);
assert_se(unit_stop(unit) >= 0); assert_se(unit_stop(unit) >= 0);
@ -253,14 +290,17 @@ static void test_path_unit(Manager *m) {
service = service_for_path(m, path, "path-mycustomunit.service"); service = service_for_path(m, path, "path-mycustomunit.service");
assert_se(unit_start(unit) >= 0); assert_se(unit_start(unit) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(touch(test_path) >= 0); assert_se(touch(test_path) >= 0);
check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(unit_stop(UNIT(service)) >= 0); assert_se(unit_stop(UNIT(service)) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0); assert_se(unit_stop(unit) >= 0);
} }
@ -281,22 +321,26 @@ static void test_path_directorynotempty(Manager *m) {
assert_se(access(test_path, F_OK) < 0); assert_se(access(test_path, F_OK) < 0);
assert_se(unit_start(unit) >= 0); assert_se(unit_start(unit) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
/* MakeDirectory default to no */ /* MakeDirectory default to no */
assert_se(access(test_path, F_OK) < 0); assert_se(access(test_path, F_OK) < 0);
assert_se(mkdir_p(test_path, 0755) >= 0); assert_se(mkdir_p(test_path, 0755) >= 0);
assert_se(touch(strjoina(test_path, "test_file")) >= 0); assert_se(touch(strjoina(test_path, "test_file")) >= 0);
check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
/* Service restarts if directory is still not empty */ /* Service restarts if directory is still not empty */
assert_se(unit_stop(UNIT(service)) >= 0); assert_se(unit_stop(UNIT(service)) >= 0);
check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0)
return;
assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
assert_se(unit_stop(UNIT(service)) >= 0); assert_se(unit_stop(UNIT(service)) >= 0);
check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0)
return;
assert_se(unit_stop(unit) >= 0); assert_se(unit_stop(unit) >= 0);
} }

View File

@ -604,7 +604,7 @@ int xdg_autostart_service_generate_unit(
fprintf(f, fprintf(f,
"\n[Service]\n" "\n[Service]\n"
"Type=simple\n" "Type=exec\n"
"ExecStart=:%s\n" "ExecStart=:%s\n"
"Restart=no\n" "Restart=no\n"
"TimeoutSec=5s\n" "TimeoutSec=5s\n"

View File

@ -2,6 +2,6 @@
Description=Service Test for Path units Description=Service Test for Path units
[Service] [Service]
ExecStart=/bin/true ExecStart=sleep infinity
Type=simple Type=exec
RemainAfterExit=true RemainAfterExit=true

View File

@ -2,6 +2,6 @@
Description=Service Test for Path units Description=Service Test for Path units
[Service] [Service]
ExecStart=/bin/true ExecStart=sleep infinity
Type=simple Type=exec
RemainAfterExit=true RemainAfterExit=true

View File

@ -2,6 +2,6 @@
Description=Service Test for Path units Description=Service Test for Path units
[Service] [Service]
ExecStart=/bin/true ExecStart=sleep infinity
Type=simple Type=exec
RemainAfterExit=true RemainAfterExit=true

View File

@ -2,6 +2,6 @@
Description=Service Test for Path units Description=Service Test for Path units
[Service] [Service]
ExecStart=/bin/true ExecStart=sleep infinity
Type=simple Type=exec
RemainAfterExit=true RemainAfterExit=true

View File

@ -2,6 +2,6 @@
Description=Service Test for Path units Description=Service Test for Path units
[Service] [Service]
ExecStart=/bin/true ExecStart=sleep infinity
Type=simple Type=exec
RemainAfterExit=true RemainAfterExit=true

View File

@ -2,6 +2,6 @@
Description=Service Test for Path units Description=Service Test for Path units
[Service] [Service]
ExecStart=/bin/true ExecStart=sleep infinity
Type=simple Type=exec
RemainAfterExit=true RemainAfterExit=true

View File

@ -2,6 +2,6 @@
Description=Service Test Path Unit Description=Service Test Path Unit
[Service] [Service]
ExecStart=/bin/true ExecStart=sleep infinity
Type=simple Type=exec
RemainAfterExit=true RemainAfterExit=true

View File

@ -1,6 +0,0 @@
[Unit]
Description=Service Test for Path units
[Service]
ExecStart=/bin/true
Type=oneshot

View File

@ -2,5 +2,5 @@
Description=ForeverPrintHola service Description=ForeverPrintHola service
[Service] [Service]
Type=simple Type=exec
ExecStart=sh -x -c 'while :; do printf "Hola\n" || touch /i-lose-my-logs; sleep 1; done' ExecStart=sh -x -c 'while :; do printf "Hola\n" || touch /i-lose-my-logs; sleep 1; done'

View File

@ -4,6 +4,6 @@ StartLimitIntervalSec=1m
StartLimitBurst=3 StartLimitBurst=3
[Service] [Service]
Type=simple Type=exec
ExecStart=false ExecStart=false
Restart=always Restart=always

View File

@ -13,7 +13,7 @@ StopWhenUnneeded=yes
[Service] [Service]
ExecStartPre=rm -f /failed /testok ExecStartPre=rm -f /failed /testok
Type=simple Type=exec
TimeoutStartSec=infinity TimeoutStartSec=infinity
ExecStartPre=/usr/lib/systemd/tests/testdata/units/%N.sh ExecStartPre=/usr/lib/systemd/tests/testdata/units/%N.sh
ExecStart=true ExecStart=true

View File

@ -11,8 +11,13 @@
# control writes into pstore. # control writes into pstore.
# #
# The crash_kexec_post_notifiers parameter enables the kernel to write # The crash_kexec_post_notifiers parameter enables the kernel to write
# dmesg (including stack trace) into pstore upon a panic, and # dmesg (including stack trace) into pstore upon a panic even if kdump
# printk.always_kmsg_dump parameter enables the kernel to write dmesg # is loaded, only needed if you want to use pstore with kdump. Without
# this parameter, kdump could block writing to pstore for stability
# reason. Note this increases the risk of kdump failure even if pstore
# is not available.
#
# The printk.always_kmsg_dump parameter enables the kernel to write dmesg
# upon a normal shutdown (shutdown, reboot, halt). # upon a normal shutdown (shutdown, reboot, halt).
# #
# To configure the kernel parameters, uncomment the appropriate # To configure the kernel parameters, uncomment the appropriate
@ -26,4 +31,4 @@
d /var/lib/systemd/pstore 0755 root root 14d d /var/lib/systemd/pstore 0755 root root 14d
#w- /sys/module/printk/parameters/always_kmsg_dump - - - - Y #w- /sys/module/printk/parameters/always_kmsg_dump - - - - Y
w- /sys/module/kernel/parameters/crash_kexec_post_notifiers - - - - Y #w- /sys/module/kernel/parameters/crash_kexec_post_notifiers - - - - Y