diff options
Diffstat (limited to 'test')
165 files changed, 7617 insertions, 0 deletions
diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000000..fd7483da0a --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,5 @@ +.testdir +test.log +/dev +/run +/sys diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000000..b651e94e99 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,955 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +include $(dir $(lastword $(MAKEFILE_LIST)))/../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +manual_tests += \ + test-ns \ + test-cgroup \ + test-install \ + test-btrfs \ + test-acd \ + test-ipv4ll-manual \ + test-ask-password-api + +unsafe_tests = \ + test-hostname \ + test-ipcrm + +ifneq ($(HAVE_LIBIPTC),) +manual_tests += \ + test-firewall-util +endif # HAVE_LIBIPTC + +ifneq ($(HAVE_KMOD),) +manual_tests += \ + test-netlink-manual +endif # HAVE_KMOD + +tests += \ + test-daemon \ + test-log \ + test-loopback \ + test-engine \ + test-watchdog \ + test-cgroup-mask \ + test-job-type \ + test-env-util \ + test-strbuf \ + test-strv \ + test-path \ + test-path-util \ + test-strxcpyx \ + test-siphash24 \ + test-unit-name \ + test-unit-file \ + test-utf8 \ + test-ellipsize \ + test-util \ + test-cpu-set-util \ + test-hexdecoct \ + test-escape \ + test-alloc-util \ + test-proc-cmdline \ + test-io-util \ + test-glob-util \ + test-xattr-util \ + test-fs-util \ + test-web-util \ + test-stat-util \ + test-fd-util \ + test-string-util \ + test-extract-word \ + test-parse-util \ + test-user-util \ + test-hostname-util \ + test-process-util \ + test-terminal-util \ + test-path-lookup \ + test-barrier \ + test-tmpfiles \ + test-namespace \ + test-date \ + test-sleep \ + test-replace-var \ + test-sched-prio \ + test-calendarspec \ + test-strip-tab-ansi \ + test-cgroup-util \ + test-fstab-util \ + test-prioq \ + test-fileio \ + test-time \ + test-clock \ + test-hashmap \ + test-set \ + test-bitmap \ + test-list \ + test-unaligned \ + test-tables \ + test-device-nodes \ + test-xml \ + test-architecture \ + test-socket-util \ + test-fdset \ + test-conf-files \ + test-conf-parser \ + test-capability \ + test-async \ + test-ratelimit \ + test-condition \ + test-uid-range \ + test-locale-util \ + test-execute \ + test-copy \ + test-cap-list \ + test-sigbus \ + test-verbs \ + test-af-list \ + test-arphrd-list \ + test-dns-domain \ + test-install-root \ + test-rlimit-util \ + test-signal-util \ + test-selinux \ + test-sizeof + +ifneq ($(HAVE_ACL),) +tests += \ + test-acl-util +endif # HAVE_ACL + +ifneq ($(HAVE_SECCOMP),) +tests += \ + test-seccomp +endif + +EXTRA_DIST += \ + test/a.service \ + test/basic.target \ + test/b.service \ + test/c.service \ + test/daughter.service \ + test/d.service \ + test/end.service \ + test/e.service \ + test/f.service \ + test/grandchild.service \ + test/g.service \ + test/hello-after-sleep.target \ + test/hello.service \ + test/h.service \ + test/parent-deep.slice \ + test/parent.slice \ + test/sched_idle_bad.service \ + test/sched_idle_ok.service \ + test/sched_rr_bad.service \ + test/sched_rr_change.service \ + test/sched_rr_ok.service \ + test/shutdown.target \ + test/sleep.service \ + test/sockets.target \ + test/son.service \ + test/sysinit.target \ + test/testsuite.target \ + test/timers.target \ + test/unstoppable.service \ + test/test-path/paths.target \ + test/test-path/basic.target \ + test/test-path/sysinit.target \ + test/test-path/path-changed.service \ + test/test-path/path-directorynotempty.service \ + test/test-path/path-existsglob.service \ + test/test-path/path-exists.service \ + test/test-path/path-makedirectory.service \ + test/test-path/path-modified.service \ + test/test-path/path-mycustomunit.service \ + test/test-path/path-service.service \ + test/test-path/path-changed.path \ + test/test-path/path-directorynotempty.path \ + test/test-path/path-existsglob.path \ + test/test-path/path-exists.path \ + test/test-path/path-makedirectory.path \ + test/test-path/path-modified.path \ + test/test-path/path-unit.path \ + test/test-execute/exec-environment-empty.service \ + test/test-execute/exec-environment-multiple.service \ + test/test-execute/exec-environment.service \ + test/test-execute/exec-passenvironment-absent.service \ + test/test-execute/exec-passenvironment-empty.service \ + test/test-execute/exec-passenvironment-repeated.service \ + test/test-execute/exec-passenvironment.service \ + test/test-execute/exec-group.service \ + test/test-execute/exec-group-nfsnobody.service \ + test/test-execute/exec-supplementarygroups.service \ + test/test-execute/exec-supplementarygroups-single-group.service \ + test/test-execute/exec-supplementarygroups-single-group-user.service \ + test/test-execute/exec-supplementarygroups-multiple-groups-default-group-user.service \ + test/test-execute/exec-supplementarygroups-multiple-groups-withgid.service \ + test/test-execute/exec-supplementarygroups-multiple-groups-withuid.service \ + test/test-execute/exec-dynamicuser-fixeduser.service \ + test/test-execute/exec-dynamicuser-fixeduser-one-supplementarygroup.service \ + test/test-execute/exec-dynamicuser-supplementarygroups.service \ + test/test-execute/exec-ignoresigpipe-no.service \ + test/test-execute/exec-ignoresigpipe-yes.service \ + test/test-execute/exec-personality-x86-64.service \ + test/test-execute/exec-personality-x86.service \ + test/test-execute/exec-personality-s390.service \ + test/test-execute/exec-personality-ppc64.service \ + test/test-execute/exec-personality-ppc64le.service \ + test/test-execute/exec-personality-aarch64.service \ + test/test-execute/exec-privatedevices-no.service \ + test/test-execute/exec-privatedevices-yes.service \ + test/test-execute/exec-privatedevices-no-capability-mknod.service \ + test/test-execute/exec-privatedevices-yes-capability-mknod.service \ + test/test-execute/exec-protectkernelmodules-no-capabilities.service \ + test/test-execute/exec-protectkernelmodules-yes-capabilities.service \ + test/test-execute/exec-protectkernelmodules-yes-mount-propagation.service \ + test/test-execute/exec-privatetmp-no.service \ + test/test-execute/exec-privatetmp-yes.service \ + test/test-execute/exec-readonlypaths.service \ + test/test-execute/exec-readonlypaths-mount-propagation.service \ + test/test-execute/exec-readwritepaths-mount-propagation.service \ + test/test-execute/exec-inaccessiblepaths-mount-propagation.service \ + test/test-execute/exec-spec-interpolation.service \ + test/test-execute/exec-systemcallerrornumber.service \ + test/test-execute/exec-systemcallfilter-failing2.service \ + test/test-execute/exec-systemcallfilter-failing.service \ + test/test-execute/exec-systemcallfilter-not-failing2.service \ + test/test-execute/exec-systemcallfilter-not-failing.service \ + test/test-execute/exec-systemcallfilter-system-user.service \ + test/test-execute/exec-systemcallfilter-system-user-nfsnobody.service \ + test/test-execute/exec-user.service \ + test/test-execute/exec-user-nfsnobody.service \ + test/test-execute/exec-workingdirectory.service \ + test/test-execute/exec-umask-0177.service \ + test/test-execute/exec-umask-default.service \ + test/test-execute/exec-privatenetwork-yes.service \ + test/test-execute/exec-environmentfile.service \ + test/test-execute/exec-oomscoreadjust-positive.service \ + test/test-execute/exec-oomscoreadjust-negative.service \ + test/test-execute/exec-ioschedulingclass-best-effort.service \ + test/test-execute/exec-ioschedulingclass-idle.service \ + test/test-execute/exec-ioschedulingclass-none.service \ + test/test-execute/exec-ioschedulingclass-realtime.service \ + test/test-execute/exec-capabilityboundingset-invert.service \ + test/test-execute/exec-capabilityboundingset-merge.service \ + test/test-execute/exec-capabilityboundingset-reset.service \ + test/test-execute/exec-capabilityboundingset-simple.service \ + test/test-execute/exec-capabilityambientset.service \ + test/test-execute/exec-capabilityambientset-nfsnobody.service \ + test/test-execute/exec-capabilityambientset-merge.service \ + test/test-execute/exec-capabilityambientset-merge-nfsnobody.service \ + test/test-execute/exec-runtimedirectory.service \ + test/test-execute/exec-runtimedirectory-mode.service \ + test/test-execute/exec-runtimedirectory-owner.service \ + test/test-execute/exec-runtimedirectory-owner-nfsnobody.service \ + test/bus-policy/hello.conf \ + test/bus-policy/methods.conf \ + test/bus-policy/ownerships.conf \ + test/bus-policy/signals.conf \ + test/bus-policy/check-own-rules.conf \ + test/bus-policy/many-rules.conf \ + test/bus-policy/test.conf + +EXTRA_DIST += \ + src/test/test-helper.h + +test_device_nodes_SOURCES = \ + src/test/test-device-nodes.c + +test_device_nodes_LDADD = \ + libsystemd-shared.la + +test_engine_SOURCES = \ + src/test/test-engine.c + +test_engine_CFLAGS = \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +test_engine_LDADD = \ + libcore.la + +test_job_type_SOURCES = \ + src/test/test-job-type.c + +test_job_type_CFLAGS = \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +test_job_type_LDADD = \ + libcore.la + +test_ns_SOURCES = \ + src/test/test-ns.c + +test_ns_CFLAGS = \ + $(SECCOMP_CFLAGS) + +test_ns_LDADD = \ + libcore.la + +test_loopback_SOURCES = \ + src/test/test-loopback.c + +test_loopback_LDADD = \ + libcore.la + +test_hostname_SOURCES = \ + src/test/test-hostname.c + +test_hostname_LDADD = \ + libcore.la + +test_dns_domain_SOURCES = \ + src/test/test-dns-domain.c + +test_dns_domain_LDADD = \ + libsystemd-network.la \ + libsystemd-shared.la + +ifneq ($(ENABLE_EFI),) +tests += \ + test-boot-timestamps + +test_boot_timestamps_SOURCES = \ + src/test/test-boot-timestamps.c + +test_boot_timestamps_LDADD = \ + libsystemd-shared.la +endif # ENABLE_EFI + +test_unit_name_SOURCES = \ + src/test/test-unit-name.c + +test_unit_name_CFLAGS = \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +test_unit_name_LDADD = \ + libcore.la + +test_unit_file_SOURCES = \ + src/test/test-unit-file.c + +test_unit_file_CFLAGS = \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +test_unit_file_LDADD = \ + libcore.la + +test_utf8_SOURCES = \ + src/test/test-utf8.c + +test_utf8_LDADD = \ + libsystemd-shared.la + +test_capability_SOURCES = \ + src/test/test-capability.c + +test_capability_CFLAGS = \ + $(CAP_CFLAGS) + +test_capability_LDADD = \ + libsystemd-shared.la \ + $(CAP_LIBS) + +test_async_SOURCES = \ + src/test/test-async.c + +test_async_LDADD = \ + libsystemd-shared.la + +test_locale_util_SOURCES = \ + src/test/test-locale-util.c + +test_locale_util_LDADD = \ + libsystemd-shared.la + +test_copy_SOURCES = \ + src/test/test-copy.c + +# Link statically to ensure file is large +test_copy_LDADD = \ + libsystemd-shared.la + +test_sigbus_SOURCES = \ + src/test/test-sigbus.c + +test_sigbus_LDADD = \ + libsystemd-shared.la + +test_condition_SOURCES = \ + src/test/test-condition.c + +test_condition_LDADD = \ + libsystemd-shared.la + +test_fdset_SOURCES = \ + src/test/test-fdset.c + +test_fdset_LDADD = \ + libsystemd-shared.la + +test_fstab_util_SOURCES = \ + src/test/test-fstab-util.c + +test_fstab_util_LDADD = \ + libsystemd-shared.la + +test_ratelimit_SOURCES = \ + src/test/test-ratelimit.c + +test_ratelimit_LDADD = \ + libsystemd-shared.la + +test_util_SOURCES = \ + src/test/test-util.c + +test_util_LDADD = \ + libsystemd-shared.la + +test_hexdecoct_SOURCES = \ + src/test/test-hexdecoct.c + +test_hexdecoct_LDADD = \ + libsystemd-shared.la + +test_alloc_util_SOURCES = \ + src/test/test-alloc-util.c + +test_alloc_util_LDADD = \ + libsystemd-shared.la + +test_xattr_util_SOURCES = \ + src/test/test-xattr-util.c + +test_xattr_util_LDADD = \ + libsystemd-shared.la + +test_io_util_SOURCES = \ + src/test/test-io-util.c + +test_io_util_LDADD = \ + libsystemd-shared.la + +test_glob_util_SOURCES = \ + src/test/test-glob-util.c + +test_glob_util_LDADD = \ + libsystemd-shared.la + +test_fs_util_SOURCES = \ + src/test/test-fs-util.c + +test_fs_util_LDADD = \ + libsystemd-shared.la + +test_proc_cmdline_SOURCES = \ + src/test/test-proc-cmdline.c + +test_proc_cmdline_LDADD = \ + libsystemd-shared.la + +test_fd_util_SOURCES = \ + src/test/test-fd-util.c + +test_fd_util_LDADD = \ + libsystemd-shared.la + +test_web_util_SOURCES = \ + src/test/test-web-util.c + +test_web_util_LDADD = \ + libsystemd-shared.la + +test_cpu_set_util_SOURCES = \ + src/test/test-cpu-set-util.c + +test_cpu_set_util_LDADD = \ + libsystemd-shared.la + +test_stat_util_SOURCES = \ + src/test/test-stat-util.c + +test_stat_util_LDADD = \ + libsystemd-shared.la + +test_escape_SOURCES = \ + src/test/test-escape.c + +test_escape_LDADD = \ + libsystemd-shared.la + +test_string_util_SOURCES = \ + src/test/test-string-util.c + +test_string_util_LDADD = \ + libsystemd-shared.la + +test_extract_word_SOURCES = \ + src/test/test-extract-word.c + +test_extract_word_LDADD = \ + libsystemd-shared.la + +test_parse_util_SOURCES = \ + src/test/test-parse-util.c + +test_parse_util_LDADD = \ + libsystemd-shared.la + +test_user_util_SOURCES = \ + src/test/test-user-util.c + +test_user_util_LDADD = \ + libsystemd-shared.la + +test_hostname_util_SOURCES = \ + src/test/test-hostname-util.c + +test_hostname_util_LDADD = \ + libsystemd-shared.la + +test_process_util_SOURCES = \ + src/test/test-process-util.c + +test_process_util_LDADD = \ + libsystemd-shared.la + +test_terminal_util_SOURCES = \ + src/test/test-terminal-util.c + +test_terminal_util_LDADD = \ + libsystemd-shared.la + +test_path_lookup_SOURCES = \ + src/test/test-path-lookup.c + +test_path_lookup_LDADD = \ + libsystemd-shared.la + +test_uid_range_SOURCES = \ + src/test/test-uid-range.c + +test_uid_range_LDADD = \ + libsystemd-shared.la + +test_cap_list_SOURCES = \ + src/test/test-cap-list.c + +test_cap_list_CFLAGS = \ + $(CAP_CFLAGS) + +test_cap_list_LDADD = \ + libsystemd-shared.la \ + $(CAP_LIBS) + +test_socket_util_SOURCES = \ + src/test/test-socket-util.c + +test_socket_util_LDADD = \ + libsystemd-shared.la + +test_barrier_SOURCES = \ + src/test/test-barrier.c + +test_barrier_LDADD = \ + libsystemd-shared.la + +test_tmpfiles_SOURCES = \ + src/test/test-tmpfiles.c + +test_tmpfiles_LDADD = \ + libsystemd-shared.la + +test_namespace_SOURCES = \ + src/test/test-namespace.c + +test_verbs_SOURCES = \ + src/test/test-verbs.c + +test_verbs_LDADD = \ + libsystemd-shared.la + +test_install_root_SOURCES = \ + src/test/test-install-root.c + +test_install_root_LDADD = \ + libsystemd-shared.la + +test_acl_util_SOURCES = \ + src/test/test-acl-util.c + +test_acl_util_LDADD = \ + libsystemd-shared.la + +test_seccomp_SOURCES = \ + src/test/test-seccomp.c + +test_seccomp_LDADD = \ + libsystemd-shared.la + +test_namespace_LDADD = \ + libcore.la + +test_rlimit_util_SOURCES = \ + src/test/test-rlimit-util.c + +test_rlimit_util_LDADD = \ + libsystemd-shared.la + +test_ask_password_api_SOURCES = \ + src/test/test-ask-password-api.c + +test_ask_password_api_LDADD = \ + libsystemd-shared.la + +test_signal_util_SOURCES = \ + src/test/test-signal-util.c + +test_signal_util_LDADD = \ + libsystemd-shared.la + +test_selinux_SOURCES = \ + src/test/test-selinux.c + +test_selinux_LDADD = \ + libsystemd-shared.la + +test_sizeof_SOURCES = \ + src/test/test-sizeof.c + +test_sizeof_LDADD = \ + libsystemd-shared.la + +BUILT_SOURCES += \ + src/test/test-hashmap-ordered.c + +$(outdir)/test-hashmap-ordered.c: src/test/test-hashmap-plain.c + $(AM_V_GEN)$(AWK) 'BEGIN { print "/* GENERATED FILE */\n#define ORDERED" } \ + { if (!match($$0, "^#include")) \ + gsub(/hashmap/, "ordered_hashmap"); \ + gsub(/HASHMAP/, "ORDERED_HASHMAP"); \ + gsub(/Hashmap/, "OrderedHashmap"); \ + print }' <$< >$@ + +nodist_test_hashmap_SOURCES = \ + src/test/test-hashmap-ordered.c + +test_hashmap_SOURCES = \ + src/test/test-hashmap.c \ + src/test/test-hashmap-plain.c + +test_hashmap_LDADD = \ + libsystemd-shared.la + +test_set_SOURCES = \ + src/test/test-set.c + +test_set_LDADD = \ + libsystemd-shared.la + +test_bitmap_SOURCES = \ + src/test/test-bitmap.c + +test_bitmap_LDADD = \ + libsystemd-shared.la + +test_xml_SOURCES = \ + src/test/test-xml.c + +test_xml_LDADD = \ + libsystemd-shared.la + +test_list_SOURCES = \ + src/test/test-list.c + +test_list_LDADD = \ + libsystemd-shared.la + +test_unaligned_LDADD = \ + libsystemd-shared.la + +test_unaligned_SOURCES = \ + src/test/test-unaligned.c + +test_tables_SOURCES = \ + src/test/test-tables.c \ + src/shared/test-tables.h \ + src/journal/journald-server.c \ + src/journal/journald-server.h + +test_tables_CPPFLAGS = \ + +test_tables_CFLAGS = \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +test_tables_LDADD = \ + libjournal-core.la \ + libcore.la \ + libudev-core.la + +test_prioq_SOURCES = \ + src/test/test-prioq.c + +test_prioq_LDADD = \ + libsystemd-shared.la + +test_fileio_SOURCES = \ + src/test/test-fileio.c + +test_fileio_LDADD = \ + libsystemd-shared.la + +test_time_SOURCES = \ + src/test/test-time.c + +test_time_LDADD = \ + libsystemd-shared.la + +test_clock_SOURCES = \ + src/test/test-clock.c + +test_clock_LDADD = \ + libsystemd-shared.la + +test_architecture_SOURCES = \ + src/test/test-architecture.c + +test_architecture_LDADD = \ + libsystemd-shared.la + +test_log_SOURCES = \ + src/test/test-log.c + +test_log_LDADD = \ + libsystemd-shared.la + +test_ipcrm_SOURCES = \ + src/test/test-ipcrm.c + +test_ipcrm_LDADD = \ + libsystemd-shared.la + +test_btrfs_SOURCES = \ + src/test/test-btrfs.c + +test_btrfs_LDADD = \ + libsystemd-shared.la + +ifneq ($(HAVE_LIBIPTC),) +test_firewall_util_SOURCES = \ + src/test/test-firewall-util.c + +test_firewall_util_CFLAGS = \ + $(LIBIPTC_CFLAGS) + +test_firewall_util_LDADD = \ + libsystemd-firewall.la \ + libsystemd-shared.la \ + $(LIBIPTC_LIBS) +endif # HAVE_LIBIPTC + +test_netlink_manual_SOURCES = \ + src/test/test-netlink-manual.c + +test_netlink_manual_CFLAGS = \ + $(KMOD_CFLAGS) + +test_netlink_manual_LDADD = \ + libsystemd-shared.la \ + $(KMOD_LIBS) + +test_ellipsize_SOURCES = \ + src/test/test-ellipsize.c + +test_ellipsize_LDADD = \ + libsystemd-shared.la + +test_date_SOURCES = \ + src/test/test-date.c + +test_date_LDADD = \ + libsystemd-shared.la + +test_sleep_SOURCES = \ + src/test/test-sleep.c + +test_sleep_LDADD = \ + libcore.la + +test_replace_var_SOURCES = \ + src/test/test-replace-var.c + +test_replace_var_LDADD = \ + libsystemd-shared.la + +test_calendarspec_SOURCES = \ + src/test/test-calendarspec.c + +test_calendarspec_LDADD = \ + libsystemd-shared.la + +test_strip_tab_ansi_SOURCES = \ + src/test/test-strip-tab-ansi.c + +test_strip_tab_ansi_LDADD = \ + libsystemd-shared.la + +test_daemon_SOURCES = \ + src/test/test-daemon.c + +test_daemon_LDADD = \ + libsystemd-shared.la + +test_cgroup_SOURCES = \ + src/test/test-cgroup.c + +test_cgroup_LDADD = \ + libsystemd-shared.la + +test_cgroup_mask_SOURCES = \ + src/test/test-cgroup-mask.c + +test_cgroup_mask_CPPFLAGS = \ + $(MOUNT_CFLAGS) + +test_cgroup_mask_CFLAGS = \ + $(SECCOMP_CFLAGS) + +test_cgroup_mask_LDADD = \ + libcore.la + +test_cgroup_util_SOURCES = \ + src/test/test-cgroup-util.c + +test_cgroup_util_LDADD = \ + libsystemd-shared.la + +test_env_util_SOURCES = \ + src/test/test-env-util.c + +test_env_util_LDADD = \ + libsystemd-shared.la + +test_strbuf_SOURCES = \ + src/test/test-strbuf.c + +test_strbuf_LDADD = \ + libsystemd-shared.la + +test_strv_SOURCES = \ + src/test/test-strv.c + +test_strv_LDADD = \ + libsystemd-shared.la + +test_path_util_SOURCES = \ + src/test/test-path-util.c + +test_path_util_LDADD = \ + libsystemd-shared.la + +test_path_SOURCES = \ + src/test/test-path.c + +test_path_CFLAGS = \ + $(MOUNT_CFLAGS) + +test_path_LDADD = \ + libcore.la + +test_execute_SOURCES = \ + src/test/test-execute.c + +test_execute_CFLAGS = \ + $(MOUNT_CFLAGS) + +test_execute_LDADD = \ + libcore.la + +test_siphash24_SOURCES = \ + src/test/test-siphash24.c + +test_siphash24_LDADD = \ + libsystemd-shared.la + +test_strxcpyx_SOURCES = \ + src/test/test-strxcpyx.c + +test_strxcpyx_LDADD = \ + libsystemd-shared.la + +test_install_SOURCES = \ + src/test/test-install.c + +test_install_LDADD = \ + libsystemd-shared.la + +test_watchdog_SOURCES = \ + src/test/test-watchdog.c + +test_watchdog_LDADD = \ + libsystemd-shared.la + +test_sched_prio_SOURCES = \ + src/test/test-sched-prio.c + +test_sched_prio_CPPFLAGS = \ + $(MOUNT_CFLAGS) + +test_sched_prio_CFLAGS = \ + $(SECCOMP_CFLAGS) + +test_sched_prio_LDADD = \ + libcore.la + +test_conf_files_SOURCES = \ + src/test/test-conf-files.c + +test_conf_files_LDADD = \ + libsystemd-shared.la + +test_conf_parser_SOURCES = \ + src/test/test-conf-parser.c + +test_conf_parser_LDADD = \ + libsystemd-shared.la + +test_af_list_SOURCES = \ + src/test/test-af-list.c + +test_af_list_LDADD = \ + libsystemd-shared.la + +test_arphrd_list_SOURCES = \ + src/test/test-arphrd-list.c + +test_arphrd_list_LDADD = \ + libsystemd-shared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/test/README.testsuite b/test/README.testsuite new file mode 100644 index 0000000000..fa7e73ce3a --- /dev/null +++ b/test/README.testsuite @@ -0,0 +1,46 @@ +The extended testsuite only works with uid=0. It contains of several +subdirectories named "test/TEST-??-*", which are run one by one. + +To run the extended testsuite do the following: + +$ make all +$ cd test +$ sudo make clean check +... +make[1]: Entering directory `/mnt/data/harald/git/systemd/test/TEST-01-BASIC' +Making all in . +Making all in po +TEST: Basic systemd setup [OK] +make[1]: Leaving directory `/mnt/data/harald/git/systemd/test/TEST-01-BASIC' +... + +If one of the tests fails, then $subdir/test.log contains the log file of +the test. + +To debug a special testcase of the testsuite do: + +$ make all +$ cd test/TEST-01-BASIC +$ sudo make clean setup run + +QEMU +==== + +If you want to log in the testsuite virtual machine, you can specify +additional kernel command line parameter with $KERNEL_APPEND. + +$ sudo make KERNEL_APPEND="systemd.unit=multi-user.target" clean setup run + +you can even skip the "clean" and "setup" if you want to run the machine again. + +$ sudo make KERNEL_APPEND="systemd.unit=multi-user.target" run + +You can specify a different kernel and initramfs with $KERNEL_BIN and $INITRD. +(Fedora's or Debian's default kernel path and initramfs are used by default) + +$ sudo make KERNEL_BIN=/boot/vmlinuz-foo INITRD=/boot/initramfs-bar clean check + +A script will try to find your QEMU binary. If you want to specify a different +one you can use $QEMU_BIN. + +$ sudo make QEMU_BIN=/path/to/qemu/qemu-kvm clean check diff --git a/test/TEST-01-BASIC/test.sh b/test/TEST-01-BASIC/test.sh new file mode 100755 index 0000000000..041195dcd8 --- /dev/null +++ b/test/TEST-01-BASIC/test.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="Basic systemd setup" + +. $TEST_BASE_DIR/test-functions + +check_result_qemu() { + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + +test_run() { + if run_qemu; then + check_result_qemu || return 1 + else + dwarn "can't run QEMU, skipping" + fi + if run_nspawn; then + check_result_nspawn || return 1 + else + dwarn "can't run systemd-nspawn, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<EOF +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/bin/sh -x -c 'systemctl --state=failed --no-legend --no-pager > /failed ; echo OK > /testok' +Type=oneshot +EOF + + setup_testsuite + ) || return 1 + setup_nspawn_root + + # mask some services that we do not want to run in these tests + ln -s /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-networkd.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-networkd.socket + ln -s /dev/null $initdir/etc/systemd/system/systemd-resolved.service + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/TEST-02-CRYPTSETUP/test.sh b/test/TEST-02-CRYPTSETUP/test.sh new file mode 100755 index 0000000000..aea0fc53f6 --- /dev/null +++ b/test/TEST-02-CRYPTSETUP/test.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="cryptsetup systemd setup" + +. $TEST_BASE_DIR/test-functions + +check_result_qemu() { + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR + cryptsetup luksOpen ${LOOPDEV}p2 varcrypt <$TESTDIR/keyfile + mount /dev/mapper/varcrypt $TESTDIR/root/var + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root/var + umount $TESTDIR/root + cryptsetup luksClose /dev/mapper/varcrypt + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + + +test_run() { + if run_qemu; then + check_result_qemu || return 1 + else + dwarn "can't run QEMU, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + echo -n test >$TESTDIR/keyfile + cryptsetup -q luksFormat ${LOOPDEV}p2 $TESTDIR/keyfile + cryptsetup luksOpen ${LOOPDEV}p2 varcrypt <$TESTDIR/keyfile + mkfs.ext3 -L var /dev/mapper/varcrypt + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + mkdir -p $TESTDIR/root/var + mount /dev/mapper/varcrypt $TESTDIR/root/var + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=/dev/mapper/varcrypt) + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<EOF +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/bin/sh -x -c 'systemctl --state=failed --no-legend --no-pager > /failed ; echo OK > /testok' +Type=oneshot +EOF + + setup_testsuite + + install_dmevent + generate_module_dependencies + cat >$initdir/etc/crypttab <<EOF +$DM_NAME UUID=$ID_FS_UUID /etc/varkey +EOF + echo -n test > $initdir/etc/varkey + cat $initdir/etc/crypttab | ddebug + + cat >>$initdir/etc/fstab <<EOF +/dev/mapper/varcrypt /var ext3 defaults 0 1 +EOF + ) || return 1 + + ddebug "umount $TESTDIR/root/var" + umount $TESTDIR/root/var + cryptsetup luksClose /dev/mapper/varcrypt + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root/var 2>/dev/null + [[ -b /dev/mapper/varcrypt ]] && cryptsetup luksClose /dev/mapper/varcrypt + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/TEST-03-JOBS/test-jobs.sh b/test/TEST-03-JOBS/test-jobs.sh new file mode 100755 index 0000000000..fa6cf4181a --- /dev/null +++ b/test/TEST-03-JOBS/test-jobs.sh @@ -0,0 +1,80 @@ +#!/bin/bash -x + +# Test merging of a --job-mode=ignore-dependencies job into a previously +# installed job. + +systemctl start --no-block hello-after-sleep.target + +systemctl list-jobs > /root/list-jobs.txt +while ! grep 'sleep\.service.*running' /root/list-jobs.txt; do + systemctl list-jobs > /root/list-jobs.txt +done + +grep 'hello\.service.*waiting' /root/list-jobs.txt || exit 1 + +# This is supposed to finish quickly, not wait for sleep to finish. +START_SEC=$(date -u '+%s') +systemctl start --job-mode=ignore-dependencies hello +END_SEC=$(date -u '+%s') +ELAPSED=$(($END_SEC-$START_SEC)) + +[ "$ELAPSED" -lt 3 ] || exit 1 + +# sleep should still be running, hello not. +systemctl list-jobs > /root/list-jobs.txt +grep 'sleep\.service.*running' /root/list-jobs.txt || exit 1 +grep 'hello\.service' /root/list-jobs.txt && exit 1 +systemctl stop sleep.service hello-after-sleep.target || exit 1 + +# Test for a crash when enqueuing a JOB_NOP when other job already exists +systemctl start --no-block hello-after-sleep.target || exit 1 +# hello.service should still be waiting, so these try-restarts will collapse +# into NOPs. +systemctl try-restart --job-mode=fail hello.service || exit 1 +systemctl try-restart hello.service || exit 1 +systemctl stop hello.service sleep.service hello-after-sleep.target || exit 1 + +# TODO: add more job queueing/merging tests here. + +# Test for irreversible jobs +systemctl start unstoppable.service || exit 1 + +# This is expected to fail with 'job cancelled' +systemctl stop unstoppable.service && exit 1 +# But this should succeed +systemctl stop --job-mode=replace-irreversibly unstoppable.service || exit 1 + +# We're going to shutdown soon. Let's see if it succeeds when +# there's an active service that tries to be unstoppable. +# Shutdown of the container/VM will hang if not. +systemctl start unstoppable.service || exit 1 + +# Test waiting for a started unit(s) to terminate again +cat <<EOF > /run/systemd/system/wait2.service +[Unit] +Description=Wait for 2 seconds +[Service] +ExecStart=/bin/sh -ec 'sleep 2' +EOF +cat <<EOF > /run/systemd/system/wait5fail.service +[Unit] +Description=Wait for 5 seconds and fail +[Service] +ExecStart=/bin/sh -ec 'sleep 5; false' +EOF + +# wait2 succeeds +START_SEC=$(date -u '+%s') +systemctl start --wait wait2.service || exit 1 +END_SEC=$(date -u '+%s') +ELAPSED=$(($END_SEC-$START_SEC)) +[[ "$ELAPSED" -ge 2 ]] && [[ "$ELAPSED" -le 3 ]] || exit 1 + +# wait5fail fails, so systemctl should fail +START_SEC=$(date -u '+%s') +! systemctl start --wait wait2.service wait5fail.service || exit 1 +END_SEC=$(date -u '+%s') +ELAPSED=$(($END_SEC-$START_SEC)) +[[ "$ELAPSED" -ge 5 ]] && [[ "$ELAPSED" -le 7 ]] || exit 1 + +touch /testok diff --git a/test/TEST-03-JOBS/test.sh b/test/TEST-03-JOBS/test.sh new file mode 100755 index 0000000000..ab0de0bfd1 --- /dev/null +++ b/test/TEST-03-JOBS/test.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="Job-related tests" + +. $TEST_BASE_DIR/test-functions + +check_result_qemu() { + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + +test_run() { + if run_qemu; then + check_result_qemu || return 1 + else + dwarn "can't run QEMU, skipping" + fi + if run_nspawn; then + check_result_nspawn || return 1 + else + dwarn "can't run systemd-nspawn, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<EOF +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/test-jobs.sh +Type=oneshot +EOF + + # copy the units used by this test + cp $TEST_BASE_DIR/{hello.service,sleep.service,hello-after-sleep.target,unstoppable.service} \ + $initdir/etc/systemd/system + cp test-jobs.sh $initdir/ + + setup_testsuite + ) || return 1 + setup_nspawn_root + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/TEST-04-JOURNAL/test-journal.sh b/test/TEST-04-JOURNAL/test-journal.sh new file mode 100755 index 0000000000..493ff00ce0 --- /dev/null +++ b/test/TEST-04-JOURNAL/test-journal.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +set -x +set -e +set -o pipefail + +# Test stdout stream + +# Skip empty lines +ID=$(journalctl --new-id128 | sed -n 2p) +>/expected +printf $'\n\n\n' | systemd-cat -t "$ID" --level-prefix false +journalctl --sync +journalctl -b -o cat -t "$ID" >/output +cmp /expected /output + +ID=$(journalctl --new-id128 | sed -n 2p) +>/expected +printf $'<5>\n<6>\n<7>\n' | systemd-cat -t "$ID" --level-prefix true +journalctl --sync +journalctl -b -o cat -t "$ID" >/output +cmp /expected /output + +# Remove trailing spaces +ID=$(journalctl --new-id128 | sed -n 2p) +printf "Trailing spaces\n">/expected +printf $'<5>Trailing spaces \t \n' | systemd-cat -t "$ID" --level-prefix true +journalctl --sync +journalctl -b -o cat -t "$ID" >/output +cmp /expected /output + +ID=$(journalctl --new-id128 | sed -n 2p) +printf "Trailing spaces\n">/expected +printf $'Trailing spaces \t \n' | systemd-cat -t "$ID" --level-prefix false +journalctl --sync +journalctl -b -o cat -t "$ID" >/output +cmp /expected /output + +# Don't remove leading spaces +ID=$(journalctl --new-id128 | sed -n 2p) +printf $' \t Leading spaces\n'>/expected +printf $'<5> \t Leading spaces\n' | systemd-cat -t "$ID" --level-prefix true +journalctl --sync +journalctl -b -o cat -t "$ID" >/output +cmp /expected /output + +ID=$(journalctl --new-id128 | sed -n 2p) +printf $' \t Leading spaces\n'>/expected +printf $' \t Leading spaces\n' | systemd-cat -t "$ID" --level-prefix false +journalctl --sync +journalctl -b -o cat -t "$ID" >/output +cmp /expected /output + +# Don't lose streams on restart +systemctl start forever-print-hola +sleep 3 +systemctl restart systemd-journald +sleep 3 +systemctl stop forever-print-hola +[[ ! -f "/i-lose-my-logs" ]] + +# https://github.com/systemd/systemd/issues/4408 +rm -f /i-lose-my-logs +systemctl start forever-print-hola +sleep 3 +systemctl kill --signal=SIGKILL systemd-journald +sleep 3 +[[ ! -f "/i-lose-my-logs" ]] + +touch /testok diff --git a/test/TEST-04-JOURNAL/test.sh b/test/TEST-04-JOURNAL/test.sh new file mode 100755 index 0000000000..3ccf113019 --- /dev/null +++ b/test/TEST-04-JOURNAL/test.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="Journal-related tests" + +. $TEST_BASE_DIR/test-functions + +check_result_qemu() { + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + +test_run() { + if run_qemu; then + check_result_qemu || return 1 + else + dwarn "can't run QEMU, skipping" + fi + if run_nspawn; then + check_result_nspawn || return 1 + else + dwarn "can't run systemd-nspawn, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<EOF +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/test-journal.sh +Type=oneshot +EOF + + cat >$initdir/etc/systemd/system/forever-print-hola.service <<EOF +[Unit] +Description=ForeverPrintHola service + +[Service] +Type=simple +ExecStart=/bin/sh -x -c 'while :; do printf "Hola\n" || touch /i-lose-my-logs; sleep 1; done' +EOF + + cp test-journal.sh $initdir/ + + setup_testsuite + ) || return 1 + setup_nspawn_root + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/TEST-05-RLIMITS/test-rlimits.sh b/test/TEST-05-RLIMITS/test-rlimits.sh new file mode 100755 index 0000000000..ba665c5968 --- /dev/null +++ b/test/TEST-05-RLIMITS/test-rlimits.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -x +set -e +set -o pipefail + +[[ "$(systemctl show -p DefaultLimitNOFILESoft)" = "DefaultLimitNOFILESoft=10000" ]] +[[ "$(systemctl show -p DefaultLimitNOFILE)" = "DefaultLimitNOFILE=16384" ]] + +[[ "$(systemctl show -p LimitNOFILESoft testsuite.service)" = "LimitNOFILESoft=10000" ]] +[[ "$(systemctl show -p LimitNOFILE testsuite.service)" = "LimitNOFILE=16384" ]] + +[[ "$(ulimit -n -S)" = "10000" ]] +[[ "$(ulimit -n -H)" = "16384" ]] + +touch /testok diff --git a/test/TEST-05-RLIMITS/test.sh b/test/TEST-05-RLIMITS/test.sh new file mode 100755 index 0000000000..a5f7e8de0b --- /dev/null +++ b/test/TEST-05-RLIMITS/test.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="Resource limits-related tests" + +. $TEST_BASE_DIR/test-functions + +check_result_qemu() { + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + +test_run() { + if run_qemu; then + check_result_qemu || return 1 + else + dwarn "can't run QEMU, skipping" + fi + if run_nspawn; then + check_result_nspawn || return 1 + else + dwarn "can't run systemd-nspawn, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + + cat >$initdir/etc/systemd/system.conf <<EOF +[Manager] +DefaultLimitNOFILE=10000:16384 +EOF + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<EOF +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/test-rlimits.sh +Type=oneshot +EOF + + cp test-rlimits.sh $initdir/ + + setup_testsuite + ) || return 1 + setup_nspawn_root + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/TEST-06-SELINUX/systemd_test.if b/test/TEST-06-SELINUX/systemd_test.if new file mode 100644 index 0000000000..25c91adce9 --- /dev/null +++ b/test/TEST-06-SELINUX/systemd_test.if @@ -0,0 +1,8 @@ +template(`systemd_test_base_template', ` + gen_require(` + attribute systemd_test_domain_type; + ') + + type $1_t, systemd_test_domain_type; + domain_type($1_t) +') diff --git a/test/TEST-06-SELINUX/systemd_test.te b/test/TEST-06-SELINUX/systemd_test.te new file mode 100644 index 0000000000..ff01c09b5e --- /dev/null +++ b/test/TEST-06-SELINUX/systemd_test.te @@ -0,0 +1,50 @@ +policy_module(systemd_test, 0.0.1) + +# declarations +attribute systemd_test_domain_type; + +systemd_test_base_template(systemd_test) +systemd_test_base_template(systemd_test_status) +systemd_test_base_template(systemd_test_start) +systemd_test_base_template(systemd_test_stop) +systemd_test_base_template(systemd_test_reload) + +# systemd_test_domain_type + +require { + role system_r; + role unconfined_r; + type bin_t; + type initrc_t; + type systemd_systemctl_exec_t; + type unconfined_service_t; +} + +role system_r types systemd_test_domain_type; +role unconfined_r types systemd_test_domain_type; + +allow systemd_test_domain_type bin_t: file entrypoint; +allow systemd_test_domain_type systemd_systemctl_exec_t: file entrypoint; +allow initrc_t systemd_test_domain_type: process transition; +allow unconfined_service_t systemd_test_domain_type: process transition; +corecmd_exec_bin(systemd_test_domain_type) +init_signal_script(systemd_test_domain_type) +init_sigchld_script(systemd_test_domain_type) +systemd_exec_systemctl(systemd_test_domain_type) +userdom_use_user_ttys(systemd_test_domain_type) +userdom_use_user_ptys(systemd_test_domain_type) + +optional_policy(` + dbus_system_bus_client(systemd_test_domain_type) + init_dbus_chat(systemd_test_domain_type) +') + +# systemd_test_*_t +require { + type systemd_unit_file_t; +} + +allow systemd_test_status_t systemd_unit_file_t: service { status }; +allow systemd_test_start_t systemd_unit_file_t: service { start }; +allow systemd_test_stop_t systemd_unit_file_t: service { stop }; +allow systemd_test_reload_t systemd_unit_file_t: service { reload }; diff --git a/test/TEST-06-SELINUX/test-selinux-checks.sh b/test/TEST-06-SELINUX/test-selinux-checks.sh new file mode 100755 index 0000000000..153fab3aac --- /dev/null +++ b/test/TEST-06-SELINUX/test-selinux-checks.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -x +set -e +set -o pipefail + +echo 1 >/sys/fs/selinux/enforce +runcon -t systemd_test_start_t systemctl start hola +runcon -t systemd_test_reload_t systemctl reload hola +runcon -t systemd_test_stop_t systemctl stop hola + +touch /testok diff --git a/test/TEST-06-SELINUX/test.sh b/test/TEST-06-SELINUX/test.sh new file mode 100755 index 0000000000..1ae4a7c0d9 --- /dev/null +++ b/test/TEST-06-SELINUX/test.sh @@ -0,0 +1,135 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="SELinux tests" + +# Requirements: +# Fedora 23 +# selinux-policy-targeted +# selinux-policy-devel + +. $TEST_BASE_DIR/test-functions +SETUP_SELINUX=yes +KERNEL_APPEND="$KERNEL_APPEND selinux=1 security=selinux" + +check_result_qemu() { + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + +test_run() { + if run_qemu; then + check_result_qemu || return 1 + else + dwarn "can't run QEMU, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + + # setup the testsuite service + cat <<EOF >$initdir/etc/systemd/system/testsuite.service +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/test-selinux-checks.sh +Type=oneshot +EOF + + cat <<EOF >$initdir/etc/systemd/system/hola.service +[Service] +Type=oneshot +ExecStart=/bin/echo Start Hola +ExecReload=/bin/echo Reload Hola +ExecStop=/bin/echo Stop Hola +RemainAfterExit=yes +EOF + + setup_testsuite + + cat <<EOF >$initdir/etc/systemd/system/load-systemd-test-module.service +[Unit] +Description=Load systemd-test module +DefaultDependencies=no +Requires=local-fs.target +Conflicts=shutdown.target +After=local-fs.target +Before=sysinit.target shutdown.target autorelabel.service +ConditionSecurity=selinux +ConditionPathExists=|/.load-systemd-test-module + +[Service] +ExecStart=/bin/sh -x -c 'echo 0 >/sys/fs/selinux/enforce && cd /systemd-test-module && make -f /usr/share/selinux/devel/Makefile load && rm /.load-systemd-test-module' +Type=oneshot +TimeoutSec=0 +RemainAfterExit=yes +EOF + + touch $initdir/.load-systemd-test-module + mkdir -p $initdir/etc/systemd/system/basic.target.wants + ln -fs load-systemd-test-module.service $initdir/etc/systemd/system/basic.target.wants/load-systemd-test-module.service + + local _modules_dir=/var/lib/selinux + rm -rf $initdir/$_modules_dir + if ! cp -ar $_modules_dir $initdir/$_modules_dir; then + dfatal "Failed to copy $_modules_dir" + exit 1 + fi + + local _policy_headers_dir=/usr/share/selinux/devel + rm -rf $initdir/$_policy_headers_dir + inst_dir /usr/share/selinux + if ! cp -ar $_policy_headers_dir $initdir/$_policy_headers_dir; then + dfatal "Failed to copy $_policy_headers_dir" + exit 1 + fi + + mkdir $initdir/systemd-test-module + cp systemd_test.te $initdir/systemd-test-module + cp systemd_test.if $initdir/systemd-test-module + cp test-selinux-checks.sh $initdir + dracut_install -o sesearch + dracut_install runcon + dracut_install checkmodule semodule semodule_package m4 make /usr/libexec/selinux/hll/pp load_policy sefcontext_compile + ) || return 1 + + # mask some services that we do not want to run in these tests + ln -s /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-networkd.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-networkd.socket + ln -s /dev/null $initdir/etc/systemd/system/systemd-resolved.service + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/TEST-07-ISSUE-1981/test-segfault.sh b/test/TEST-07-ISSUE-1981/test-segfault.sh new file mode 100755 index 0000000000..48f05d89fb --- /dev/null +++ b/test/TEST-07-ISSUE-1981/test-segfault.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -x +set -e + +>/failed + +cat <<'EOL' >/lib/systemd/system/my.service +[Service] +Type=oneshot +ExecStart=/bin/echo Timer runs me +EOL + +cat <<'EOL' >/lib/systemd/system/my.timer +[Timer] +OnBootSec=10s +OnUnitInactiveSec=1h +EOL + +systemctl unmask my.timer + +systemctl start my.timer + +mkdir -p /etc/systemd/system/my.timer.d/ +cat <<'EOL' >/etc/systemd/system/my.timer.d/override.conf +[Timer] +OnBootSec=10s +OnUnitInactiveSec=1h +EOL + +systemctl daemon-reload + +systemctl mask my.timer + +touch /testok +rm /failed diff --git a/test/TEST-07-ISSUE-1981/test.sh b/test/TEST-07-ISSUE-1981/test.sh new file mode 100755 index 0000000000..2f7f01058e --- /dev/null +++ b/test/TEST-07-ISSUE-1981/test.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="https://github.com/systemd/systemd/issues/1981" + +. $TEST_BASE_DIR/test-functions + +NSPAWN_TIMEOUT=30s + +test_run() { + dwarn "skipping QEMU" + if run_nspawn; then + check_result_nspawn || return 1 + else + dwarn "can't run systemd-nspawn, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<EOF +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/test-segfault.sh +Type=oneshot +EOF + + cp test-segfault.sh $initdir/ + + setup_testsuite + ) || return 1 + setup_nspawn_root + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/TEST-08-ISSUE-2730/test.sh b/test/TEST-08-ISSUE-2730/test.sh new file mode 100755 index 0000000000..44831983b3 --- /dev/null +++ b/test/TEST-08-ISSUE-2730/test.sh @@ -0,0 +1,112 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="https://github.com/systemd/systemd/issues/2730" + +. $TEST_BASE_DIR/test-functions +SKIP_INITRD=yes +QEMU_TIMEOUT=180 +FSTYPE=ext4 + +check_result_qemu() { + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + [ -n "$TIMED_OUT" ] && ret=$(($ret+1)) + return $ret +} + +test_run() { + if run_qemu; then + check_result_qemu || return 1 + else + dwarn "can't run QEMU, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<EOF +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/bin/sh -x -c 'mount -o remount,rw /dev/sda1 && echo OK > /testok; systemctl poweroff' +Type=oneshot +EOF + + rm $initdir/etc/fstab + cat >$initdir/etc/systemd/system/-.mount <<EOF +[Unit] +Before=local-fs.target + +[Mount] +What=/dev/sda1 +Where=/ +Type=ext4 +Options=errors=remount-ro,noatime + +[Install] +WantedBy=local-fs.target +Alias=root.mount +EOF + + cat >$initdir/etc/systemd/system/systemd-remount-fs.service <<EOF +[Unit] +DefaultDependencies=no +Conflicts=shutdown.target +After=systemd-fsck-root.service +Before=local-fs-pre.target local-fs.target shutdown.target +Wants=local-fs-pre.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/systemctl reload / +EOF + + setup_testsuite + ) || return 1 + + ln -s /etc/systemd/system/-.mount $initdir/etc/systemd/system/root.mount + mkdir -p $initdir/etc/systemd/system/local-fs.target.wants + ln -s /etc/systemd/system/-.mount $initdir/etc/systemd/system/local-fs.target.wants/-.mount + + # mask some services that we do not want to run in these tests + ln -s /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-networkd.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-networkd.socket + ln -s /dev/null $initdir/etc/systemd/system/systemd-resolved.service + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/TEST-09-ISSUE-2691/test.sh b/test/TEST-09-ISSUE-2691/test.sh new file mode 100755 index 0000000000..8ae02e61ac --- /dev/null +++ b/test/TEST-09-ISSUE-2691/test.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="https://github.com/systemd/systemd/issues/2691" + +. $TEST_BASE_DIR/test-functions +SKIP_INITRD=yes +QEMU_TIMEOUT=90 + +check_result_qemu() { + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + [ -n "$TIMED_OUT" ] && ret=$(($ret+1)) + return $ret +} + +test_run() { + if run_qemu; then + check_result_qemu || return 1 + else + dwarn "can't run QEMU, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<'EOF' +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +Type=oneshot +ExecStart=/bin/sh -c '>/testok' +RemainAfterExit=yes +ExecStop=/bin/sh -c 'kill -SEGV $$$$' +TimeoutStopSec=180s +EOF + + setup_testsuite + ) || return 1 + + # mask some services that we do not want to run in these tests + ln -s /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-networkd.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-networkd.socket + ln -s /dev/null $initdir/etc/systemd/system/systemd-resolved.service + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/TEST-10-ISSUE-2467/test.sh b/test/TEST-10-ISSUE-2467/test.sh new file mode 100755 index 0000000000..4eca6784bc --- /dev/null +++ b/test/TEST-10-ISSUE-2467/test.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="https://github.com/systemd/systemd/issues/2467" + +. $TEST_BASE_DIR/test-functions +SKIP_INITRD=yes + +check_result_qemu() { + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + +test_run() { + if run_qemu; then + check_result_qemu || return 1 + else + dwarn "can't run QEMU, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + dracut_install nc true rm + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<'EOF' +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +Type=oneshot +ExecStart=/bin/sh -e -x -c 'rm -f /tmp/nonexistent; systemctl start test.socket; echo a | nc -U /run/test.ctl; >/testok' +TimeoutStartSec=10s +EOF + + cat >$initdir/etc/systemd/system/test.socket <<'EOF' +[Socket] +ListenStream=/run/test.ctl +EOF + + cat > $initdir/etc/systemd/system/test.service <<'EOF' +[Unit] +Requires=test.socket +ConditionPathExistsGlob=/tmp/nonexistent + +[Service] +ExecStart=/bin/true +EOF + + setup_testsuite + ) || return 1 + + # mask some services that we do not want to run in these tests + ln -s /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-networkd.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-networkd.socket + ln -s /dev/null $initdir/etc/systemd/system/systemd-resolved.service + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/TEST-11-ISSUE-3166/test.sh b/test/TEST-11-ISSUE-3166/test.sh new file mode 100755 index 0000000000..0f269c8211 --- /dev/null +++ b/test/TEST-11-ISSUE-3166/test.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="https://github.com/systemd/systemd/issues/3166" + +. $TEST_BASE_DIR/test-functions +SKIP_INITRD=yes + +check_result_qemu() { + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + +test_run() { + if run_qemu; then + check_result_qemu || return 1 + else + dwarn "can't run QEMU, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + dracut_install false touch + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<EOF +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/test-fail-on-restart.sh +Type=oneshot +EOF + + cat >$initdir/etc/systemd/system/fail-on-restart.service <<EOF +[Unit] +Description=Fail on restart + +[Service] +Type=simple +ExecStart=/bin/false +Restart=always +EOF + + + cat >$initdir/test-fail-on-restart.sh <<'EOF' +#!/bin/bash -x + +systemctl start fail-on-restart.service +active_state=$(systemctl show --property ActiveState fail-on-restart.service) +while [[ "$active_state" == "ActiveState=activating" || "$active_state" == "ActiveState=active" ]]; do + sleep 1 + active_state=$(systemctl show --property ActiveState fail-on-restart.service) +done +systemctl is-failed fail-on-restart.service || exit 1 +touch /testok +EOF + + chmod 0755 $initdir/test-fail-on-restart.sh + setup_testsuite + ) || return 1 + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/TEST-12-ISSUE-3171/test.sh b/test/TEST-12-ISSUE-3171/test.sh new file mode 100755 index 0000000000..e20f470143 --- /dev/null +++ b/test/TEST-12-ISSUE-3171/test.sh @@ -0,0 +1,109 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="https://github.com/systemd/systemd/issues/3171" + +. $TEST_BASE_DIR/test-functions + +test_run() { + if run_nspawn; then + check_result_nspawn || return 1 + else + dwarn "can't run systemd-nspawn, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + dracut_install cat mv stat nc + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<EOF +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/test-socket-group.sh +Type=oneshot +EOF + + + cat >$initdir/test-socket-group.sh <<'EOF' +#!/bin/bash +set -x +set -e +set -o pipefail + +U=/run/systemd/system/test.socket +cat <<'EOL' >$U +[Unit] +Description=Test socket +[Socket] +Accept=yes +ListenStream=/run/test.socket +SocketGroup=adm +SocketMode=0660 +EOL + +cat <<'EOL' > /run/systemd/system/test@.service +[Unit] +Description=Test service +[Service] +StandardInput=socket +ExecStart=/bin/sh -x -c cat +EOL + +systemctl start test.socket +systemctl is-active test.socket +[[ "$(stat --format='%G' /run/test.socket)" == adm ]] +echo A | nc -U /run/test.socket + +mv $U ${U}.disabled +systemctl daemon-reload +systemctl is-active test.socket +[[ "$(stat --format='%G' /run/test.socket)" == adm ]] +echo B | nc -U /run/test.socket && exit 1 + +mv ${U}.disabled $U +systemctl daemon-reload +systemctl is-active test.socket +echo C | nc -U /run/test.socket && exit 1 +[[ "$(stat --format='%G' /run/test.socket)" == adm ]] + +systemctl restart test.socket +systemctl is-active test.socket +echo D | nc -U /run/test.socket +[[ "$(stat --format='%G' /run/test.socket)" == adm ]] + + +touch /testok +EOF + + chmod 0755 $initdir/test-socket-group.sh + setup_testsuite + ) || return 1 + + setup_nspawn_root + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/TEST-13-NSPAWN-SMOKE/create-busybox-container b/test/TEST-13-NSPAWN-SMOKE/create-busybox-container new file mode 100755 index 0000000000..868dfd852a --- /dev/null +++ b/test/TEST-13-NSPAWN-SMOKE/create-busybox-container @@ -0,0 +1,53 @@ +#!/bin/bash + +set -e +set -u +set -o pipefail + +root="${1:?Usage $0 container-root}" +mkdir -p "$root" +mkdir "$root/bin" +cp $(type -P busybox) "$root/bin" + +mkdir -p "$root/usr/lib" +touch "$root/usr/lib/os-release" + +ln -s busybox "$root/bin/sh" +ln -s busybox "$root/bin/cat" +ln -s busybox "$root/bin/tr" +ln -s busybox "$root/bin/ps" +ln -s busybox "$root/bin/ip" + +mkdir -p "$root/sbin" +cat <<'EOF' >"$root/sbin/init" +#!/bin/sh + +printf "ps aufx:\n" +ps aufx + +printf "/proc/1/cmdline:\n" +printf "%s\n\n" "$(tr '\0' ' ' </proc/1/cmdline)" + +printf "/proc/1/environ:\n" +printf "%s\n\n" "$(tr '\0' '\n' </proc/1/environ)" + +printf "/proc/1/mountinfo:\n" +cat /proc/self/mountinfo +printf "\n" + +printf "/proc/1/cgroup:\n" +printf "%s\n\n" "$(cat /proc/1/cgroup)" + +printf "/proc/1/uid_map:\n" +printf "%s\n\n" "$(cat /proc/1/uid_map)" + +printf "/proc/1/setgroups:\n" +printf "%s\n\n" "$(cat /proc/1/setgroups)" + +printf "/proc/1/gid_map:\n" +printf "%s\n\n" "$(cat /proc/1/gid_map)" + +printf "ip link:\n" +ip link +EOF +chmod +x "$root/sbin/init" diff --git a/test/TEST-13-NSPAWN-SMOKE/test.sh b/test/TEST-13-NSPAWN-SMOKE/test.sh new file mode 100755 index 0000000000..e6977a7f1c --- /dev/null +++ b/test/TEST-13-NSPAWN-SMOKE/test.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="systemd-nspawn smoke test" +SKIP_INITRD=yes +. $TEST_BASE_DIR/test-functions + +check_result_qemu() { + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + +test_run() { + if run_qemu; then + check_result_qemu || return 1 + else + dwarn "can't run QEMU, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + dracut_install busybox chmod rmdir + + cp create-busybox-container $initdir/ + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<EOF +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/test-nspawn.sh +Type=oneshot +EOF + + cat >$initdir/test-nspawn.sh <<'EOF' +#!/bin/bash +set -x +set -e +set -u +set -o pipefail + +export SYSTEMD_LOG_LEVEL=debug + +# check cgroup-v2 +is_v2_supported=no +mkdir -p /tmp/cgroup2 +if mount -t cgroup2 cgroup2 /tmp/cgroup2; then + is_v2_supported=yes + umount /tmp/cgroup2 +fi +rmdir /tmp/cgroup2 + +# check cgroup namespaces +is_cgns_supported=no +if [[ -f /proc/1/ns/cgroup ]]; then + is_cgns_supported=yes +fi + +function run { + if [[ "$1" = "yes" && "$is_v2_supported" = "no" ]]; then + printf "Unified cgroup hierarchy is not supported. Skipping.\n" >&2 + return 0 + fi + if [[ "$2" = "yes" && "$is_cgns_supported" = "no" ]]; then + printf "Cgroup namespaces are not supported. Skipping.\n" >&2 + return 0 + fi + + local _root="/var/lib/machines/unified-$1-cgns-$2" + /create-busybox-container "$_root" + UNIFIED_CGROUP_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" systemd-nspawn --register=no -D "$_root" -b + UNIFIED_CGROUP_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" systemd-nspawn --register=no -D "$_root" --private-network -b + UNIFIED_CGROUP_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" systemd-nspawn --register=no -D "$_root" -U -b + UNIFIED_CGROUP_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" systemd-nspawn --register=no -D "$_root" --private-network -U -b + + return 0 +} + +run no no +run yes no +run no yes +run yes yes + +touch /testok +EOF + + chmod 0755 $initdir/test-nspawn.sh + setup_testsuite + ) || return 1 + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/a.service b/test/a.service new file mode 100644 index 0000000000..4168d2d051 --- /dev/null +++ b/test/a.service @@ -0,0 +1,7 @@ +[Unit] +Description=A +Requires=b.service +Before=b.service + +[Service] +ExecStart=/bin/true diff --git a/test/b.service b/test/b.service new file mode 100644 index 0000000000..e03bae36be --- /dev/null +++ b/test/b.service @@ -0,0 +1,6 @@ +[Unit] +Description=B +Wants=f.service + +[Service] +ExecStart=/bin/true diff --git a/test/basic.target b/test/basic.target new file mode 120000 index 0000000000..0612934682 --- /dev/null +++ b/test/basic.target @@ -0,0 +1 @@ +../units/basic.target
\ No newline at end of file diff --git a/test/bus-policy/check-own-rules.conf b/test/bus-policy/check-own-rules.conf new file mode 100644 index 0000000000..bc2f415fcb --- /dev/null +++ b/test/bus-policy/check-own-rules.conf @@ -0,0 +1,14 @@ +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <user>mybususer</user> + <listen>unix:path=/foo/bar</listen> + <listen>tcp:port=1234</listen> + <servicedir>/usr/share/foo</servicedir> + <policy context="default"> + <allow user="*"/> + <deny own="*"/> + <allow own_prefix="org.freedesktop.ManySystems"/> + </policy> + +</busconfig> diff --git a/test/bus-policy/hello.conf b/test/bus-policy/hello.conf new file mode 100644 index 0000000000..af09893de6 --- /dev/null +++ b/test/bus-policy/hello.conf @@ -0,0 +1,14 @@ +<?xml version="1.0"?> <!--*-nxml-*--> +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + +<busconfig> + + <policy context="default"> + <allow user="*"/> + + <deny user="1"/> + <deny group="1"/> + </policy> + +</busconfig> diff --git a/test/bus-policy/many-rules.conf b/test/bus-policy/many-rules.conf new file mode 100644 index 0000000000..70dd538c11 --- /dev/null +++ b/test/bus-policy/many-rules.conf @@ -0,0 +1,61 @@ +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <user>mybususer</user> + <listen>unix:path=/foo/bar</listen> + <listen>tcp:port=1234</listen> + <includedir>basic.d</includedir> + <standard_session_servicedirs /> + <servicedir>/usr/share/foo</servicedir> + <include ignore_missing="yes">nonexistent.conf</include> + <policy context="default"> + <allow user="*"/> + <deny send_interface="org.freedesktop.System" send_member="Reboot"/> + <deny receive_interface="org.freedesktop.System" receive_member="Reboot"/> + <deny send_path="/foo/bar/SystemObjectThing" send_member="Reboot"/> + <deny own="org.freedesktop.System"/> + <deny own_prefix="org.freedesktop.ManySystems"/> + <deny send_destination="org.freedesktop.System"/> + <deny receive_sender="org.freedesktop.System"/> + <deny user="root"/> + <deny group="bin"/> + <allow send_type="error"/> + <allow send_type="method_call"/> + <allow send_type="method_return"/> + <allow send_type="signal"/> + <deny send_destination="org.freedesktop.Bar" send_interface="org.freedesktop.Foo"/> + <deny send_destination="org.freedesktop.Bar" send_interface="org.freedesktop.Foo" send_type="method_call"/> + </policy> + + <policy context="mandatory"> + <allow user="*"/> + <deny send_interface="org.freedesktop.System" send_member="Reboot"/> + <deny receive_interface="org.freedesktop.System" receive_member="Reboot"/> + <deny send_path="/foo/bar/SystemObjectThing" send_member="Reboot"/> + <deny own="org.freedesktop.System"/> + <deny own_prefix="org.freedesktop.ManySystems"/> + <deny send_destination="org.freedesktop.System"/> + <deny receive_sender="org.freedesktop.System"/> + <deny user="root"/> + <deny group="bin"/> + <allow send_type="error"/> + <allow send_type="method_call"/> + <allow send_type="method_return"/> + <allow send_type="signal"/> + <deny send_destination="org.freedesktop.Bar" send_interface="org.freedesktop.Foo"/> + <deny send_destination="org.freedesktop.Bar" send_interface="org.freedesktop.Foo" send_type="method_call"/> + </policy> + + <limit name="max_incoming_bytes">5000</limit> + <limit name="max_outgoing_bytes">5000</limit> + <limit name="max_message_size">300</limit> + <limit name="service_start_timeout">5000</limit> + <limit name="auth_timeout">6000</limit> + <limit name="max_completed_connections">50</limit> + <limit name="max_incomplete_connections">80</limit> + <limit name="max_connections_per_user">64</limit> + <limit name="max_pending_service_starts">64</limit> + <limit name="max_names_per_connection">256</limit> + <limit name="max_match_rules_per_connection">512</limit> + +</busconfig> diff --git a/test/bus-policy/methods.conf b/test/bus-policy/methods.conf new file mode 100644 index 0000000000..4bc38f9151 --- /dev/null +++ b/test/bus-policy/methods.conf @@ -0,0 +1,17 @@ +<?xml version="1.0"?> <!--*-nxml-*--> +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + +<busconfig> + + <policy context="default"> + <deny send_type="method_call"/> + + <deny send_destination="org.test.test1"/> + <allow send_destination="org.test.test1" send_interface="org.test.int1"/> + <allow send_destination="org.test.test1" send_interface="org.test.int2"/> + + <allow receive_sender="org.test.test3" receive_interface="org.test.int3" receive_member="Member111"/> + </policy> + +</busconfig> diff --git a/test/bus-policy/ownerships.conf b/test/bus-policy/ownerships.conf new file mode 100644 index 0000000000..bc3a230a26 --- /dev/null +++ b/test/bus-policy/ownerships.conf @@ -0,0 +1,24 @@ +<?xml version="1.0"?> <!--*-nxml-*--> +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + +<busconfig> + + <policy context="default"> + <allow own="org.test.test1"/> + </policy> + + <policy context="mandatory"> + <deny own="org.test.test3"/> + </policy> + + <policy user="root"> + <allow own="org.test.test2"/> + <allow own="org.test.test3"/> + </policy> + + <policy user="1"> + <allow own="org.test.test4"/> + </policy> + +</busconfig> diff --git a/test/bus-policy/signals.conf b/test/bus-policy/signals.conf new file mode 100644 index 0000000000..440e3fe6d0 --- /dev/null +++ b/test/bus-policy/signals.conf @@ -0,0 +1,15 @@ +<?xml version="1.0"?> <!--*-nxml-*--> +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + +<busconfig> + + <policy context="default"> + <allow send_type="signal"/> + </policy> + + <policy user="1"> + <deny send_type="signal"/> + </policy> + +</busconfig> diff --git a/test/bus-policy/test.conf b/test/bus-policy/test.conf new file mode 100644 index 0000000000..ee6afcdfbb --- /dev/null +++ b/test/bus-policy/test.conf @@ -0,0 +1,20 @@ +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <!-- The following demonstrates how to punch holes in a default deny-all + policy so that a particular user can own a service, and other + connections can get messages from it --> + + <!-- Only root can own the FooService service, and + this user can only send the one kind of message --> + <policy user="root"> + <allow own="org.foo.FooService"/> + <allow send_interface="org.foo.FooBroadcastInterface"/> + </policy> + + <!-- Allow any connection to receive the message, but + only if the message is sent by the owner of FooService --> + <policy context="default"> + <allow receive_interface="org.foo.FooBroadcastInterface" receive_sender="org.foo.FooService"/> + </policy> +</busconfig> diff --git a/test/c.service b/test/c.service new file mode 100644 index 0000000000..e2f60a8fbf --- /dev/null +++ b/test/c.service @@ -0,0 +1,6 @@ +[Unit] +Description=C +Requires=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/d.service b/test/d.service new file mode 100644 index 0000000000..921fd2ee1b --- /dev/null +++ b/test/d.service @@ -0,0 +1,8 @@ +[Unit] +Description=D:Cyclic +After=b.service +Before=a.service +Requires=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/daughter.service b/test/daughter.service new file mode 100644 index 0000000000..aebedca348 --- /dev/null +++ b/test/daughter.service @@ -0,0 +1,7 @@ +[Unit] +Description=Daughter Service + +[Service] +Slice=parent.slice +Type=oneshot +ExecStart=/bin/true diff --git a/test/e.service b/test/e.service new file mode 100644 index 0000000000..5ba98c7c43 --- /dev/null +++ b/test/e.service @@ -0,0 +1,8 @@ +[Unit] +Description=E:Cyclic +After=b.service +Before=a.service +Wants=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/end.service b/test/end.service new file mode 100644 index 0000000000..6e1996fd02 --- /dev/null +++ b/test/end.service @@ -0,0 +1,10 @@ +[Unit] +Description=End the test +After=testsuite.service +OnFailure=poweroff.target +OnFailureJobMode=replace-irreversibly + +[Service] +Type=oneshot +ExecStart=/bin/sh -x -c 'systemctl poweroff --no-block' +TimeoutStartSec=5m diff --git a/test/f.service b/test/f.service new file mode 100644 index 0000000000..7dde681c17 --- /dev/null +++ b/test/f.service @@ -0,0 +1,5 @@ +[Unit] +Description=F + +[Service] +ExecStart=/bin/true diff --git a/test/g.service b/test/g.service new file mode 100644 index 0000000000..cbfa82a454 --- /dev/null +++ b/test/g.service @@ -0,0 +1,6 @@ +[Unit] +Description=G +Conflicts=e.service + +[Service] +ExecStart=/bin/true diff --git a/test/grandchild.service b/test/grandchild.service new file mode 100644 index 0000000000..ab641300e4 --- /dev/null +++ b/test/grandchild.service @@ -0,0 +1,7 @@ +[Unit] +Description=Grandchild Service + +[Service] +Slice=parent-deep.slice +Type=oneshot +ExecStart=/bin/true diff --git a/test/h.service b/test/h.service new file mode 100644 index 0000000000..74a7751cad --- /dev/null +++ b/test/h.service @@ -0,0 +1,6 @@ +[Unit] +Description=H +Wants=g.service + +[Service] +ExecStart=/bin/true diff --git a/test/hello-after-sleep.target b/test/hello-after-sleep.target new file mode 100644 index 0000000000..526fbd2a12 --- /dev/null +++ b/test/hello-after-sleep.target @@ -0,0 +1,5 @@ +[Unit] +Description=Sleep for a minute, then say hello. +Wants=sleep.service hello.service +After=sleep.service +Before=hello.service diff --git a/test/hello.service b/test/hello.service new file mode 100644 index 0000000000..82907b64e1 --- /dev/null +++ b/test/hello.service @@ -0,0 +1,5 @@ +[Unit] +Description=Hello World + +[Service] +ExecStart=/bin/echo "Hello World" diff --git a/test/loopy.service b/test/loopy.service new file mode 100644 index 0000000000..9eb645748e --- /dev/null +++ b/test/loopy.service @@ -0,0 +1,2 @@ +[Service] +ExecStart=/bin/true diff --git a/test/loopy.service.d/compat.conf b/test/loopy.service.d/compat.conf new file mode 100644 index 0000000000..51b84b89ed --- /dev/null +++ b/test/loopy.service.d/compat.conf @@ -0,0 +1,5 @@ +[Unit] +BindsTo=loopy2.service + +[Install] +Also=loopy2.service diff --git a/test/loopy2.service b/test/loopy2.service new file mode 120000 index 0000000000..961b1fe9bc --- /dev/null +++ b/test/loopy2.service @@ -0,0 +1 @@ +loopy.service
\ No newline at end of file diff --git a/test/loopy3.service b/test/loopy3.service new file mode 100644 index 0000000000..606e26b5da --- /dev/null +++ b/test/loopy3.service @@ -0,0 +1,5 @@ +[Service] +ExecStart=/bin/true + +[Unit] +Conflicts=loopy4.service diff --git a/test/loopy4.service b/test/loopy4.service new file mode 120000 index 0000000000..43e5658bcd --- /dev/null +++ b/test/loopy4.service @@ -0,0 +1 @@ +loopy3.service
\ No newline at end of file diff --git a/test/mocks/fsck b/test/mocks/fsck new file mode 100755 index 0000000000..77b50d7234 --- /dev/null +++ b/test/mocks/fsck @@ -0,0 +1,27 @@ +#!/bin/bash +fd=0 + +OPTIND=1 +while getopts "C:aTlM" opt; do + case "$opt" in + C) + fd=$OPTARG + ;; + \?);; + esac +done + +shift "$((OPTIND-1))" +device=$1 + +echo "Running fake fsck on $device" + +declare -a maxpass=(30 5 2 30 60) + +for pass in {1..5}; do + maxprogress=${maxpass[$((pass-1))]} + for (( current=0; current<=${maxprogress}; current++)); do + echo "$pass $current $maxprogress $device">&$fd + sleep 0.1 + done +done diff --git a/test/networkd-test.py b/test/networkd-test.py new file mode 100755 index 0000000000..3091722fc1 --- /dev/null +++ b/test/networkd-test.py @@ -0,0 +1,599 @@ +#!/usr/bin/env python3 +# +# networkd integration test +# This uses temporary configuration in /run and temporary veth devices, and +# does not write anything on disk or change any system configuration; +# but it assumes (and checks at the beginning) that networkd is not currently +# running. +# +# This can be run on a normal installation, in QEMU, nspawn (with +# --private-network), LXD (with "--config raw.lxc=lxc.aa_profile=unconfined"), +# or LXC system containers. You need at least the "ip" tool from the iproute +# package; it is recommended to install dnsmasq too to get full test coverage. +# +# ATTENTION: This uses the *installed* networkd, not the one from the built +# source tree. +# +# (C) 2015 Canonical Ltd. +# Author: Martin Pitt <martin.pitt@ubuntu.com> +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +import time +import unittest +import tempfile +import subprocess +import shutil + +networkd_active = subprocess.call(['systemctl', 'is-active', '--quiet', + 'systemd-networkd']) == 0 +have_dnsmasq = shutil.which('dnsmasq') + +RESOLV_CONF = '/run/systemd/resolve/resolv.conf' + + +@unittest.skipIf(networkd_active, + 'networkd is already active') +class ClientTestBase: + def setUp(self): + self.iface = 'test_eth42' + self.if_router = 'router_eth42' + self.workdir_obj = tempfile.TemporaryDirectory() + self.workdir = self.workdir_obj.name + self.config = '/run/systemd/network/test_eth42.network' + + # avoid "Failed to open /dev/tty" errors in containers + os.environ['SYSTEMD_LOG_TARGET'] = 'journal' + + # determine path to systemd-networkd-wait-online + for p in ['/usr/lib/systemd/systemd-networkd-wait-online', + '/lib/systemd/systemd-networkd-wait-online']: + if os.path.exists(p): + self.networkd_wait_online = p + break + else: + self.fail('systemd-networkd-wait-online not found') + + # get current journal cursor + out = subprocess.check_output(['journalctl', '-b', '--quiet', + '--no-pager', '-n0', '--show-cursor'], + universal_newlines=True) + self.assertTrue(out.startswith('-- cursor:')) + self.journal_cursor = out.split()[-1] + + def tearDown(self): + self.shutdown_iface() + subprocess.call(['systemctl', 'stop', 'systemd-networkd']) + + def writeConfig(self, fname, contents): + os.makedirs(os.path.dirname(fname), exist_ok=True) + with open(fname, 'w') as f: + f.write(contents) + self.addCleanup(os.remove, fname) + + def show_journal(self, unit): + '''Show journal of given unit since start of the test''' + + print('---- %s ----' % unit) + sys.stdout.flush() + subprocess.call(['journalctl', '-b', '--no-pager', '--quiet', + '--cursor', self.journal_cursor, '-u', unit]) + + def create_iface(self, ipv6=False): + '''Create test interface with DHCP server behind it''' + + raise NotImplementedError('must be implemented by a subclass') + + def shutdown_iface(self): + '''Remove test interface and stop DHCP server''' + + raise NotImplementedError('must be implemented by a subclass') + + def print_server_log(self): + '''Print DHCP server log for debugging failures''' + + raise NotImplementedError('must be implemented by a subclass') + + def do_test(self, coldplug=True, ipv6=False, extra_opts='', + online_timeout=10, dhcp_mode='yes'): + subprocess.check_call(['systemctl', 'start', 'systemd-resolved']) + self.writeConfig(self.config, '''\ +[Match] +Name=%s +[Network] +DHCP=%s +%s''' % (self.iface, dhcp_mode, extra_opts)) + + if coldplug: + # create interface first, then start networkd + self.create_iface(ipv6=ipv6) + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + else: + # start networkd first, then create interface + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + self.create_iface(ipv6=ipv6) + + try: + subprocess.check_call([self.networkd_wait_online, '--interface', + self.iface, '--timeout=%i' % online_timeout]) + + if ipv6: + # check iface state and IP 6 address; FIXME: we need to wait a bit + # longer, as the iface is "configured" already with IPv4 *or* + # IPv6, but we want to wait for both + for timeout in range(10): + out = subprocess.check_output(['ip', 'a', 'show', 'dev', self.iface]) + if b'state UP' in out and b'inet6 2600' in out and b'inet 192.168' in out: + break + time.sleep(1) + else: + self.fail('timed out waiting for IPv6 configuration') + + self.assertRegex(out, b'inet6 2600::.* scope global .*dynamic') + self.assertRegex(out, b'inet6 fe80::.* scope link') + else: + # should have link-local address on IPv6 only + out = subprocess.check_output(['ip', '-6', 'a', 'show', 'dev', self.iface]) + self.assertRegex(out, b'inet6 fe80::.* scope link') + self.assertNotIn(b'scope global', out) + + # should have IPv4 address + out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface]) + self.assertIn(b'state UP', out) + self.assertRegex(out, b'inet 192.168.5.\d+/.* scope global dynamic') + + # check networkctl state + out = subprocess.check_output(['networkctl']) + self.assertRegex(out, ('%s\s+ether\s+routable\s+unmanaged' % self.if_router).encode()) + self.assertRegex(out, ('%s\s+ether\s+routable\s+configured' % self.iface).encode()) + + out = subprocess.check_output(['networkctl', 'status', self.iface]) + self.assertRegex(out, b'Type:\s+ether') + self.assertRegex(out, b'State:\s+routable.*configured') + self.assertRegex(out, b'Address:\s+192.168.5.\d+') + if ipv6: + self.assertRegex(out, b'2600::') + else: + self.assertNotIn(b'2600::', out) + self.assertRegex(out, b'fe80::') + self.assertRegex(out, b'Gateway:\s+192.168.5.1') + self.assertRegex(out, b'DNS:\s+192.168.5.1') + except (AssertionError, subprocess.CalledProcessError): + # show networkd status, journal, and DHCP server log on failure + with open(self.config) as f: + print('\n---- %s ----\n%s' % (self.config, f.read())) + print('---- interface status ----') + sys.stdout.flush() + subprocess.call(['ip', 'a', 'show', 'dev', self.iface]) + print('---- networkctl status %s ----' % self.iface) + sys.stdout.flush() + subprocess.call(['networkctl', 'status', self.iface]) + self.show_journal('systemd-networkd.service') + self.print_server_log() + raise + + for timeout in range(50): + with open(RESOLV_CONF) as f: + contents = f.read() + if 'nameserver 192.168.5.1\n' in contents: + break + time.sleep(0.1) + else: + self.fail('nameserver 192.168.5.1 not found in ' + RESOLV_CONF) + + if not coldplug: + # check post-down.d hook + self.shutdown_iface() + + def test_coldplug_dhcp_yes_ip4(self): + # we have a 12s timeout on RA, so we need to wait longer + self.do_test(coldplug=True, ipv6=False, online_timeout=15) + + def test_coldplug_dhcp_yes_ip4_no_ra(self): + # with disabling RA explicitly things should be fast + self.do_test(coldplug=True, ipv6=False, + extra_opts='IPv6AcceptRA=False') + + def test_coldplug_dhcp_ip4_only(self): + # we have a 12s timeout on RA, so we need to wait longer + self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', + online_timeout=15) + + def test_coldplug_dhcp_ip4_only_no_ra(self): + # with disabling RA explicitly things should be fast + self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', + extra_opts='IPv6AcceptRA=False') + + def test_coldplug_dhcp_ip6(self): + self.do_test(coldplug=True, ipv6=True) + + def test_hotplug_dhcp_ip4(self): + # With IPv4 only we have a 12s timeout on RA, so we need to wait longer + self.do_test(coldplug=False, ipv6=False, online_timeout=15) + + def test_hotplug_dhcp_ip6(self): + self.do_test(coldplug=False, ipv6=True) + + def test_route_only_dns(self): + self.writeConfig('/run/systemd/network/myvpn.netdev', '''\ +[NetDev] +Name=dummy0 +Kind=dummy +MACAddress=12:34:56:78:9a:bc''') + self.writeConfig('/run/systemd/network/myvpn.network', '''\ +[Match] +Name=dummy0 +[Network] +Address=192.168.42.100 +DNS=192.168.42.1 +Domains= ~company''') + + self.do_test(coldplug=True, ipv6=False, + extra_opts='IPv6AcceptRouterAdvertisements=False') + + with open(RESOLV_CONF) as f: + contents = f.read() + # ~company is not a search domain, only a routing domain + self.assertNotRegex(contents, 'search.*company') + # our global server should appear + self.assertIn('nameserver 192.168.5.1\n', contents) + # should not have domain-restricted server as global server + self.assertNotIn('nameserver 192.168.42.1\n', contents) + + def test_route_only_dns_all_domains(self): + with open('/run/systemd/network/myvpn.netdev', 'w') as f: + f.write('''[NetDev] +Name=dummy0 +Kind=dummy +MACAddress=12:34:56:78:9a:bc''') + with open('/run/systemd/network/myvpn.network', 'w') as f: + f.write('''[Match] +Name=dummy0 +[Network] +Address=192.168.42.100 +DNS=192.168.42.1 +Domains= ~company ~.''') + self.addCleanup(os.remove, '/run/systemd/network/myvpn.netdev') + self.addCleanup(os.remove, '/run/systemd/network/myvpn.network') + + self.do_test(coldplug=True, ipv6=False, + extra_opts='IPv6AcceptRouterAdvertisements=False') + + with open(RESOLV_CONF) as f: + contents = f.read() + + # ~company is not a search domain, only a routing domain + self.assertNotRegex(contents, 'search.*company') + + # our global server should appear + self.assertIn('nameserver 192.168.5.1\n', contents) + # should have company server as global server due to ~. + self.assertIn('nameserver 192.168.42.1\n', contents) + + +@unittest.skipUnless(have_dnsmasq, 'dnsmasq not installed') +class DnsmasqClientTest(ClientTestBase, unittest.TestCase): + '''Test networkd client against dnsmasq''' + + def setUp(self): + super().setUp() + self.dnsmasq = None + + def create_iface(self, ipv6=False, dnsmasq_opts=None): + '''Create test interface with DHCP server behind it''' + + # add veth pair + subprocess.check_call(['ip', 'link', 'add', 'name', self.iface, 'type', + 'veth', 'peer', 'name', self.if_router]) + + # give our router an IP + subprocess.check_call(['ip', 'a', 'flush', 'dev', self.if_router]) + subprocess.check_call(['ip', 'a', 'add', '192.168.5.1/24', 'dev', self.if_router]) + if ipv6: + subprocess.check_call(['ip', 'a', 'add', '2600::1/64', 'dev', self.if_router]) + subprocess.check_call(['ip', 'link', 'set', self.if_router, 'up']) + + # add DHCP server + self.dnsmasq_log = os.path.join(self.workdir, 'dnsmasq.log') + lease_file = os.path.join(self.workdir, 'dnsmasq.leases') + if ipv6: + extra_opts = ['--enable-ra', '--dhcp-range=2600::10,2600::20'] + else: + extra_opts = [] + if dnsmasq_opts: + extra_opts += dnsmasq_opts + self.dnsmasq = subprocess.Popen( + ['dnsmasq', '--keep-in-foreground', '--log-queries', + '--log-facility=' + self.dnsmasq_log, '--conf-file=/dev/null', + '--dhcp-leasefile=' + lease_file, '--bind-interfaces', + '--interface=' + self.if_router, '--except-interface=lo', + '--dhcp-range=192.168.5.10,192.168.5.200'] + extra_opts) + + def shutdown_iface(self): + '''Remove test interface and stop DHCP server''' + + if self.if_router: + subprocess.check_call(['ip', 'link', 'del', 'dev', self.if_router]) + self.if_router = None + if self.dnsmasq: + self.dnsmasq.kill() + self.dnsmasq.wait() + self.dnsmasq = None + + def print_server_log(self): + '''Print DHCP server log for debugging failures''' + + with open(self.dnsmasq_log) as f: + sys.stdout.write('\n\n---- dnsmasq log ----\n%s\n------\n\n' % f.read()) + + def test_resolved_domain_restricted_dns(self): + '''resolved: domain-restricted DNS servers''' + + # create interface for generic connections; this will map all DNS names + # to 192.168.42.1 + self.create_iface(dnsmasq_opts=['--address=/#/192.168.42.1']) + self.writeConfig('/run/systemd/network/general.network', '''\ +[Match] +Name=%s +[Network] +DHCP=ipv4 +IPv6AcceptRA=False''' % self.iface) + + # create second device/dnsmasq for a .company/.lab VPN interface + # static IPs for simplicity + subprocess.check_call(['ip', 'link', 'add', 'name', 'testvpnclient', 'type', + 'veth', 'peer', 'name', 'testvpnrouter']) + self.addCleanup(subprocess.call, ['ip', 'link', 'del', 'dev', 'testvpnrouter']) + subprocess.check_call(['ip', 'a', 'flush', 'dev', 'testvpnrouter']) + subprocess.check_call(['ip', 'a', 'add', '10.241.3.1/24', 'dev', 'testvpnrouter']) + subprocess.check_call(['ip', 'link', 'set', 'testvpnrouter', 'up']) + + vpn_dnsmasq_log = os.path.join(self.workdir, 'dnsmasq-vpn.log') + vpn_dnsmasq = subprocess.Popen( + ['dnsmasq', '--keep-in-foreground', '--log-queries', + '--log-facility=' + vpn_dnsmasq_log, '--conf-file=/dev/null', + '--dhcp-leasefile=/dev/null', '--bind-interfaces', + '--interface=testvpnrouter', '--except-interface=lo', + '--address=/math.lab/10.241.3.3', '--address=/cantina.company/10.241.4.4']) + self.addCleanup(vpn_dnsmasq.wait) + self.addCleanup(vpn_dnsmasq.kill) + + self.writeConfig('/run/systemd/network/vpn.network', '''\ +[Match] +Name=testvpnclient +[Network] +IPv6AcceptRA=False +Address=10.241.3.2/24 +DNS=10.241.3.1 +Domains= ~company ~lab''') + + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + subprocess.check_call([self.networkd_wait_online, '--interface', self.iface, + '--interface=testvpnclient', '--timeout=20']) + + # ensure we start fresh with every test + subprocess.check_call(['systemctl', 'restart', 'systemd-resolved']) + + # test vpnclient specific domains; these should *not* be answered by + # the general DNS + out = subprocess.check_output(['systemd-resolve', 'math.lab']) + self.assertIn(b'math.lab: 10.241.3.3', out) + out = subprocess.check_output(['systemd-resolve', 'kettle.cantina.company']) + self.assertIn(b'kettle.cantina.company: 10.241.4.4', out) + + # test general domains + out = subprocess.check_output(['systemd-resolve', 'megasearch.net']) + self.assertIn(b'megasearch.net: 192.168.42.1', out) + + with open(self.dnsmasq_log) as f: + general_log = f.read() + with open(vpn_dnsmasq_log) as f: + vpn_log = f.read() + + # VPN domains should only be sent to VPN DNS + self.assertRegex(vpn_log, 'query.*math.lab') + self.assertRegex(vpn_log, 'query.*cantina.company') + self.assertNotIn('lab', general_log) + self.assertNotIn('company', general_log) + + # general domains should not be sent to the VPN DNS + self.assertRegex(general_log, 'query.*megasearch.net') + self.assertNotIn('megasearch.net', vpn_log) + + +class NetworkdClientTest(ClientTestBase, unittest.TestCase): + '''Test networkd client against networkd server''' + + def setUp(self): + super().setUp() + self.dnsmasq = None + + def create_iface(self, ipv6=False): + '''Create test interface with DHCP server behind it''' + + # run "router-side" networkd in own mount namespace to shield it from + # "client-side" configuration and networkd + (fd, script) = tempfile.mkstemp(prefix='networkd-router.sh') + self.addCleanup(os.remove, script) + with os.fdopen(fd, 'w+') as f: + f.write('''\ +#!/bin/sh -eu +mkdir -p /run/systemd/network +mkdir -p /run/systemd/netif +mount -t tmpfs none /run/systemd/network +mount -t tmpfs none /run/systemd/netif +[ ! -e /run/dbus ] || mount -t tmpfs none /run/dbus +# create router/client veth pair +cat << EOF > /run/systemd/network/test.netdev +[NetDev] +Name=%(ifr)s +Kind=veth + +[Peer] +Name=%(ifc)s +EOF + +cat << EOF > /run/systemd/network/test.network +[Match] +Name=%(ifr)s + +[Network] +Address=192.168.5.1/24 +%(addr6)s +DHCPServer=yes + +[DHCPServer] +PoolOffset=10 +PoolSize=50 +DNS=192.168.5.1 +EOF + +# run networkd as in systemd-networkd.service +exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ { s/^.*=//; p}') +''' % {'ifr': self.if_router, 'ifc': self.iface, 'addr6': ipv6 and 'Address=2600::1/64' or ''}) + + os.fchmod(fd, 0o755) + + subprocess.check_call(['systemd-run', '--unit=networkd-test-router.service', + '-p', 'InaccessibleDirectories=-/etc/systemd/network', + '-p', 'InaccessibleDirectories=-/run/systemd/network', + '-p', 'InaccessibleDirectories=-/run/systemd/netif', + '--service-type=notify', script]) + + # wait until devices got created + for timeout in range(50): + out = subprocess.check_output(['ip', 'a', 'show', 'dev', self.if_router]) + if b'state UP' in out and b'scope global' in out: + break + time.sleep(0.1) + + def shutdown_iface(self): + '''Remove test interface and stop DHCP server''' + + if self.if_router: + subprocess.check_call(['systemctl', 'stop', 'networkd-test-router.service']) + # ensure failed transient unit does not stay around + subprocess.call(['systemctl', 'reset-failed', 'networkd-test-router.service']) + subprocess.call(['ip', 'link', 'del', 'dev', self.if_router]) + self.if_router = None + + def print_server_log(self): + '''Print DHCP server log for debugging failures''' + + self.show_journal('networkd-test-router.service') + + @unittest.skip('networkd does not have DHCPv6 server support') + def test_hotplug_dhcp_ip6(self): + pass + + @unittest.skip('networkd does not have DHCPv6 server support') + def test_coldplug_dhcp_ip6(self): + pass + + def test_search_domains(self): + + # we don't use this interface for this test + self.if_router = None + + self.writeConfig('/run/systemd/network/test.netdev', '''\ +[NetDev] +Name=dummy0 +Kind=dummy +MACAddress=12:34:56:78:9a:bc''') + self.writeConfig('/run/systemd/network/test.network', '''\ +[Match] +Name=dummy0 +[Network] +Address=192.168.42.100 +DNS=192.168.42.1 +Domains= one two three four five six seven eight nine ten''') + + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + + for timeout in range(50): + with open(RESOLV_CONF) as f: + contents = f.read() + if ' one' in contents: + break + time.sleep(0.1) + self.assertRegex(contents, 'search .*one two three four') + self.assertNotIn('seven\n', contents) + self.assertIn('# Too many search domains configured, remaining ones ignored.\n', contents) + + def test_search_domains_too_long(self): + + # we don't use this interface for this test + self.if_router = None + + name_prefix = 'a' * 60 + + self.writeConfig('/run/systemd/network/test.netdev', '''\ +[NetDev] +Name=dummy0 +Kind=dummy +MACAddress=12:34:56:78:9a:bc''') + self.writeConfig('/run/systemd/network/test.network', '''\ +[Match] +Name=dummy0 +[Network] +Address=192.168.42.100 +DNS=192.168.42.1 +Domains={p}0 {p}1 {p}2 {p}3 {p}4'''.format(p=name_prefix)) + + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + + for timeout in range(50): + with open(RESOLV_CONF) as f: + contents = f.read() + if ' one' in contents: + break + time.sleep(0.1) + self.assertRegex(contents, 'search .*{p}0 {p}1 {p}2'.format(p=name_prefix)) + self.assertIn('# Total length of all search domains is too long, remaining ones ignored.', contents) + + def test_dropin(self): + # we don't use this interface for this test + self.if_router = None + + self.writeConfig('/run/systemd/network/test.netdev', '''\ +[NetDev] +Name=dummy0 +Kind=dummy +MACAddress=12:34:56:78:9a:bc''') + self.writeConfig('/run/systemd/network/test.network', '''\ +[Match] +Name=dummy0 +[Network] +Address=192.168.42.100 +DNS=192.168.42.1''') + self.writeConfig('/run/systemd/network/test.network.d/dns.conf', '''\ +[Network] +DNS=127.0.0.1''') + + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + + for timeout in range(50): + with open(RESOLV_CONF) as f: + contents = f.read() + if ' 127.0.0.1' in contents: + break + time.sleep(0.1) + self.assertIn('nameserver 192.168.42.1\n', contents) + self.assertIn('nameserver 127.0.0.1\n', contents) + +if __name__ == '__main__': + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, + verbosity=2)) diff --git a/test/parent-deep.slice b/test/parent-deep.slice new file mode 100644 index 0000000000..79b302f38d --- /dev/null +++ b/test/parent-deep.slice @@ -0,0 +1,5 @@ +[Unit] +Description=Deeper Parent Slice + +[Slice] +MemoryLimit=3G diff --git a/test/parent.slice b/test/parent.slice new file mode 100644 index 0000000000..a95f90392d --- /dev/null +++ b/test/parent.slice @@ -0,0 +1,5 @@ +[Unit] +Description=Parent Slice + +[Slice] +IOWeight=200 diff --git a/test/rule-syntax-check.py b/test/rule-syntax-check.py new file mode 100644 index 0000000000..e43a3daeb3 --- /dev/null +++ b/test/rule-syntax-check.py @@ -0,0 +1,72 @@ +# Simple udev rules syntax checker +# +# (C) 2010 Canonical Ltd. +# Author: Martin Pitt <martin.pitt@ubuntu.com> +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. + +import re +import sys +import os +from glob import glob + +if len(sys.argv) > 1: + # explicit rule file list + rules_files = sys.argv[1:] +else: + # take them from the build dir + root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + rules_dir = os.path.join(os.environ.get('top_srcdir', root_dir), 'rules') + if not os.path.isdir(rules_dir): + sys.stderr.write('No rules files given, and %s does not exist, aborting' % rules_dir) + sys.exit(2) + rules_files = glob(os.path.join(rules_dir, '*.rules')) + +no_args_tests = re.compile('(ACTION|DEVPATH|KERNELS?|NAME|SYMLINK|SUBSYSTEMS?|DRIVERS?|TAG|RESULT|TEST)\s*(?:=|!)=\s*"([^"]*)"$') +args_tests = re.compile('(ATTRS?|ENV|TEST){([a-zA-Z0-9/_.*%-]+)}\s*(?:=|!)=\s*"([^"]*)"$') +no_args_assign = re.compile('(NAME|SYMLINK|OWNER|GROUP|MODE|TAG|PROGRAM|RUN|LABEL|GOTO|OPTIONS|IMPORT)\s*(?:\+=|:=|=)\s*"([^"]*)"$') +args_assign = re.compile('(ATTR|ENV|IMPORT|RUN){([a-zA-Z0-9/_.*%-]+)}\s*(=|\+=)\s*"([^"]*)"$') + +result = 0 +buffer = '' +for path in rules_files: + lineno = 0 + for line in open(path): + lineno += 1 + + # handle line continuation + if line.endswith('\\\n'): + buffer += line[:-2] + continue + else: + line = buffer + line + buffer = '' + + # filter out comments and empty lines + line = line.strip() + if not line or line.startswith('#'): + continue + + for clause in line.split(','): + clause = clause.strip() + if not (no_args_tests.match(clause) or args_tests.match(clause) or + no_args_assign.match(clause) or args_assign.match(clause)): + + print('Invalid line %s:%i: %s' % (path, lineno, line)) + print(' clause: %s' % clause) + print('') + result = 1 + break + +sys.exit(result) diff --git a/test/sched_idle_bad.service b/test/sched_idle_bad.service new file mode 100644 index 0000000000..589a87ccfd --- /dev/null +++ b/test/sched_idle_bad.service @@ -0,0 +1,6 @@ +[Unit] +Description=Bad sched priority for Idle + +[Service] +ExecStart=/bin/true +CPUSchedulingPriority=1 diff --git a/test/sched_idle_ok.service b/test/sched_idle_ok.service new file mode 100644 index 0000000000..262ef3e319 --- /dev/null +++ b/test/sched_idle_ok.service @@ -0,0 +1,6 @@ +[Unit] +Description=Sched idle with prio 0 + +[Service] +ExecStart=/bin/true +CPUSchedulingPriority=0 diff --git a/test/sched_rr_bad.service b/test/sched_rr_bad.service new file mode 100644 index 0000000000..0be534a546 --- /dev/null +++ b/test/sched_rr_bad.service @@ -0,0 +1,8 @@ +[Unit] +Description=Bad sched priority for RR + +[Service] +ExecStart=/bin/true +CPUSchedulingPolicy=rr +CPUSchedulingPriority=0 +CPUSchedulingPriority=100 diff --git a/test/sched_rr_change.service b/test/sched_rr_change.service new file mode 100644 index 0000000000..b3e3a000f8 --- /dev/null +++ b/test/sched_rr_change.service @@ -0,0 +1,9 @@ +[Unit] +Description=Change prio + +[Service] +ExecStart=/bin/true +CPUSchedulingPolicy=rr +CPUSchedulingPriority=1 +CPUSchedulingPriority=2 +CPUSchedulingPriority=99 diff --git a/test/sched_rr_ok.service b/test/sched_rr_ok.service new file mode 100644 index 0000000000..b88adc5434 --- /dev/null +++ b/test/sched_rr_ok.service @@ -0,0 +1,6 @@ +[Unit] +Description=Default prio for RR + +[Service] +ExecStart=/bin/true +CPUSchedulingPolicy=rr diff --git a/test/shutdown.target b/test/shutdown.target new file mode 120000 index 0000000000..1a3c2eec84 --- /dev/null +++ b/test/shutdown.target @@ -0,0 +1 @@ +../units/shutdown.target
\ No newline at end of file diff --git a/test/sleep.service b/test/sleep.service new file mode 100644 index 0000000000..946c44b621 --- /dev/null +++ b/test/sleep.service @@ -0,0 +1,6 @@ +[Unit] +Description=Sleep for 1 minute + +[Service] +Type=oneshot +ExecStart=/bin/sleep 60 diff --git a/test/sockets.target b/test/sockets.target new file mode 120000 index 0000000000..8ff86a0775 --- /dev/null +++ b/test/sockets.target @@ -0,0 +1 @@ +../units/sockets.target
\ No newline at end of file diff --git a/test/son.service b/test/son.service new file mode 100644 index 0000000000..50bb96a941 --- /dev/null +++ b/test/son.service @@ -0,0 +1,8 @@ +[Unit] +Description=Son Service + +[Service] +Slice=parent.slice +Type=oneshot +ExecStart=/bin/true +CPUShares=100 diff --git a/test/splash.bmp b/test/splash.bmp Binary files differnew file mode 100644 index 0000000000..27247f7a22 --- /dev/null +++ b/test/splash.bmp diff --git a/test/sys.tar.xz b/test/sys.tar.xz Binary files differnew file mode 100644 index 0000000000..49ee8027b2 --- /dev/null +++ b/test/sys.tar.xz diff --git a/test/sysinit.target b/test/sysinit.target new file mode 120000 index 0000000000..3301338185 --- /dev/null +++ b/test/sysinit.target @@ -0,0 +1 @@ +../units/sysinit.target
\ No newline at end of file diff --git a/test/sysv-generator-test.py b/test/sysv-generator-test.py new file mode 100755 index 0000000000..838dd57a6f --- /dev/null +++ b/test/sysv-generator-test.py @@ -0,0 +1,428 @@ +#!/usr/bin/python +# +# systemd-sysv-generator integration test +# +# (C) 2015 Canonical Ltd. +# Author: Martin Pitt <martin.pitt@ubuntu.com> +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. + +import unittest +import sys +import os +import subprocess +import tempfile +import shutil +from glob import glob +import collections + +try: + from configparser import RawConfigParser +except ImportError: + # python 2 + from ConfigParser import RawConfigParser + +sysv_generator = os.path.join(os.environ.get('builddir', '.'), 'systemd-sysv-generator') + +class MultiDict(collections.OrderedDict): + def __setitem__(self, key, value): + if isinstance(value, list) and key in self: + self[key].extend(value) + else: + super(MultiDict, self).__setitem__(key, value) + +class SysvGeneratorTest(unittest.TestCase): + def setUp(self): + self.workdir = tempfile.mkdtemp(prefix='sysv-gen-test.') + self.init_d_dir = os.path.join(self.workdir, 'init.d') + os.mkdir(self.init_d_dir) + self.rcnd_dir = self.workdir + self.unit_dir = os.path.join(self.workdir, 'systemd') + os.mkdir(self.unit_dir) + self.out_dir = os.path.join(self.workdir, 'output') + os.mkdir(self.out_dir) + + def tearDown(self): + shutil.rmtree(self.workdir) + + # + # Helper methods + # + + def run_generator(self, expect_error=False): + '''Run sysv-generator. + + Fail if stderr contains any "Fail", unless expect_error is True. + Return (stderr, filename -> ConfigParser) pair with ouput to stderr and + parsed generated units. + ''' + env = os.environ.copy() + env['SYSTEMD_LOG_LEVEL'] = 'debug' + env['SYSTEMD_LOG_TARGET'] = 'console' + env['SYSTEMD_SYSVINIT_PATH'] = self.init_d_dir + env['SYSTEMD_SYSVRCND_PATH'] = self.rcnd_dir + env['SYSTEMD_UNIT_PATH'] = self.unit_dir + gen = subprocess.Popen( + [sysv_generator, 'ignored', 'ignored', self.out_dir], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True, env=env) + (out, err) = gen.communicate() + if not expect_error: + self.assertFalse('Fail' in err, err) + self.assertEqual(gen.returncode, 0, err) + + results = {} + for service in glob(self.out_dir + '/*.service'): + if os.path.islink(service): + continue + try: + # for python3 we need here strict=False to parse multiple + # lines with the same key + cp = RawConfigParser(dict_type=MultiDict, strict=False) + except TypeError: + # RawConfigParser in python2 does not have the strict option + # but it allows multiple lines with the same key by default + cp = RawConfigParser(dict_type=MultiDict) + cp.optionxform = lambda o: o # don't lower-case option names + with open(service) as f: + cp.readfp(f) + results[os.path.basename(service)] = cp + + return (err, results) + + def add_sysv(self, fname, keys, enable=False, prio=1): + '''Create a SysV init script with the given keys in the LSB header + + There are sensible default values for all fields. + If enable is True, links will be created in the rcN.d dirs. In that + case, the priority can be given with "prio" (default to 1). + + Return path of generated script. + ''' + name_without_sh = fname.endswith('.sh') and fname[:-3] or fname + keys.setdefault('Provides', name_without_sh) + keys.setdefault('Required-Start', '$local_fs') + keys.setdefault('Required-Stop', keys['Required-Start']) + keys.setdefault('Default-Start', '2 3 4 5') + keys.setdefault('Default-Stop', '0 1 6') + keys.setdefault('Short-Description', 'test %s service' % + name_without_sh) + keys.setdefault('Description', 'long description for test %s service' % + name_without_sh) + script = os.path.join(self.init_d_dir, fname) + with open(script, 'w') as f: + f.write('#!/bin/init-d-interpreter\n### BEGIN INIT INFO\n') + for k, v in keys.items(): + if v is not None: + f.write('#%20s %s\n' % (k + ':', v)) + f.write('### END INIT INFO\ncode --goes here\n') + os.chmod(script, 0o755) + + if enable: + def make_link(prefix, runlevel): + d = os.path.join(self.rcnd_dir, 'rc%s.d' % runlevel) + if not os.path.isdir(d): + os.mkdir(d) + os.symlink('../init.d/' + fname, os.path.join(d, prefix + fname)) + + for rl in keys['Default-Start'].split(): + make_link('S%02i' % prio, rl) + for rl in keys['Default-Stop'].split(): + make_link('K%02i' % (99 - prio), rl) + + return script + + def assert_enabled(self, unit, targets): + '''assert that a unit is enabled in precisely the given targets''' + + all_targets = ['multi-user', 'graphical'] + + # should be enabled + for target in all_targets: + link = os.path.join(self.out_dir, '%s.target.wants' % target, unit) + if target in targets: + unit_file = os.readlink(link) + self.assertTrue(os.path.exists(unit_file)) + self.assertEqual(os.path.basename(unit_file), unit) + else: + self.assertFalse(os.path.exists(link), + '%s unexpectedly exists' % link) + + # + # test cases + # + + def test_nothing(self): + '''no input files''' + + results = self.run_generator()[1] + self.assertEqual(results, {}) + self.assertEqual(os.listdir(self.out_dir), []) + + def test_simple_disabled(self): + '''simple service without dependencies, disabled''' + + self.add_sysv('foo', {}, enable=False) + err, results = self.run_generator() + self.assertEqual(len(results), 1) + + # no enablement links or other stuff + self.assertEqual(os.listdir(self.out_dir), ['foo.service']) + + s = results['foo.service'] + self.assertEqual(s.sections(), ['Unit', 'Service']) + self.assertEqual(s.get('Unit', 'Description'), 'LSB: test foo service') + # $local_fs does not need translation, don't expect any dependency + # fields here + self.assertEqual(set(s.options('Unit')), + set(['Documentation', 'SourcePath', 'Description'])) + + self.assertEqual(s.get('Service', 'Type'), 'forking') + init_script = os.path.join(self.init_d_dir, 'foo') + self.assertEqual(s.get('Service', 'ExecStart'), + '%s start' % init_script) + self.assertEqual(s.get('Service', 'ExecStop'), + '%s stop' % init_script) + + self.assertNotIn('Overwriting', err) + + def test_simple_enabled_all(self): + '''simple service without dependencies, enabled in all runlevels''' + + self.add_sysv('foo', {}, enable=True) + err, results = self.run_generator() + self.assertEqual(list(results), ['foo.service']) + self.assert_enabled('foo.service', ['multi-user', 'graphical']) + self.assertNotIn('Overwriting', err) + + def test_simple_escaped(self): + '''simple service without dependencies, that requires escaping the name''' + + self.add_sysv('foo+', {}) + self.add_sysv('foo-admin', {}) + err, results = self.run_generator() + self.assertEqual(set(results), {'foo-admin.service', 'foo\\x2b.service'}) + self.assertNotIn('Overwriting', err) + + def test_simple_enabled_some(self): + '''simple service without dependencies, enabled in some runlevels''' + + self.add_sysv('foo', {'Default-Start': '2 4'}, enable=True) + err, results = self.run_generator() + self.assertEqual(list(results), ['foo.service']) + self.assert_enabled('foo.service', ['multi-user']) + + def test_lsb_macro_dep_single(self): + '''single LSB macro dependency: $network''' + + self.add_sysv('foo', {'Required-Start': '$network'}) + s = self.run_generator()[1]['foo.service'] + self.assertEqual(set(s.options('Unit')), + set(['Documentation', 'SourcePath', 'Description', 'After', 'Wants'])) + self.assertEqual(s.get('Unit', 'After'), 'network-online.target') + self.assertEqual(s.get('Unit', 'Wants'), 'network-online.target') + + def test_lsb_macro_dep_multi(self): + '''multiple LSB macro dependencies''' + + self.add_sysv('foo', {'Required-Start': '$named $portmap'}) + s = self.run_generator()[1]['foo.service'] + self.assertEqual(set(s.options('Unit')), + set(['Documentation', 'SourcePath', 'Description', 'After'])) + self.assertEqual(s.get('Unit', 'After').split(), ['nss-lookup.target', 'rpcbind.target']) + + def test_lsb_deps(self): + '''LSB header dependencies to other services''' + + # also give symlink priorities here; they should be ignored + self.add_sysv('foo', {'Required-Start': 'must1 must2', + 'Should-Start': 'may1 ne_may2'}, + enable=True, prio=40) + self.add_sysv('must1', {}, enable=True, prio=10) + self.add_sysv('must2', {}, enable=True, prio=15) + self.add_sysv('may1', {}, enable=True, prio=20) + # do not create ne_may2 + err, results = self.run_generator() + self.assertEqual(sorted(results), + ['foo.service', 'may1.service', 'must1.service', 'must2.service']) + + # foo should depend on all of them + self.assertEqual(sorted(results['foo.service'].get('Unit', 'After').split()), + ['may1.service', 'must1.service', 'must2.service', 'ne_may2.service']) + + # other services should not depend on each other + self.assertFalse(results['must1.service'].has_option('Unit', 'After')) + self.assertFalse(results['must2.service'].has_option('Unit', 'After')) + self.assertFalse(results['may1.service'].has_option('Unit', 'After')) + + def test_symlink_prio_deps(self): + '''script without LSB headers use rcN.d priority''' + + # create two init.d scripts without LSB header and enable them with + # startup priorities + for prio, name in [(10, 'provider'), (15, 'consumer')]: + with open(os.path.join(self.init_d_dir, name), 'w') as f: + f.write('#!/bin/init-d-interpreter\ncode --goes here\n') + os.fchmod(f.fileno(), 0o755) + + d = os.path.join(self.rcnd_dir, 'rc2.d') + if not os.path.isdir(d): + os.mkdir(d) + os.symlink('../init.d/' + name, os.path.join(d, 'S%02i%s' % (prio, name))) + + err, results = self.run_generator() + self.assertEqual(sorted(results), ['consumer.service', 'provider.service']) + self.assertFalse(results['provider.service'].has_option('Unit', 'After')) + self.assertEqual(results['consumer.service'].get('Unit', 'After'), + 'provider.service') + + def test_multiple_provides(self): + '''multiple Provides: names''' + + self.add_sysv('foo', {'Provides': 'foo bar baz'}) + err, results = self.run_generator() + self.assertEqual(list(results), ['foo.service']) + self.assertEqual(set(results['foo.service'].options('Unit')), + set(['Documentation', 'SourcePath', 'Description'])) + # should create symlinks for the alternative names + for f in ['bar.service', 'baz.service']: + self.assertEqual(os.readlink(os.path.join(self.out_dir, f)), + 'foo.service') + self.assertNotIn('Overwriting', err) + + def test_provides_escaped(self): + '''a script that Provides: a name that requires escaping''' + + self.add_sysv('foo', {'Provides': 'foo foo+'}) + err, results = self.run_generator() + self.assertEqual(list(results), ['foo.service']) + self.assertEqual(os.readlink(os.path.join(self.out_dir, 'foo\\x2b.service')), + 'foo.service') + self.assertNotIn('Overwriting', err) + + def test_same_provides_in_multiple_scripts(self): + '''multiple init.d scripts provide the same name''' + + self.add_sysv('foo', {'Provides': 'foo common'}, enable=True, prio=1) + self.add_sysv('bar', {'Provides': 'bar common'}, enable=True, prio=2) + err, results = self.run_generator() + self.assertEqual(sorted(results), ['bar.service', 'foo.service']) + # should create symlink for the alternative name for either unit + self.assertIn(os.readlink(os.path.join(self.out_dir, 'common.service')), + ['foo.service', 'bar.service']) + + def test_provide_other_script(self): + '''init.d scripts provides the name of another init.d script''' + + self.add_sysv('foo', {'Provides': 'foo bar'}, enable=True) + self.add_sysv('bar', {'Provides': 'bar'}, enable=True) + err, results = self.run_generator() + self.assertEqual(sorted(results), ['bar.service', 'foo.service']) + # we do expect an overwrite here, bar.service should overwrite the + # alias link from foo.service + self.assertIn('Overwriting', err) + + def test_nonexecutable_script(self): + '''ignores non-executable init.d script''' + + os.chmod(self.add_sysv('foo', {}), 0o644) + err, results = self.run_generator() + self.assertEqual(results, {}) + + def test_sh_suffix(self): + '''init.d script with .sh suffix''' + + self.add_sysv('foo.sh', {}, enable=True) + err, results = self.run_generator() + s = results['foo.service'] + + self.assertEqual(s.sections(), ['Unit', 'Service']) + # should not have a .sh + self.assertEqual(s.get('Unit', 'Description'), 'LSB: test foo service') + + # calls correct script with .sh + init_script = os.path.join(self.init_d_dir, 'foo.sh') + self.assertEqual(s.get('Service', 'ExecStart'), + '%s start' % init_script) + self.assertEqual(s.get('Service', 'ExecStop'), + '%s stop' % init_script) + + self.assert_enabled('foo.service', ['multi-user', 'graphical']) + + def test_sh_suffix_with_provides(self): + '''init.d script with .sh suffix and Provides:''' + + self.add_sysv('foo.sh', {'Provides': 'foo bar'}) + err, results = self.run_generator() + # ensure we don't try to create a symlink to itself + self.assertNotIn('itself', err) + self.assertEqual(list(results), ['foo.service']) + self.assertEqual(results['foo.service'].get('Unit', 'Description'), + 'LSB: test foo service') + + # should create symlink for the alternative name + self.assertEqual(os.readlink(os.path.join(self.out_dir, 'bar.service')), + 'foo.service') + + def test_hidden_files(self): + '''init.d script with hidden file suffix''' + + script = self.add_sysv('foo', {}, enable=True) + # backup files (not enabled in rcN.d/) + shutil.copy(script, script + '.dpkg-new') + shutil.copy(script, script + '.dpkg-dist') + shutil.copy(script, script + '.swp') + shutil.copy(script, script + '.rpmsave') + + err, results = self.run_generator() + self.assertEqual(list(results), ['foo.service']) + + self.assert_enabled('foo.service', ['multi-user', 'graphical']) + + def test_backup_file(self): + '''init.d script with backup file''' + + script = self.add_sysv('foo', {}, enable=True) + # backup files (not enabled in rcN.d/) + shutil.copy(script, script + '.bak') + shutil.copy(script, script + '.old') + shutil.copy(script, script + '.tmp') + shutil.copy(script, script + '.new') + + err, results = self.run_generator() + print(err) + self.assertEqual(sorted(results), ['foo.service', 'foo.tmp.service']) + + # ensure we don't try to create a symlink to itself + self.assertNotIn('itself', err) + + self.assert_enabled('foo.service', ['multi-user', 'graphical']) + self.assert_enabled('foo.bak.service', []) + self.assert_enabled('foo.old.service', []) + + def test_existing_native_unit(self): + '''existing native unit''' + + with open(os.path.join(self.unit_dir, 'foo.service'), 'w') as f: + f.write('[Unit]\n') + + self.add_sysv('foo.sh', {'Provides': 'foo bar'}, enable=True) + err, results = self.run_generator() + self.assertEqual(list(results), []) + # no enablement or alias links, as native unit is disabled + self.assertEqual(os.listdir(self.out_dir), []) + + +if __name__ == '__main__': + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) diff --git a/test/test-execute/exec-capabilityambientset-merge-nfsnobody.service b/test/test-execute/exec-capabilityambientset-merge-nfsnobody.service new file mode 100644 index 0000000000..00bec581b5 --- /dev/null +++ b/test/test-execute/exec-capabilityambientset-merge-nfsnobody.service @@ -0,0 +1,9 @@ +[Unit] +Description=Test for AmbientCapabilities + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000003000"' +Type=oneshot +User=nfsnobody +AmbientCapabilities=CAP_NET_ADMIN +AmbientCapabilities=CAP_NET_RAW diff --git a/test/test-execute/exec-capabilityambientset-merge.service b/test/test-execute/exec-capabilityambientset-merge.service new file mode 100644 index 0000000000..64964380e2 --- /dev/null +++ b/test/test-execute/exec-capabilityambientset-merge.service @@ -0,0 +1,9 @@ +[Unit] +Description=Test for AmbientCapabilities + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000003000"' +Type=oneshot +User=nobody +AmbientCapabilities=CAP_NET_ADMIN +AmbientCapabilities=CAP_NET_RAW diff --git a/test/test-execute/exec-capabilityambientset-nfsnobody.service b/test/test-execute/exec-capabilityambientset-nfsnobody.service new file mode 100644 index 0000000000..614cfdd584 --- /dev/null +++ b/test/test-execute/exec-capabilityambientset-nfsnobody.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for AmbientCapabilities + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000003000"' +Type=oneshot +User=nfsnobody +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW diff --git a/test/test-execute/exec-capabilityambientset.service b/test/test-execute/exec-capabilityambientset.service new file mode 100644 index 0000000000..d63f884ef8 --- /dev/null +++ b/test/test-execute/exec-capabilityambientset.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for AmbientCapabilities + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000003000"' +Type=oneshot +User=nobody +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW diff --git a/test/test-execute/exec-capabilityboundingset-invert.service b/test/test-execute/exec-capabilityboundingset-invert.service new file mode 100644 index 0000000000..fd5d248702 --- /dev/null +++ b/test/test-execute/exec-capabilityboundingset-invert.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for CapabilityBoundingSet + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "^Bounding set .*cap_chown"); test -z "$$c"' +Type=oneshot +CapabilityBoundingSet=~CAP_CHOWN diff --git a/test/test-execute/exec-capabilityboundingset-merge.service b/test/test-execute/exec-capabilityboundingset-merge.service new file mode 100644 index 0000000000..5c7fcaf437 --- /dev/null +++ b/test/test-execute/exec-capabilityboundingset-merge.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for CapabilityBoundingSet + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_chown,cap_fowner,cap_kill"' +Type=oneshot +CapabilityBoundingSet=CAP_FOWNER +CapabilityBoundingSet=CAP_KILL CAP_CHOWN diff --git a/test/test-execute/exec-capabilityboundingset-reset.service b/test/test-execute/exec-capabilityboundingset-reset.service new file mode 100644 index 0000000000..d7d3320204 --- /dev/null +++ b/test/test-execute/exec-capabilityboundingset-reset.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for CapabilityBoundingSet + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set ="' +Type=oneshot +CapabilityBoundingSet=CAP_FOWNER CAP_KILL +CapabilityBoundingSet= diff --git a/test/test-execute/exec-capabilityboundingset-simple.service b/test/test-execute/exec-capabilityboundingset-simple.service new file mode 100644 index 0000000000..bf1a7f575a --- /dev/null +++ b/test/test-execute/exec-capabilityboundingset-simple.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for CapabilityBoundingSet + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(capsh --print | grep "Bounding set "); test "$$c" = "Bounding set =cap_fowner,cap_kill"' +Type=oneshot +CapabilityBoundingSet=CAP_FOWNER CAP_KILL diff --git a/test/test-execute/exec-dynamicuser-fixeduser-one-supplementarygroup.service b/test/test-execute/exec-dynamicuser-fixeduser-one-supplementarygroup.service new file mode 100644 index 0000000000..de1a6e7303 --- /dev/null +++ b/test/test-execute/exec-dynamicuser-fixeduser-one-supplementarygroup.service @@ -0,0 +1,9 @@ +[Unit] +Description=Test DynamicUser with User= and SupplementaryGroups= + +[Service] +ExecStart=/bin/sh -x -c 'test "$$(id -G)" = "1" && test "$$(id -g)" = "1" && test "$$(id -u)" = "1"' +Type=oneshot +User=1 +DynamicUser=yes +SupplementaryGroups=1 diff --git a/test/test-execute/exec-dynamicuser-fixeduser.service b/test/test-execute/exec-dynamicuser-fixeduser.service new file mode 100644 index 0000000000..1d84af02ed --- /dev/null +++ b/test/test-execute/exec-dynamicuser-fixeduser.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test DynamicUser with User= + +[Service] +ExecStart=/bin/sh -x -c 'test "$$(id -G)" = "1" && test "$$(id -g)" = "1" && test "$$(id -u)" = "1"' +Type=oneshot +User=1 +DynamicUser=yes diff --git a/test/test-execute/exec-dynamicuser-supplementarygroups.service b/test/test-execute/exec-dynamicuser-supplementarygroups.service new file mode 100644 index 0000000000..a47b7fab78 --- /dev/null +++ b/test/test-execute/exec-dynamicuser-supplementarygroups.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test DynamicUser with SupplementaryGroups= + +[Service] +ExecStart=/bin/sh -x -c 'test "$$(id -G | cut -d " " --complement -f 1)" = "1 2 3"' +Type=oneshot +DynamicUser=yes +SupplementaryGroups=1 2 3 diff --git a/test/test-execute/exec-environment-empty.service b/test/test-execute/exec-environment-empty.service new file mode 100644 index 0000000000..9c92d4bc81 --- /dev/null +++ b/test/test-execute/exec-environment-empty.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for Environment + +[Service] +ExecStart=/bin/sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset"' +Type=oneshot +Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6" +Environment= diff --git a/test/test-execute/exec-environment-multiple.service b/test/test-execute/exec-environment-multiple.service new file mode 100644 index 0000000000..b9bc225635 --- /dev/null +++ b/test/test-execute/exec-environment-multiple.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for Environment + +[Service] +ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = foobar' +Type=oneshot +Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6" +Environment="VAR3=foobar" diff --git a/test/test-execute/exec-environment.service b/test/test-execute/exec-environment.service new file mode 100644 index 0000000000..06e77af220 --- /dev/null +++ b/test/test-execute/exec-environment.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for Environment + +[Service] +ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6"' +Type=oneshot +Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6" diff --git a/test/test-execute/exec-environmentfile.service b/test/test-execute/exec-environmentfile.service new file mode 100644 index 0000000000..f6b8462719 --- /dev/null +++ b/test/test-execute/exec-environmentfile.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for EnvironmentFile + +[Service] +ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6"' +Type=oneshot +EnvironmentFile=/tmp/test-exec_environmentfile.conf diff --git a/test/test-execute/exec-group-nfsnobody.service b/test/test-execute/exec-group-nfsnobody.service new file mode 100644 index 0000000000..e02100a869 --- /dev/null +++ b/test/test-execute/exec-group-nfsnobody.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for Group + +[Service] +ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "nfsnobody"' +Type=oneshot +Group=nfsnobody diff --git a/test/test-execute/exec-group.service b/test/test-execute/exec-group.service new file mode 100644 index 0000000000..be7c796912 --- /dev/null +++ b/test/test-execute/exec-group.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for Group + +[Service] +ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "nobody"' +Type=oneshot +Group=nobody diff --git a/test/test-execute/exec-ignoresigpipe-no.service b/test/test-execute/exec-ignoresigpipe-no.service new file mode 100644 index 0000000000..73addf5f05 --- /dev/null +++ b/test/test-execute/exec-ignoresigpipe-no.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for IgnoreSIGPIPE=no + +[Service] +ExecStart=/bin/sh -x -c 'kill -PIPE 0' +Type=oneshot +IgnoreSIGPIPE=no diff --git a/test/test-execute/exec-ignoresigpipe-yes.service b/test/test-execute/exec-ignoresigpipe-yes.service new file mode 100644 index 0000000000..f81c01719e --- /dev/null +++ b/test/test-execute/exec-ignoresigpipe-yes.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for IgnoreSIGPIPE=yes + +[Service] +ExecStart=/bin/sh -x -c 'kill -PIPE 0' +Type=oneshot +IgnoreSIGPIPE=yes diff --git a/test/test-execute/exec-inaccessiblepaths-mount-propagation.service b/test/test-execute/exec-inaccessiblepaths-mount-propagation.service new file mode 100644 index 0000000000..23c6ff3f93 --- /dev/null +++ b/test/test-execute/exec-inaccessiblepaths-mount-propagation.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test to make sure that InaccessiblePaths= disconnect mount propagation + +[Service] +InaccessiblePaths=-/i-dont-exist +ExecStart=/bin/sh -x -c 'mkdir -p /TEST; mount -t tmpfs tmpfs /TEST; grep TEST /proc/self/mountinfo && ! grep TEST /proc/$${PPID}/mountinfo && ! grep TEST /proc/1/mountinfo' +Type=oneshot diff --git a/test/test-execute/exec-ioschedulingclass-best-effort.service b/test/test-execute/exec-ioschedulingclass-best-effort.service new file mode 100644 index 0000000000..29bb8510b4 --- /dev/null +++ b/test/test-execute/exec-ioschedulingclass-best-effort.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for IOSchedulingClass=best-effort + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "best-effort"' +Type=oneshot +IOSchedulingClass=best-effort diff --git a/test/test-execute/exec-ioschedulingclass-idle.service b/test/test-execute/exec-ioschedulingclass-idle.service new file mode 100644 index 0000000000..87dbed14c1 --- /dev/null +++ b/test/test-execute/exec-ioschedulingclass-idle.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for IOSchedulingClass=idle + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "idle"' +Type=oneshot +IOSchedulingClass=idle diff --git a/test/test-execute/exec-ioschedulingclass-none.service b/test/test-execute/exec-ioschedulingclass-none.service new file mode 100644 index 0000000000..b6af122a1e --- /dev/null +++ b/test/test-execute/exec-ioschedulingclass-none.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for IOSchedulingClass=none + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "none"' +Type=oneshot +IOSchedulingClass=none diff --git a/test/test-execute/exec-ioschedulingclass-realtime.service b/test/test-execute/exec-ioschedulingclass-realtime.service new file mode 100644 index 0000000000..d920d5c687 --- /dev/null +++ b/test/test-execute/exec-ioschedulingclass-realtime.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for IOSchedulingClass=realtime + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(LC_ALL=C ionice); test "$${c%%:*}" = "realtime"' +Type=oneshot +IOSchedulingClass=realtime diff --git a/test/test-execute/exec-oomscoreadjust-negative.service b/test/test-execute/exec-oomscoreadjust-negative.service new file mode 100644 index 0000000000..2234c53c3f --- /dev/null +++ b/test/test-execute/exec-oomscoreadjust-negative.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for OOMScoreAdjust + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(cat /proc/self/oom_score_adj); test "$$c" -eq -100' +Type=oneshot +OOMScoreAdjust=-100 diff --git a/test/test-execute/exec-oomscoreadjust-positive.service b/test/test-execute/exec-oomscoreadjust-positive.service new file mode 100644 index 0000000000..456a8f80cf --- /dev/null +++ b/test/test-execute/exec-oomscoreadjust-positive.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for OOMScoreAdjust + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(cat /proc/self/oom_score_adj); test "$$c" -eq 100' +Type=oneshot +OOMScoreAdjust=100 diff --git a/test/test-execute/exec-passenvironment-absent.service b/test/test-execute/exec-passenvironment-absent.service new file mode 100644 index 0000000000..7d5e32a4eb --- /dev/null +++ b/test/test-execute/exec-passenvironment-absent.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for PassEnvironment with variables absent from the execution environment + +[Service] +ExecStart=/bin/sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset"' +Type=oneshot +PassEnvironment=VAR1 VAR2 VAR3 diff --git a/test/test-execute/exec-passenvironment-empty.service b/test/test-execute/exec-passenvironment-empty.service new file mode 100644 index 0000000000..c93c197c10 --- /dev/null +++ b/test/test-execute/exec-passenvironment-empty.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for PassEnvironment and erasing the variable list + +[Service] +ExecStart=/bin/sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2-unset}" = "unset" && test "$${VAR3-unset}" = "unset"' +Type=oneshot +PassEnvironment=VAR1 VAR2 VAR3 +PassEnvironment= diff --git a/test/test-execute/exec-passenvironment-repeated.service b/test/test-execute/exec-passenvironment-repeated.service new file mode 100644 index 0000000000..5e8c56f26a --- /dev/null +++ b/test/test-execute/exec-passenvironment-repeated.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for PassEnvironment with a variable name repeated + +[Service] +ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6"' +Type=oneshot +PassEnvironment=VAR1 VAR2 +PassEnvironment=VAR1 VAR3 diff --git a/test/test-execute/exec-passenvironment.service b/test/test-execute/exec-passenvironment.service new file mode 100644 index 0000000000..b4a9909682 --- /dev/null +++ b/test/test-execute/exec-passenvironment.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for PassEnvironment + +[Service] +ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6"' +Type=oneshot +PassEnvironment=VAR1 VAR2 VAR3 diff --git a/test/test-execute/exec-personality-aarch64.service b/test/test-execute/exec-personality-aarch64.service new file mode 100644 index 0000000000..40b6d95e3a --- /dev/null +++ b/test/test-execute/exec-personality-aarch64.service @@ -0,0 +1,7 @@ +Unit] +Description=Test for Personality=aarch64 + +[Service] +ExecStart=/bin/sh -c 'echo $(uname -m); exit $(test $(uname -m) = "aarch64")' +Type=oneshot +Personality=aarch64 diff --git a/test/test-execute/exec-personality-ppc64.service b/test/test-execute/exec-personality-ppc64.service new file mode 100644 index 0000000000..ccc2c8d83d --- /dev/null +++ b/test/test-execute/exec-personality-ppc64.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for Personality=ppc64 + +[Service] +ExecStart=/bin/sh -c 'echo $(uname -m); exit $(test $(uname -m) = "ppc64")' +Type=oneshot +Personality=ppc64 diff --git a/test/test-execute/exec-personality-ppc64le.service b/test/test-execute/exec-personality-ppc64le.service new file mode 100644 index 0000000000..2a7625087d --- /dev/null +++ b/test/test-execute/exec-personality-ppc64le.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for Personality=ppc64le + +[Service] +ExecStart=/bin/sh -c 'echo $(uname -m); exit $(test $(uname -m) = "ppc64le")' +Type=oneshot +Personality=ppc64le diff --git a/test/test-execute/exec-personality-s390.service b/test/test-execute/exec-personality-s390.service new file mode 100644 index 0000000000..89f7de89d0 --- /dev/null +++ b/test/test-execute/exec-personality-s390.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for Personality=s390 + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(uname -m); test "$$c" = "s390"' +Type=oneshot +Personality=s390 diff --git a/test/test-execute/exec-personality-x86-64.service b/test/test-execute/exec-personality-x86-64.service new file mode 100644 index 0000000000..433e69a6d1 --- /dev/null +++ b/test/test-execute/exec-personality-x86-64.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for Personality=x86-64 + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(uname -m); test "$$c" = "x86_64"' +Type=oneshot +Personality=x86-64 diff --git a/test/test-execute/exec-personality-x86.service b/test/test-execute/exec-personality-x86.service new file mode 100644 index 0000000000..a623a08cbe --- /dev/null +++ b/test/test-execute/exec-personality-x86.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for Personality=x86 + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(uname -m); test "$$c" = "i686"' +Type=oneshot +Personality=x86 diff --git a/test/test-execute/exec-privatedevices-no-capability-mknod.service b/test/test-execute/exec-privatedevices-no-capability-mknod.service new file mode 100644 index 0000000000..6d39469da8 --- /dev/null +++ b/test/test-execute/exec-privatedevices-no-capability-mknod.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test CAP_MKNOD capability for PrivateDevices=no + +[Service] +PrivateDevices=no +ExecStart=/bin/sh -x -c 'capsh --print | grep cap_mknod' +Type=oneshot diff --git a/test/test-execute/exec-privatedevices-no-capability-sys-rawio.service b/test/test-execute/exec-privatedevices-no-capability-sys-rawio.service new file mode 100644 index 0000000000..e7f529c44c --- /dev/null +++ b/test/test-execute/exec-privatedevices-no-capability-sys-rawio.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test CAP_SYS_RAWIO capability for PrivateDevices=no + +[Service] +PrivateDevices=no +ExecStart=/bin/sh -x -c 'capsh --print | grep cap_sys_rawio' +Type=oneshot diff --git a/test/test-execute/exec-privatedevices-no.service b/test/test-execute/exec-privatedevices-no.service new file mode 100644 index 0000000000..77aeb951b5 --- /dev/null +++ b/test/test-execute/exec-privatedevices-no.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for PrivateDev=no + +[Service] +ExecStart=/bin/sh -x -c 'test -c /dev/mem' +Type=oneshot +PrivateDevices=no diff --git a/test/test-execute/exec-privatedevices-yes-capability-mknod.service b/test/test-execute/exec-privatedevices-yes-capability-mknod.service new file mode 100644 index 0000000000..fb1fc2875a --- /dev/null +++ b/test/test-execute/exec-privatedevices-yes-capability-mknod.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test CAP_MKNOD capability for PrivateDevices=yes + +[Service] +PrivateDevices=yes +ExecStart=/bin/sh -x -c '! capsh --print | grep cap_mknod' +Type=oneshot diff --git a/test/test-execute/exec-privatedevices-yes-capability-sys-rawio.service b/test/test-execute/exec-privatedevices-yes-capability-sys-rawio.service new file mode 100644 index 0000000000..cebc493a7a --- /dev/null +++ b/test/test-execute/exec-privatedevices-yes-capability-sys-rawio.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test CAP_SYS_RAWIO capability for PrivateDevices=yes + +[Service] +PrivateDevices=yes +ExecStart=/bin/sh -x -c '! capsh --print | grep cap_sys_rawio' +Type=oneshot diff --git a/test/test-execute/exec-privatedevices-yes.service b/test/test-execute/exec-privatedevices-yes.service new file mode 100644 index 0000000000..ab958b646e --- /dev/null +++ b/test/test-execute/exec-privatedevices-yes.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for PrivateDev=yes + +[Service] +ExecStart=/bin/sh -c 'test ! -c /dev/mem' +Type=oneshot +PrivateDevices=yes diff --git a/test/test-execute/exec-privatenetwork-yes.service b/test/test-execute/exec-privatenetwork-yes.service new file mode 100644 index 0000000000..3df543ec93 --- /dev/null +++ b/test/test-execute/exec-privatenetwork-yes.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for PrivateNetwork + +[Service] +ExecStart=/bin/sh -x -c 'i=$$(ip link | grep ": " | grep -v ": lo:"); test -z "$$i"' +Type=oneshot +PrivateNetwork=yes diff --git a/test/test-execute/exec-privatetmp-no.service b/test/test-execute/exec-privatetmp-no.service new file mode 100644 index 0000000000..59f60f4755 --- /dev/null +++ b/test/test-execute/exec-privatetmp-no.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for PrivateTmp=no + +[Service] +ExecStart=/bin/sh -x -c 'test -f /tmp/test-exec_privatetmp' +Type=oneshot +PrivateTmp=no diff --git a/test/test-execute/exec-privatetmp-yes.service b/test/test-execute/exec-privatetmp-yes.service new file mode 100644 index 0000000000..907c291b81 --- /dev/null +++ b/test/test-execute/exec-privatetmp-yes.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for PrivateTmp=yes + +[Service] +ExecStart=/bin/sh -x -c 'test ! -f /tmp/test-exec_privatetmp' +Type=oneshot +PrivateTmp=yes diff --git a/test/test-execute/exec-protectkernelmodules-no-capabilities.service b/test/test-execute/exec-protectkernelmodules-no-capabilities.service new file mode 100644 index 0000000000..b2f2cd6b8a --- /dev/null +++ b/test/test-execute/exec-protectkernelmodules-no-capabilities.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test CAP_SYS_MODULE ProtectKernelModules=no + +[Service] +ProtectKernelModules=no +ExecStart=/bin/sh -x -c 'capsh --print | grep cap_sys_module' +Type=oneshot diff --git a/test/test-execute/exec-protectkernelmodules-yes-capabilities.service b/test/test-execute/exec-protectkernelmodules-yes-capabilities.service new file mode 100644 index 0000000000..84bf39be56 --- /dev/null +++ b/test/test-execute/exec-protectkernelmodules-yes-capabilities.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test CAP_SYS_MODULE for ProtectKernelModules=yes + +[Service] +ProtectKernelModules=yes +ExecStart=/bin/sh -x -c '! capsh --print | grep cap_sys_module' +Type=oneshot diff --git a/test/test-execute/exec-protectkernelmodules-yes-mount-propagation.service b/test/test-execute/exec-protectkernelmodules-yes-mount-propagation.service new file mode 100644 index 0000000000..e438783df3 --- /dev/null +++ b/test/test-execute/exec-protectkernelmodules-yes-mount-propagation.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test to make sure that passing ProtectKernelModules=yes disconnect mount propagation + +[Service] +ProtectKernelModules=yes +ExecStart=/bin/sh -x -c 'mkdir -p /TEST; mount -t tmpfs tmpfs /TEST; grep TEST /proc/self/mountinfo && ! grep TEST /proc/$${PPID}/mountinfo && ! grep TEST /proc/1/mountinfo' +Type=oneshot diff --git a/test/test-execute/exec-readonlypaths-mount-propagation.service b/test/test-execute/exec-readonlypaths-mount-propagation.service new file mode 100644 index 0000000000..237cbb2efb --- /dev/null +++ b/test/test-execute/exec-readonlypaths-mount-propagation.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test to make sure that passing ReadOnlyPaths= disconnect mount propagation + +[Service] +ReadOnlyPaths=-/i-dont-exist +ExecStart=/bin/sh -x -c 'mkdir -p /TEST; mount -t tmpfs tmpfs /TEST; grep TEST /proc/self/mountinfo && ! grep TEST /proc/$${PPID}/mountinfo && ! grep TEST /proc/1/mountinfo' +Type=oneshot diff --git a/test/test-execute/exec-readonlypaths.service b/test/test-execute/exec-readonlypaths.service new file mode 100644 index 0000000000..6866fdc700 --- /dev/null +++ b/test/test-execute/exec-readonlypaths.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for ReadOnlyPaths= + +[Service] +ReadOnlyPaths=/etc -/i-dont-exist /usr +ExecStart=/bin/sh -x -c 'test ! -w /etc && test ! -w /usr && test ! -e /i-dont-exist && test -w /var' +Type=oneshot diff --git a/test/test-execute/exec-readwritepaths-mount-propagation.service b/test/test-execute/exec-readwritepaths-mount-propagation.service new file mode 100644 index 0000000000..466ce6c747 --- /dev/null +++ b/test/test-execute/exec-readwritepaths-mount-propagation.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test to make sure that passing ReadWritePaths= disconnect mount propagation + +[Service] +ReadWritePaths=-/i-dont-exist +ExecStart=/bin/sh -x -c 'mkdir -p /TEST; mount -t tmpfs tmpfs /TEST; grep TEST /proc/self/mountinfo && ! grep TEST /proc/$${PPID}/mountinfo && ! grep TEST /proc/1/mountinfo' +Type=oneshot diff --git a/test/test-execute/exec-runtimedirectory-mode.service b/test/test-execute/exec-runtimedirectory-mode.service new file mode 100644 index 0000000000..842721d5c2 --- /dev/null +++ b/test/test-execute/exec-runtimedirectory-mode.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for RuntimeDirectoryMode + +[Service] +ExecStart=/bin/sh -x -c 'mode=$$(stat -c %%a /tmp/test-exec_runtimedirectory-mode); test "$$mode" = "750"' +Type=oneshot +RuntimeDirectory=test-exec_runtimedirectory-mode +RuntimeDirectoryMode=0750 diff --git a/test/test-execute/exec-runtimedirectory-owner-nfsnobody.service b/test/test-execute/exec-runtimedirectory-owner-nfsnobody.service new file mode 100644 index 0000000000..e962af8a4b --- /dev/null +++ b/test/test-execute/exec-runtimedirectory-owner-nfsnobody.service @@ -0,0 +1,9 @@ +[Unit] +Description=Test for RuntimeDirectory owner (must not be the default group of the user if Group is set) + +[Service] +ExecStart=/bin/sh -x -c 'group=$$(stat -c %%G /tmp/test-exec_runtimedirectory-owner); test "$$group" = "nfsnobody"' +Type=oneshot +Group=nfsnobody +User=root +RuntimeDirectory=test-exec_runtimedirectory-owner diff --git a/test/test-execute/exec-runtimedirectory-owner.service b/test/test-execute/exec-runtimedirectory-owner.service new file mode 100644 index 0000000000..1f438c182e --- /dev/null +++ b/test/test-execute/exec-runtimedirectory-owner.service @@ -0,0 +1,9 @@ +[Unit] +Description=Test for RuntimeDirectory owner (must not be the default group of the user if Group is set) + +[Service] +ExecStart=/bin/sh -x -c 'group=$$(stat -c %%G /tmp/test-exec_runtimedirectory-owner); test "$$group" = "nobody"' +Type=oneshot +Group=nobody +User=root +RuntimeDirectory=test-exec_runtimedirectory-owner diff --git a/test/test-execute/exec-runtimedirectory.service b/test/test-execute/exec-runtimedirectory.service new file mode 100644 index 0000000000..ec46c9d49b --- /dev/null +++ b/test/test-execute/exec-runtimedirectory.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for RuntimeDirectory + +[Service] +ExecStart=/bin/sh -x -c 'test -d /tmp/test-exec_runtimedirectory' +Type=oneshot +RuntimeDirectory=test-exec_runtimedirectory diff --git a/test/test-execute/exec-spec-interpolation.service b/test/test-execute/exec-spec-interpolation.service new file mode 100644 index 0000000000..3e62662aa9 --- /dev/null +++ b/test/test-execute/exec-spec-interpolation.service @@ -0,0 +1,6 @@ +[Unit] +Description=https://github.com/systemd/systemd/issues/2637 + +[Service] +Type=oneshot +ExecStart=/bin/sh -x -c "perl -e 'exit(!(qq{%%U} eq qq{\\x25U}))'" diff --git a/test/test-execute/exec-supplementarygroups-multiple-groups-default-group-user.service b/test/test-execute/exec-supplementarygroups-multiple-groups-default-group-user.service new file mode 100644 index 0000000000..a49c9d26a1 --- /dev/null +++ b/test/test-execute/exec-supplementarygroups-multiple-groups-default-group-user.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for Supplementary Group with multiple groups without Group and User + +[Service] +ExecStart=/bin/sh -x -c 'test "$$(id -G)" = "0 1 2 3" && test "$$(id -g)" = "0" && test "$$(id -u)" = "0"' +Type=oneshot +SupplementaryGroups=1 2 3 diff --git a/test/test-execute/exec-supplementarygroups-multiple-groups-withgid.service b/test/test-execute/exec-supplementarygroups-multiple-groups-withgid.service new file mode 100644 index 0000000000..5c62c1d639 --- /dev/null +++ b/test/test-execute/exec-supplementarygroups-multiple-groups-withgid.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for Supplementary Group with multiple groups and Group=1 + +[Service] +ExecStart=/bin/sh -x -c 'test "$$(id -G)" = "1 2 3" && test "$$(id -g)" = "1" && test "$$(id -u)" = "0"' +Type=oneshot +Group=1 +SupplementaryGroups=1 2 3 diff --git a/test/test-execute/exec-supplementarygroups-multiple-groups-withuid.service b/test/test-execute/exec-supplementarygroups-multiple-groups-withuid.service new file mode 100644 index 0000000000..00523e383b --- /dev/null +++ b/test/test-execute/exec-supplementarygroups-multiple-groups-withuid.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for Supplementary Group with multiple groups and Uid=1 + +[Service] +ExecStart=/bin/sh -x -c 'test "$$(id -G)" = "1 2 3" && test "$$(id -g)" = "1" && test "$$(id -u)" = "1"' +Type=oneshot +User=1 +SupplementaryGroups=1 2 3 diff --git a/test/test-execute/exec-supplementarygroups-single-group-user.service b/test/test-execute/exec-supplementarygroups-single-group-user.service new file mode 100644 index 0000000000..ed6276d303 --- /dev/null +++ b/test/test-execute/exec-supplementarygroups-single-group-user.service @@ -0,0 +1,9 @@ +[Unit] +Description=Test for Supplementary Group with only one group and uid 1 + +[Service] +ExecStart=/bin/sh -x -c 'test "$$(id -G)" = "1" && test "$$(id -g)" = "1" && test "$$(id -u)" = "1"' +Type=oneshot +User=1 +Group=1 +SupplementaryGroups=1 diff --git a/test/test-execute/exec-supplementarygroups-single-group.service b/test/test-execute/exec-supplementarygroups-single-group.service new file mode 100644 index 0000000000..ee502b3d37 --- /dev/null +++ b/test/test-execute/exec-supplementarygroups-single-group.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for Supplementary Group with only one group + +[Service] +ExecStart=/bin/sh -x -c 'test "$$(id -G)" = "1" && test "$$(id -g)" = "1" && test "$$(id -u)" = "0"' +Type=oneshot +Group=1 +SupplementaryGroups=1 diff --git a/test/test-execute/exec-supplementarygroups.service b/test/test-execute/exec-supplementarygroups.service new file mode 100644 index 0000000000..43a9a981f2 --- /dev/null +++ b/test/test-execute/exec-supplementarygroups.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for Supplementary Group + +[Service] +ExecStart=/bin/sh -x -c 'test "$$(id -G)" = "0 1"' +Type=oneshot +SupplementaryGroups=1 diff --git a/test/test-execute/exec-systemcallerrornumber.service b/test/test-execute/exec-systemcallerrornumber.service new file mode 100644 index 0000000000..ff7da3c1a4 --- /dev/null +++ b/test/test-execute/exec-systemcallerrornumber.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for SystemCallErrorNumber + +[Service] +ExecStart=/bin/sh -x -c 'uname -a' +Type=oneshot +SystemCallFilter=~uname +SystemCallErrorNumber=EACCES diff --git a/test/test-execute/exec-systemcallfilter-failing.service b/test/test-execute/exec-systemcallfilter-failing.service new file mode 100644 index 0000000000..5c6422f0fd --- /dev/null +++ b/test/test-execute/exec-systemcallfilter-failing.service @@ -0,0 +1,9 @@ +[Unit] +Description=Test for SystemCallFilter + +[Service] +ExecStart=/bin/echo "This should not be seen" +Type=oneshot +SystemCallFilter=ioperm +SystemCallFilter=~ioperm +SystemCallFilter=ioperm diff --git a/test/test-execute/exec-systemcallfilter-failing2.service b/test/test-execute/exec-systemcallfilter-failing2.service new file mode 100644 index 0000000000..3516078e1f --- /dev/null +++ b/test/test-execute/exec-systemcallfilter-failing2.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for SystemCallFilter + +[Service] +ExecStart=/bin/echo "This should not be seen" +Type=oneshot +SystemCallFilter=~write open execve exit_group close mmap munmap fstat DONOTEXIST diff --git a/test/test-execute/exec-systemcallfilter-not-failing.service b/test/test-execute/exec-systemcallfilter-not-failing.service new file mode 100644 index 0000000000..c794b67edd --- /dev/null +++ b/test/test-execute/exec-systemcallfilter-not-failing.service @@ -0,0 +1,10 @@ +[Unit] +Description=Test for SystemCallFilter + +[Service] +ExecStart=/bin/echo "Foo bar" +Type=oneshot +SystemCallFilter=~read write open execve ioperm +SystemCallFilter=ioctl +SystemCallFilter=read write open execve +SystemCallFilter=~ioperm diff --git a/test/test-execute/exec-systemcallfilter-not-failing2.service b/test/test-execute/exec-systemcallfilter-not-failing2.service new file mode 100644 index 0000000000..a62c81bd48 --- /dev/null +++ b/test/test-execute/exec-systemcallfilter-not-failing2.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for SystemCallFilter + +[Service] +ExecStart=/bin/echo "Foo bar" +Type=oneshot +SystemCallFilter= diff --git a/test/test-execute/exec-systemcallfilter-system-user-nfsnobody.service b/test/test-execute/exec-systemcallfilter-system-user-nfsnobody.service new file mode 100644 index 0000000000..9393e0a998 --- /dev/null +++ b/test/test-execute/exec-systemcallfilter-system-user-nfsnobody.service @@ -0,0 +1,11 @@ +[Unit] +Description=Test for SystemCallFilter in system mode with User set + +[Service] +ExecStart=/bin/echo "Foo bar" +Type=oneshot +User=nfsnobody +SystemCallFilter=~read write open execve ioperm +SystemCallFilter=ioctl +SystemCallFilter=read write open execve +SystemCallFilter=~ioperm diff --git a/test/test-execute/exec-systemcallfilter-system-user.service b/test/test-execute/exec-systemcallfilter-system-user.service new file mode 100644 index 0000000000..462f94133d --- /dev/null +++ b/test/test-execute/exec-systemcallfilter-system-user.service @@ -0,0 +1,11 @@ +[Unit] +Description=Test for SystemCallFilter in system mode with User set + +[Service] +ExecStart=/bin/echo "Foo bar" +Type=oneshot +User=nobody +SystemCallFilter=~read write open execve ioperm +SystemCallFilter=ioctl +SystemCallFilter=read write open execve +SystemCallFilter=~ioperm diff --git a/test/test-execute/exec-umask-0177.service b/test/test-execute/exec-umask-0177.service new file mode 100644 index 0000000000..a5e8fc4dbc --- /dev/null +++ b/test/test-execute/exec-umask-0177.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for UMask + +[Service] +ExecStart=/bin/sh -x -c 'touch /tmp/test-exec-umask; mode=$$(stat -c %%a /tmp/test-exec-umask); test "$$mode" = "600"' +Type=oneshot +UMask=0177 +PrivateTmp=yes diff --git a/test/test-execute/exec-umask-default.service b/test/test-execute/exec-umask-default.service new file mode 100644 index 0000000000..487f5e9b94 --- /dev/null +++ b/test/test-execute/exec-umask-default.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for UMask default + +[Service] +ExecStart=/bin/sh -x -c 'touch /tmp/test-exec-umask; mode=$$(stat -c %%a /tmp/test-exec-umask); test "$$mode" = "644"' +Type=oneshot +PrivateTmp=yes diff --git a/test/test-execute/exec-user-nfsnobody.service b/test/test-execute/exec-user-nfsnobody.service new file mode 100644 index 0000000000..aafda3aa26 --- /dev/null +++ b/test/test-execute/exec-user-nfsnobody.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for User + +[Service] +ExecStart=/bin/sh -x -c 'test "$$USER" = "nfsnobody"' +Type=oneshot +User=nfsnobody diff --git a/test/test-execute/exec-user.service b/test/test-execute/exec-user.service new file mode 100644 index 0000000000..0a00c1abc4 --- /dev/null +++ b/test/test-execute/exec-user.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for User + +[Service] +ExecStart=/bin/sh -x -c 'test "$$USER" = "nobody"' +Type=oneshot +User=nobody diff --git a/test/test-execute/exec-workingdirectory.service b/test/test-execute/exec-workingdirectory.service new file mode 100644 index 0000000000..fe3c420d2d --- /dev/null +++ b/test/test-execute/exec-workingdirectory.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for WorkingDirectory + +[Service] +ExecStart=/bin/sh -x -c 'test "$$PWD" = "/tmp/test-exec_workingdirectory"' +Type=oneshot +WorkingDirectory=/tmp/test-exec_workingdirectory diff --git a/test/test-functions b/test/test-functions new file mode 100644 index 0000000000..2a21a64c5c --- /dev/null +++ b/test/test-functions @@ -0,0 +1,1360 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +PATH=/sbin:/bin:/usr/sbin:/usr/bin +export PATH + +LOOKS_LIKE_DEBIAN=$(source /etc/os-release && [[ "$ID" = "debian" || "$ID_LIKE" = "debian" ]] && echo yes) +KERNEL_VER=${KERNEL_VER-$(uname -r)} +KERNEL_MODS="/lib/modules/$KERNEL_VER/" +QEMU_TIMEOUT="${QEMU_TIMEOUT:-infinity}" +NSPAWN_TIMEOUT="${NSPAWN_TIMEOUT:-infinity}" +TIMED_OUT= # will be 1 after run_* if *_TIMEOUT is set and test timed out +FSTYPE="${FSTYPE:-ext3}" +UNIFIED_CGROUP_HIERARCHY="${UNIFIED_CGROUP_HIERARCHY:-no}" + +if ! ROOTLIBDIR=$(pkg-config --variable=systemdutildir systemd); then + echo "WARNING! Cannot determine rootlibdir from pkg-config, assuming /usr/lib/systemd" >&2 + ROOTLIBDIR=/usr/lib/systemd +fi + +BASICTOOLS="sh bash setsid loadkeys setfont login sulogin gzip sleep echo mount umount cryptsetup date dmsetup modprobe sed cmp tee rm" +DEBUGTOOLS="df free ls stty cat ps ln ip route dmesg dhclient mkdir cp ping dhclient strace less grep id tty touch du sort hostname find" + +function find_qemu_bin() { + # SUSE and Red Hat call the binary qemu-kvm + # Debian and Gentoo call it kvm + [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a kvm qemu-kvm 2>/dev/null | grep '^/' -m1) + + [ "$ARCH" ] || ARCH=$(uname -m) + case $ARCH in + x86_64) + # QEMU's own build system calls it qemu-system-x86_64 + [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu-system-x86_64 2>/dev/null | grep '^/' -m1) + ;; + i*86) + # new i386 version of QEMU + [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu-system-i386 2>/dev/null | grep '^/' -m1) + + # i386 version of QEMU + [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu 2>/dev/null | grep '^/' -m1) + ;; + esac + + if [ ! -e "$QEMU_BIN" ]; then + echo "Could not find a suitable QEMU binary" >&2 + return 1 + fi +} + +# Return 0 if QEMU did run (then you must check the result state/logs for actual +# success), or 1 if QEMU is not available. +run_qemu() { + if [ -f /etc/machine-id ]; then + read MACHINE_ID < /etc/machine-id + [ -z "$INITRD" ] && [ -e "/boot/$MACHINE_ID/$KERNEL_VER/initrd" ] \ + && INITRD="/boot/$MACHINE_ID/$KERNEL_VER/initrd" + [ -z "$KERNEL_BIN" ] && [ -e "/boot/$MACHINE_ID/$KERNEL_VER/linux" ] \ + && KERNEL_BIN="/boot/$MACHINE_ID/$KERNEL_VER/linux" + fi + + default_fedora_initrd=/boot/initramfs-${KERNEL_VER}.img + default_debian_initrd=/boot/initrd.img-${KERNEL_VER} + [ "$KERNEL_BIN" ] || KERNEL_BIN=/boot/vmlinuz-$KERNEL_VER + [ "$INITRD" ] || { [ -e "$default_fedora_initrd" ] && INITRD=$default_fedora_initrd; } + [ "$INITRD" ] || { [ "$LOOKS_LIKE_DEBIAN" ] && [ -e "$default_debian_initrd" ] && INITRD=$default_debian_initrd; } + [ "$QEMU_SMP" ] || QEMU_SMP=1 + + find_qemu_bin || return 1 + + KERNEL_APPEND="root=/dev/sda1 \ +raid=noautodetect \ +loglevel=2 \ +init=$ROOTLIBDIR/systemd \ +ro \ +console=ttyS0 \ +selinux=0 \ +systemd.unified_cgroup_hierarchy=$UNIFIED_CGROUP_HIERARCHY \ +$KERNEL_APPEND \ +" + + QEMU_OPTIONS="-smp $QEMU_SMP \ +-net none \ +-m 512M \ +-nographic \ +-kernel $KERNEL_BIN \ +-drive format=raw,cache=unsafe,file=${TESTDIR}/rootdisk.img \ +" + + if [[ "$INITRD" && "$SKIP_INITRD" != "yes" ]]; then + QEMU_OPTIONS="$QEMU_OPTIONS -initrd $INITRD" + fi + + if [ -c /dev/kvm ]; then + QEMU_OPTIONS="$QEMU_OPTIONS -machine accel=kvm -enable-kvm -cpu host" + fi + + if [[ "$QEMU_TIMEOUT" != "infinity" ]]; then + QEMU_BIN="timeout --foreground $QEMU_TIMEOUT $QEMU_BIN" + fi + (set -x; $QEMU_BIN $QEMU_OPTIONS -append "$KERNEL_APPEND") + rc=$? + if [ "$rc" = 124 ] && [ "$QEMU_TIMEOUT" != "infinity" ]; then + derror "test timed out after $QEMU_TIMEOUT s" + TIMED_OUT=1 + else + [ "$rc" != 0 ] && derror "QEMU failed with exit code $rc" + fi + return 0 +} + +# Return 0 if nspawn did run (then you must check the result state/logs for actual +# success), or 1 if nspawn is not available. +run_nspawn() { + [[ -d /run/systemd/system ]] || return 1 + + local _nspawn_cmd="../../systemd-nspawn --register=no --kill-signal=SIGKILL --directory=$TESTDIR/nspawn-root $ROOTLIBDIR/systemd $KERNEL_APPEND" + if [[ "$NSPAWN_TIMEOUT" != "infinity" ]]; then + _nspawn_cmd="timeout --foreground $NSPAWN_TIMEOUT $_nspawn_cmd" + fi + + _nspawn_cmd="env UNIFIED_CGROUP_HIERARCHY=$UNIFIED_CGROUP_HIERARCHY $_nspawn_cmd" + + (set -x; $_nspawn_cmd) + rc=$? + if [ "$rc" = 124 ] && [ "$NSPAWN_TIMEOUT" != "infinity" ]; then + derror "test timed out after $NSPAWN_TIMEOUT s" + TIMED_OUT=1 + else + [ "$rc" != 0 ] && derror "nspawn failed with exit code $rc" + fi + return 0 +} + +setup_basic_environment() { + # create the basic filesystem layout + setup_basic_dirs + + install_systemd + install_missing_libraries + install_config_files + create_rc_local + install_basic_tools + install_libnss + install_pam + install_dbus + install_fonts + install_keymaps + install_terminfo + install_execs + install_fsck + install_plymouth + install_debug_tools + install_ld_so_conf + setup_selinux + strip_binaries + install_depmod_files + generate_module_dependencies +} + +setup_selinux() { + # don't forget KERNEL_APPEND='... selinux=1 ...' + if [[ "$SETUP_SELINUX" != "yes" ]]; then + ddebug "Don't setup SELinux" + return 0 + fi + ddebug "Setup SELinux" + local _conf_dir=/etc/selinux + local _fixfiles_tools="bash uname cat sort uniq awk grep egrep head expr find rm secon setfiles" + + rm -rf $initdir/$_conf_dir + if ! cp -ar $_conf_dir $initdir/$_conf_dir; then + dfatal "Failed to copy $_conf_dir" + exit 1 + fi + + cat <<EOF >$initdir/etc/systemd/system/autorelabel.service +[Unit] +Description=Relabel all filesystems +DefaultDependencies=no +Requires=local-fs.target +Conflicts=shutdown.target +After=local-fs.target +Before=sysinit.target shutdown.target +ConditionSecurity=selinux +ConditionPathExists=|/.autorelabel + +[Service] +ExecStart=/bin/sh -x -c 'echo 0 >/sys/fs/selinux/enforce && fixfiles -f -F relabel && rm /.autorelabel && systemctl --force reboot' +Type=oneshot +TimeoutSec=0 +RemainAfterExit=yes +EOF + + touch $initdir/.autorelabel + mkdir -p $initdir/etc/systemd/system/basic.target.wants + ln -fs autorelabel.service $initdir/etc/systemd/system/basic.target.wants/autorelabel.service + + dracut_install $_fixfiles_tools + dracut_install fixfiles + dracut_install sestatus +} + +install_valgrind() { + if ! type -p valgrind; then + dfatal "Failed to install valgrind" + exit 1 + fi + + local _valgrind_bins=$(strace -e execve valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if /^execve\("([^"]+)"/') + dracut_install $_valgrind_bins + + local _valgrind_libs=$(LD_DEBUG=files valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if m{calling init: (/.*vgpreload_.*)}') + dracut_install $_valgrind_libs + + local _valgrind_dbg_and_supp=$( + strace -e open valgrind /bin/true 2>&1 >/dev/null | + perl -lne 'if (my ($fname) = /^open\("([^"]+).*= (?!-)\d+/) { print $fname if $fname =~ /debug|\.supp$/ }' + ) + dracut_install $_valgrind_dbg_and_supp +} + +create_valgrind_wrapper() { + local _valgrind_wrapper=$initdir/$ROOTLIBDIR/systemd-under-valgrind + ddebug "Create $_valgrind_wrapper" + cat >$_valgrind_wrapper <<EOF +#!/bin/bash + +mount -t proc proc /proc +exec valgrind --leak-check=full --log-file=/valgrind.out $ROOTLIBDIR/systemd "\$@" +EOF + chmod 0755 $_valgrind_wrapper +} + +create_strace_wrapper() { + local _strace_wrapper=$initdir/$ROOTLIBDIR/systemd-under-strace + ddebug "Create $_strace_wrapper" + cat >$_strace_wrapper <<EOF +#!/bin/bash + +exec strace -D -o /strace.out $ROOTLIBDIR/systemd "\$@" +EOF + chmod 0755 $_strace_wrapper +} + +install_fsck() { + dracut_install /sbin/fsck* + dracut_install -o /bin/fsck* + + # fskc.reiserfs calls reiserfsck. so, install it + dracut_install -o reiserfsck +} + +install_dmevent() { + instmods dm_crypt =crypto + type -P dmeventd >/dev/null && dracut_install dmeventd + inst_libdir_file "libdevmapper-event.so*" + if [[ "$LOOKS_LIKE_DEBIAN" ]]; then + # dmsetup installs 55-dm and 60-persistent-storage-dm on Debian/Ubuntu + # see https://anonscm.debian.org/cgit/pkg-lvm/lvm2.git/tree/debian/patches/0007-udev.patch + inst_rules 55-dm.rules 60-persistent-storage-dm.rules + else + inst_rules 10-dm.rules 13-dm-disk.rules 95-dm-notify.rules + fi +} + +install_systemd() { + # install compiled files + (cd $TEST_BASE_DIR/..; set -x; make DESTDIR=$initdir install) + # remove unneeded documentation + rm -fr $initdir/usr/share/{man,doc} + # we strip binaries since debug symbols increase binaries size a lot + # and it could fill the available space + strip_binaries + + # enable debug logging in PID1 + echo LogLevel=debug >> $initdir/etc/systemd/system.conf +} + +get_ldpath() { + local _bin="$1" + objdump -p "$_bin" 2>/dev/null | awk "/R(UN)?PATH/ { print \"$initdir\" \$2 }" | paste -sd : +} + +install_missing_libraries() { + # install possible missing libraries + for i in $initdir{,/usr}/{sbin,bin}/* $initdir{,/usr}/lib/systemd/*; do + LD_LIBRARY_PATH=$(get_ldpath $i) inst_libs $i + done +} + +create_empty_image() { + rm -f "$TESTDIR/rootdisk.img" + # Create the blank file to use as a root filesystem + dd if=/dev/null of="$TESTDIR/rootdisk.img" bs=1M seek=400 + LOOPDEV=$(losetup --show -P -f $TESTDIR/rootdisk.img) + [ -b "$LOOPDEV" ] || return 1 + echo "LOOPDEV=$LOOPDEV" >> $STATEFILE + sfdisk "$LOOPDEV" <<EOF +,390M +, +EOF + + local _label="-L systemd" + # mkfs.reiserfs doesn't know -L. so, use --label instead + [[ "$FSTYPE" == "reiserfs" ]] && _label="--label systemd" + if ! mkfs -t "${FSTYPE}" ${_label} "${LOOPDEV}p1" -q; then + dfatal "Failed to mkfs -t ${FSTYPE}" + exit 1 + fi +} + +check_result_nspawn() { + ret=1 + [[ -e $TESTDIR/nspawn-root/testok ]] && ret=0 + [[ -f $TESTDIR/nspawn-root/failed ]] && cp -a $TESTDIR/nspawn-root/failed $TESTDIR + cp -a $TESTDIR/nspawn-root/var/log/journal $TESTDIR + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + [ -n "$TIMED_OUT" ] && ret=$(($ret+1)) + return $ret +} + +strip_binaries() { + if [[ "$STRIP_BINARIES" = "no" ]]; then + ddebug "Don't strip binaries" + return 0 + fi + ddebug "Strip binaries" + find "$initdir" -executable -not -path '*/lib/modules/*.ko' -type f | xargs strip --strip-unneeded | ddebug +} + +create_rc_local() { + mkdir -p $initdir/etc/rc.d + cat >$initdir/etc/rc.d/rc.local <<EOF +#!/bin/bash +exit 0 +EOF + chmod 0755 $initdir/etc/rc.d/rc.local +} + +install_execs() { + ddebug "install any Execs from the service files" + ( + export PKG_CONFIG_PATH=$TEST_BASE_DIR/../src/core/ + systemdsystemunitdir=$(pkg-config --variable=systemdsystemunitdir systemd) + systemduserunitdir=$(pkg-config --variable=systemduserunitdir systemd) + egrep -ho '^Exec[^ ]*=[^ ]+' $initdir/{$systemdsystemunitdir,$systemduserunitdir}/*.service \ + | while read i; do + i=${i##Exec*=}; i=${i##-} + inst $i + done + ) +} + +generate_module_dependencies() { + if [[ -d $initdir/lib/modules/$KERNEL_VER ]] && \ + ! depmod -a -b "$initdir" $KERNEL_VER; then + dfatal "\"depmod -a $KERNEL_VER\" failed." + exit 1 + fi +} + +install_depmod_files() { + inst /lib/modules/$KERNEL_VER/modules.order + inst /lib/modules/$KERNEL_VER/modules.builtin +} + +install_plymouth() { + # install plymouth, if found... else remove plymouth service files + # if [ -x /usr/libexec/plymouth/plymouth-populate-initrd ]; then + # PLYMOUTH_POPULATE_SOURCE_FUNCTIONS="$TEST_BASE_DIR/test-functions" \ + # /usr/libexec/plymouth/plymouth-populate-initrd -t $initdir + # dracut_install plymouth plymouthd + # else + rm -f $initdir/{usr/lib,etc}/systemd/system/plymouth* $initdir/{usr/lib,etc}/systemd/system/*/plymouth* + # fi +} + +install_ld_so_conf() { + cp -a /etc/ld.so.conf* $initdir/etc + ldconfig -r "$initdir" +} + +install_config_files() { + inst /etc/sysconfig/init + inst /etc/passwd + inst /etc/shadow + inst /etc/login.defs + inst /etc/group + inst /etc/shells + inst /etc/nsswitch.conf + inst /etc/pam.conf + inst /etc/securetty + inst /etc/os-release + inst /etc/localtime + # we want an empty environment + > $initdir/etc/environment + > $initdir/etc/machine-id + # set the hostname + echo systemd-testsuite > $initdir/etc/hostname + # fstab + cat >$initdir/etc/fstab <<EOF +LABEL=systemd / ${FSTYPE} rw 0 1 +EOF +} + +install_basic_tools() { + [[ $BASICTOOLS ]] && dracut_install $BASICTOOLS + dracut_install -o sushell + # in Debian ldconfig is just a shell script wrapper around ldconfig.real + dracut_install -o ldconfig.real +} + +install_debug_tools() { + [[ $DEBUGTOOLS ]] && dracut_install $DEBUGTOOLS +} + +install_libnss() { + # install libnss_files for login + NSS_LIBS=$(LD_DEBUG=files getent passwd 2>&1 >/dev/null |sed -n '/calling init: .*libnss_/ {s!^.* /!/!; p}') + dracut_install $NSS_LIBS +} + +install_dbus() { + inst $ROOTLIBDIR/system/dbus.socket + inst $ROOTLIBDIR/system/dbus.service + + find \ + /etc/dbus-1 /usr/share/dbus-1 -xtype f \ + | while read file; do + inst $file + done +} + +install_pam() { + ( + [[ "$LOOKS_LIKE_DEBIAN" ]] && type -p dpkg-architecture &>/dev/null && find "/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/security" -xtype f + find \ + /etc/pam.d \ + /etc/security \ + /lib64/security \ + /lib/security -xtype f \ + ) | while read file; do + inst $file + done + + # pam_unix depends on unix_chkpwd. + # see http://www.linux-pam.org/Linux-PAM-html/sag-pam_unix.html + dracut_install -o unix_chkpwd + + [[ "$LOOKS_LIKE_DEBIAN" ]] && + cp /etc/pam.d/systemd-user $initdir/etc/pam.d/ + + # set empty root password for easy debugging + sed -i 's/^root:x:/root::/' $initdir/etc/passwd +} + +install_keymaps() { + for i in \ + /usr/lib/kbd/keymaps/include/* \ + /usr/lib/kbd/keymaps/i386/include/* \ + /usr/lib/kbd/keymaps/i386/qwerty/us.*; do + [[ -f $i ]] || continue + inst $i + done +} + +install_fonts() { + for i in \ + /usr/lib/kbd/consolefonts/eurlatgr* \ + /usr/lib/kbd/consolefonts/latarcyrheb-sun16*; do + [[ -f $i ]] || continue + inst $i + done +} + +install_terminfo() { + for _terminfodir in /lib/terminfo /etc/terminfo /usr/share/terminfo; do + [ -f ${_terminfodir}/l/linux ] && break + done + dracut_install -o ${_terminfodir}/l/linux +} + +setup_testsuite() { + cp $TEST_BASE_DIR/testsuite.target $initdir/etc/systemd/system/ + cp $TEST_BASE_DIR/end.service $initdir/etc/systemd/system/ + + mkdir -p $initdir/etc/systemd/system/testsuite.target.wants + ln -fs $TEST_BASE_DIR/testsuite.service $initdir/etc/systemd/system/testsuite.target.wants/testsuite.service + ln -fs $TEST_BASE_DIR/end.service $initdir/etc/systemd/system/testsuite.target.wants/end.service + + # make the testsuite the default target + ln -fs testsuite.target $initdir/etc/systemd/system/default.target +} + +setup_nspawn_root() { + rm -fr $TESTDIR/nspawn-root + ddebug "cp -ar $initdir $TESTDIR/nspawn-root" + cp -ar $initdir $TESTDIR/nspawn-root + # we don't mount in the nspawn root + rm -f $TESTDIR/nspawn-root/etc/fstab +} + +setup_basic_dirs() { + mkdir -p $initdir/run + mkdir -p $initdir/etc/systemd/system + mkdir -p $initdir/var/log/journal + + for d in usr/bin usr/sbin bin etc lib "$libdir" sbin tmp usr var var/log dev proc sys sysroot root run run/lock run/initramfs; do + if [ -L "/$d" ]; then + inst_symlink "/$d" + else + inst_dir "/$d" + fi + done + + ln -sfn /run "$initdir/var/run" + ln -sfn /run/lock "$initdir/var/lock" +} + +inst_libs() { + local _bin=$1 + local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)' + local _file _line + + LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do + [[ $_line = 'not a dynamic executable' ]] && break + + if [[ $_line =~ $_so_regex ]]; then + _file=${BASH_REMATCH[1]} + [[ -e ${initdir}/$_file ]] && continue + inst_library "$_file" + continue + fi + + if [[ $_line =~ not\ found ]]; then + dfatal "Missing a shared library required by $_bin." + dfatal "Run \"ldd $_bin\" to find out what it is." + dfatal "$_line" + dfatal "dracut cannot create an initrd." + exit 1 + fi + done +} + +import_testdir() { + STATEFILE=".testdir" + [[ -e $STATEFILE ]] && . $STATEFILE + if [[ -z "$TESTDIR" ]] || [[ ! -d "$TESTDIR" ]]; then + TESTDIR=$(mktemp --tmpdir=/var/tmp -d -t systemd-test.XXXXXX) + echo "TESTDIR=\"$TESTDIR\"" > $STATEFILE + export TESTDIR + fi +} + +import_initdir() { + initdir=$TESTDIR/root + export initdir +} + +## @brief Converts numeric logging level to the first letter of level name. +# +# @param lvl Numeric logging level in range from 1 to 6. +# @retval 1 if @a lvl is out of range. +# @retval 0 if @a lvl is correct. +# @result Echoes first letter of level name. +_lvl2char() { + case "$1" in + 1) echo F;; + 2) echo E;; + 3) echo W;; + 4) echo I;; + 5) echo D;; + 6) echo T;; + *) return 1;; + esac +} + +## @brief Internal helper function for _do_dlog() +# +# @param lvl Numeric logging level. +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +# +# @note This function is not supposed to be called manually. Please use +# dtrace(), ddebug(), or others instead which wrap this one. +# +# This function calls _do_dlog() either with parameter msg, or if +# none is given, it will read standard input and will use every line as +# a message. +# +# This enables: +# dwarn "This is a warning" +# echo "This is a warning" | dwarn +LOG_LEVEL=4 + +dlog() { + [ -z "$LOG_LEVEL" ] && return 0 + [ $1 -le $LOG_LEVEL ] || return 0 + local lvl="$1"; shift + local lvlc=$(_lvl2char "$lvl") || return 0 + + if [ $# -ge 1 ]; then + echo "$lvlc: $*" + else + while read line; do + echo "$lvlc: " "$line" + done + fi +} + +## @brief Logs message at TRACE level (6) +# +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +dtrace() { + set +x + dlog 6 "$@" + [ -n "$debug" ] && set -x || : +} + +## @brief Logs message at DEBUG level (5) +# +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +ddebug() { +# set +x + dlog 5 "$@" +# [ -n "$debug" ] && set -x || : +} + +## @brief Logs message at INFO level (4) +# +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +dinfo() { + set +x + dlog 4 "$@" + [ -n "$debug" ] && set -x || : +} + +## @brief Logs message at WARN level (3) +# +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +dwarn() { + set +x + dlog 3 "$@" + [ -n "$debug" ] && set -x || : +} + +## @brief Logs message at ERROR level (2) +# +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +derror() { +# set +x + dlog 2 "$@" +# [ -n "$debug" ] && set -x || : +} + +## @brief Logs message at FATAL level (1) +# +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +dfatal() { + set +x + dlog 1 "$@" + [ -n "$debug" ] && set -x || : +} + + +# Generic substring function. If $2 is in $1, return 0. +strstr() { [ "${1#*$2*}" != "$1" ]; } + +# normalize_path <path> +# Prints the normalized path, where it removes any duplicated +# and trailing slashes. +# Example: +# $ normalize_path ///test/test// +# /test/test +normalize_path() { + shopt -q -s extglob + set -- "${1//+(\/)//}" + shopt -q -u extglob + echo "${1%/}" +} + +# convert_abs_rel <from> <to> +# Prints the relative path, when creating a symlink to <to> from <from>. +# Example: +# $ convert_abs_rel /usr/bin/test /bin/test-2 +# ../../bin/test-2 +# $ ln -s $(convert_abs_rel /usr/bin/test /bin/test-2) /usr/bin/test +convert_abs_rel() { + local __current __absolute __abssize __cursize __newpath + local -i __i __level + + set -- "$(normalize_path "$1")" "$(normalize_path "$2")" + + # corner case #1 - self looping link + [[ "$1" == "$2" ]] && { echo "${1##*/}"; return; } + + # corner case #2 - own dir link + [[ "${1%/*}" == "$2" ]] && { echo "."; return; } + + IFS="/" __current=($1) + IFS="/" __absolute=($2) + + __abssize=${#__absolute[@]} + __cursize=${#__current[@]} + + while [[ ${__absolute[__level]} == ${__current[__level]} ]] + do + (( __level++ )) + if (( __level > __abssize || __level > __cursize )) + then + break + fi + done + + for ((__i = __level; __i < __cursize-1; __i++)) + do + if ((__i > __level)) + then + __newpath=$__newpath"/" + fi + __newpath=$__newpath".." + done + + for ((__i = __level; __i < __abssize; __i++)) + do + if [[ -n $__newpath ]] + then + __newpath=$__newpath"/" + fi + __newpath=$__newpath${__absolute[__i]} + done + + echo "$__newpath" +} + + +# Install a directory, keeping symlinks as on the original system. +# Example: if /lib points to /lib64 on the host, "inst_dir /lib/file" +# will create ${initdir}/lib64, ${initdir}/lib64/file, +# and a symlink ${initdir}/lib -> lib64. +inst_dir() { + [[ -e ${initdir}/"$1" ]] && return 0 # already there + + local _dir="$1" _part="${1%/*}" _file + while [[ "$_part" != "${_part%/*}" ]] && ! [[ -e "${initdir}/${_part}" ]]; do + _dir="$_part $_dir" + _part=${_part%/*} + done + + # iterate over parent directories + for _file in $_dir; do + [[ -e "${initdir}/$_file" ]] && continue + if [[ -L $_file ]]; then + inst_symlink "$_file" + else + # create directory + mkdir -m 0755 -p "${initdir}/$_file" || return 1 + [[ -e "$_file" ]] && chmod --reference="$_file" "${initdir}/$_file" + chmod u+w "${initdir}/$_file" + fi + done +} + +# $1 = file to copy to ramdisk +# $2 (optional) Name for the file on the ramdisk +# Location of the image dir is assumed to be $initdir +# We never overwrite the target if it exists. +inst_simple() { + [[ -f "$1" ]] || return 1 + strstr "$1" "/" || return 1 + + local _src=$1 target="${2:-$1}" + if ! [[ -d ${initdir}/$target ]]; then + [[ -e ${initdir}/$target ]] && return 0 + [[ -L ${initdir}/$target ]] && return 0 + [[ -d "${initdir}/${target%/*}" ]] || inst_dir "${target%/*}" + fi + # install checksum files also + if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then + inst "${_src%/*}/.${_src##*/}.hmac" "${target%/*}/.${target##*/}.hmac" + fi + ddebug "Installing $_src" + cp --sparse=always -pfL "$_src" "${initdir}/$target" +} + +# find symlinks linked to given library file +# $1 = library file +# Function searches for symlinks by stripping version numbers appended to +# library filename, checks if it points to the same target and finally +# prints the list of symlinks to stdout. +# +# Example: +# rev_lib_symlinks libfoo.so.8.1 +# output: libfoo.so.8 libfoo.so +# (Only if libfoo.so.8 and libfoo.so exists on host system.) +rev_lib_symlinks() { + [[ ! $1 ]] && return 0 + + local fn="$1" orig="$(readlink -f "$1")" links='' + + [[ ${fn} =~ .*\.so\..* ]] || return 1 + + until [[ ${fn##*.} == so ]]; do + fn="${fn%.*}" + [[ -L ${fn} && $(readlink -f "${fn}") == ${orig} ]] && links+=" ${fn}" + done + + echo "${links}" +} + +# Same as above, but specialized to handle dynamic libraries. +# It handles making symlinks according to how the original library +# is referenced. +inst_library() { + local _src="$1" _dest=${2:-$1} _lib _reallib _symlink + strstr "$1" "/" || return 1 + [[ -e $initdir/$_dest ]] && return 0 + if [[ -L $_src ]]; then + # install checksum files also + if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then + inst "${_src%/*}/.${_src##*/}.hmac" "${_dest%/*}/.${_dest##*/}.hmac" + fi + _reallib=$(readlink -f "$_src") + inst_simple "$_reallib" "$_reallib" + inst_dir "${_dest%/*}" + [[ -d "${_dest%/*}" ]] && _dest=$(readlink -f "${_dest%/*}")/${_dest##*/} + ln -sfn $(convert_abs_rel "${_dest}" "${_reallib}") "${initdir}/${_dest}" + else + inst_simple "$_src" "$_dest" + fi + + # Create additional symlinks. See rev_symlinks description. + for _symlink in $(rev_lib_symlinks $_src) $(rev_lib_symlinks $_reallib); do + [[ ! -e $initdir/$_symlink ]] && { + ddebug "Creating extra symlink: $_symlink" + inst_symlink $_symlink + } + done +} + +# find a binary. If we were not passed the full path directly, +# search in the usual places to find the binary. +find_binary() { + if [[ -z ${1##/*} ]]; then + if [[ -x $1 ]] || { strstr "$1" ".so" && ldd $1 &>/dev/null; }; then + echo $1 + return 0 + fi + fi + + type -P $1 +} + +# Same as above, but specialized to install binary executables. +# Install binary executable, and all shared library dependencies, if any. +inst_binary() { + local _bin _target + _bin=$(find_binary "$1") || return 1 + _target=${2:-$_bin} + [[ -e $initdir/$_target ]] && return 0 + [[ -L $_bin ]] && inst_symlink $_bin $_target && return 0 + local _file _line + local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)' + # I love bash! + LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do + [[ $_line = 'not a dynamic executable' ]] && break + + if [[ $_line =~ $_so_regex ]]; then + _file=${BASH_REMATCH[1]} + [[ -e ${initdir}/$_file ]] && continue + inst_library "$_file" + continue + fi + + if [[ $_line =~ not\ found ]]; then + dfatal "Missing a shared library required by $_bin." + dfatal "Run \"ldd $_bin\" to find out what it is." + dfatal "$_line" + dfatal "dracut cannot create an initrd." + exit 1 + fi + done + inst_simple "$_bin" "$_target" +} + +# same as above, except for shell scripts. +# If your shell script does not start with shebang, it is not a shell script. +inst_script() { + local _bin + _bin=$(find_binary "$1") || return 1 + shift + local _line _shebang_regex + read -r -n 80 _line <"$_bin" + # If debug is set, clean unprintable chars to prevent messing up the term + [[ $debug ]] && _line=$(echo -n "$_line" | tr -c -d '[:print:][:space:]') + _shebang_regex='(#! *)(/[^ ]+).*' + [[ $_line =~ $_shebang_regex ]] || return 1 + inst "${BASH_REMATCH[2]}" && inst_simple "$_bin" "$@" +} + +# same as above, but specialized for symlinks +inst_symlink() { + local _src=$1 _target=${2:-$1} _realsrc + strstr "$1" "/" || return 1 + [[ -L $1 ]] || return 1 + [[ -L $initdir/$_target ]] && return 0 + _realsrc=$(readlink -f "$_src") + if ! [[ -e $initdir/$_realsrc ]]; then + if [[ -d $_realsrc ]]; then + inst_dir "$_realsrc" + else + inst "$_realsrc" + fi + fi + [[ ! -e $initdir/${_target%/*} ]] && inst_dir "${_target%/*}" + [[ -d ${_target%/*} ]] && _target=$(readlink -f ${_target%/*})/${_target##*/} + ln -sfn $(convert_abs_rel "${_target}" "${_realsrc}") "$initdir/$_target" +} + +# attempt to install any programs specified in a udev rule +inst_rule_programs() { + local _prog _bin + + if grep -qE 'PROGRAM==?"[^ "]+' "$1"; then + for _prog in $(grep -E 'PROGRAM==?"[^ "]+' "$1" | sed -r 's/.*PROGRAM==?"([^ "]+).*/\1/'); do + if [ -x /lib/udev/$_prog ]; then + _bin=/lib/udev/$_prog + else + _bin=$(find_binary "$_prog") || { + dinfo "Skipping program $_prog using in udev rule $(basename $1) as it cannot be found" + continue; + } + fi + + #dinfo "Installing $_bin due to it's use in the udev rule $(basename $1)" + dracut_install "$_bin" + done + fi +} + +# udev rules always get installed in the same place, so +# create a function to install them to make life simpler. +inst_rules() { + local _target=/etc/udev/rules.d _rule _found + + inst_dir "/lib/udev/rules.d" + inst_dir "$_target" + for _rule in "$@"; do + if [ "${rule#/}" = "$rule" ]; then + for r in /lib/udev/rules.d /etc/udev/rules.d; do + if [[ -f $r/$_rule ]]; then + _found="$r/$_rule" + inst_simple "$_found" + inst_rule_programs "$_found" + fi + done + fi + for r in '' ./ $dracutbasedir/rules.d/; do + if [[ -f ${r}$_rule ]]; then + _found="${r}$_rule" + inst_simple "$_found" "$_target/${_found##*/}" + inst_rule_programs "$_found" + fi + done + [[ $_found ]] || dinfo "Skipping udev rule: $_rule" + done +} + +# general purpose installation function +# Same args as above. +inst() { + local _x + + case $# in + 1) ;; + 2) [[ ! $initdir && -d $2 ]] && export initdir=$2 + [[ $initdir = $2 ]] && set $1;; + 3) [[ -z $initdir ]] && export initdir=$2 + set $1 $3;; + *) dfatal "inst only takes 1 or 2 or 3 arguments" + exit 1;; + esac + for _x in inst_symlink inst_script inst_binary inst_simple; do + $_x "$@" && return 0 + done + return 1 +} + +# install any of listed files +# +# If first argument is '-d' and second some destination path, first accessible +# source is installed into this path, otherwise it will installed in the same +# path as source. If none of listed files was installed, function return 1. +# On first successful installation it returns with 0 status. +# +# Example: +# +# inst_any -d /bin/foo /bin/bar /bin/baz +# +# Lets assume that /bin/baz exists, so it will be installed as /bin/foo in +# initramfs. +inst_any() { + local to f + + [[ $1 = '-d' ]] && to="$2" && shift 2 + + for f in "$@"; do + if [[ -e $f ]]; then + [[ $to ]] && inst "$f" "$to" && return 0 + inst "$f" && return 0 + fi + done + + return 1 +} + +# dracut_install [-o ] <file> [<file> ... ] +# Install <file> to the initramfs image +# -o optionally install the <file> and don't fail, if it is not there +dracut_install() { + local _optional=no + if [[ $1 = '-o' ]]; then + _optional=yes + shift + fi + while (($# > 0)); do + if ! inst "$1" ; then + if [[ $_optional = yes ]]; then + dinfo "Skipping program $1 as it cannot be found and is" \ + "flagged to be optional" + else + dfatal "Failed to install $1" + exit 1 + fi + fi + shift + done +} + +# Install a single kernel module along with any firmware it may require. +# $1 = full path to kernel module to install +install_kmod_with_fw() { + # no need to go further if the module is already installed + + [[ -e "${initdir}/lib/modules/$KERNEL_VER/${1##*/lib/modules/$KERNEL_VER/}" ]] \ + && return 0 + + [[ -e "$initdir/.kernelmodseen/${1##*/}" ]] && return 0 + + if [[ $omit_drivers ]]; then + local _kmod=${1##*/} + _kmod=${_kmod%.ko} + _kmod=${_kmod/-/_} + if [[ "$_kmod" =~ $omit_drivers ]]; then + dinfo "Omitting driver $_kmod" + return 1 + fi + if [[ "${1##*/lib/modules/$KERNEL_VER/}" =~ $omit_drivers ]]; then + dinfo "Omitting driver $_kmod" + return 1 + fi + fi + + [ -d "$initdir/.kernelmodseen" ] && \ + > "$initdir/.kernelmodseen/${1##*/}" + + inst_simple "$1" "/lib/modules/$KERNEL_VER/${1##*/lib/modules/$KERNEL_VER/}" \ + || return $? + + local _modname=${1##*/} _fwdir _found _fw + _modname=${_modname%.ko*} + for _fw in $(modinfo -k $KERNEL_VER -F firmware $1 2>/dev/null); do + _found='' + for _fwdir in $fw_dir; do + if [[ -d $_fwdir && -f $_fwdir/$_fw ]]; then + inst_simple "$_fwdir/$_fw" "/lib/firmware/$_fw" + _found=yes + fi + done + if [[ $_found != yes ]]; then + if ! grep -qe "\<${_modname//-/_}\>" /proc/modules; then + dinfo "Possible missing firmware \"${_fw}\" for kernel module" \ + "\"${_modname}.ko\"" + else + dwarn "Possible missing firmware \"${_fw}\" for kernel module" \ + "\"${_modname}.ko\"" + fi + fi + done + return 0 +} + +# Do something with all the dependencies of a kernel module. +# Note that kernel modules depend on themselves using the technique we use +# $1 = function to call for each dependency we find +# It will be passed the full path to the found kernel module +# $2 = module to get dependencies for +# rest of args = arguments to modprobe +# _fderr specifies FD passed from surrounding scope +for_each_kmod_dep() { + local _func=$1 _kmod=$2 _cmd _modpath _options _found=0 + shift 2 + modprobe "$@" --ignore-install --show-depends $_kmod 2>&${_fderr} | ( + while read _cmd _modpath _options; do + [[ $_cmd = insmod ]] || continue + $_func ${_modpath} || exit $? + _found=1 + done + [[ $_found -eq 0 ]] && exit 1 + exit 0 + ) +} + +# filter kernel modules to install certain modules that meet specific +# requirements. +# $1 = search only in subdirectory of /kernel/$1 +# $2 = function to call with module name to filter. +# This function will be passed the full path to the module to test. +# The behavior of this function can vary depending on whether $hostonly is set. +# If it is, we will only look at modules that are already in memory. +# If it is not, we will look at all kernel modules +# This function returns the full filenames of modules that match $1 +filter_kernel_modules_by_path () ( + local _modname _filtercmd + if ! [[ $hostonly ]]; then + _filtercmd='find "$KERNEL_MODS/kernel/$1" "$KERNEL_MODS/extra"' + _filtercmd+=' "$KERNEL_MODS/weak-updates" -name "*.ko" -o -name "*.ko.gz"' + _filtercmd+=' -o -name "*.ko.xz"' + _filtercmd+=' 2>/dev/null' + else + _filtercmd='cut -d " " -f 1 </proc/modules|xargs modinfo -F filename ' + _filtercmd+='-k $KERNEL_VER 2>/dev/null' + fi + for _modname in $(eval $_filtercmd); do + case $_modname in + *.ko) "$2" "$_modname" && echo "$_modname";; + *.ko.gz) gzip -dc "$_modname" > $initdir/$$.ko + $2 $initdir/$$.ko && echo "$_modname" + rm -f $initdir/$$.ko + ;; + *.ko.xz) xz -dc "$_modname" > $initdir/$$.ko + $2 $initdir/$$.ko && echo "$_modname" + rm -f $initdir/$$.ko + ;; + esac + done +) +find_kernel_modules_by_path () ( + if ! [[ $hostonly ]]; then + find "$KERNEL_MODS/kernel/$1" "$KERNEL_MODS/extra" "$KERNEL_MODS/weak-updates" \ + -name "*.ko" -o -name "*.ko.gz" -o -name "*.ko.xz" 2>/dev/null + else + cut -d " " -f 1 </proc/modules \ + | xargs modinfo -F filename -k $KERNEL_VER 2>/dev/null + fi +) + +filter_kernel_modules () { + filter_kernel_modules_by_path drivers "$1" +} + +find_kernel_modules () { + find_kernel_modules_by_path drivers +} + +# instmods [-c] <kernel module> [<kernel module> ... ] +# instmods [-c] <kernel subsystem> +# install kernel modules along with all their dependencies. +# <kernel subsystem> can be e.g. "=block" or "=drivers/usb/storage" +instmods() { + [[ $no_kernel = yes ]] && return + # called [sub]functions inherit _fderr + local _fderr=9 + local _check=no + if [[ $1 = '-c' ]]; then + _check=yes + shift + fi + + function inst1mod() { + local _ret=0 _mod="$1" + case $_mod in + =*) + if [ -f $KERNEL_MODS/modules.${_mod#=} ]; then + ( [[ "$_mpargs" ]] && echo $_mpargs + cat "${KERNEL_MODS}/modules.${_mod#=}" ) \ + | instmods + else + ( [[ "$_mpargs" ]] && echo $_mpargs + find "$KERNEL_MODS" -path "*/${_mod#=}/*" -printf '%f\n' ) \ + | instmods + fi + ;; + --*) _mpargs+=" $_mod" ;; + i2o_scsi) return ;; # Do not load this diagnostic-only module + *) + _mod=${_mod##*/} + # if we are already installed, skip this module and go on + # to the next one. + [[ -f "$initdir/.kernelmodseen/${_mod%.ko}.ko" ]] && return + + if [[ $omit_drivers ]] && [[ "$1" =~ $omit_drivers ]]; then + dinfo "Omitting driver ${_mod##$KERNEL_MODS}" + return + fi + # If we are building a host-specific initramfs and this + # module is not already loaded, move on to the next one. + [[ $hostonly ]] && ! grep -qe "\<${_mod//-/_}\>" /proc/modules \ + && ! echo $add_drivers | grep -qe "\<${_mod}\>" \ + && return + + # We use '-d' option in modprobe only if modules prefix path + # differs from default '/'. This allows us to use Dracut with + # old version of modprobe which doesn't have '-d' option. + local _moddirname=${KERNEL_MODS%%/lib/modules/*} + [[ -n ${_moddirname} ]] && _moddirname="-d ${_moddirname}/" + + # ok, load the module, all its dependencies, and any firmware + # it may require + for_each_kmod_dep install_kmod_with_fw $_mod \ + --set-version $KERNEL_VER ${_moddirname} $_mpargs + ((_ret+=$?)) + ;; + esac + return $_ret + } + + function instmods_1() { + local _mod _mpargs + if (($# == 0)); then # filenames from stdin + while read _mod; do + inst1mod "${_mod%.ko*}" || { + if [ "$_check" = "yes" ]; then + dfatal "Failed to install $_mod" + return 1 + fi + } + done + fi + while (($# > 0)); do # filenames as arguments + inst1mod ${1%.ko*} || { + if [ "$_check" = "yes" ]; then + dfatal "Failed to install $1" + return 1 + fi + } + shift + done + return 0 + } + + local _ret _filter_not_found='FATAL: Module .* not found.' + set -o pipefail + # Capture all stderr from modprobe to _fderr. We could use {var}>... + # redirections, but that would make dracut require bash4 at least. + eval "( instmods_1 \"\$@\" ) ${_fderr}>&1" \ + | while read line; do [[ "$line" =~ $_filter_not_found ]] && echo $line || echo $line >&2 ;done | derror + _ret=$? + set +o pipefail + return $_ret +} + +# inst_libdir_file [-n <pattern>] <file> [<file>...] +# Install a <file> located on a lib directory to the initramfs image +# -n <pattern> install non-matching files +inst_libdir_file() { + if [[ "$1" == "-n" ]]; then + local _pattern=$1 + shift 2 + for _dir in $libdirs; do + for _i in "$@"; do + for _f in "$_dir"/$_i; do + [[ "$_i" =~ $_pattern ]] || continue + [[ -e "$_i" ]] && dracut_install "$_i" + done + done + done + else + for _dir in $libdirs; do + for _i in "$@"; do + for _f in "$_dir"/$_i; do + [[ -e "$_f" ]] && dracut_install "$_f" + done + done + done + fi +} + +do_test() { + if [[ $UID != "0" ]]; then + echo "TEST: $TEST_DESCRIPTION [SKIPPED]: not root" >&2 + exit 0 + fi + +# Detect lib paths + [[ $libdir ]] || for libdir in /lib64 /lib; do + [[ -d $libdir ]] && libdirs+=" $libdir" && break + done + + [[ $usrlibdir ]] || for usrlibdir in /usr/lib64 /usr/lib; do + [[ -d $usrlibdir ]] && libdirs+=" $usrlibdir" && break + done + + import_testdir + import_initdir + + while (($# > 0)); do + case $1 in + --run) + echo "TEST RUN: $TEST_DESCRIPTION" + test_run + ret=$? + if [ $ret -eq 0 ]; then + echo "TEST RUN: $TEST_DESCRIPTION [OK]" + else + echo "TEST RUN: $TEST_DESCRIPTION [FAILED]" + fi + exit $ret;; + --setup) + echo "TEST SETUP: $TEST_DESCRIPTION" + test_setup + exit $?;; + --clean) + echo "TEST CLEANUP: $TEST_DESCRIPTION" + test_cleanup + rm -fr "$TESTDIR" + rm -f .testdir + exit $?;; + --all) + echo -n "TEST: $TEST_DESCRIPTION "; + ( + test_setup && test_run + ret=$? + test_cleanup + rm -fr "$TESTDIR" + rm -f .testdir + exit $ret + ) </dev/null >test.log 2>&1 + ret=$? + if [ $ret -eq 0 ]; then + rm test.log + echo "[OK]" + else + echo "[FAILED]" + echo "see $(pwd)/test.log" + fi + exit $ret;; + *) break ;; + esac + shift + done +} diff --git a/test/test-path/basic.target b/test/test-path/basic.target new file mode 120000 index 0000000000..a882b72cc9 --- /dev/null +++ b/test/test-path/basic.target @@ -0,0 +1 @@ +../../units/basic.target
\ No newline at end of file diff --git a/test/test-path/path-changed.path b/test/test-path/path-changed.path new file mode 100644 index 0000000000..e58bdd925f --- /dev/null +++ b/test/test-path/path-changed.path @@ -0,0 +1,8 @@ +[Unit] +Description=Test PathChanged + +[Path] +PathChanged=/tmp/test-path_changed + +[Install] +WantedBy=multi-user.target diff --git a/test/test-path/path-changed.service b/test/test-path/path-changed.service new file mode 120000 index 0000000000..8bdf178830 --- /dev/null +++ b/test/test-path/path-changed.service @@ -0,0 +1 @@ +path-service.service
\ No newline at end of file diff --git a/test/test-path/path-directorynotempty.path b/test/test-path/path-directorynotempty.path new file mode 100644 index 0000000000..17e599fc0e --- /dev/null +++ b/test/test-path/path-directorynotempty.path @@ -0,0 +1,8 @@ +[Unit] +Description=Test DirectoryNotEmpty + +[Path] +DirectoryNotEmpty=/tmp/test-path_directorynotempty/ + +[Install] +WantedBy=multi-user.target diff --git a/test/test-path/path-directorynotempty.service b/test/test-path/path-directorynotempty.service new file mode 120000 index 0000000000..8bdf178830 --- /dev/null +++ b/test/test-path/path-directorynotempty.service @@ -0,0 +1 @@ +path-service.service
\ No newline at end of file diff --git a/test/test-path/path-exists.path b/test/test-path/path-exists.path new file mode 100644 index 0000000000..c4c9105af4 --- /dev/null +++ b/test/test-path/path-exists.path @@ -0,0 +1,8 @@ +[Unit] +Description=Test PathExists + +[Path] +PathExists=/tmp/test-path_exists + +[Install] +WantedBy=multi-user.target diff --git a/test/test-path/path-exists.service b/test/test-path/path-exists.service new file mode 120000 index 0000000000..8bdf178830 --- /dev/null +++ b/test/test-path/path-exists.service @@ -0,0 +1 @@ +path-service.service
\ No newline at end of file diff --git a/test/test-path/path-existsglob.path b/test/test-path/path-existsglob.path new file mode 100644 index 0000000000..a058599605 --- /dev/null +++ b/test/test-path/path-existsglob.path @@ -0,0 +1,8 @@ +[Unit] +Description=Test PathExistsGlob + +[Path] +PathExistsGlob=/tmp/test-path_existsglob* + +[Install] +WantedBy=multi-user.target diff --git a/test/test-path/path-existsglob.service b/test/test-path/path-existsglob.service new file mode 120000 index 0000000000..8bdf178830 --- /dev/null +++ b/test/test-path/path-existsglob.service @@ -0,0 +1 @@ +path-service.service
\ No newline at end of file diff --git a/test/test-path/path-makedirectory.path b/test/test-path/path-makedirectory.path new file mode 100644 index 0000000000..9408479c0f --- /dev/null +++ b/test/test-path/path-makedirectory.path @@ -0,0 +1,10 @@ +[Unit] +Description=Test MakeDirectory & DirectoryMode + +[Path] +DirectoryNotEmpty=/tmp/test-path_makedirectory/ +MakeDirectory=yes +DirectoryMode=0744 + +[Install] +WantedBy=multi-user.target diff --git a/test/test-path/path-makedirectory.service b/test/test-path/path-makedirectory.service new file mode 120000 index 0000000000..8bdf178830 --- /dev/null +++ b/test/test-path/path-makedirectory.service @@ -0,0 +1 @@ +path-service.service
\ No newline at end of file diff --git a/test/test-path/path-modified.path b/test/test-path/path-modified.path new file mode 100644 index 0000000000..18363227ba --- /dev/null +++ b/test/test-path/path-modified.path @@ -0,0 +1,8 @@ +[Unit] +Description=Test PathModified + +[Path] +PathModified=/tmp/test-path_modified + +[Install] +WantedBy=multi-user.target diff --git a/test/test-path/path-modified.service b/test/test-path/path-modified.service new file mode 120000 index 0000000000..8bdf178830 --- /dev/null +++ b/test/test-path/path-modified.service @@ -0,0 +1 @@ +path-service.service
\ No newline at end of file diff --git a/test/test-path/path-mycustomunit.service b/test/test-path/path-mycustomunit.service new file mode 100644 index 0000000000..172ac0d0d5 --- /dev/null +++ b/test/test-path/path-mycustomunit.service @@ -0,0 +1,6 @@ +[Unit] +Description=Service Test Path Unit= + +[Service] +ExecStart=/bin/true +Type=oneshot diff --git a/test/test-path/path-service.service b/test/test-path/path-service.service new file mode 100644 index 0000000000..f8499ec619 --- /dev/null +++ b/test/test-path/path-service.service @@ -0,0 +1,6 @@ +[Unit] +Description=Service Test for Path units + +[Service] +ExecStart=/bin/true +Type=oneshot diff --git a/test/test-path/path-unit.path b/test/test-path/path-unit.path new file mode 100644 index 0000000000..95e572d6d5 --- /dev/null +++ b/test/test-path/path-unit.path @@ -0,0 +1,9 @@ +[Unit] +Description=Test Path Unit= + +[Path] +PathExists=/tmp/test-path_unit +Unit=path-mycustomunit.service + +[Install] +WantedBy=multi-user.target diff --git a/test/test-path/paths.target b/test/test-path/paths.target new file mode 120000 index 0000000000..b402796cb9 --- /dev/null +++ b/test/test-path/paths.target @@ -0,0 +1 @@ +../../units/paths.target
\ No newline at end of file diff --git a/test/test-path/sysinit.target b/test/test-path/sysinit.target new file mode 120000 index 0000000000..9d10e5b2e2 --- /dev/null +++ b/test/test-path/sysinit.target @@ -0,0 +1 @@ +../../units/sysinit.target
\ No newline at end of file diff --git a/test/testsuite.target b/test/testsuite.target new file mode 100644 index 0000000000..1a7e5b371a --- /dev/null +++ b/test/testsuite.target @@ -0,0 +1,6 @@ +[Unit] +Description=Testsuite target +Requires=multi-user.target +After=multi-user.target +Conflicts=rescue.target +AllowIsolate=yes diff --git a/test/timers.target b/test/timers.target new file mode 120000 index 0000000000..576d47fed7 --- /dev/null +++ b/test/timers.target @@ -0,0 +1 @@ +../units/timers.target
\ No newline at end of file diff --git a/test/udev-test.pl b/test/udev-test.pl new file mode 100755 index 0000000000..9723386b23 --- /dev/null +++ b/test/udev-test.pl @@ -0,0 +1,1601 @@ +#!/usr/bin/perl + +# udev test +# +# Provides automated testing of the udev binary. +# The whole test is self contained in this file, except the matching sysfs tree. +# Simply extend the @tests array, to add a new test variant. +# +# Every test is driven by its own temporary config file. +# This program prepares the environment, creates the config and calls udev. +# +# udev parses the rules, looks at the provided sysfs and +# first creates and then removes the device node. +# After creation and removal the result is checked against the +# expected value and the result is printed. +# +# Copyright (C) 2004-2012 Kay Sievers <kay@vrfy.org> +# Copyright (C) 2004 Leann Ogasawara <ogasawara@osdl.org> + +use warnings; +use strict; + +my $udev_bin = "./test-udev"; +my $valgrind = 0; +my $gdb = 0; +my $strace = 0; +my $udev_bin_valgrind = "valgrind --tool=memcheck --leak-check=yes --track-origins=yes --quiet $udev_bin"; +my $udev_bin_gdb = "gdb --args $udev_bin"; +my $udev_bin_strace = "strace -efile $udev_bin"; +my $udev_run = "test/run"; +my $udev_tmpfs = "test/tmpfs"; +my $udev_sys = "${udev_tmpfs}/sys"; +my $udev_dev = "${udev_tmpfs}/dev"; +my $udev_rules_dir = "$udev_run/udev/rules.d"; +my $udev_rules = "$udev_rules_dir/udev-test.rules"; +my $EXIT_TEST_SKIP = 77; + +my $rules_10k_tags = ""; +for (my $i = 1; $i <= 10000; ++$i) { + $rules_10k_tags .= 'KERNEL=="sda", TAG+="test' . $i . "\"\n"; +} + +my @tests = ( + { + desc => "no rules", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "sda" , + exp_rem_error => "yes", + rules => <<EOF +# +EOF + }, + { + desc => "label test of scsi disc", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "boot_disk" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "label test of scsi disc", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "boot_disk" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "label test of scsi disc", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "boot_disk" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "label test of scsi partition", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "boot_disk1" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" +EOF + }, + { + desc => "label test of pattern match", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "boot_disk1" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="?ATA", SYMLINK+="boot_disk%n-1" +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA?", SYMLINK+="boot_disk%n-2" +SUBSYSTEMS=="scsi", ATTRS{vendor}=="A??", SYMLINK+="boot_disk%n" +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATAS", SYMLINK+="boot_disk%n-3" +EOF + }, + { + desc => "label test of multiple sysfs files", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "boot_disk1" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS X ", SYMLINK+="boot_diskX%n" +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="boot_disk%n" +EOF + }, + { + desc => "label test of max sysfs files (skip invalid rule)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "boot_disk1" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n" +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n" +EOF + }, + { + desc => "catch device by *", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem/0" , + rules => <<EOF +KERNEL=="ttyACM*", SYMLINK+="modem/%n" +EOF + }, + { + desc => "catch device by * - take 2", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem/0" , + rules => <<EOF +KERNEL=="*ACM1", SYMLINK+="bad" +KERNEL=="*ACM0", SYMLINK+="modem/%n" +EOF + }, + { + desc => "catch device by ?", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem/0" , + rules => <<EOF +KERNEL=="ttyACM??*", SYMLINK+="modem/%n-1" +KERNEL=="ttyACM??", SYMLINK+="modem/%n-2" +KERNEL=="ttyACM?", SYMLINK+="modem/%n" +EOF + }, + { + desc => "catch device by character class", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem/0" , + rules => <<EOF +KERNEL=="ttyACM[A-Z]*", SYMLINK+="modem/%n-1" +KERNEL=="ttyACM?[0-9]", SYMLINK+="modem/%n-2" +KERNEL=="ttyACM[0-9]*", SYMLINK+="modem/%n" +EOF + }, + { + desc => "replace kernel name", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "Handle comment lines in config file (and replace kernel name)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF +# this is a comment +KERNEL=="ttyACM0", SYMLINK+="modem" + +EOF + }, + { + desc => "Handle comment lines in config file with whitespace (and replace kernel name)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF + # this is a comment with whitespace before the comment +KERNEL=="ttyACM0", SYMLINK+="modem" + +EOF + }, + { + desc => "Handle whitespace only lines (and replace kernel name)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "whitespace" , + rules => <<EOF + + + + # this is a comment with whitespace before the comment +KERNEL=="ttyACM0", SYMLINK+="whitespace" + + + +EOF + }, + { + desc => "Handle empty lines in config file (and replace kernel name)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF + +KERNEL=="ttyACM0", SYMLINK+="modem" + +EOF + }, + { + desc => "Handle backslashed multi lines in config file (and replace kernel name)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF +KERNEL=="ttyACM0", \\ +SYMLINK+="modem" + +EOF + }, + { + desc => "preserve backslashes, if they are not for a newline", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "aaa", + rules => <<EOF +KERNEL=="ttyACM0", PROGRAM=="/bin/echo -e \\101", RESULT=="A", SYMLINK+="aaa" +EOF + }, + { + desc => "Handle stupid backslashed multi lines in config file (and replace kernel name)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF + +# +\\ + +\\ + +#\\ + +KERNEL=="ttyACM0", \\ + SYMLINK+="modem" + +EOF + }, + { + desc => "subdirectory handling", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "sub/direct/ory/modem" , + rules => <<EOF +KERNEL=="ttyACM0", SYMLINK+="sub/direct/ory/modem" +EOF + }, + { + desc => "parent device name match of scsi partition", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "first_disk5" , + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="first_disk%n" +EOF + }, + { + desc => "test substitution chars", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "Major:8:minor:5:kernelnumber:5:id:0:0:0:0" , + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:%M:minor:%m:kernelnumber:%n:id:%b" +EOF + }, + { + desc => "import of shell-value returned from program", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node12345678", + rules => <<EOF +SUBSYSTEMS=="scsi", IMPORT{program}="/bin/echo -e \' TEST_KEY=12345678\\n TEST_key2=98765\'", SYMLINK+="node\$env{TEST_KEY}" +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "sustitution of sysfs value (%s{file})", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "disk-ATA-sda" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="disk-%s{vendor}-%k" +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "program result substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "special-device-5" , + not_exp_name => "not" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="-special-*", SYMLINK+="not" +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="special-*", SYMLINK+="%c-%n" +EOF + }, + { + desc => "program result substitution (newline removal)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "newline_removed" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo test", RESULT=="test", SYMLINK+="newline_removed" +EOF + }, + { + desc => "program result substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "test-0:0:0:0" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n test-%b", RESULT=="test-0:0*", SYMLINK+="%c" +EOF + }, + { + desc => "program with lots of arguments", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "foo9" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="%c{7}" +EOF + }, + { + desc => "program with subshell", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "bar9" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'echo foo3 foo4 foo5 foo6 foo7 foo8 foo9 | sed s/foo9/bar9/'", KERNEL=="sda5", SYMLINK+="%c{7}" +EOF + }, + { + desc => "program arguments combined with apostrophes", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "foo7" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n 'foo3 foo4' 'foo5 foo6 foo7 foo8'", KERNEL=="sda5", SYMLINK+="%c{5}" +EOF + }, + { + desc => "characters before the %c{N} substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "my-foo9" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{7}" +EOF + }, + { + desc => "substitute the second to last argument", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "my-foo8" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{6}" +EOF + }, + { + desc => "test substitution by variable name", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "Major:8-minor:5-kernelnumber:5-id:0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:\$major-minor:\$minor-kernelnumber:\$number-id:\$id" +EOF + }, + { + desc => "test substitution by variable name 2", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "Major:8-minor:5-kernelnumber:5-id:0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="Major:\$major-minor:%m-kernelnumber:\$number-id:\$id" +EOF + }, + { + desc => "test substitution by variable name 3", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "850:0:0:05" , + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="%M%m%b%n" +EOF + }, + { + desc => "test substitution by variable name 4", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "855" , + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="\$major\$minor\$number" +EOF + }, + { + desc => "test substitution by variable name 5", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "8550:0:0:0" , + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="\$major%m%n\$id" +EOF + }, + { + desc => "non matching SUBSYSTEMS for device with no parent", + devpath => "/devices/virtual/tty/console", + exp_name => "TTY", + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo", RESULT=="foo", SYMLINK+="foo" +KERNEL=="console", SYMLINK+="TTY" +EOF + }, + { + desc => "non matching SUBSYSTEMS", + devpath => "/devices/virtual/tty/console", + exp_name => "TTY" , + rules => <<EOF +SUBSYSTEMS=="foo", ATTRS{dev}=="5:1", SYMLINK+="foo" +KERNEL=="console", SYMLINK+="TTY" +EOF + }, + { + desc => "ATTRS match", + devpath => "/devices/virtual/tty/console", + exp_name => "foo" , + rules => <<EOF +KERNEL=="console", SYMLINK+="TTY" +ATTRS{dev}=="5:1", SYMLINK+="foo" +EOF + }, + { + desc => "ATTR (empty file)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "empty" , + rules => <<EOF +KERNEL=="sda", ATTR{test_empty_file}=="?*", SYMLINK+="something" +KERNEL=="sda", ATTR{test_empty_file}!="", SYMLINK+="not-empty" +KERNEL=="sda", ATTR{test_empty_file}=="", SYMLINK+="empty" +KERNEL=="sda", ATTR{test_empty_file}!="?*", SYMLINK+="not-something" +EOF + }, + { + desc => "ATTR (non-existent file)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "non-existent" , + rules => <<EOF +KERNEL=="sda", ATTR{nofile}=="?*", SYMLINK+="something" +KERNEL=="sda", ATTR{nofile}!="", SYMLINK+="not-empty" +KERNEL=="sda", ATTR{nofile}=="", SYMLINK+="empty" +KERNEL=="sda", ATTR{nofile}!="?*", SYMLINK+="not-something" +KERNEL=="sda", TEST!="nofile", SYMLINK+="non-existent" +KERNEL=="sda", SYMLINK+="wrong" +EOF + }, + { + desc => "program and bus type match", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "scsi-0:0:0:0" , + rules => <<EOF +SUBSYSTEMS=="usb", PROGRAM=="/bin/echo -n usb-%b", SYMLINK+="%c" +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n scsi-%b", SYMLINK+="%c" +SUBSYSTEMS=="foo", PROGRAM=="/bin/echo -n foo-%b", SYMLINK+="%c" +EOF + }, + { + desc => "sysfs parent hierarchy", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF +ATTRS{idProduct}=="007b", SYMLINK+="modem" +EOF + }, + { + desc => "name test with ! in the name", + devpath => "/devices/virtual/block/fake!blockdev0", + exp_name => "is/a/fake/blockdev0" , + rules => <<EOF +SUBSYSTEMS=="scsi", SYMLINK+="is/not/a/%k" +SUBSYSTEM=="block", SYMLINK+="is/a/%k" +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "name test with ! in the name, but no matching rule", + devpath => "/devices/virtual/block/fake!blockdev0", + exp_name => "fake/blockdev0" , + exp_rem_error => "yes", + rules => <<EOF +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "KERNELS rule", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "scsi-0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="usb", KERNELS=="0:0:0:0", SYMLINK+="not-scsi" +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:1", SYMLINK+="no-match" +SUBSYSTEMS=="scsi", KERNELS==":0", SYMLINK+="short-id" +SUBSYSTEMS=="scsi", KERNELS=="/0:0:0:0", SYMLINK+="no-match" +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="scsi-0:0:0:0" +EOF + }, + { + desc => "KERNELS wildcard all", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "scsi-0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="*:1", SYMLINK+="no-match" +SUBSYSTEMS=="scsi", KERNELS=="*:0:1", SYMLINK+="no-match" +SUBSYSTEMS=="scsi", KERNELS=="*:0:0:1", SYMLINK+="no-match" +SUBSYSTEMS=="scsi", KERNEL=="0:0:0:0", SYMLINK+="before" +SUBSYSTEMS=="scsi", KERNELS=="*", SYMLINK+="scsi-0:0:0:0" +EOF + }, + { + desc => "KERNELS wildcard partial", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "scsi-0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before" +SUBSYSTEMS=="scsi", KERNELS=="*:0", SYMLINK+="scsi-0:0:0:0" +EOF + }, + { + desc => "KERNELS wildcard partial 2", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "scsi-0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before" +SUBSYSTEMS=="scsi", KERNELS=="*:0:0:0", SYMLINK+="scsi-0:0:0:0" +EOF + }, + { + desc => "substitute attr with link target value (first match)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "driver-is-sd", + rules => <<EOF +SUBSYSTEMS=="scsi", SYMLINK+="driver-is-\$attr{driver}" +EOF + }, + { + desc => "substitute attr with link target value (currently selected device)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "driver-is-ahci", + rules => <<EOF +SUBSYSTEMS=="pci", SYMLINK+="driver-is-\$attr{driver}" +EOF + }, + { + desc => "ignore ATTRS attribute whitespace", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "ignored", + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE", SYMLINK+="ignored" +EOF + }, + { + desc => "do not ignore ATTRS attribute whitespace", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "matched-with-space", + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE ", SYMLINK+="wrong-to-ignore" +SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE ", SYMLINK+="matched-with-space" +EOF + }, + { + desc => "permissions USER=bad GROUP=name", + devpath => "/devices/virtual/tty/tty33", + exp_name => "tty33", + exp_perms => "0:0:0600", + rules => <<EOF +KERNEL=="tty33", OWNER="bad", GROUP="name" +EOF + }, + { + desc => "permissions OWNER=1", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => "1::0600", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1" +EOF + }, + { + desc => "permissions GROUP=1", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => ":1:0660", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="1" +EOF + }, + { + desc => "textual user id", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => "nobody::0600", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="nobody" +EOF + }, + { + desc => "textual group id", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => ":daemon:0660", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="daemon" +EOF + }, + { + desc => "textual user/group id", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => "root:mail:0660", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="root", GROUP="mail" +EOF + }, + { + desc => "permissions MODE=0777", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => "::0777", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", MODE="0777" +EOF + }, + { + desc => "permissions OWNER=1 GROUP=1 MODE=0777", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => "1:1:0777", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1", GROUP="1", MODE="0777" +EOF + }, + { + desc => "permissions OWNER to 1", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => "1::", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1" +EOF + }, + { + desc => "permissions GROUP to 1", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => ":1:0660", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="1" +EOF + }, + { + desc => "permissions MODE to 0060", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => "::0060", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", MODE="0060" +EOF + }, + { + desc => "permissions OWNER, GROUP, MODE", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => "1:1:0777", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1", GROUP="1", MODE="0777" +EOF + }, + { + desc => "permissions only rule", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => "1:1:0777", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", OWNER="1", GROUP="1", MODE="0777" +KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444" +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n" +EOF + }, + { + desc => "multiple permissions only rule", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => "1:1:0777", + rules => <<EOF +SUBSYSTEM=="tty", OWNER="1" +SUBSYSTEM=="tty", GROUP="1" +SUBSYSTEM=="tty", MODE="0777" +KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444" +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n" +EOF + }, + { + desc => "permissions only rule with override at SYMLINK+ rule", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => "1:2:0777", + rules => <<EOF +SUBSYSTEM=="tty", OWNER="1" +SUBSYSTEM=="tty", GROUP="1" +SUBSYSTEM=="tty", MODE="0777" +KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444" +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="2" +EOF + }, + { + desc => "major/minor number test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_majorminor => "8:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node" +EOF + }, + { + desc => "big major number test", + devpath => "/devices/virtual/misc/misc-fake1", + exp_name => "node", + exp_majorminor => "4095:1", + rules => <<EOF +KERNEL=="misc-fake1", SYMLINK+="node" +EOF + }, + { + desc => "big major and big minor number test", + devpath => "/devices/virtual/misc/misc-fake89999", + exp_name => "node", + exp_majorminor => "4095:89999", + rules => <<EOF +KERNEL=="misc-fake89999", SYMLINK+="node" +EOF + }, + { + desc => "multiple symlinks with format char", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "symlink2-ttyACM0", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK="symlink1-%n symlink2-%k symlink3-%b" +EOF + }, + { + desc => "multiple symlinks with a lot of s p a c e s", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "one", + not_exp_name => " ", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK=" one two " +EOF + }, + { + desc => "symlink creation (same directory)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem0", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK="modem%n" +EOF + }, + { + desc => "multiple symlinks", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "second-0" , + rules => <<EOF +KERNEL=="ttyACM0", SYMLINK="first-%n second-%n third-%n" +EOF + }, + { + desc => "symlink name '.'", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => ".", + exp_add_error => "yes", + exp_rem_error => "yes", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="." +EOF + }, + { + desc => "symlink node to itself", + devpath => "/devices/virtual/tty/tty0", + exp_name => "link", + exp_add_error => "yes", + exp_rem_error => "yes", + option => "clean", + rules => <<EOF +KERNEL=="tty0", SYMLINK+="tty0" +EOF + }, + { + desc => "symlink %n substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "symlink0", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink%n" +EOF + }, + { + desc => "symlink %k substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "symlink-ttyACM0", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink-%k" +EOF + }, + { + desc => "symlink %M:%m substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "major-166:0", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="major-%M:%m" +EOF + }, + { + desc => "symlink %b substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "symlink-0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="symlink-%b" +EOF + }, + { + desc => "symlink %c substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "test", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo test", SYMLINK+="%c" +EOF + }, + { + desc => "symlink %c{N} substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "test", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2}" +EOF + }, + { + desc => "symlink %c{N+} substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "this", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2+}" +EOF + }, + { + desc => "symlink only rule with %c{N+}", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "test", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/bin/echo link test this" SYMLINK+="%c{2+}" +EOF + }, + { + desc => "symlink %s{filename} substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "166:0", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="%s{dev}" +EOF + }, + { + desc => "program result substitution (numbered part of)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "link1", + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2", RESULT=="node *", SYMLINK+="%c{2} %c{3}" +EOF + }, + { + desc => "program result substitution (numbered part of+)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "link4", + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2 link3 link4", RESULT=="node *", SYMLINK+="%c{2+}" +EOF + }, + { + desc => "SUBSYSTEM match test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", SUBSYSTEM=="vc" +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", SUBSYSTEM=="block" +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match2", SUBSYSTEM=="vc" +EOF + }, + { + desc => "DRIVERS match test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", DRIVERS=="sd-wrong" +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", DRIVERS=="sd" +EOF + }, + { + desc => "devnode substitution test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/usr/bin/test -b %N" SYMLINK+="node" +EOF + }, + { + desc => "parent node name substitution test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "sda-part-1", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-1" +EOF + }, + { + desc => "udev_root substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "start-/dev-end", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="start-%r-end" +EOF + }, + { + desc => "last_rule option", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "last", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="last", OPTIONS="last_rule" +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="very-last" +EOF + }, + { + desc => "negation KERNEL!=", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "match", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL!="sda1", SYMLINK+="matches-but-is-negated" +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" +SUBSYSTEMS=="scsi", KERNEL!="xsda1", SYMLINK+="match" +EOF + }, + { + desc => "negation SUBSYSTEM!=", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "not-anything", + rules => <<EOF +SUBSYSTEMS=="scsi", SUBSYSTEM=="block", KERNEL!="sda1", SYMLINK+="matches-but-is-negated" +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" +SUBSYSTEMS=="scsi", SUBSYSTEM!="anything", SYMLINK+="not-anything" +EOF + }, + { + desc => "negation PROGRAM!= exit code", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "nonzero-program", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" +KERNEL=="sda1", PROGRAM!="/bin/false", SYMLINK+="nonzero-program" +EOF + }, + { + desc => "ENV{} test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "true", + rules => <<EOF +ENV{ENV_KEY_TEST}="test" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", SYMLINK+="true" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad" +EOF + }, + { + desc => "ENV{} test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "true", + rules => <<EOF +ENV{ENV_KEY_TEST}="test" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="yes", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sdax1", SYMLINK+="no" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sda1", SYMLINK+="true" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad" +EOF + }, + { + desc => "ENV{} test (assign)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "true", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no" +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", SYMLINK+="true" +EOF + }, + { + desc => "ENV{} test (assign 2 times)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "true", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="absolutely-\$env{ASSIGN}" +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="absolutely-true", SYMLINK+="true" +EOF + }, + { + desc => "ENV{} test (assign2)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "part", + rules => <<EOF +SUBSYSTEM=="block", KERNEL=="*[0-9]", ENV{PARTITION}="true", ENV{MAINDEVICE}="false" +SUBSYSTEM=="block", KERNEL=="*[!0-9]", ENV{PARTITION}="false", ENV{MAINDEVICE}="true" +ENV{MAINDEVICE}=="true", SYMLINK+="disk" +SUBSYSTEM=="block", SYMLINK+="before" +ENV{PARTITION}=="true", SYMLINK+="part" +EOF + }, + { + desc => "untrusted string sanitize", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "sane", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e name; (/usr/bin/badprogram)", RESULT=="name_ _/usr/bin/badprogram_", SYMLINK+="sane" +EOF + }, + { + desc => "untrusted string sanitize (don't replace utf8)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "uber", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \\xc3\\xbcber" RESULT=="\xc3\xbcber", SYMLINK+="uber" +EOF + }, + { + desc => "untrusted string sanitize (replace invalid utf8)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "replaced", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \\xef\\xe8garbage", RESULT=="__garbage", SYMLINK+="replaced" +EOF + }, + { + desc => "read sysfs value from parent device", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "serial-354172020305000", + rules => <<EOF +KERNEL=="ttyACM*", ATTRS{serial}=="?*", SYMLINK+="serial-%s{serial}" +EOF + }, + { + desc => "match against empty key string", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "ok", + rules => <<EOF +KERNEL=="sda", ATTRS{nothing}!="", SYMLINK+="not-1-ok" +KERNEL=="sda", ATTRS{nothing}=="", SYMLINK+="not-2-ok" +KERNEL=="sda", ATTRS{vendor}!="", SYMLINK+="ok" +KERNEL=="sda", ATTRS{vendor}=="", SYMLINK+="not-3-ok" +EOF + }, + { + desc => "check ACTION value", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "ok", + rules => <<EOF +ACTION=="unknown", KERNEL=="sda", SYMLINK+="unknown-not-ok" +ACTION=="add", KERNEL=="sda", SYMLINK+="ok" +EOF + }, + { + desc => "final assignment", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "ok", + exp_perms => "root:tty:0640", + rules => <<EOF +KERNEL=="sda", GROUP:="tty" +KERNEL=="sda", GROUP="not-ok", MODE="0640", SYMLINK+="ok" +EOF + }, + { + desc => "final assignment 2", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "ok", + exp_perms => "root:tty:0640", + rules => <<EOF +KERNEL=="sda", GROUP:="tty" +SUBSYSTEM=="block", MODE:="640" +KERNEL=="sda", GROUP="not-ok", MODE="0666", SYMLINK+="ok" +EOF + }, + { + desc => "env substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node-add-me", + rules => <<EOF +KERNEL=="sda", MODE="0666", SYMLINK+="node-\$env{ACTION}-me" +EOF + }, + { + desc => "reset list to current value", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "three", + not_exp_name => "two", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="one" +KERNEL=="ttyACM[0-9]*", SYMLINK+="two" +KERNEL=="ttyACM[0-9]*", SYMLINK="three" +EOF + }, + { + desc => "test empty SYMLINK+ (empty override)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "right", + not_exp_name => "wrong", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="wrong" +KERNEL=="ttyACM[0-9]*", SYMLINK="" +KERNEL=="ttyACM[0-9]*", SYMLINK+="right" +EOF + }, + { + desc => "test multi matches", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "right", + rules => <<EOF +KERNEL=="ttyACM*", SYMLINK+="before" +KERNEL=="ttyACM*|nothing", SYMLINK+="right" +EOF + }, + { + desc => "test multi matches 2", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "right", + rules => <<EOF +KERNEL=="dontknow*|*nothing", SYMLINK+="nomatch" +KERNEL=="ttyACM*", SYMLINK+="before" +KERNEL=="dontknow*|ttyACM*|nothing*", SYMLINK+="right" +EOF + }, + { + desc => "test multi matches 3", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "right", + rules => <<EOF +KERNEL=="dontknow|nothing", SYMLINK+="nomatch" +KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1" +KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2" +KERNEL=="dontknow|ttyACM0|nothing", SYMLINK+="right" +EOF + }, + { + desc => "test multi matches 4", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "right", + rules => <<EOF +KERNEL=="dontknow|nothing", SYMLINK+="nomatch" +KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1" +KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2" +KERNEL=="all|dontknow|ttyACM0", SYMLINK+="right" +KERNEL=="ttyACM0a|nothing", SYMLINK+="wrong3" +EOF + }, + { + desc => "IMPORT parent test sequence 1/2 (keep)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "parent", + option => "keep", + rules => <<EOF +KERNEL=="sda", IMPORT{program}="/bin/echo -e \'PARENT_KEY=parent_right\\nWRONG_PARENT_KEY=parent_wrong'" +KERNEL=="sda", SYMLINK+="parent" +EOF + }, + { + desc => "IMPORT parent test sequence 2/2 (keep)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "parentenv-parent_right", + option => "clean", + rules => <<EOF +KERNEL=="sda1", IMPORT{parent}="PARENT*", SYMLINK+="parentenv-\$env{PARENT_KEY}\$env{WRONG_PARENT_KEY}" +EOF + }, + { + desc => "GOTO test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "right", + rules => <<EOF +KERNEL=="sda1", GOTO="TEST" +KERNEL=="sda1", SYMLINK+="wrong" +KERNEL=="sda1", GOTO="BAD" +KERNEL=="sda1", SYMLINK+="", LABEL="NO" +KERNEL=="sda1", SYMLINK+="right", LABEL="TEST", GOTO="end" +KERNEL=="sda1", SYMLINK+="wrong2", LABEL="BAD" +LABEL="end" +EOF + }, + { + desc => "GOTO label does not exist", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "right", + rules => <<EOF +KERNEL=="sda1", GOTO="does-not-exist" +KERNEL=="sda1", SYMLINK+="right", +LABEL="exists" +EOF + }, + { + desc => "SYMLINK+ compare test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "right", + not_exp_name => "wrong", + rules => <<EOF +KERNEL=="sda1", SYMLINK+="link" +KERNEL=="sda1", SYMLINK=="link*", SYMLINK+="right" +KERNEL=="sda1", SYMLINK=="nolink*", SYMLINK+="wrong" +EOF + }, + { + desc => "invalid key operation", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "yes", + rules => <<EOF +KERNEL="sda1", SYMLINK+="no" +KERNEL=="sda1", SYMLINK+="yes" +EOF + }, + { + desc => "operator chars in attribute", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "yes", + rules => <<EOF +KERNEL=="sda", ATTR{test:colon+plus}=="?*", SYMLINK+="yes" +EOF + }, + { + desc => "overlong comment line", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "yes", + rules => <<EOF +# 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +KERNEL=="sda1", SYMLINK+=="no" +KERNEL=="sda1", SYMLINK+="yes" +EOF + }, + { + desc => "magic subsys/kernel lookup", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "00:16:41:e2:8d:ff", + rules => <<EOF +KERNEL=="sda", SYMLINK+="\$attr{[net/eth0]address}" +EOF + }, + { + desc => "TEST absolute path", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "there", + rules => <<EOF +TEST=="/etc/machine-id", SYMLINK+="there" +TEST!="/etc/machine-id", SYMLINK+="notthere" +EOF + }, + { + desc => "TEST subsys/kernel lookup", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "yes", + rules => <<EOF +KERNEL=="sda", TEST=="[net/eth0]", SYMLINK+="yes" +EOF + }, + { + desc => "TEST relative path", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "relative", + rules => <<EOF +KERNEL=="sda", TEST=="size", SYMLINK+="relative" +EOF + }, + { + desc => "TEST wildcard substitution (find queue/nr_requests)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "found-subdir", + rules => <<EOF +KERNEL=="sda", TEST=="*/nr_requests", SYMLINK+="found-subdir" +EOF + }, + { + desc => "TEST MODE=0000", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "sda", + exp_perms => "0:0:0000", + exp_rem_error => "yes", + rules => <<EOF +KERNEL=="sda", MODE="0000" +EOF + }, + { + desc => "TEST PROGRAM feeds OWNER, GROUP, MODE", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "sda", + exp_perms => "1:1:0400", + exp_rem_error => "yes", + rules => <<EOF +KERNEL=="sda", MODE="666" +KERNEL=="sda", PROGRAM=="/bin/echo 1 1 0400", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}" +EOF + }, + { + desc => "TEST PROGRAM feeds MODE with overflow", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "sda", + exp_perms => "0:0:0440", + exp_rem_error => "yes", + rules => <<EOF +KERNEL=="sda", MODE="440" +KERNEL=="sda", PROGRAM=="/bin/echo 0 0 0400letsdoabuffferoverflow0123456789012345789012345678901234567890", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}" +EOF + }, + { + desc => "magic [subsys/sysname] attribute substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "sda-8741C4G-end", + exp_perms => "0:0:0600", + rules => <<EOF +KERNEL=="sda", PROGRAM="/bin/true create-envp" +KERNEL=="sda", ENV{TESTENV}="change-envp" +KERNEL=="sda", SYMLINK+="%k-%s{[dmi/id]product_name}-end" +EOF + }, + { + desc => "builtin path_id", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0", + rules => <<EOF +KERNEL=="sda", IMPORT{builtin}="path_id" +KERNEL=="sda", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/\$env{ID_PATH}" +EOF + }, + { + desc => "add and match tag", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "found", + not_exp_name => "bad" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", TAG+="green" +TAGS=="green", SYMLINK+="found" +TAGS=="blue", SYMLINK+="bad" +EOF + }, + { + desc => "don't crash with lots of tags", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "found", + rules => $rules_10k_tags . <<EOF +TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="found" +EOF + }, +); + +sub udev { + my ($action, $devpath, $rules) = @_; + + # create temporary rules + system("mkdir", "-p", "$udev_rules_dir"); + open CONF, ">$udev_rules" || die "unable to create rules file: $udev_rules"; + print CONF $$rules; + close CONF; + + if ($valgrind > 0) { + return system("$udev_bin_valgrind $action $devpath"); + } elsif ($gdb > 0) { + return system("$udev_bin_gdb $action $devpath"); + } elsif ($strace > 0) { + return system("$udev_bin_strace $action $devpath"); + } else { + return system("$udev_bin", "$action", "$devpath"); + } +} + +my $error = 0; + +sub permissions_test { + my($rules, $uid, $gid, $mode) = @_; + + my $wrong = 0; + my $userid; + my $groupid; + + $rules->{exp_perms} =~ m/^(.*):(.*):(.*)$/; + if ($1 ne "") { + if (defined(getpwnam($1))) { + $userid = int(getpwnam($1)); + } else { + $userid = $1; + } + if ($uid != $userid) { $wrong = 1; } + } + if ($2 ne "") { + if (defined(getgrnam($2))) { + $groupid = int(getgrnam($2)); + } else { + $groupid = $2; + } + if ($gid != $groupid) { $wrong = 1; } + } + if ($3 ne "") { + if (($mode & 07777) != oct($3)) { $wrong = 1; }; + } + if ($wrong == 0) { + print "permissions: ok\n"; + } else { + printf " expected permissions are: %s:%s:%#o\n", $1, $2, oct($3); + printf " created permissions are : %i:%i:%#o\n", $uid, $gid, $mode & 07777; + print "permissions: error\n"; + $error++; + sleep(1); + } +} + +sub major_minor_test { + my($rules, $rdev) = @_; + + my $major = ($rdev >> 8) & 0xfff; + my $minor = ($rdev & 0xff) | (($rdev >> 12) & 0xfff00); + my $wrong = 0; + + $rules->{exp_majorminor} =~ m/^(.*):(.*)$/; + if ($1 ne "") { + if ($major != $1) { $wrong = 1; }; + } + if ($2 ne "") { + if ($minor != $2) { $wrong = 1; }; + } + if ($wrong == 0) { + print "major:minor: ok\n"; + } else { + printf " expected major:minor is: %i:%i\n", $1, $2; + printf " created major:minor is : %i:%i\n", $major, $minor; + print "major:minor: error\n"; + $error++; + sleep(1); + } +} + +sub udev_setup { + system("umount", $udev_tmpfs); + rmdir($udev_tmpfs); + mkdir($udev_tmpfs) || die "unable to create udev_tmpfs: $udev_tmpfs\n"; + system("mount", "-o", "rw,mode=755,nosuid,noexec,nodev", "-t", "tmpfs", "tmpfs", $udev_tmpfs) && die "unable to mount tmpfs"; + + mkdir($udev_dev) || die "unable to create udev_dev: $udev_dev\n"; + # setting group and mode of udev_dev ensures the tests work + # even if the parent directory has setgid bit enabled. + chown (0, 0, $udev_dev) || die "unable to chown $udev_dev\n"; + chmod (0755, $udev_dev) || die "unable to chmod $udev_dev\n"; + + system("cp", "-r", "test/sys/", $udev_sys) && die "unable to copy test/sys"; + + system("rm", "-rf", "$udev_run"); +} + +sub run_test { + my ($rules, $number) = @_; + my $rc; + + print "TEST $number: $rules->{desc}\n"; + print "device \'$rules->{devpath}\' expecting node/link \'$rules->{exp_name}\'\n"; + + $rc = udev("add", $rules->{devpath}, \$rules->{rules}); + if ($rc != 0) { + print "$udev_bin add failed with code $rc\n"; + $error++; + } + if (defined($rules->{not_exp_name})) { + if ((-e "$udev_dev/$rules->{not_exp_name}") || + (-l "$udev_dev/$rules->{not_exp_name}")) { + print "nonexistent: error \'$rules->{not_exp_name}\' not expected to be there\n"; + $error++; + sleep(1); + } + } + + if ((-e "$udev_dev/$rules->{exp_name}") || + (-l "$udev_dev/$rules->{exp_name}")) { + + my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, + $atime, $mtime, $ctime, $blksize, $blocks) = stat("$udev_dev/$rules->{exp_name}"); + + if (defined($rules->{exp_perms})) { + permissions_test($rules, $uid, $gid, $mode); + } + if (defined($rules->{exp_majorminor})) { + major_minor_test($rules, $rdev); + } + print "add: ok\n"; + } else { + print "add: error"; + if ($rules->{exp_add_error}) { + print " as expected\n"; + } else { + print "\n"; + system("tree", "$udev_dev"); + print "\n"; + $error++; + sleep(1); + } + } + + if (defined($rules->{option}) && $rules->{option} eq "keep") { + print "\n\n"; + return; + } + + $rc = udev("remove", $rules->{devpath}, \$rules->{rules}); + if ($rc != 0) { + print "$udev_bin remove failed with code $rc\n"; + $error++; + } + if ((-e "$udev_dev/$rules->{exp_name}") || + (-l "$udev_dev/$rules->{exp_name}")) { + print "remove: error"; + if ($rules->{exp_rem_error}) { + print " as expected\n"; + } else { + print "\n"; + system("tree", "$udev_dev"); + print "\n"; + $error++; + sleep(1); + } + } else { + print "remove: ok\n"; + } + + print "\n"; + + if (defined($rules->{option}) && $rules->{option} eq "clean") { + udev_setup(); + } + +} + +# only run if we have root permissions +# due to mknod restrictions +if (!($<==0)) { + print "Must have root permissions to run properly.\n"; + exit($EXIT_TEST_SKIP); +} + +# skip the test when running in a chroot +system("systemd-detect-virt", "-r", "-q"); +if ($? >> 8 == 0) { + print "Running in a chroot, skipping the test.\n"; + exit($EXIT_TEST_SKIP); +} + +# skip the test when running in a container +system("systemd-detect-virt", "-c", "-q"); +if ($? >> 8 == 0) { + print "Running in a container, skipping the test.\n"; + exit($EXIT_TEST_SKIP); +} + +udev_setup(); + +my $test_num = 1; +my @list; + +foreach my $arg (@ARGV) { + if ($arg =~ m/--valgrind/) { + $valgrind = 1; + printf("using valgrind\n"); + } elsif ($arg =~ m/--gdb/) { + $gdb = 1; + printf("using gdb\n"); + } elsif ($arg =~ m/--strace/) { + $strace = 1; + printf("using strace\n"); + } else { + push(@list, $arg); + } +} + +if ($list[0]) { + foreach my $arg (@list) { + if (defined($tests[$arg-1]->{desc})) { + print "udev-test will run test number $arg:\n\n"; + run_test($tests[$arg-1], $arg); + } else { + print "test does not exist.\n"; + } + } +} else { + # test all + print "\nudev-test will run ".($#tests + 1)." tests:\n\n"; + + foreach my $rules (@tests) { + run_test($rules, $test_num); + $test_num++; + } +} + +print "$error errors occurred\n\n"; + +# cleanup +system("rm", "-rf", "$udev_run"); +system("umount", "$udev_tmpfs"); +rmdir($udev_tmpfs); + +if ($error > 0) { + exit(1); +} +exit(0); diff --git a/test/unstoppable.service b/test/unstoppable.service new file mode 100644 index 0000000000..56b72c98f7 --- /dev/null +++ b/test/unstoppable.service @@ -0,0 +1,5 @@ +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/echo "I'm unstoppable!" +ExecStop=/bin/systemctl start --no-block unstoppable.service |