mirror of
https://github.com/systemd/systemd
synced 2026-03-30 11:44:49 +02:00
Compare commits
No commits in common. "3f91ffe0fe900660a8c073ec54b3951e33b5c74c" and "c6c43d677abe66c404a2fb4aa66707bcabff4dff" have entirely different histories.
3f91ffe0fe
...
c6c43d677a
@ -1 +1,176 @@
|
||||
[This content has moved to the UAPI group website](https://uapi-group.org/specifications/specs/elf_dlopen_metadata/)
|
||||
---
|
||||
title: Dlopen Metadata for ELF Files
|
||||
category: Interfaces
|
||||
layout: default
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
---
|
||||
|
||||
# `dlopen()` Metadata for ELF Files
|
||||
|
||||
*Intended audience: hackers working on packaging ELF files that use dlopen to load libraries.*
|
||||
|
||||
## Motivation
|
||||
|
||||
Using `dlopen()` to load optional dependencies brings several advantages: programs can gracefully downgrade
|
||||
a feature when a library is not available, and the shared library is only loaded into the process (and its
|
||||
ELF constructors are run) only when the requested feature is actually used. But it also has some drawbacks,
|
||||
and the main one is that it is harder to track a program's dependencies, since unlike build-time dynamic
|
||||
linking there will not be a mention in the ELF metadata. This specification aims to solve this problem by
|
||||
providing a standardized specification for a custom ELF note that can be used to list `dlopen()`
|
||||
dependencies.
|
||||
|
||||
## Implementation
|
||||
|
||||
This document will attempt to define a common metadata format specification, so that multiple implementers
|
||||
might use it when coding upstream software, and packagers might use it when building packages and setting
|
||||
dependencies.
|
||||
|
||||
The metadata will be embedded in a series of new, 4-byte-aligned, allocated, 0-padded, read-only ELF header
|
||||
sections, in a JSON array containing name-value objects, either one ELF note per dependency or as a single
|
||||
note listing multiple dependencies in the top-level array. Implementers working on parsing ELF files should
|
||||
not assume a specific list of names, but parse anything that is included in the section, and should look for
|
||||
the note using the `note type`. Implementers working on build tools should strive to use the same names, for
|
||||
consistency. The most common will be listed here.
|
||||
|
||||
* Section header
|
||||
|
||||
```
|
||||
SECTION: `.note.dlopen`
|
||||
note type: `0x407c0c0a`
|
||||
Owner: `FDO` (FreeDesktop.org)
|
||||
Value: an array of JSON objects encoded as a zero-terminated UTF-8 string
|
||||
```
|
||||
|
||||
* JSON payload
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"soname": ["libfoo.so.1"],
|
||||
"feature": "foo",
|
||||
"description": "Enables the foo feature",
|
||||
"priority": "recommended"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The format is a single JSON array containing objects, encoded as a zero-terminated `UTF-8` string. Each key
|
||||
in each object shall be unique as per recommendations of [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259#section-4).
|
||||
Strings shall not contain any control characters or use `\uXXX` escaping.
|
||||
|
||||
Reference implementations of [packaging tools for `.deb` and `.rpm`](https://github.com/systemd/package-notes)
|
||||
are available, and provide macros/helpers to parse the note when building packages and adding dependencies.
|
||||
|
||||
## Well-known keys
|
||||
|
||||
The metadata format is intentionally extensible, so that upstreams and later revisions of this spec can add
|
||||
their own information. The 'soname' array is required, with at least one element, everything else is
|
||||
optional. If alternative soname versions for the same library are supported at the same time, an array can
|
||||
be used, listing the most preferred first, and parsers are expected to select only the first one that is
|
||||
available on the system, as it is a mechanism to specify alternatives. If the `priority` field is used, it
|
||||
must follow the specification and use one of the values specified in the table. If it is not specified, a
|
||||
parser should assume 'recommended' if a priority is needed. If the `feature` field is used, it will identify
|
||||
an individual feature, and multiple entries using the same `feature` denote functionality that requires all
|
||||
of the libraries they specify in order to be enabled.
|
||||
|
||||
| Key name | Key type | Mandatory | Key description | Example value |
|
||||
|-------------|----------------------------|-----------|--------------------------------------------------------------------------|----------------------------------|
|
||||
| soname | array of strings | yes | The library names loaded by `dlopen()` | [ "libfoo.so.1", "libfoo.so.0" ] |
|
||||
| feature | string | no | A keyword identifying the feature that the library contributes to enable | "foo" |
|
||||
| description | string | no | A human-readable text string describing the feature | "Enables the foo feature" |
|
||||
| priority | string | no | The priority of the feature, one of: required, recommended, suggested | "recommended" |
|
||||
|
||||
### Priority definition
|
||||
|
||||
| Priority | Semantics |
|
||||
|-------------|--------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| required | Core functionality needs the dependency, the binary will not work if it cannot be found |
|
||||
| recommended | Important functionality needs the dependency, the binary will work but in most cases the dependency should be provided |
|
||||
| suggested | Secondary functionality needs the dependency, the binary will work and the dependency is only needed for full-featured installations |
|
||||
|
||||
### Displaying `dlopen()` notes
|
||||
|
||||
The raw ELF section can be extracted using `objdump`:
|
||||
```console
|
||||
$ objdump -j .note.dlopen -s /usr/lib64/systemd/libsystemd-shared-257.so
|
||||
|
||||
/usr/lib64/systemd/libsystemd-shared-257.so: file format elf64-x86-64
|
||||
|
||||
Contents of section .note.dlopen:
|
||||
0334 04000000 8e000000 0a0c7c40 46444f00 ..........|@FDO.
|
||||
0344 5b7b2266 65617475 7265223a 22627066 [{"feature":"bpf
|
||||
0354 222c2264 65736372 69707469 6f6e223a ","description":
|
||||
0364 22537570 706f7274 20666972 6577616c "Support firewal
|
||||
0374 6c696e67 20616e64 2073616e 64626f78 ling and sandbox
|
||||
0384 696e6720 77697468 20425046 222c2270 ing with BPF","p
|
||||
0394 72696f72 69747922 3a227375 67676573 riority":"sugges
|
||||
03a4 74656422 2c22736f 6e616d65 223a5b22 ted","soname":["
|
||||
03b4 6c696262 70662e73 6f2e3122 2c226c69 libbpf.so.1","li
|
||||
03c4 62627066 2e736f2e 30225d7d 5d000000 bbpf.so.0"]}]...
|
||||
03d4 04000000 9e000000 0a0c7c40 46444f00 ..........|@FDO.
|
||||
...
|
||||
```
|
||||
|
||||
It is more convenient to use a higher level tool:
|
||||
```console
|
||||
$ dlopen-notes /usr/lib64/systemd/libsystemd-shared-257.so
|
||||
# /usr/lib64/systemd/libsystemd-shared-257.so
|
||||
[
|
||||
{
|
||||
"feature": "archive",
|
||||
"description": "Support for decompressing archive files",
|
||||
"priority": "suggested",
|
||||
"soname": [
|
||||
"libarchive.so.13"
|
||||
]
|
||||
},
|
||||
{
|
||||
"feature": "bpf",
|
||||
"description": "Support firewalling and sandboxing with BPF",
|
||||
"priority": "suggested",
|
||||
"soname": [
|
||||
"libbpf.so.1",
|
||||
"libbpf.so.0"
|
||||
]
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
`dlopen-notes` can display the notes grouped in a few different ways.
|
||||
One option is to filter the libraries by "feature". This answers the
|
||||
question "what libraries are needed to provide specified features":
|
||||
|
||||
```console
|
||||
$ dlopen-notes.py -f archive,bpf /usr/lib64/systemd/libsystemd-shared-257.so
|
||||
# grouped by feature
|
||||
{
|
||||
"bpf": {
|
||||
"description": "Support firewalling and sandboxing with BPF",
|
||||
"sonames": {
|
||||
"libbpf.so.1": "suggested",
|
||||
"libbpf.so.0": "suggested"
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
"description": "Support for decompressing archive files",
|
||||
"sonames": {
|
||||
"libarchive.so.13": "suggested"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
The format that is used when building `deb` packages:
|
||||
```console
|
||||
$ dlopen-notes -s /usr/lib64/systemd/libsystemd-shared-257.so
|
||||
libarchive.so.13 suggested
|
||||
libbpf.so.0 suggested
|
||||
libbpf.so.1 suggested
|
||||
...
|
||||
```
|
||||
|
||||
The format that can be useful when building `rpm` packages:
|
||||
```console
|
||||
$ dlopen-notes --rpm-requires archive --rpm-recommends bpf /usr/lib64/systemd/libsystemd-shared-257.so
|
||||
Requires: libarchive.so.13()(64bit)
|
||||
Recommends: libbpf.so.1()(64bit)
|
||||
```
|
||||
|
||||
@ -1 +1,236 @@
|
||||
[This content has moved to the UAPI group website](https://uapi-group.org/specifications/specs/package_metadata_for_executable_files/)
|
||||
---
|
||||
title: Package Metadata for Executable Files
|
||||
category: Interfaces
|
||||
layout: default
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
---
|
||||
|
||||
# Package Metadata for Executable Files
|
||||
|
||||
*Intended audience: hackers working on userspace subsystems that
|
||||
create or manipulate ELF or PE/COFF binaries
|
||||
or parse core files.*
|
||||
|
||||
## Motivation
|
||||
|
||||
ELF binaries get stamped with a unique, build-time generated hex string identifier called `build-id`,
|
||||
[which gets embedded as an ELF note called `.note.gnu.build-id`](https://fedoraproject.org/wiki/Releases/FeatureBuildId).
|
||||
In most cases, this allows a stripped binary to be associated with its debugging information.
|
||||
It is used, for example, to dynamically fetch DWARF symbols from a debuginfo server, or
|
||||
to query the local package manager and find out the package metadata or, again, the DWARF
|
||||
symbols or program sources.
|
||||
|
||||
However, this usage of the `build-id` requires either local metadata, usually set up by
|
||||
the package manager, or access to a remote server over the network. Both of those might
|
||||
be unavailable or forbidden.
|
||||
|
||||
Thus it becomes desirable to add additional metadata to a binary at build time, so that
|
||||
`systemd-coredump` and other services analyzing core files are able to extract said
|
||||
metadata simply from the core file itself, without external dependencies.
|
||||
|
||||
This metadata is stored as a section in the executable file,
|
||||
so that it will be loaded into memory along with the text and data of the binary,
|
||||
and will be preserved in a core dump.
|
||||
This metadata can also be easily read from the file on disk,
|
||||
so it can be used to identify provenience of files,
|
||||
independently of any package management system,
|
||||
even if the file is renamed or copied.
|
||||
|
||||
## Implementation
|
||||
|
||||
This document will attempt to define a common metadata format specification, so that
|
||||
multiple implementers might use it when building packages, or core file analyzers, and
|
||||
so on.
|
||||
|
||||
Implementers working on parsing the metadata should not assume a specific list of names,
|
||||
but parse anything that is included in the JSON object.
|
||||
|
||||
Implementers working on build tools should strive to use the same names, for consistency.
|
||||
The most common will be listed here.
|
||||
When corresponding to the content of os-release, the values should match, again for consistency.
|
||||
|
||||
If available, the metadata should also include the debuginfod server URL that can provide
|
||||
the original executable, debuginfo and sources, to further facilitate debugging.
|
||||
|
||||
### ELF header section
|
||||
|
||||
The metadata will be embedded in a single, 4 byte-aligned, allocated, NUL-padded,
|
||||
read-only ELF header section, in a name-value JSON object format.
|
||||
The JSON string is terminated with a NUL
|
||||
and subsequently padded with NULs to a multiple of four bytes.
|
||||
|
||||
The `note type` must be set during creation and checked when reading.
|
||||
|
||||
Section: `.note.package`<br/>
|
||||
`note type`: `0xcafe1a7e`<br/>
|
||||
Owner: `FDO` (FreeDesktop.org)<br/>
|
||||
Value: a single JSON object encoded as a NUL-terminated UTF-8 string
|
||||
|
||||
### PE/COFF section
|
||||
|
||||
The metadata will be embedded in a single, allocated, NUL-padded,
|
||||
read-only COFF data section,
|
||||
in a name-value JSON object format.
|
||||
The JSON string is terminated with a NUL
|
||||
and subsequently padded with NULs if appropriate.
|
||||
The `IMAGE_SCN_CNT_INITIALIZED_DATA` section flag shall be set.
|
||||
The alignment and padding shall be chosen as appropriate for the use of the PE/COFF file.
|
||||
|
||||
Section: `.pkgnote`<br/>
|
||||
Value: a single JSON object encoded as a NUL-terminated UTF-8 string
|
||||
|
||||
### JSON payload
|
||||
|
||||
```json
|
||||
{
|
||||
"type":"rpm", # this provides a namespace for the package+package-version fields
|
||||
"os":"fedora",
|
||||
"osVersion":"33",
|
||||
"name":"coreutils",
|
||||
"version":"4711.0815.fc13",
|
||||
"architecture":"arm32",
|
||||
"osCpe": "cpe:2.3:o:fedoraproject:fedora:33", # A CPE name for the operating system, `CPE_NAME` from os-release is a good default
|
||||
"appCpe": "cpe:2.3:a:gnu:coreutils:5.0", # A CPE name for the upstream application, use NVD CPE search
|
||||
"debugInfoUrl": "https://debuginfod.fedoraproject.org/"
|
||||
}
|
||||
```
|
||||
|
||||
The format is a single JSON object,
|
||||
encoded as a NUL-terminated `UTF-8` string.
|
||||
Each name in the object shall be unique as per recommendations of
|
||||
[RFC8259](https://datatracker.ietf.org/doc/html/rfc8259#section-4).
|
||||
Strings shall not contain any control characters or use `\uXXX` escaping.
|
||||
|
||||
When it comes to JSON numbers, this specification assumes that JSON parsers
|
||||
processing this information are capable of reproducing the full signed 53bit
|
||||
integer range (i.e. -2⁵³+1…+2⁵³-1) as well as the full 64-bit IEEE floating
|
||||
point number range losslessly (with the exception of NaN/-inf/+inf, since JSON
|
||||
cannot encode that), as per recommendations of
|
||||
[RFC8259](https://datatracker.ietf.org/doc/html/rfc8259#page-8). Fields in
|
||||
these JSON objects are thus permitted to encode numeric values from these
|
||||
ranges as JSON numbers, and should not use numeric values not covered by these
|
||||
types and ranges.
|
||||
|
||||
If available, the metadata should also include the debuginfod server URL that can provide
|
||||
the original executable, debuginfo and sources, to further facilitate debugging.
|
||||
|
||||
Reference implementations of [packaging tools for .deb and .rpm](https://github.com/systemd/package-notes)
|
||||
are available, and provide macros/helpers to include the note in binaries built
|
||||
by the package build system.
|
||||
They make use of the new `--package-metadata=` flag that is available in the
|
||||
`bfd`, `gold`, `mold`, and `lld` linkers
|
||||
(versions 2.39, 2.39, 1.3.0, and 15.0 respectively).
|
||||
This linker flag takes the JSON payload as parameter.
|
||||
|
||||
## Well-known keys
|
||||
|
||||
The metadata format is intentionally left open, so that vendors can add their own information.
|
||||
A set of well-known keys is defined here, and hopefully shared among all vendors.
|
||||
|
||||
| Key name | Key description | Example value |
|
||||
|--------------|--------------------------------------------------------------------------|---------------------------------------|
|
||||
| type | The packaging type | rpm |
|
||||
| os | The OS name, typically corresponding to ID in os-release | fedora |
|
||||
| osVersion | The OS version, typically corresponding to VERSION_ID in os-release | 33 |
|
||||
| name | The source package name | coreutils |
|
||||
| version | The source package version | 4711.0815.fc13 |
|
||||
| architecture | The binary package architecture | arm32 |
|
||||
| osCpe | A CPE name for the OS, typically corresponding to CPE_NAME in os-release | cpe:2.3:o:fedoraproject:fedora:33 |
|
||||
| appCpe | A CPE name for the upstream Application, as found in [NVD CPE search] | cpe:2.3:a:gnu:coreutils:5.0 |
|
||||
| debugInfoUrl | The debuginfod server url, if available | https://debuginfod.fedoraproject.org/ |
|
||||
|
||||
[NVD CPE search]: https://nvd.nist.gov/products/cpe/search
|
||||
|
||||
### Displaying package notes
|
||||
|
||||
The raw ELF section can be extracted using `objdump`:
|
||||
```console
|
||||
$ objdump -j .note.package -s /usr/bin/ls
|
||||
|
||||
/usr/bin/ls: file format elf64-x86-64
|
||||
|
||||
Contents of section .note.package:
|
||||
03cc 04000000 7c000000 7e1afeca 46444f00 ....|...~...FDO.
|
||||
03dc 7b227479 7065223a 2272706d 222c226e {"type":"rpm","n
|
||||
03ec 616d6522 3a22636f 72657574 696c7322 ame":"coreutils"
|
||||
03fc 2c227665 7273696f 6e223a22 392e342d ,"version":"9.4-
|
||||
040c 372e6663 3430222c 22617263 68697465 7.fc40","archite
|
||||
041c 63747572 65223a22 7838365f 3634222c cture":"x86_64",
|
||||
042c 226f7343 7065223a 22637065 3a2f6f3a "osCpe":"cpe:/o:
|
||||
043c 6665646f 72617072 6f6a6563 743a6665 fedoraproject:fe
|
||||
044c 646f7261 3a343022 7d000000 dora:40"}...
|
||||
```
|
||||
|
||||
It is more convenient to use a higher level tool:
|
||||
```console
|
||||
$ readelf --notes /usr/bin/ls
|
||||
...
|
||||
Displaying notes found in: .note.gnu.build-id
|
||||
Owner Data size Description
|
||||
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
|
||||
Build ID: 40e5a1570a9d97fc48f5c61cfb7690fec0f872b2
|
||||
|
||||
Displaying notes found in: .note.ABI-tag
|
||||
Owner Data size Description
|
||||
GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)
|
||||
OS: Linux, ABI: 3.2.0
|
||||
|
||||
Displaying notes found in: .note.package
|
||||
Owner Data size Description
|
||||
FDO 0x0000007c FDO_PACKAGING_METADATA
|
||||
Packaging Metadata: {"type":"rpm","name":"coreutils","version":"9.4-7.fc40","architecture":"x86_64","osCpe":"cpe:/o:fedoraproject:fedora:40"}
|
||||
...
|
||||
|
||||
$ systemd-analyze inspect-elf /usr/bin/ls
|
||||
path: /usr/bin/ls
|
||||
elfType: executable
|
||||
elfArchitecture: AMD x86-64
|
||||
|
||||
type: rpm
|
||||
name: coreutils
|
||||
version: 9.4-7.fc40
|
||||
architecture: x86_64
|
||||
osCpe: cpe:/o:fedoraproject:fedora:40
|
||||
buildId: 40e5a1570a9d97fc48f5c61cfb7690fec0f872b2
|
||||
```
|
||||
|
||||
If the binary crashes, `systemd-coredump` will display the combined information
|
||||
from the crashing binary and any shared libraries it links to:
|
||||
|
||||
```console
|
||||
$ coredumpctl info
|
||||
PID: 3987823 (ls)
|
||||
Signal: 11 (SEGV)
|
||||
Command Line: ls --color=tty -lR /
|
||||
Executable: /usr/bin/ls
|
||||
...
|
||||
Storage: /var/lib/systemd/coredump/core.ls.1000.88dea1b9831c420dbb398f9d2ad9b41e.3987823.1726230641000000.zst (present)
|
||||
Size on Disk: 194.4K
|
||||
Package: coreutils/9.4-7.fc40
|
||||
build-id: 40e5a1570a9d97fc48f5c61cfb7690fec0f872b2
|
||||
Message: Process 3987823 (ls) of user 1000 dumped core.
|
||||
|
||||
Module /usr/bin/ls from rpm coreutils-9.4-7.fc40.x86_64
|
||||
Module libz.so.1 from rpm zlib-ng-2.1.7-1.fc40.x86_64
|
||||
Module libcrypto.so.3 from rpm openssl-3.2.2-3.fc40.x86_64
|
||||
Module libmount.so.1 from rpm util-linux-2.40.1-1.fc40.x86_64
|
||||
Module libcrypt.so.2 from rpm libxcrypt-4.4.36-5.fc40.x86_64
|
||||
Module libblkid.so.1 from rpm util-linux-2.40.1-1.fc40.x86_64
|
||||
Module libnss_sss.so.2 from rpm sssd-2.9.5-1.fc40.x86_64
|
||||
Module libpcre2-8.so.0 from rpm pcre2-10.44-1.fc40.x86_64
|
||||
Module libcap.so.2 from rpm libcap-2.69-8.fc40.x86_64
|
||||
Module libselinux.so.1 from rpm libselinux-3.6-4.fc40.x86_64
|
||||
Stack trace of thread 3987823:
|
||||
#0 0x00007f19331c3f7e lgetxattr (libc.so.6 + 0x116f7e)
|
||||
#1 0x00007f19332be4c0 lgetfilecon_raw (libselinux.so.1 + 0x134c0)
|
||||
#2 0x00007f19332c3bd9 lgetfilecon (libselinux.so.1 + 0x18bd9)
|
||||
#3 0x000056038273ad55 gobble_file.constprop.0 (/usr/bin/ls + 0x17d55)
|
||||
#4 0x0000560382733c55 print_dir (/usr/bin/ls + 0x10c55)
|
||||
#5 0x0000560382727c35 main (/usr/bin/ls + 0x4c35)
|
||||
#6 0x00007f19330d7088 __libc_start_call_main (libc.so.6 + 0x2a088)
|
||||
#7 0x00007f19330d714b __libc_start_main@@GLIBC_2.34 (libc.so.6 + 0x2a14b)
|
||||
#8 0x0000560382728f15 _start (/usr/bin/ls + 0x5f15)
|
||||
ELF object binary architecture: AMD x86-64
|
||||
```
|
||||
|
||||
(This is just a simulation. `ls` is not prone to crashing with a segmentation violation.)
|
||||
|
||||
@ -120,7 +120,6 @@
|
||||
<literal>development</literal>,
|
||||
<literal>integration</literal>,
|
||||
<literal>staging</literal>,
|
||||
<literal>testing</literal>,
|
||||
<literal>production</literal>.
|
||||
</para>
|
||||
|
||||
|
||||
@ -747,9 +747,6 @@ DuplicateAddressDetection=none</programlisting></para>
|
||||
This is a short-hand for a [Route] section only containing a <varname>Gateway=</varname> key.
|
||||
This option may be specified more than once.</para>
|
||||
|
||||
<para>If an empty string is specified, then the all previous assignments in both [Network] and
|
||||
[Route] sections are cleared.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v211"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
@ -998,21 +998,6 @@ int fd_vet_accmode(int fd, int mode) {
|
||||
return -EPROTOTYPE;
|
||||
}
|
||||
|
||||
int fd_is_writable(int fd) {
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
r = fd_vet_accmode(fd, O_WRONLY);
|
||||
if (r >= 0)
|
||||
return true;
|
||||
|
||||
if (IN_SET(r, -EPROTOTYPE, -EBADFD, -EISDIR))
|
||||
return false;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int fd_verify_safe_flags_full(int fd, int extra_flags) {
|
||||
int flags, unexpected_flags;
|
||||
|
||||
|
||||
@ -152,7 +152,6 @@ int fd_reopen_condition(int fd, int flags, int mask, int *ret_new_fd);
|
||||
|
||||
int fd_is_opath(int fd);
|
||||
int fd_vet_accmode(int fd, int mode);
|
||||
int fd_is_writable(int fd);
|
||||
|
||||
int fd_verify_safe_flags_full(int fd, int extra_flags);
|
||||
static inline int fd_verify_safe_flags(int fd) {
|
||||
|
||||
@ -507,6 +507,9 @@ static int setup_output(
|
||||
i = fixup_input(context, socket_fd, params->flags & EXEC_APPLY_TTY_STDIN);
|
||||
o = fixup_output(context->std_output, socket_fd);
|
||||
|
||||
// FIXME: we probably should spend some time here to verify that if we inherit an fd from stdin
|
||||
// (possibly indirect via inheritance from stdout) it is actually opened for write!
|
||||
|
||||
if (fileno == STDERR_FILENO) {
|
||||
ExecOutput e;
|
||||
e = fixup_output(context->std_error, socket_fd);
|
||||
@ -523,17 +526,8 @@ static int setup_output(
|
||||
return fileno;
|
||||
|
||||
/* Duplicate from stdout if possible */
|
||||
if (can_inherit_stderr_from_stdout(context, o, e)) {
|
||||
r = fd_is_writable(STDOUT_FILENO);
|
||||
if (r <= 0) {
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to check if inherited stdout is writable for stderr, falling back to /dev/null.");
|
||||
else
|
||||
log_warning("Inherited stdout is not writable for stderr, falling back to /dev/null.");
|
||||
return open_null_as(O_WRONLY, fileno);
|
||||
}
|
||||
if (can_inherit_stderr_from_stdout(context, o, e))
|
||||
return RET_NERRNO(dup2(STDOUT_FILENO, fileno));
|
||||
}
|
||||
|
||||
o = e;
|
||||
|
||||
@ -543,19 +537,8 @@ static int setup_output(
|
||||
return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno);
|
||||
|
||||
/* If the input is connected to anything that's not a /dev/null or a data fd, inherit that... */
|
||||
if (!IN_SET(i, EXEC_INPUT_NULL, EXEC_INPUT_DATA)) {
|
||||
r = fd_is_writable(STDIN_FILENO);
|
||||
if (r <= 0) {
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to check if inherited stdin is writable for %s, falling back to /dev/null.",
|
||||
fileno == STDOUT_FILENO ? "stdout" : "stderr");
|
||||
else
|
||||
log_warning("Inherited stdin is not writable for %s, falling back to /dev/null.",
|
||||
fileno == STDOUT_FILENO ? "stdout" : "stderr");
|
||||
return open_null_as(O_WRONLY, fileno);
|
||||
}
|
||||
if (!IN_SET(i, EXEC_INPUT_NULL, EXEC_INPUT_DATA))
|
||||
return RET_NERRNO(dup2(STDIN_FILENO, fileno));
|
||||
}
|
||||
|
||||
/* If we are not started from PID 1 we just inherit STDOUT from our parent process. */
|
||||
if (getppid() != 1)
|
||||
@ -571,19 +554,8 @@ static int setup_output(
|
||||
return open_null_as(O_WRONLY, fileno);
|
||||
|
||||
case EXEC_OUTPUT_TTY:
|
||||
if (exec_input_is_terminal(i)) {
|
||||
r = fd_is_writable(STDIN_FILENO);
|
||||
if (r <= 0) {
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to check if inherited stdin is writable for TTY's %s, falling back to opening terminal.",
|
||||
fileno == STDOUT_FILENO ? "stdout" : "stderr");
|
||||
else
|
||||
log_warning("Inherited stdin is not writable for TTY's %s, falling back to opening terminal.",
|
||||
fileno == STDOUT_FILENO ? "stdout" : "stderr");
|
||||
return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno);
|
||||
}
|
||||
if (exec_input_is_terminal(i))
|
||||
return RET_NERRNO(dup2(STDIN_FILENO, fileno));
|
||||
}
|
||||
|
||||
return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno);
|
||||
|
||||
|
||||
@ -1027,16 +1027,9 @@ static void socket_close_fds(Socket *s) {
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(Socket*, socket_close_fds, NULL);
|
||||
|
||||
#define log_socket_option_errno(s, e, option) \
|
||||
({ \
|
||||
int _e_ = (e); \
|
||||
log_unit_full_errno( \
|
||||
UNIT(s), \
|
||||
ERRNO_IS_NOT_SUPPORTED(_e_) ? LOG_DEBUG : LOG_WARNING, \
|
||||
_e_, \
|
||||
"Failed to set %s socket option, ignoring: %m", \
|
||||
option); \
|
||||
})
|
||||
#define SOCKET_OPTION_WARNING_FORMAT_STR "Failed to set %s socket option, ignoring: %m"
|
||||
#define log_socket_option_warning_errno(s, error, option) \
|
||||
log_unit_warning_errno(UNIT(s), (error), SOCKET_OPTION_WARNING_FORMAT_STR, STRINGIFY(option))
|
||||
|
||||
static void socket_apply_socket_options(Socket *s, SocketPort *p, int fd) {
|
||||
int r;
|
||||
@ -1048,79 +1041,82 @@ static void socket_apply_socket_options(Socket *s, SocketPort *p, int fd) {
|
||||
if (s->keep_alive) {
|
||||
r = setsockopt_int(fd, SOL_SOCKET, SO_KEEPALIVE, true);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "SO_KEEPALIVE");
|
||||
log_socket_option_warning_errno(s, r, SO_KEEPALIVE);
|
||||
}
|
||||
|
||||
if (timestamp_is_set(s->keep_alive_time)) {
|
||||
r = setsockopt_int(fd, SOL_TCP, TCP_KEEPIDLE, s->keep_alive_time / USEC_PER_SEC);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "TCP_KEEPIDLE");
|
||||
log_socket_option_warning_errno(s, r, TCP_KEEPIDLE);
|
||||
}
|
||||
|
||||
if (s->keep_alive_interval > 0) {
|
||||
r = setsockopt_int(fd, SOL_TCP, TCP_KEEPINTVL, s->keep_alive_interval / USEC_PER_SEC);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "TCP_KEEPINTVL");
|
||||
log_socket_option_warning_errno(s, r, TCP_KEEPINTVL);
|
||||
}
|
||||
|
||||
if (s->keep_alive_cnt > 0) {
|
||||
r = setsockopt_int(fd, SOL_TCP, TCP_KEEPCNT, s->keep_alive_cnt);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "TCP_KEEPCNT");
|
||||
log_socket_option_warning_errno(s, r, TCP_KEEPCNT);
|
||||
}
|
||||
|
||||
if (s->defer_accept > 0) {
|
||||
r = setsockopt_int(fd, SOL_TCP, TCP_DEFER_ACCEPT, s->defer_accept / USEC_PER_SEC);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "TCP_DEFER_ACCEPT");
|
||||
log_socket_option_warning_errno(s, r, TCP_DEFER_ACCEPT);
|
||||
}
|
||||
|
||||
if (s->no_delay) {
|
||||
if (s->socket_protocol == IPPROTO_SCTP) {
|
||||
r = setsockopt_int(fd, SOL_SCTP, SCTP_NODELAY, true);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "SCTP_NODELAY");
|
||||
log_socket_option_warning_errno(s, r, SCTP_NODELAY);
|
||||
} else {
|
||||
r = setsockopt_int(fd, SOL_TCP, TCP_NODELAY, true);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "TCP_NODELAY");
|
||||
log_socket_option_warning_errno(s, r, TCP_NODELAY);
|
||||
}
|
||||
}
|
||||
|
||||
if (s->broadcast) {
|
||||
r = setsockopt_int(fd, SOL_SOCKET, SO_BROADCAST, true);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "SO_BROADCAST");
|
||||
log_socket_option_warning_errno(s, r, SO_BROADCAST);
|
||||
}
|
||||
|
||||
if (s->pass_cred) {
|
||||
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "SO_PASSCRED");
|
||||
log_socket_option_warning_errno(s, r, SO_PASSCRED);
|
||||
}
|
||||
|
||||
if (s->pass_pidfd) {
|
||||
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSPIDFD, true);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "SO_PASSPIDFD");
|
||||
log_unit_full_errno(UNIT(s), ERRNO_IS_NEG_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r,
|
||||
SOCKET_OPTION_WARNING_FORMAT_STR, "SO_PASSPIDFD");
|
||||
}
|
||||
|
||||
if (s->pass_sec) {
|
||||
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSSEC, true);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "SO_PASSSEC");
|
||||
log_unit_full_errno(UNIT(s), ERRNO_IS_NEG_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r,
|
||||
SOCKET_OPTION_WARNING_FORMAT_STR, "SO_PASSSEC");
|
||||
}
|
||||
|
||||
if (s->pass_pktinfo) {
|
||||
r = socket_set_recvpktinfo(fd, socket_address_family(&p->address), true);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "packet info");
|
||||
log_unit_warning_errno(UNIT(s), r, SOCKET_OPTION_WARNING_FORMAT_STR, "packet info");
|
||||
}
|
||||
|
||||
if (!s->pass_rights) {
|
||||
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSRIGHTS, false);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "SO_PASSRIGHTS");
|
||||
log_unit_full_errno(UNIT(s), ERRNO_IS_NEG_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r,
|
||||
SOCKET_OPTION_WARNING_FORMAT_STR, "SO_PASSRIGHTS");
|
||||
}
|
||||
|
||||
if (s->timestamping != SOCKET_TIMESTAMPING_OFF) {
|
||||
@ -1128,59 +1124,61 @@ static void socket_apply_socket_options(Socket *s, SocketPort *p, int fd) {
|
||||
s->timestamping == SOCKET_TIMESTAMPING_NS ? SO_TIMESTAMPNS : SO_TIMESTAMP,
|
||||
true);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "timestamping");
|
||||
log_unit_warning_errno(UNIT(s), r, SOCKET_OPTION_WARNING_FORMAT_STR, "timestamping");
|
||||
}
|
||||
|
||||
if (s->priority >= 0) {
|
||||
r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, s->priority);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "SO_PRIORITY");
|
||||
log_socket_option_warning_errno(s, r, SO_PRIORITY);
|
||||
}
|
||||
|
||||
if (s->receive_buffer > 0) {
|
||||
r = fd_set_rcvbuf(fd, s->receive_buffer, false);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "SO_RCVBUF/SO_RCVBUFFORCE");
|
||||
log_unit_full_errno(UNIT(s), ERRNO_IS_NEG_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r,
|
||||
SOCKET_OPTION_WARNING_FORMAT_STR, "SO_RCVBUF/SO_RCVBUFFORCE");
|
||||
}
|
||||
|
||||
if (s->send_buffer > 0) {
|
||||
r = fd_set_sndbuf(fd, s->send_buffer, false);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "SO_SNDBUF/SO_SNDBUFFORCE");
|
||||
log_unit_full_errno(UNIT(s), ERRNO_IS_NEG_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r,
|
||||
SOCKET_OPTION_WARNING_FORMAT_STR, "SO_SNDBUF/SO_SNDBUFFORCE");
|
||||
}
|
||||
|
||||
if (s->mark >= 0) {
|
||||
r = setsockopt_int(fd, SOL_SOCKET, SO_MARK, s->mark);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "SO_MARK");
|
||||
log_socket_option_warning_errno(s, r, SO_MARK);
|
||||
}
|
||||
|
||||
if (s->ip_tos >= 0) {
|
||||
r = setsockopt_int(fd, IPPROTO_IP, IP_TOS, s->ip_tos);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "IP_TOS");
|
||||
log_socket_option_warning_errno(s, r, IP_TOS);
|
||||
}
|
||||
|
||||
if (s->ip_ttl >= 0) {
|
||||
r = socket_set_ttl(fd, socket_address_family(&p->address), s->ip_ttl);
|
||||
if (r < 0)
|
||||
log_socket_option_errno(s, r, "IP_TTL/IPV6_UNICAST_HOPS");
|
||||
log_unit_warning_errno(UNIT(s), r, SOCKET_OPTION_WARNING_FORMAT_STR, "IP_TTL/IPV6_UNICAST_HOPS");
|
||||
}
|
||||
|
||||
if (s->tcp_congestion)
|
||||
if (setsockopt(fd, SOL_TCP, TCP_CONGESTION, s->tcp_congestion, strlen(s->tcp_congestion)+1) < 0)
|
||||
log_socket_option_errno(s, errno, "TCP_CONGESTION");
|
||||
log_socket_option_warning_errno(s, errno, TCP_CONGESTION);
|
||||
|
||||
if (s->smack_ip_in) {
|
||||
r = mac_smack_apply_fd(fd, SMACK_ATTR_IPIN, s->smack_ip_in);
|
||||
if (r < 0)
|
||||
log_unit_warning_errno(UNIT(s), r, "Failed to apply SMACK label for IP input, ignoring: %m");
|
||||
log_unit_error_errno(UNIT(s), r, "Failed to apply SMACK label for IP input, ignoring: %m");
|
||||
}
|
||||
|
||||
if (s->smack_ip_out) {
|
||||
r = mac_smack_apply_fd(fd, SMACK_ATTR_IPOUT, s->smack_ip_out);
|
||||
if (r < 0)
|
||||
log_unit_warning_errno(UNIT(s), r, "Failed to apply SMACK label for IP output, ignoring: %m");
|
||||
log_unit_error_errno(UNIT(s), r, "Failed to apply SMACK label for IP output, ignoring: %m");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2008,12 +2008,6 @@ int config_parse_route_section(
|
||||
if (streq(section, "Network")) {
|
||||
assert(streq_ptr(lvalue, "Gateway"));
|
||||
|
||||
/* Clear all previously defined routes when Gateway= (empty) is set in [Network] section */
|
||||
if (isempty(rvalue)) {
|
||||
network->routes_by_section = hashmap_free(network->routes_by_section);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* we are not in an Route section, so use line number instead */
|
||||
r = route_new_static(network, filename, line, &route);
|
||||
} else
|
||||
|
||||
@ -425,17 +425,10 @@ void test_prepare(int argc, char *argv[], int log_level) {
|
||||
test_setup_logging(log_level);
|
||||
}
|
||||
|
||||
/* Returns:
|
||||
* ASSERT_SIGNAL_FORK_CHILD = We are in the child process
|
||||
* ASSERT_SIGNAL_FORK_PARENT = We are in the parent process (signal/status stored in *ret_signal)
|
||||
* <0 = Error (negative errno)
|
||||
*/
|
||||
int assert_signal_internal(int *ret_signal) {
|
||||
int assert_signal_internal(void) {
|
||||
siginfo_t siginfo = {};
|
||||
int r;
|
||||
|
||||
assert(ret_signal);
|
||||
|
||||
r = fork();
|
||||
if (r < 0)
|
||||
return -errno;
|
||||
@ -446,23 +439,14 @@ int assert_signal_internal(int *ret_signal) {
|
||||
|
||||
/* But still set an rlimit just in case */
|
||||
(void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(0));
|
||||
return ASSERT_SIGNAL_FORK_CHILD;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = wait_for_terminate(r, &siginfo);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* si_status means different things depending on si_code:
|
||||
* - CLD_EXITED: si_status is the exit code
|
||||
* - CLD_KILLED/CLD_DUMPED: si_status is the signal number that killed the process
|
||||
* We need to return the signal number only if the child was killed by a signal. */
|
||||
if (IN_SET(siginfo.si_code, CLD_KILLED, CLD_DUMPED))
|
||||
*ret_signal = siginfo.si_status;
|
||||
else
|
||||
*ret_signal = 0;
|
||||
|
||||
return ASSERT_SIGNAL_FORK_PARENT;
|
||||
return siginfo.si_status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -592,33 +592,23 @@ _noreturn_ void log_test_failed_internal(const char *file, int line, const char
|
||||
})
|
||||
#endif
|
||||
|
||||
enum {
|
||||
ASSERT_SIGNAL_FORK_CHILD = 0, /* We are in the child process */
|
||||
ASSERT_SIGNAL_FORK_PARENT = 1, /* We are in the parent process */
|
||||
};
|
||||
|
||||
int assert_signal_internal(int *ret_status);
|
||||
int assert_signal_internal(void);
|
||||
|
||||
#ifdef __COVERITY__
|
||||
# define ASSERT_SIGNAL(expr, signal) __coverity_check__(((expr), false))
|
||||
#else
|
||||
# define ASSERT_SIGNAL(expr, signal) __ASSERT_SIGNAL(UNIQ, expr, signal)
|
||||
# define __ASSERT_SIGNAL(uniq, expr, sgnl) \
|
||||
# define ASSERT_SIGNAL(expr, signal) \
|
||||
({ \
|
||||
ASSERT_TRUE(SIGNAL_VALID(sgnl)); \
|
||||
int UNIQ_T(_status, uniq); \
|
||||
int UNIQ_T(_path, uniq) = assert_signal_internal(&UNIQ_T(_status, uniq)); \
|
||||
ASSERT_OK_ERRNO(UNIQ_T(_path, uniq)); \
|
||||
if (UNIQ_T(_path, uniq) == ASSERT_SIGNAL_FORK_CHILD) { \
|
||||
(void) signal(sgnl, SIG_DFL); \
|
||||
ASSERT_TRUE(SIGNAL_VALID(signal)); \
|
||||
int _r = assert_signal_internal(); \
|
||||
ASSERT_OK_ERRNO(_r); \
|
||||
if (_r == 0) { \
|
||||
expr; \
|
||||
_exit(EXIT_SUCCESS); \
|
||||
} \
|
||||
ASSERT_EQ(UNIQ_T(_path, uniq), ASSERT_SIGNAL_FORK_PARENT); \
|
||||
if (UNIQ_T(_status, uniq) != sgnl) \
|
||||
if (_r != signal) \
|
||||
log_test_failed("\"%s\" died with signal %s, but %s was expected", \
|
||||
#expr, signal_to_string(UNIQ_T(_status, uniq)), \
|
||||
signal_to_string(sgnl)); \
|
||||
#expr, signal_to_string(_r), signal_to_string(signal)); \
|
||||
})
|
||||
#endif
|
||||
|
||||
|
||||
@ -903,27 +903,4 @@ TEST(fd_vet_accmode) {
|
||||
ASSERT_ERROR(fd_vet_accmode(fd_opath, O_RDWR), EBADFD);
|
||||
}
|
||||
|
||||
TEST(fd_is_writable) {
|
||||
_cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fd-writable.XXXXXX";
|
||||
_cleanup_close_ int fd_ro = -EBADF, fd_wo = -EBADF, fd_rw = -EBADF, fd_path = -EBADF;
|
||||
|
||||
ASSERT_OK(fd_rw = mkostemp_safe(name));
|
||||
ASSERT_OK_POSITIVE(fd_is_writable(fd_rw));
|
||||
|
||||
ASSERT_OK(fd_ro = open(name, O_RDONLY | O_CLOEXEC));
|
||||
ASSERT_OK_ZERO(fd_is_writable(fd_ro));
|
||||
|
||||
ASSERT_OK(fd_wo = open(name, O_WRONLY | O_CLOEXEC));
|
||||
ASSERT_OK_POSITIVE(fd_is_writable(fd_wo));
|
||||
|
||||
ASSERT_OK(fd_path = open(name, O_PATH | O_CLOEXEC));
|
||||
ASSERT_OK_ZERO(fd_is_writable(fd_path));
|
||||
|
||||
ASSERT_SIGNAL(fd_is_writable(-1), SIGABRT);
|
||||
|
||||
safe_close(fd_ro);
|
||||
ASSERT_ERROR(fd_is_writable(fd_ro), EBADF);
|
||||
TAKE_FD(fd_ro);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||
|
||||
@ -137,79 +137,4 @@ TEST(ASSERT_OK_OR) {
|
||||
ASSERT_SIGNAL(ASSERT_OK_OR(-1, -2), SIGABRT);
|
||||
}
|
||||
|
||||
/* Regression test for issue where assert_signal_internal() wasn't checking si_code before returning
|
||||
* si_status.
|
||||
*
|
||||
* In the bug case, siginfo.si_status has different meanings depending on siginfo.si_code:
|
||||
*
|
||||
* - If si_code == CLD_EXITED: si_status is the exit code (0-255)
|
||||
* - If si_code == CLD_KILLED/CLD_DUMPED: si_status is the signal number
|
||||
*
|
||||
* In the bug case where st_code is not checked, exit codes would be confused with signal numbers. For
|
||||
* example, if a child exits with code 6, it would incorrectly look like SIGABRT.
|
||||
*
|
||||
* This test verifies that exit codes are NOT confused with signal numbers, even when the exit code
|
||||
* numerically matches a signal number.
|
||||
*/
|
||||
TEST(ASSERT_SIGNAL_exit_code_vs_signal) {
|
||||
/* These exit codes numerically match common signal numbers, but ASSERT_SIGNAL should correctly
|
||||
* identify them as exit codes (si_code==CLD_EXITED), not signals. The inner ASSERT_SIGNAL expects a
|
||||
* signal but gets an exit code, so it should fail (aborting with SIGABRT), which the outer
|
||||
* ASSERT_SIGNAL then catches. */
|
||||
|
||||
ASSERT_SIGNAL(ASSERT_SIGNAL(_exit(6), SIGABRT), SIGABRT); /* 6 = SIGABRT */
|
||||
ASSERT_SIGNAL(ASSERT_SIGNAL(_exit(9), SIGKILL), SIGABRT); /* 9 = SIGKILL */
|
||||
ASSERT_SIGNAL(ASSERT_SIGNAL(_exit(11), SIGSEGV), SIGABRT); /* 11 = SIGSEGV */
|
||||
ASSERT_SIGNAL(ASSERT_SIGNAL(_exit(15), SIGTERM), SIGABRT); /* 15 = SIGTERM */
|
||||
|
||||
/* _exit(0) should not be confused with any signal */
|
||||
ASSERT_SIGNAL(ASSERT_SIGNAL(_exit(0), SIGABRT), SIGABRT);
|
||||
}
|
||||
|
||||
/* Regression test for issue where returning 0 from assert_signal_internal() was ambiguous.
|
||||
*
|
||||
* In the bug case, when assert_signal_internal() returned 0, it could mean two different things:
|
||||
*
|
||||
* 1. We're in the child process (fork() just returned 0)
|
||||
* 2. We're in the parent and the child exited normally (no signal)
|
||||
*
|
||||
* The ASSERT_SIGNAL macro couldn't distinguish between these cases. When case #2 occurred, the macro would
|
||||
* re-enter the "if (_r == 0)" block, re-run the expression in the parent, and call _exit(EXIT_SUCCESS),
|
||||
* causing tests to incorrectly pass even when no signal occurred.
|
||||
*
|
||||
* The fix separates the question of which process we are in from which signal occurred:
|
||||
*
|
||||
* - assert_signal_internal() now returns ASSERT_SIGNAL_FORK_CHILD (0) or ASSERT_SIGNAL_FORK_PARENT (1) to
|
||||
* indicate execution path
|
||||
* - The actual signal/status is passed via an output parameter (*ret_status)
|
||||
*
|
||||
* This allows the macro to unambiguously distinguish between being the child (path ==
|
||||
* ASSERT_SIGNAL_FORK_CHILD) and being the parent when the child has exited normally (path ==
|
||||
* ASSERT_SIGNAL_FORK_PARENT && status == 0).
|
||||
*
|
||||
* This test verifies that when a child exits normally (with exit code 0), ASSERT_SIGNAL correctly detects
|
||||
* that NO signal was raised, rather than being confused and thinking it's still in the child process.
|
||||
*/
|
||||
TEST(ASSERT_SIGNAL_exit_vs_child_process) {
|
||||
/* When a child calls _exit(0), it exits normally with code 0 (no signal). The parent's
|
||||
* assert_signal_internal() returns ASSERT_SIGNAL_FORK_PARENT, and sets ret_status to 0, meaning
|
||||
* there was no signal. This should NOT be confused with being the child process. The inner
|
||||
* ASSERT_SIGNAL expects SIGABRT but sees no signal, so it should fail, which the outerj
|
||||
* ASSERT_SIGNAL catches. */
|
||||
ASSERT_SIGNAL(ASSERT_SIGNAL(_exit(EXIT_SUCCESS), SIGABRT), SIGABRT);
|
||||
}
|
||||
|
||||
TEST(ASSERT_SIGNAL_basic) {
|
||||
/* Correct behavior: expression raises expected signal */
|
||||
ASSERT_SIGNAL(abort(), SIGABRT);
|
||||
ASSERT_SIGNAL(raise(SIGTERM), SIGTERM);
|
||||
ASSERT_SIGNAL(raise(SIGSEGV), SIGSEGV);
|
||||
ASSERT_SIGNAL(raise(SIGILL), SIGILL);
|
||||
|
||||
/* Wrong signal: inner ASSERT_SIGNAL expects SIGABRT but gets SIGTERM, so it fails (aborts), which
|
||||
* outer ASSERT_SIGNAL catches. */
|
||||
ASSERT_SIGNAL(ASSERT_SIGNAL(raise(SIGTERM), SIGABRT), SIGABRT);
|
||||
ASSERT_SIGNAL(ASSERT_SIGNAL(raise(SIGKILL), SIGTERM), SIGABRT);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
[Match]
|
||||
Name=dummy98
|
||||
|
||||
[Network]
|
||||
Address=10.0.0.1/24
|
||||
Gateway=10.0.0.2
|
||||
|
||||
[Route]
|
||||
Destination=192.168.1.0/24
|
||||
Gateway=10.0.0.254
|
||||
|
||||
[Route]
|
||||
Destination=192.168.2.0/24
|
||||
Gateway=10.0.0.253
|
||||
|
||||
[Network]
|
||||
Gateway=
|
||||
@ -4633,21 +4633,6 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
|
||||
self.assertNotIn('149.10.124.59', output)
|
||||
self.assertIn('default via 149.10.124.60 proto static', output)
|
||||
|
||||
def test_gateway_clear_routes(self):
|
||||
copy_network_unit('25-gateway-clear-routes.network', '12-dummy.netdev')
|
||||
start_networkd()
|
||||
self.wait_online('dummy98:routable')
|
||||
|
||||
print('### ip -4 route show dev dummy98')
|
||||
output = check_output('ip -4 route show dev dummy98')
|
||||
print(output)
|
||||
# All routes should be cleared - no default gateway, no [Route] section routes
|
||||
self.assertNotIn('default via 10.0.0.2', output)
|
||||
self.assertNotIn('192.168.1.0/24', output)
|
||||
self.assertNotIn('192.168.2.0/24', output)
|
||||
# Only the directly connected network should remain
|
||||
self.assertIn('10.0.0.0/24 proto kernel scope link src 10.0.0.1', output)
|
||||
|
||||
def test_ip_route_ipv6_src_route(self):
|
||||
# a dummy device does not make the addresses go through tentative state, so we
|
||||
# reuse a bond from an earlier test, which does make the addresses go through
|
||||
|
||||
@ -207,7 +207,7 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target", "pid": {"pid": 1}}')
|
||||
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"cgroup": "/init.scope"}'
|
||||
invocation_id="$(systemctl show -P InvocationID systemd-journald.service)"
|
||||
invocation_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | .runtime.InvocationID' | grep -v null | tail -n 1)
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocationID\": \"$invocation_id\"}"
|
||||
|
||||
# test io.systemd.Manager in user manager
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user