diff options
author | Luke Shumaker <lukeshu@sbcglobal.net> | 2016-12-17 02:37:55 -0500 |
---|---|---|
committer | Luke Shumaker <lukeshu@sbcglobal.net> | 2016-12-17 02:37:55 -0500 |
commit | 3f0ba0278cd61b2d3bb01abc744d98a6ba3d7730 (patch) | |
tree | dc70188bf68d634496a429225130f777c155b990 | |
parent | 54ebaa42b40d22bd0288507420b7a7241896c357 (diff) | |
parent | 58a6dd15582c038a25bd7059435833943e2e4617 (diff) |
Merge commit '58a6dd15582c038a25bd7059435833943e2e4617' into notsystemd/premove
# Conflicts:
# Makefile.am
# src/boot/bootctl.c
# system-preset/90-systemd.preset
172 files changed, 5572 insertions, 1445 deletions
diff --git a/Makefile-man.am b/Makefile-man.am index 8ab733360d..3ac1906a4a 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -23,6 +23,7 @@ MANPAGES += \ man/localtime.5 \ man/machine-id.5 \ man/machine-info.5 \ + man/nss-systemd.8 \ man/os-release.5 \ man/sd-bus-errors.3 \ man/sd-bus.3 \ @@ -255,6 +256,7 @@ MANPAGES_ALIAS += \ man/SD_WARNING.3 \ man/init.1 \ man/journald.conf.d.5 \ + man/libnss_systemd.so.2.8 \ man/poweroff.8 \ man/reboot.8 \ man/sd_bus_creds_get_audit_login_uid.3 \ @@ -587,6 +589,7 @@ man/SD_NOTICE.3: man/sd-daemon.3 man/SD_WARNING.3: man/sd-daemon.3 man/init.1: man/systemd.1 man/journald.conf.d.5: man/journald.conf.5 +man/libnss_systemd.so.2.8: man/nss-systemd.8 man/poweroff.8: man/halt.8 man/reboot.8: man/halt.8 man/sd_bus_creds_get_audit_login_uid.3: man/sd_bus_creds_get_pid.3 @@ -1071,6 +1074,9 @@ man/init.html: man/systemd.html man/journald.conf.d.html: man/journald.conf.html $(html-alias) +man/libnss_systemd.so.2.html: man/nss-systemd.html + $(html-alias) + man/poweroff.html: man/halt.html $(html-alias) @@ -2519,6 +2525,7 @@ EXTRA_DIST += \ man/nss-myhostname.xml \ man/nss-mymachines.xml \ man/nss-resolve.xml \ + man/nss-systemd.xml \ man/os-release.xml \ man/pam_systemd.xml \ man/resolved.conf.xml \ diff --git a/Makefile.am b/Makefile.am index 35338b70be..2cffd44421 100644 --- a/Makefile.am +++ b/Makefile.am @@ -658,7 +658,9 @@ endif # HAVE_UTMP dist_userunit_DATA = \ units/user/basic.target \ units/user/default.target \ - units/user/exit.target + units/user/exit.target \ + units/user/graphical-session.target \ + units/user/graphical-session-pre.target nodist_userunit_DATA = \ units/user/systemd-exit.service @@ -1286,6 +1288,8 @@ libcore_la_SOURCES = \ src/core/load-dropin.h \ src/core/execute.c \ src/core/execute.h \ + src/core/dynamic-user.c \ + src/core/dynamic-user.h \ src/core/kill.c \ src/core/kill.h \ src/core/dbus.c \ @@ -1717,6 +1721,9 @@ EXTRA_DIST += \ 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-privatetmp-no.service \ @@ -4009,7 +4016,8 @@ TESTS += \ if HAVE_PYTHON TESTS += \ - test/rule-syntax-check.py + test/rule-syntax-check.py \ + hwdb/parse_hwdb.py if HAVE_SYSV_COMPAT TESTS += \ @@ -4061,7 +4069,8 @@ EXTRA_DIST += \ test/udev-test.pl \ test/rule-syntax-check.py \ test/sysv-generator-test.py \ - test/mocks/fsck + test/mocks/fsck \ + hwdb/parse_hwdb.py #@auto/ata_id/Makefile --------------------------------------------------------- ata_id_SOURCES = \ @@ -4807,9 +4816,6 @@ nodist_udevrules_DATA += \ nodist_systemunit_DATA += \ units/systemd-vconsole-setup.service - -SYSINIT_TARGET_WANTS += \ - systemd-vconsole-setup.service endif # ENABLE_VCONSOLE EXTRA_DIST += \ @@ -5192,6 +5198,27 @@ test_nss_LDADD = \ manual_tests += \ test-nss +#@auto/nss-systemd/Makefile ---------------------------------------------------- +libnss_systemd_la_SOURCES = \ + src/nss-systemd/nss-systemd.sym \ + src/nss-systemd/nss-systemd.c + +libnss_systemd_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -shrext .so.2 \ + -Wl,--version-script=$(top_srcdir)/src/nss-systemd/nss-systemd.sym + +libnss_systemd_la_LIBADD = \ + libsystemd-internal.la \ + libbasic.la + +lib_LTLIBRARIES += \ + libnss_systemd.la + #@auto/nss-myhostname/Makefile ------------------------------------------------- if HAVE_MYHOSTNAME libnss_myhostname_la_SOURCES = \ @@ -1,5 +1,12 @@ systemd System and Service Manager +CHANGES WITH 232 in spe + + * Journald's SplitMode=login setting has been deprecated. It has been + removed from documentation, and it's use is discouraged. In a future + release it will be completely removed, and made equivalent to current + default of SplitMode=uid. + CHANGES WITH 231: * In service units the various ExecXYZ= settings have been extended @@ -201,7 +201,7 @@ USERS AND GROUPS: "systemd-coredump" system user and group to exist. NSS: - systemd ships with three NSS modules: + systemd ships with four glibc NSS modules: nss-myhostname resolves the local hostname to locally configured IP addresses, as well as "localhost" to @@ -210,15 +210,22 @@ NSS: nss-resolve enables DNS resolution via the systemd-resolved DNS/LLMNR caching stub resolver "systemd-resolved". - nss-mymachines enables resolution of all local containers - registered with machined to their respective IP addresses. + nss-mymachines enables resolution of all local containers registered + with machined to their respective IP addresses. It also maps UID/GIDs + ranges used by containers to useful names. - To make use of these NSS modules, please add them to the - "hosts: " line in /etc/nsswitch.conf. The "resolve" module - should replace the glibc "dns" module in this file. + nss-systemd enables resolution of all dynamically allocated service + users. (See the DynamicUser= setting in unit files.) - The three modules should be used in the following order: + To make use of these NSS modules, please add them to the "hosts:", + "passwd:" and "group:" lines in /etc/nsswitch.conf. The "resolve" + module should replace the glibc "dns" module in this file (and don't + worry, it chain-loads the "dns" module if it can't talk to resolved). + The four modules should be used in the following order: + + passwd: compat mymachines systemd + group: compat mymachines systemd hosts: files mymachines resolve myhostname SYSV INIT.D SCRIPTS: @@ -33,6 +33,19 @@ Janitorial Clean-ups: Features: +* RemoveIPC= in unit files for removing POSIX/SysV IPC objects + +* Introduce ProtectSystem=strict for making the entire OS hierarchy read-only + except for a select few + +* nspawn: start UID allocation loop from hash of container name + +* nspawn: support that /proc, /sys/, /dev are pre-mounted + +* define gpt header bits to select volatility mode + +* nspawn: mount loopback filesystems with "discard" + * ProtectKernelLogs= (drops CAP_SYSLOG, add seccomp for syslog() syscall, and DeviceAllow to /dev/kmsg) in service files * ProtectClock= (drops CAP_SYS_TIMES, adds seecomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc @@ -43,10 +56,9 @@ Features: * ProtectKeyRing= to take keyring calls away -* PrivateUsers= which maps the all user ids except root and the one specified - in User= to nobody +* ProtectControlGroups= which mounts all of /sys/fs/cgroup read-only -* Add AllocateUser= for allowing dynamic user ids per-service +* RemoveKeyRing= to remove all keyring entries of the specified user * Add DataDirectory=, CacheDirectory= and LogDirectory= to match RuntimeDirectory=, and create it as necessary when starting a service, owned by the right user. @@ -60,8 +72,6 @@ Features: * RestrictNamespaces= or so in services (taking away the ability to create namespaces, with setns, unshare, clone) -* nspawn: make /proc/sys/net writable? - * make sure the ratelimit object can deal with USEC_INFINITY as way to turn off things * journalctl: make sure -f ends when the container indicated by -M terminates @@ -69,6 +79,11 @@ Features: * expose the "privileged" flag of ExecCommand on the bus, and open it up to transient units +* in nss-systemd, if we run inside of RootDirectory= with PrivateUsers= set, + find a way to map the User=/Group= of the service to the right name. This way + a user/group for a service only has to exist on the host for the right + mapping to work. + * allow attaching additional journald log fields to cgroups * rework fopen_temporary() to make use of open_tmpfile_linkable() (problem: the diff --git a/configure.ac b/configure.ac index cf595e68c0..4d1c96606f 100644 --- a/configure.ac +++ b/configure.ac @@ -211,9 +211,12 @@ AS_CASE([$CC], [*clang*], -Wno-gnu-variable-sized-type-not-at-end \ ])]) +AC_ARG_ENABLE([lto], [AS_HELP_STRING([--disable-lto], [disable -flto])], + [], [enable_lto=yes]) AS_CASE([$CFLAGS], [*-O[[12345sz\ ]]*], - [CC_CHECK_FLAGS_APPEND([with_cflags], [CFLAGS], [\ - -flto])], + [AS_IF([test "x$enable_lto" = "xyes"], + [CC_CHECK_FLAGS_APPEND([with_cflags], [CFLAGS], [-flto])], + [AC_MSG_RESULT([disabling -flto as requested])])], [AC_MSG_RESULT([skipping -flto, optimization not enabled])]) AC_SUBST([OUR_CFLAGS], "$with_cflags $sanitizer_cflags") @@ -553,12 +556,30 @@ AC_SUBST(CERTIFICATEROOT) AC_ARG_WITH([support-url], AS_HELP_STRING([--with-support-url=URL], - [Specify the supoport URL to show in catalog entries included in systemd]), + [Specify the support URL to show in catalog entries included in systemd]), [SUPPORT_URL="$withval"], [SUPPORT_URL=http://lists.freedesktop.org/mailman/listinfo/systemd-devel]) AC_SUBST(SUPPORT_URL) +AC_ARG_WITH([nobody-user], + AS_HELP_STRING([--with-nobody-user=NAME], + [Specify the name of the nobody user (the one with UID 65534)]), + [NOBODY_USER_NAME="$withval"], + [NOBODY_USER_NAME=nobody]) + +AC_SUBST(NOBODY_USER_NAME) +AC_DEFINE_UNQUOTED(NOBODY_USER_NAME, ["$NOBODY_USER_NAME"], [The name of the nobody user (the one with UID 65534)]) + +AC_ARG_WITH([nobody-group], + AS_HELP_STRING([--with-nobody-group=NAME], + [Specify the name of the nobody group (the one with GID 65534)]), + [NOBODY_GROUP_NAME="$withval"], + [NOBODY_GROUP_NAME=nobody]) + +AC_SUBST(NOBODY_GROUP_NAME) +AC_DEFINE_UNQUOTED(NOBODY_GROUP_NAME, ["$NOBODY_GROUP_NAME"], [The name of the nobody group (the one with GID 65534)]) + # ------------------------------------------------------------------------------ have_xz=no AC_ARG_ENABLE(xz, AS_HELP_STRING([--disable-xz], [Disable optional XZ support])) @@ -1674,6 +1695,8 @@ AC_MSG_RESULT([ Maximum System GID: ${SYSTEM_GID_MAX} Certificate root: ${CERTIFICATEROOT} Support URL: ${SUPPORT_URL} + Nobody User Name: ${NOBODY_USER_NAME} + Nobody Group Name: ${NOBODY_GROUP_NAME} CFLAGS: ${OUR_CFLAGS} ${CFLAGS} CPPFLAGS: ${OUR_CPPFLAGS} ${CPPFLAGS} diff --git a/hwdb/60-evdev.hwdb b/hwdb/60-evdev.hwdb index d4cd61c24d..4d14a6a2f4 100644 --- a/hwdb/60-evdev.hwdb +++ b/hwdb/60-evdev.hwdb @@ -76,9 +76,9 @@ evdev:input:b0003v05ACp0254* EVDEV_ABS_36=::92 # MacbookPro10,1 (unibody, June 2012) -evdev:input:b0003v05ACp0259* -evdev:input:b0003v05ACp025A* -evdev:input:b0003v05ACp025B* +evdev:input:b0003v05ACp0262* +evdev:input:b0003v05ACp0263* +evdev:input:b0003v05ACp0264* # MacbookPro10,2 (unibody, October 2012) evdev:input:b0003v05ACp0259* evdev:input:b0003v05ACp025A* diff --git a/hwdb/60-keyboard.hwdb b/hwdb/60-keyboard.hwdb index fd49b03493..1176881650 100644 --- a/hwdb/60-keyboard.hwdb +++ b/hwdb/60-keyboard.hwdb @@ -213,7 +213,7 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnDell*:pn* KEYBOARD_KEY_8a=suspend # Fn+F1 hibernate KEYBOARD_KEY_8b=switchvideomode # Fn+F8 CRT/LCD (high keycode: "displaytoggle") KEYBOARD_KEY_8c=unknown # Fn+Right Auto Brightness - KEYBOARD_KEY_8F=switchvideomode # Fn+F7 aspect ratio + KEYBOARD_KEY_8f=switchvideomode # Fn+F7 aspect ratio KEYBOARD_KEY_90=previoussong # Front panel previous song KEYBOARD_KEY_91=prog1 # Wi-Fi Catcher (Dell-specific) KEYBOARD_KEY_92=media # MediaDirect button (house icon) @@ -482,8 +482,7 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHewlett-Packard*:pnFalco:pvr* KEYBOARD_KEY_43=volumedown KEYBOARD_KEY_44=volumeup KEYBOARD_KEY_db=search # Same position as caps lock key on most keyboards -# KEYBOARD_KEY_3e=fullscreen, no defined key sym - + # KEYBOARD_KEY_3e=fullscreen, no defined key sym # HP EliteBook 725 G2 evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHewlett-Packard*:pnHPLicrice:pvr* @@ -694,7 +693,7 @@ evdev:input:b0003v046DpC308* KEYBOARD_KEY_90003=finance # Finance KEYBOARD_KEY_90004=prog1 # My Sites KEYBOARD_KEY_90005=prog2 # Community - KEYBOARD_KEY_C0183=media # Media + KEYBOARD_KEY_c0183=media # Media # Cordless Desktop S510 evdev:input:b0003v046DpC50C* @@ -763,18 +762,18 @@ evdev:input:b0003v046DpC309* KEYBOARD_KEY_90002=camera # webcam KEYBOARD_KEY_90003=prog1 # iTouch KEYBOARD_KEY_90004=shop # Shopping - KEYBOARD_KEY_C0201=new # New (F1) - KEYBOARD_KEY_C0289=reply # Reply mail (F2) - KEYBOARD_KEY_C028B=forwardmail # Forward mail (F3) - KEYBOARD_KEY_C028C=send # Send (F4) - KEYBOARD_KEY_C021A=undo # Undo (F5). - KEYBOARD_KEY_C0279=redo # Redo (F6). - KEYBOARD_KEY_C0208=print # Print (F7) - KEYBOARD_KEY_C0207=save # Save (F8) - KEYBOARD_KEY_C0194=file # My Computer (F9) - KEYBOARD_KEY_C01A7=documents # My Documents (F10) - KEYBOARD_KEY_C01B6=images # My Pictures (F11) ?? - KEYBOARD_KEY_C01B7=sound # My Music (F12) ?? + KEYBOARD_KEY_c0201=new # New (F1) + KEYBOARD_KEY_c0289=reply # Reply mail (F2) + KEYBOARD_KEY_c028b=forwardmail # Forward mail (F3) + KEYBOARD_KEY_c028c=send # Send (F4) + KEYBOARD_KEY_c021a=undo # Undo (F5). + KEYBOARD_KEY_c0279=redo # Redo (F6). + KEYBOARD_KEY_c0208=print # Print (F7) + KEYBOARD_KEY_c0207=save # Save (F8) + KEYBOARD_KEY_c0194=file # My Computer (F9) + KEYBOARD_KEY_c01a7=documents # My Documents (F10) + KEYBOARD_KEY_c01b6=images # My Pictures (F11) ?? + KEYBOARD_KEY_c01b7=sound # My Music (F12) ?? ########################################################### @@ -797,6 +796,14 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMAXDATA:pnPro*7000*:pvr* # Medion ########################################################### +# Akoya +evdev:atkbd:dmi:bvn*:bvr*:svnMedion*:pnAkoya*:pvr* + KEYBOARD_KEY_a0=!mute + KEYBOARD_KEY_ae=!volumedown + KEYBOARD_KEY_b0=!volumeup + KEYBOARD_KEY_19=!p + KEYBOARD_KEY_df=sleep + # FID2060 evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMEDION*:pn*FID2060*:pvr* KEYBOARD_KEY_6b=channeldown # Thottle Down @@ -889,7 +896,7 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnOLPC:pnXO:* KEYBOARD_KEY_c2=f8 KEYBOARD_KEY_c3=f9 KEYBOARD_KEY_c4=f10 - KEYBOARD_KEY_c7=f11 + # KEYBOARD_KEY_c7=f11 # FIXME! KEYBOARD_KEY_d8=f12 KEYBOARD_KEY_f7=f13 KEYBOARD_KEY_f6=f14 @@ -909,7 +916,7 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnOLPC:pnXO:* KEYBOARD_KEY_c9=pageup KEYBOARD_KEY_d1=pagedown KEYBOARD_KEY_c7=home - KEYBOARD_KEY_cF=end + KEYBOARD_KEY_cf=end KEYBOARD_KEY_73=hp KEYBOARD_KEY_7e=hp KEYBOARD_KEY_db=leftmeta # left grab diff --git a/hwdb/70-mouse.hwdb b/hwdb/70-mouse.hwdb index a5b39dc41e..d8215a6179 100644 --- a/hwdb/70-mouse.hwdb +++ b/hwdb/70-mouse.hwdb @@ -44,10 +44,18 @@ # udevadm info /dev/input/eventXX. # # Allowed properties are: +# ID_INPUT_TRACKBALL # MOUSE_DPI # MOUSE_WHEEL_CLICK_ANGLE # ######################################### +# ID_INPUT_TRACKBALL # +######################################### +# +# Specified *in additition* to ID_INPUT_MOUSE if the device is a trackball. +# Removing ID_INPUT_MOUSE will break backwards compatibility. +# +######################################### # MOUSE_DPI # ######################################### # @@ -102,6 +110,14 @@ # For mice with switchable resolution, sort by the starred entry. ########################################## +# Generic +########################################## +mouse:*:name:*Trackball*: +mouse:*:name:*trackball*: +mouse:*:name:*TrackBall*: + ID_INPUT_TRACKBALL=1 + +########################################## # Apple ########################################## @@ -231,6 +247,7 @@ mouse:usb:v04b3p310c:name:USB Optical Mouse: # Logitech M570 trackball mouse:usb:v046dp1028:name:Logitech M570: MOUSE_DPI=540@167 + ID_INPUT_TRACKBALL=1 # Logitech USB-PS/2 M-BZ96C mouse:usb:v046dpc045:name:Logitech USB-PS/2 Optical Mouse: @@ -280,7 +297,7 @@ mouse:usb:v046dpc049:name:Logitech USB Gaming Mouse: mouse:usb:v046dpc24e:name:Logitech G500s Laser Gaming Mouse: MOUSE_DPI=400@500 *800@500 2000@500 - # Logitech G9 +# Logitech G9 mouse:usb:v046dpc048:name:Logitech G9 Laser Mouse: MOUSE_DPI=400@1000 800@1000 *1600@1000 @@ -325,8 +342,6 @@ mouse:usb:v046dp402d:name:Logitech M560: mouse:usb:v046dpc52b:name:Logitech Unifying Device. Wireless PID:402d: MOUSE_DPI=1000@125 -# Logitech V220 Cordless Optical Mouse -mouse:usb:v046dpc51b:name:Logitech USB Receiver: # Logitech Performance MX mouse:usb:v046dp101a:name:Logitech Performance MX: # Logitech MX Master @@ -356,11 +371,6 @@ mouse:usb:v046dp4027:name:Logitech T620: mouse:usb:v046dpc52b:name:Logitech Unifying Device. Wireless PID:4027: MOUSE_DPI=1200@250 -# Logitech LX8 Cordless Laser Mouse -mouse:usb:v046dpc51b:name:Logitech USB Receiver: - MOUSE_DPI=1300@125 - MOUSE_WHEEL_CLICK_ANGLE=15 - # Logitech ZoneTouch Mouse T400 mouse:usb:v046dp4026:name:Logitech T400: mouse:usb:v046dpc52b:name:Logitech Unifying Device. Wireless PID:4026: @@ -483,3 +493,11 @@ mouse:usb:v1038p1369:name:SteelSeries Sensei Raw Gaming Mouse: # Trust illuminated mouse gxt 152 mouse:usb:v145fp01ac:name:HID-compliant Mouse Trust Gaming Mouse: MOUSE_DPI=*800@528 1200@537 1600@536 2400@521 + + ########################################## + # Zelotes + ########################################## + +# Zelotes 5500 DPI 7 Button USB Wired Gaming Mouse +mouse:usb:v1d57pad17:* + MOUSE_DPI=1000@500 1600@500 2400@500 3200@500 5500@500 *1000@1000 1600@1000 2400@1000 3200@1000 5500@1000 diff --git a/hwdb/70-pointingstick.hwdb b/hwdb/70-pointingstick.hwdb index ec166ead40..5e2ab393bd 100644 --- a/hwdb/70-pointingstick.hwdb +++ b/hwdb/70-pointingstick.hwdb @@ -77,15 +77,15 @@ # Latitude D620 evdev:name:*DualPoint Stick:dmi:bvn*:bvr*:bd*:svnDellInc.:pnLatitudeD620*:pvr* - POINTINGSTICK_CONST_ACCEL=0.5 + POINTINGSTICK_CONST_ACCEL=0.5 # Latitude E6320 evdev:name:*DualPoint Stick:dmi:bvn*:bvr*:bd*:svnDellInc.:pnLatitudeE6320*:pvr* - POINTINGSTICK_CONST_ACCEL=2.0 + POINTINGSTICK_CONST_ACCEL=2.0 # Latitude E6400 evdev:name:*DualPoint Stick:dmi:bvn*:bvr*:bd*:svnDellInc.:pnLatitudeE6400*:pvr* - POINTINGSTICK_CONST_ACCEL=1.5 + POINTINGSTICK_CONST_ACCEL=1.5 ######################################### # Lenovo @@ -95,25 +95,26 @@ evdev:name:*DualPoint Stick:dmi:bvn*:bvr*:bd*:svnDellInc.:pnLatitudeE6400*:pvr* evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPadX230:* # Lenovo Thinkpad X230 tablet evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPadX230Tablet:* -# Lenovo Thinkpad X240 -evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPadX240:* -# Lenovo Thinkpad T440s -evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPadT440s:* -# Lenovo Thinkpad T540p -evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPadT540p:* -# Lenovo Thinkpad T550 / W550s -evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPadT550:* +# Lenovo Thinkpad *40 series +evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPad??40:* +evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPad??40?:* +# Lenovo Thinkpad *50 series +evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPad??50:* +evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPad??50?:* +# Lenovo Thinkpad *60 series +evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPad??60:* +evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPad??60?:* # Lenovo Thinkpad X1 Carbon 3rd gen evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPadX1Carbon3rd:* # Lenovo Thinkpad X1 Carbon 4th gen evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPadX1Carbon4th:* - POINTINGSTICK_SENSITIVITY=200 - POINTINGSTICK_CONST_ACCEL=1.0 + POINTINGSTICK_SENSITIVITY=200 + POINTINGSTICK_CONST_ACCEL=1.0 # Lenovo Thinkpad X200s / X201s # Note these come with 2 revisions of keyboard, with the trackpoints having a # different sensitivity in the different revisions. 1.25 is a bit slow for the # least sensitive revision, but it is better to be a bit slow than too fast. evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPadX20?s:* - POINTINGSTICK_SENSITIVITY=200 - POINTINGSTICK_CONST_ACCEL=1.25 + POINTINGSTICK_SENSITIVITY=200 + POINTINGSTICK_CONST_ACCEL=1.25 diff --git a/hwdb/parse_hwdb.py b/hwdb/parse_hwdb.py new file mode 100755 index 0000000000..99d034b4e0 --- /dev/null +++ b/hwdb/parse_hwdb.py @@ -0,0 +1,183 @@ +#!/usr/bin/python3 +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ +# +# This file is part of systemd. +# +# Copyright 2016 Zbigniew Jędrzejewski-Szmek +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. + +import functools +import glob +import string +import sys +import os + +try: + from pyparsing import (Word, White, Literal, ParserElement, Regex, + LineStart, LineEnd, + ZeroOrMore, OneOrMore, Combine, Or, Optional, Suppress, Group, + nums, alphanums, printables, + stringEnd, pythonStyleComment, + ParseBaseException) +except ImportError: + print('pyparsing is not available') + sys.exit(77) + +try: + from evdev.ecodes import ecodes +except ImportError: + ecodes = None + print('WARNING: evdev is not available') + +EOL = LineEnd().suppress() +EMPTYLINE = LineStart() + LineEnd() +COMMENTLINE = pythonStyleComment + EOL +INTEGER = Word(nums) +REAL = Combine((INTEGER + Optional('.' + Optional(INTEGER))) ^ ('.' + INTEGER)) +UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_') + +TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'), + 'evdev': ('name', 'atkbd', 'input'), + 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'), + 'keyboard': ('name', ), + } + +@functools.lru_cache() +def hwdb_grammar(): + ParserElement.setDefaultWhitespaceChars('') + + prefix = Or(category + ':' + Or(conn) + ':' + for category, conn in TYPES.items()) + matchline = Combine(prefix + Word(printables + ' ' + '®')) + EOL + propertyline = (White(' ', exact=1).suppress() + + Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.! ') - Optional(pythonStyleComment)) + + EOL) + propertycomment = White(' ', exact=1) + pythonStyleComment + EOL + + group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) - + OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) - + (EMPTYLINE ^ stringEnd()).suppress() ) + commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress() + + grammar = OneOrMore(group('GROUPS*') ^ commentgroup) + stringEnd() + + return grammar + +@functools.lru_cache() +def property_grammar(): + ParserElement.setDefaultWhitespaceChars(' ') + + setting = Optional('*')('DEFAULT') + INTEGER('DPI') + Suppress('@') + INTEGER('HZ') + props = (('MOUSE_DPI', Group(OneOrMore(setting('SETTINGS*')))), + ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER), + ('ID_INPUT_TRACKBALL', Literal('1')), + ('POINTINGSTICK_SENSITIVITY', INTEGER), + ('POINTINGSTICK_CONST_ACCEL', REAL), + ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))), + ) + fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE') + for name, val in props] + kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME') + - Suppress('=') - + ('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE') + ] + abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME') + - Suppress('=') - + Word(nums + ':')('VALUE') + ] + + grammar = Or(fixed_props + kbd_props + abs_props) + + return grammar + +ERROR = False +def error(fmt, *args, **kwargs): + global ERROR + ERROR = True + print(fmt.format(*args, **kwargs)) + +def convert_properties(group): + matches = [m[0] for m in group.MATCHES] + props = [p[0] for p in group.PROPERTIES] + return matches, props + +def parse(fname): + grammar = hwdb_grammar() + try: + parsed = grammar.parseFile(fname) + except ParseBaseException as e: + error('Cannot parse {}: {}', fname, e) + return [] + return [convert_properties(g) for g in parsed.GROUPS] + +def check_match_uniqueness(groups): + matches = sum((group[0] for group in groups), []) + matches.sort() + prev = None + for match in matches: + if match == prev: + error('Match {!r} is duplicated', match) + prev = match + +def check_one_default(prop, settings): + defaults = [s for s in settings if s.DEFAULT] + if len(defaults) > 1: + error('More than one star entry: {!r}', prop) + +def check_one_keycode(prop, value): + if value != '!' and ecodes is not None: + key = 'KEY_' + value.upper() + if key not in ecodes: + error('Keycode {} unknown', key) + +def check_properties(groups): + grammar = property_grammar() + for matches, props in groups: + prop_names = set() + for prop in props: + # print('--', prop) + prop = prop.partition('#')[0].rstrip() + try: + parsed = grammar.parseString(prop) + except ParseBaseException as e: + error('Failed to parse: {!r}', prop) + continue + # print('{!r}'.format(parsed)) + if parsed.NAME in prop_names: + error('Property {} is duplicated', parsed.NAME) + prop_names.add(parsed.NAME) + if parsed.NAME == 'MOUSE_DPI': + check_one_default(prop, parsed.VALUE.SETTINGS) + elif parsed.NAME.startswith('KEYBOARD_KEY_'): + check_one_keycode(prop, parsed.VALUE) + +def print_summary(fname, groups): + print('{}: {} match groups, {} matches, {} properties' + .format(fname, + len(groups), + sum(len(matches) for matches, props in groups), + sum(len(props) for matches, props in groups), + )) + +if __name__ == '__main__': + args = sys.argv[1:] or glob.glob(os.path.dirname(sys.argv[0]) + '/[67]0-*.hwdb') + + for fname in args: + groups = parse(fname) + print_summary(fname, groups) + check_match_uniqueness(groups) + check_properties(groups) + + sys.exit(ERROR) diff --git a/man/bootctl.xml b/man/bootctl.xml index 6e835c037f..e2575a4751 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -47,16 +47,16 @@ <refsynopsisdiv> <cmdsynopsis> - <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg>status</command> + <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg> status</command> </cmdsynopsis> <cmdsynopsis> - <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg>update</command> + <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg> update</command> </cmdsynopsis> <cmdsynopsis> - <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg>install</command> + <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg> install</command> </cmdsynopsis> <cmdsynopsis> - <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg>remove</command> + <command>bootctl <arg choice="opt" rep="repeat">OPTIONS</arg> remove</command> </cmdsynopsis> </refsynopsisdiv> @@ -71,19 +71,14 @@ currently installed versions of the boot loader binaries and all current EFI boot variables.</para> - <para><command>bootctl update</command> updates all installed - versions of systemd-boot, if the current version is newer than the - version installed in the EFI system partition. This also includes - the EFI default/fallback loader at /EFI/BOOT/BOOT*.EFI. A - systemd-boot entry in the EFI boot variables is created if there - is no current entry. The created entry will be added to the end of - the boot order list.</para> + <para><command>bootctl update</command> updates all installed versions of systemd-boot, if the current version is + newer than the version installed in the EFI system partition. This also includes the EFI default/fallback loader at + <filename>/EFI/BOOT/BOOT*.EFI</filename>. A systemd-boot entry in the EFI boot variables is created if there is no + current entry. The created entry will be added to the end of the boot order list.</para> - <para><command>bootctl install</command> installs systemd-boot into - the EFI system partition. A copy of systemd-boot will be stored as - the EFI default/fallback loader at /EFI/BOOT/BOOT*.EFI. A systemd-boot - entry in the EFI boot variables is created and added to the top - of the boot order list.</para> + <para><command>bootctl install</command> installs systemd-boot into the EFI system partition. A copy of + systemd-boot will be stored as the EFI default/fallback loader at <filename>/EFI/BOOT/BOOT*.EFI</filename>. A + systemd-boot entry in the EFI boot variables is created and added to the top of the boot order list.</para> <para><command>bootctl remove</command> removes all installed versions of systemd-boot from the EFI system partition, and removes @@ -101,8 +96,10 @@ <xi:include href="standard-options.xml" xpointer="help" /> <xi:include href="standard-options.xml" xpointer="version" /> <varlistentry> - <term><option>--path</option></term> - <listitem><para>Path to the EFI system partition. The default is /boot.</para></listitem> + <term><option>--path=</option></term> + <listitem><para>Path to the EFI System Partition (ESP). If not specified, <filename>/efi</filename>, + <filename>/boot</filename>, and <filename>/boot/efi</filename> are checked in turn. It is recommended to mount + the ESP to <filename>/boot</filename>, if possible.</para></listitem> </varlistentry> <varlistentry> diff --git a/man/journalctl.xml b/man/journalctl.xml index e77621d7b3..c448a29a51 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -250,6 +250,18 @@ <varlistentry> <term> + <option>short-full</option> + </term> + <listitem> + <para>is very similar, but shows timestamps in the format the <option>--since=</option> and + <option>--until=</option> options accept. Unlike the timestamp information shown in + <option>short</option> output mode this mode includes weekday, year and timezone information in the + output, and is locale-independent.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term> <option>short-iso</option> </term> <listitem> @@ -572,24 +584,17 @@ <term><option>-U</option></term> <term><option>--until=</option></term> - <listitem><para>Start showing entries on or newer than the - specified date, or on or older than the specified date, - respectively. Date specifications should be of the format - <literal>2012-10-30 18:17:16</literal>. If the time part is - omitted, <literal>00:00:00</literal> is assumed. If only the - seconds component is omitted, <literal>:00</literal> is - assumed. If the date component is omitted, the current day is - assumed. Alternatively the strings - <literal>yesterday</literal>, <literal>today</literal>, - <literal>tomorrow</literal> are understood, which refer to - 00:00:00 of the day before the current day, the current day, - or the day after the current day, - respectively. <literal>now</literal> refers to the current - time. Finally, relative times may be specified, prefixed with - <literal>-</literal> or <literal>+</literal>, referring to - times before or after the current time, respectively. For complete - time and date specification, see - <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>. + <listitem><para>Start showing entries on or newer than the specified date, or on or older than the specified + date, respectively. Date specifications should be of the format <literal>2012-10-30 18:17:16</literal>. If the + time part is omitted, <literal>00:00:00</literal> is assumed. If only the seconds component is omitted, + <literal>:00</literal> is assumed. If the date component is omitted, the current day is assumed. Alternatively + the strings <literal>yesterday</literal>, <literal>today</literal>, <literal>tomorrow</literal> are understood, + which refer to 00:00:00 of the day before the current day, the current day, or the day after the current day, + respectively. <literal>now</literal> refers to the current time. Finally, relative times may be specified, + prefixed with <literal>-</literal> or <literal>+</literal>, referring to times before or after the current + time, respectively. For complete time and date specification, see + <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>. Note that + <option>--output=short-full</option> prints timestamps that follow precisely this format. </para> </listitem> </varlistentry> diff --git a/man/journald.conf.xml b/man/journald.conf.xml index fef4fde898..a9562c121a 100644 --- a/man/journald.conf.xml +++ b/man/journald.conf.xml @@ -129,23 +129,15 @@ <varlistentry> <term><varname>SplitMode=</varname></term> - <listitem><para>Controls whether to split up journal files per user. Split-up journal files are primarily - useful for access control: on UNIX/Linux access control is managed per file, and the journal daemon will assign - users read access to their journal files. This setting takes one of <literal>uid</literal>, - <literal>login</literal> or <literal>none</literal>. If <literal>uid</literal>, all regular users will get each - their own journal files regardless of whether their processes possess login sessions or not, however system - users will log into the system journal. If <literal>login</literal>, actually logged-in users will get each - their own journal files, but users without login session and system users will log into the system - journal. Note that in this mode, user code running outside of any login session will log into the system log - instead of the split-out user logs. Most importantly, this means that information about core dumps of user - processes collected via the - <citerefentry><refentrytitle>systemd-coredump</refentrytitle><manvolnum>8</manvolnum></citerefentry> subsystem - will end up in the system logs instead of the user logs, and thus not be accessible to the owning users. If - <literal>none</literal>, journal files are not split up by user and all messages are instead stored in the - single system journal. In this mode unprivileged users generally do not have access to their own log data. Note - that splitting up journal files by user is only available for journals stored persistently. If journals are - stored on volatile storage (see above), only a single journal file for all user IDs is kept. Defaults to - <literal>uid</literal>.</para></listitem> + <listitem><para>Controls whether to split up journal files per user, either <literal>uid</literal> or + <literal>none</literal>. Split journal files are primarily useful for access control: on UNIX/Linux access + control is managed per file, and the journal daemon will assign users read access to their journal files. If + <literal>uid</literal>, all regular users will each get their own journal files, and system users will log to + the system journal. If <literal>none</literal>, journal files are not split up by user and all messages are + instead stored in the single system journal. In this mode unprivileged users generally do not have access to + their own log data. Note that splitting up journal files by user is only available for journals stored + persistently. If journals are stored on volatile storage (see <varname>Storage=</varname> above), only a single + journal file is used. Defaults to <literal>uid</literal>.</para></listitem> </varlistentry> <varlistentry> diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 3ecc969c10..1fa31a14b7 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -224,15 +224,14 @@ <varlistentry> <term><varname>vconsole.keymap=</varname></term> - <term><varname>vconsole.keymap.toggle=</varname></term> + <term><varname>vconsole.keymap_toggle=</varname></term> <term><varname>vconsole.font=</varname></term> - <term><varname>vconsole.font.map=</varname></term> - <term><varname>vconsole.font.unimap=</varname></term> + <term><varname>vconsole.font_map=</varname></term> + <term><varname>vconsole.font_unimap=</varname></term> <listitem> - <para>Parameters understood by the virtual console setup - logic. For details, see - <citerefentry><refentrytitle>systemd-vconsole-setup.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> + <para>Parameters understood by the virtual console setup logic. For details, see + <citerefentry><refentrytitle>vconsole.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para> </listitem> </varlistentry> diff --git a/man/localectl.xml b/man/localectl.xml index 8d2becb5d9..31238272f3 100644 --- a/man/localectl.xml +++ b/man/localectl.xml @@ -223,7 +223,7 @@ <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-localed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry>, - <citerefentry><refentrytitle>mkinitrd</refentrytitle><manvolnum>8</manvolnum></citerefentry> + <citerefentry project='die-net'><refentrytitle>mkinitrd</refentrytitle><manvolnum>8</manvolnum></citerefentry> </para> </refsect1> diff --git a/man/networkd.conf.xml b/man/networkd.conf.xml index 4bfc4f773a..57e647a31b 100644 --- a/man/networkd.conf.xml +++ b/man/networkd.conf.xml @@ -120,7 +120,7 @@ <para>The DUID value specified here overrides the DUID that systemd-networkd generates using the machine-id from the <filename>/etc/machine-id</filename> file. To configure DUID per-network, see - <citerefentry><refentrytitle>systemd.network </refentrytitle><manvolnum>5</manvolnum></citerefentry>. + <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>. The configured DHCP DUID should conform to the specification in <ulink url="http://tools.ietf.org/html/rfc3315#section-9">RFC 3315</ulink>, <ulink url="http://tools.ietf.org/html/rfc6355">RFC 6355</ulink>. To configure IAID, see diff --git a/man/nss-myhostname.xml b/man/nss-myhostname.xml index a920ec334f..b1daaba02b 100644 --- a/man/nss-myhostname.xml +++ b/man/nss-myhostname.xml @@ -106,8 +106,8 @@ <para>Here is an example <filename>/etc/nsswitch.conf</filename> file that enables <command>nss-myhostname</command> correctly:</para> -<programlisting>passwd: compat mymachines -group: compat mymachines +<programlisting>passwd: compat mymachines systemd +group: compat mymachines systemd shadow: compat hosts: files mymachines resolve <command>myhostname</command> @@ -138,6 +138,7 @@ netgroup: nis</programlisting> <title>See Also</title> <para> <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>nss-mymachines</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>nsswitch.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, diff --git a/man/nss-mymachines.xml b/man/nss-mymachines.xml index ec047449bf..a70119e256 100644 --- a/man/nss-mymachines.xml +++ b/man/nss-mymachines.xml @@ -82,8 +82,8 @@ <para>Here is an example <filename>/etc/nsswitch.conf</filename> file that enables <command>nss-mymachines</command> correctly:</para> - <programlisting>passwd: compat <command>mymachines</command> -group: compat <command>mymachines</command> + <programlisting>passwd: compat <command>mymachines</command> systemd +group: compat <command>mymachines</command> systemd shadow: compat hosts: files <command>mymachines</command> resolve myhostname @@ -103,6 +103,7 @@ netgroup: nis</programlisting> <para> <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-machined.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>nss-myhostname</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>nsswitch.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, diff --git a/man/nss-resolve.xml b/man/nss-resolve.xml index d9e56453e8..e6cc1d982a 100644 --- a/man/nss-resolve.xml +++ b/man/nss-resolve.xml @@ -81,8 +81,8 @@ <para>Here is an example <filename>/etc/nsswitch.conf</filename> file that enables <command>nss-resolve</command> correctly:</para> -<programlisting>passwd: compat mymachines -group: compat mymachines +<programlisting>passwd: compat mymachines systemd +group: compat mymachines systemd shadow: compat hosts: files mymachines <command>resolve</command> myhostname @@ -102,8 +102,9 @@ netgroup: nis</programlisting> <para> <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-resolved</refentrytitle><manvolnum>8</manvolnum></citerefentry>, - <citerefentry><refentrytitle>nss-mymachines</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>nss-myhostname</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>nss-mymachines</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>nsswitch.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> </para> </refsect1> diff --git a/man/nss-systemd.xml b/man/nss-systemd.xml new file mode 100644 index 0000000000..56d26e7d1f --- /dev/null +++ b/man/nss-systemd.xml @@ -0,0 +1,111 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +--> + +<refentry id="nss-systemd"> + + <refentryinfo> + <title>nss-systemd</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Lennart</firstname> + <surname>Poettering</surname> + <email>lennart@poettering.net</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>nss-systemd</refentrytitle> + <manvolnum>8</manvolnum> + </refmeta> + + <refnamediv> + <refname>nss-systemd</refname> + <refname>libnss_systemd.so.2</refname> + <refpurpose>Provide UNIX user and group name resolution for dynamic users and groups.</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <para><filename>libnss_systemd.so.2</filename></para> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><command>nss-systemd</command> is a plug-in module for the GNU Name Service Switch (NSS) functionality of the + GNU C Library (<command>glibc</command>), providing UNIX user and group name resolution for dynamic users and + groups allocated through the <varname>DynamicUser=</varname> option in systemd unit files. See + <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for details on + this option.</para> + + <para>This module also ensures that the root and nobody users and groups (i.e. the users/groups with the UIDs/GIDs + 0 and 65534) remain resolvable at all times, even if they aren't listed in <filename>/etc/passwd</filename> or + <filename>/etc/group</filename>, or if these files are missing.</para> + + <para>To activate the NSS module, add <literal>systemd</literal> to the lines starting with + <literal>passwd:</literal> and <literal>group:</literal> in <filename>/etc/nsswitch.conf</filename>.</para> + + <para>It is recommended to place <literal>systemd</literal> after the <literal>files</literal> or + <literal>compat</literal> entry of the <filename>/etc/nsswitch.conf</filename> lines so that + <filename>/etc/passwd</filename> and <filename>/etc/group</filename> based mappings take precedence.</para> + </refsect1> + + <refsect1> + <title>Example</title> + + <para>Here is an example <filename>/etc/nsswitch.conf</filename> file that enables + <command>nss-systemd</command> correctly:</para> + + <programlisting>passwd: compat mymachines <command>systemd</command> +group: compat mymachines <command>systemd</command> +shadow: compat + +hosts: files mymachines resolve myhostname +networks: files + +protocols: db files +services: db files +ethers: db files +rpc: db files + +netgroup: nis</programlisting> + + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>nss-myhostname</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>nss-mymachines</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry project='man-pages'><refentrytitle>nsswitch.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry project='man-pages'><refentrytitle>getent</refentrytitle><manvolnum>1</manvolnum></citerefentry> + </para> + </refsect1> + +</refentry> diff --git a/man/sd-journal.xml b/man/sd-journal.xml index 09747a480c..936a83acf7 100644 --- a/man/sd-journal.xml +++ b/man/sd-journal.xml @@ -77,7 +77,6 @@ <citerefentry><refentrytitle>sd_journal_get_realtime_usec</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_add_match</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_seek_head</refentrytitle><manvolnum>3</manvolnum></citerefentry>, - <citerefentry><refentrytitle>sd_journal_query_enumerate</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_enumerate_fields</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_cursor</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_cutoff_realtime_usec</refentrytitle><manvolnum>3</manvolnum></citerefentry>, @@ -113,7 +112,6 @@ <citerefentry><refentrytitle>sd_journal_get_realtime_usec</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_add_match</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_seek_head</refentrytitle><manvolnum>3</manvolnum></citerefentry>, - <citerefentry><refentrytitle>sd_journal_query_enumerate</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_enumerate_fields</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_cursor</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_cutoff_realtime_usec</refentrytitle><manvolnum>3</manvolnum></citerefentry>, diff --git a/man/systemctl.xml b/man/systemctl.xml index e7880d24f7..78607c9ba3 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -156,6 +156,10 @@ <para>To list all units installed in the file system, use the <command>list-unit-files</command> command instead.</para> + + <para>When listing units with <command>list-dependencies</command>, recursively show + dependencies of all dependent units (by default only dependencies of target units are + shown).</para> </listitem> </varlistentry> @@ -1088,7 +1092,8 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service enabled and disabled, or only enabled, or only disabled.</para> <para>If the unit carries no install information, it will be silently ignored - by this command.</para> + by this command. <replaceable>NAME</replaceable> must be the real unit name, + any alias names are ignored silently.</para> <para>For more information on the preset policy format, see <citerefentry><refentrytitle>systemd.preset</refentrytitle><manvolnum>5</manvolnum></citerefentry>. diff --git a/man/systemd-detect-virt.xml b/man/systemd-detect-virt.xml index 2b7f4e69ab..61a5f8937f 100644 --- a/man/systemd-detect-virt.xml +++ b/man/systemd-detect-virt.xml @@ -88,7 +88,7 @@ </thead> <tbody> <row> - <entry valign="top" morerows="9">VM</entry> + <entry valign="top" morerows="10">VM</entry> <entry><varname>qemu</varname></entry> <entry>QEMU software virtualization</entry> </row> @@ -138,6 +138,11 @@ <entry>Parallels Desktop, Parallels Server</entry> </row> + <row> + <entry><varname>bhyve</varname></entry> + <entry>bhyve, FreeBSD hypervisor</entry> + </row> + <row> <entry valign="top" morerows="5">Container</entry> <entry><varname>openvz</varname></entry> diff --git a/man/systemd-gpt-auto-generator.xml b/man/systemd-gpt-auto-generator.xml index e890c4dce2..d26206710f 100644 --- a/man/systemd-gpt-auto-generator.xml +++ b/man/systemd-gpt-auto-generator.xml @@ -137,6 +137,11 @@ <entry>Swap</entry> <entry>All swap partitions located on the disk the root partition is located on are enabled.</entry> </row> + <row> + <entry>c12a7328-f81f-11d2-ba4b-00a0c93ec93b</entry> + <entry>EFI System Partition (ESP)</entry> + <entry>The first ESP located on the disk the root partition is located on is mounted to <filename>/boot</filename> or <filename>/efi</filename>, see below.</entry> + </row> </tbody> </tgroup> </table> @@ -150,16 +155,14 @@ <filename>/etc/crypttab</filename> with a different device mapper device name.</para> - <para>Mount and automount units for the EFI System Partition (ESP), - mounting it to <filename>/boot</filename>, are generated on EFI - systems where the boot loader communicates the used ESP to the operating - system. Since this generator creates an automount unit, the mount will - only be activated on-demand, when accessed. On systems where - <filename>/boot</filename> is an explicitly configured mount - (for example, listed in - <citerefentry project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>) - or where the <filename>/boot</filename> mount point is non-empty, no - mount units are generated.</para> + <para>Mount and automount units for the EFI System Partition (ESP) are generated on EFI systems. The ESP is mounted + to <filename>/boot</filename>, unless a mount point directory <filename>/efi</filename> exists, in which case it is + mounted there. Since this generator creates an automount unit, the mount will only be activated on-demand, when + accessed. On systems where <filename>/boot</filename> (or <filename>/efi</filename> if it exists) is an explicitly + configured mount (for example, listed in <citerefentry + project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>) or where the + <filename>/boot</filename> (or <filename>/efi</filename>) mount point is non-empty, no mount units are + generated.</para> <para>When using this generator in conjunction with btrfs file systems, make sure to set the correct default subvolumes on them, diff --git a/man/systemd-journal-gatewayd.service.xml b/man/systemd-journal-gatewayd.service.xml index 9ed85c3950..2cb114f6e3 100644 --- a/man/systemd-journal-gatewayd.service.xml +++ b/man/systemd-journal-gatewayd.service.xml @@ -100,6 +100,16 @@ with <option>--cert=</option>.</para></listitem> </varlistentry> + <varlistentry> + <term><option>-D <replaceable>DIR</replaceable></option></term> + <term><option>--directory=<replaceable>DIR</replaceable></option></term> + + <listitem><para>Takes a directory path as argument. If + specified, <command>systemd-journal-gatewayd</command> will serve the + specified journal directory <replaceable>DIR</replaceable> instead of + the default runtime and system journal paths.</para></listitem> + </varlistentry> + <xi:include href="standard-options.xml" xpointer="help" /> <xi:include href="standard-options.xml" xpointer="version" /> </variablelist> diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 69d2f6ff7d..93f93880c3 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -274,8 +274,7 @@ signals. It is recommended to use this mode to invoke arbitrary commands in containers, unless they have been modified to run correctly as PID 1. Or in other words: this switch should be used for pretty much all commands, except when the command refers to an init or shell implementation, as these are generally capable of running - correctly as PID 1. This option may not be combined with <option>--boot</option> or - <option>--share-system</option>.</para> + correctly as PID 1. This option may not be combined with <option>--boot</option>.</para> </listitem> </varlistentry> @@ -285,8 +284,7 @@ <listitem><para>Automatically search for an init binary and invoke it as PID 1, instead of a shell or a user supplied program. If this option is used, arguments specified on the command line are used as arguments for the - init binary. This option may not be combined with <option>--as-pid2</option> or - <option>--share-system</option>.</para> + init binary. This option may not be combined with <option>--as-pid2</option>.</para> <para>The following table explains the different modes of invocation and relationship to <option>--as-pid2</option> (see above):</para> @@ -847,23 +845,6 @@ </varlistentry> <varlistentry> - <term><option>--share-system</option></term> - - <listitem><para>Allows the container to share certain system - facilities with the host. More specifically, this turns off - PID namespacing, UTS namespacing and IPC namespacing, and thus - allows the guest to see and interact more easily with - processes outside of the container. Note that using this - option makes it impossible to start up a full Operating System - in the container, as an init system cannot operate in this - mode. It is only useful to run specific programs or - applications this way, without involving an init system in the - container. This option implies <option>--register=no</option>. - This option may not be combined with - <option>--boot</option>.</para></listitem> - </varlistentry> - - <varlistentry> <term><option>--register=</option></term> <listitem><para>Controls whether the container is registered @@ -877,9 +858,7 @@ and shown by tools such as <citerefentry project='man-pages'><refentrytitle>ps</refentrytitle><manvolnum>1</manvolnum></citerefentry>. If the container does not run an init system, it is - recommended to set this option to <literal>no</literal>. Note - that <option>--share-system</option> implies - <option>--register=no</option>. </para></listitem> + recommended to set this option to <literal>no</literal>.</para></listitem> </varlistentry> <varlistentry> diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml index aa1c2365e5..56f67960ce 100644 --- a/man/systemd-resolved.service.xml +++ b/man/systemd-resolved.service.xml @@ -68,12 +68,12 @@ link-local networking).</para></listitem> <listitem><para>The glibc - <citerefentry><refentrytitle>getaddrinfo</refentrytitle><manvolnum>3</manvolnum></citerefentry> API as defined + <citerefentry project='man-pages'><refentrytitle>getaddrinfo</refentrytitle><manvolnum>3</manvolnum></citerefentry> API as defined by <ulink url="https://tools.ietf.org/html/rfc3493">RFC3493</ulink> and its related resolver functions, - including <citerefentry><refentrytitle>gethostbyname</refentrytitle><manvolnum>3</manvolnum></citerefentry>. This + including <citerefentry project='man-pages'><refentrytitle>gethostbyname</refentrytitle><manvolnum>3</manvolnum></citerefentry>. This API is widely supported, including beyond the Linux platform. In its current form it does not expose DNSSEC validation status information however, and is synchronous only. This API is backed by the glibc Name Service - Switch (<citerefentry><refentrytitle>nss</refentrytitle><manvolnum>5</manvolnum></citerefentry>). Usage of the + Switch (<citerefentry project='man-pages'><refentrytitle>nss</refentrytitle><manvolnum>5</manvolnum></citerefentry>). Usage of the glibc NSS module <citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry> is required in order to allow glibc's NSS resolver functions to resolve host names via <command>systemd-resolved</command>.</para></listitem> @@ -164,7 +164,7 @@ <title><filename>/etc/resolv.conf</filename></title> <para>Three modes of handling <filename>/etc/resolv.conf</filename> (see - <citerefentry><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>) are + <citerefentry project='man-pages'><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>) are supported:</para> <itemizedlist> diff --git a/man/systemd-vconsole-setup.service.xml b/man/systemd-vconsole-setup.service.xml index ff079761c1..e048258621 100644 --- a/man/systemd-vconsole-setup.service.xml +++ b/man/systemd-vconsole-setup.service.xml @@ -63,41 +63,7 @@ <para>See <citerefentry><refentrytitle>vconsole.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> - for information about the configuration files understood by this - service.</para> - - - </refsect1> - - <refsect1> - <title>Kernel Command Line</title> - - <para>A few configuration parameters from - <filename>vconsole.conf</filename> may be overridden on the kernel - command line:</para> - - <variablelist class='kernel-commandline-options'> - <varlistentry> - <term><varname>vconsole.keymap=</varname></term> - <term><varname>vconsole.keymap.toggle=</varname></term> - - <listitem><para>Overrides the key mapping table for the - keyboard and the second toggle keymap.</para></listitem> - </varlistentry> - <varlistentry> - - <term><varname>vconsole.font=</varname></term> - <term><varname>vconsole.font.map=</varname></term> - <term><varname>vconsole.font.unimap=</varname></term> - - <listitem><para>Configures the console font, the console map, - and the unicode font map.</para></listitem> - </varlistentry> - </variablelist> - - <para>See - <citerefentry><refentrytitle>vconsole.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> - for information about these settings.</para> + for information about the configuration files and kernel command line options understood by this program.</para> </refsect1> <refsect1> diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 41ae6e76de..bf82326096 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -107,46 +107,67 @@ <varlistentry> <term><varname>WorkingDirectory=</varname></term> - <listitem><para>Takes a directory path relative to the service's root - directory specified by <varname>RootDirectory=</varname>, or the - special value <literal>~</literal>. Sets the working directory - for executed processes. If set to <literal>~</literal>, the - home directory of the user specified in - <varname>User=</varname> is used. If not set, defaults to the - root directory when systemd is running as a system instance - and the respective user's home directory if run as user. If - the setting is prefixed with the <literal>-</literal> - character, a missing working directory is not considered - fatal. If <varname>RootDirectory=</varname> is not set, then - <varname>WorkingDirectory=</varname> is relative to the root of - the system running the service manager. - Note that setting this parameter might result in - additional dependencies to be added to the unit (see - above).</para></listitem> + <listitem><para>Takes a directory path relative to the service's root directory specified by + <varname>RootDirectory=</varname>, or the special value <literal>~</literal>. Sets the working directory for + executed processes. If set to <literal>~</literal>, the home directory of the user specified in + <varname>User=</varname> is used. If not set, defaults to the root directory when systemd is running as a + system instance and the respective user's home directory if run as user. If the setting is prefixed with the + <literal>-</literal> character, a missing working directory is not considered fatal. If + <varname>RootDirectory=</varname> is not set, then <varname>WorkingDirectory=</varname> is relative to the root + of the system running the service manager. Note that setting this parameter might result in additional + dependencies to be added to the unit (see above).</para></listitem> </varlistentry> <varlistentry> <term><varname>RootDirectory=</varname></term> - <listitem><para>Takes a directory path relative to the host's root directory - (i.e. the root of the system running the service manager). Sets the - root directory for executed processes, with the <citerefentry - project='man-pages'><refentrytitle>chroot</refentrytitle><manvolnum>2</manvolnum></citerefentry> - system call. If this is used, it must be ensured that the - process binary and all its auxiliary files are available in - the <function>chroot()</function> jail. Note that setting this - parameter might result in additional dependencies to be added - to the unit (see above).</para></listitem> + <listitem><para>Takes a directory path relative to the host's root directory (i.e. the root of the system + running the service manager). Sets the root directory for executed processes, with the <citerefentry + project='man-pages'><refentrytitle>chroot</refentrytitle><manvolnum>2</manvolnum></citerefentry> system + call. If this is used, it must be ensured that the process binary and all its auxiliary files are available in + the <function>chroot()</function> jail. Note that setting this parameter might result in additional + dependencies to be added to the unit (see above).</para> + + <para>The <varname>PrivateUsers=</varname> setting is particularly useful in conjunction with + <varname>RootDirectory=</varname>. For details, see below.</para></listitem> </varlistentry> <varlistentry> <term><varname>User=</varname></term> <term><varname>Group=</varname></term> - <listitem><para>Sets the Unix user or group that the processes - are executed as, respectively. Takes a single user or group - name or ID as argument. If no group is set, the default group - of the user is chosen. These do not affect commands prefixed with <literal>+</literal>.</para></listitem> + <listitem><para>Set the UNIX user or group that the processes are executed as, respectively. Takes a single + user or group name, or numeric ID as argument. If no group is set, the default group of the user is used. This + setting does not affect commands whose command line is prefixed with <literal>+</literal>.</para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>DynamicUser=</varname></term> + + <listitem><para>Takes a boolean parameter. If set, a UNIX user and group pair is allocated dynamically when the + unit is started, and released as soon as it is stopped. The user and group will not be added to + <filename>/etc/passwd</filename> or <filename>/etc/group</filename>, but are managed transiently during + runtime. The <citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry> + glibc NSS module provides integration of these dynamic users/groups into the system's user and group + databases. The user and group name to use may be configured via <varname>User=</varname> and + <varname>Group=</varname> (see above). If these options are not used and dynamic user/group allocation is + enabled for a unit, the name of the dynamic user/group is implicitly derived from the unit name. If the unit + name without the type suffix qualifies as valid user name it is used directly, otherwise a name incorporating a + hash of it is used. If a statically allocated user or group of the configured name already exists, it is used + and no dynamic user/group is allocated. Dynamic users/groups are allocated from the UID/GID range + 61184…65519. It is recommended to avoid this range for regular system or login users. At any point in time + each UID/GID from this range is only assigned to zero or one dynamically allocated users/groups in + use. However, UID/GIDs are recycled after a unit is terminated. Care should be taken that any processes running + as part of a unit for which dynamic users/groups are enabled do not leave files or directories owned by these + users/groups around, as a different unit might get the same UID/GID assigned later on, and thus gain access to + these files or directories. If <varname>DynamicUser=</varname> is enabled, <varname>PrivateTmp=</varname> is + implied. This ensures that the lifetime of temporary files created by the executed processes is bound to the + runtime of the service, and hence the lifetime of the dynamic user/group. Since <filename>/tmp</filename> and + <filename>/var/tmp</filename> are usually the only world-writable directories on a system this ensures that a + unit making use of dynamic user/group allocation cannot leave files around after unit termination. Use + <varname>RuntimeDirectory=</varname> (see below) in order to assign a writable runtime directory to a service, + owned by the dynamic user/group and removed automatically when the unit is terminated. Defaults to + off.</para></listitem> </varlistentry> <varlistentry> @@ -971,6 +992,28 @@ </varlistentry> <varlistentry> + <term><varname>PrivateUsers=</varname></term> + + <listitem><para>Takes a boolean argument. If true, sets up a new user namespace for the executed processes and + configures a minimal user and group mapping, that maps the <literal>root</literal> user and group as well as + the unit's own user and group to themselves and everything else to the <literal>nobody</literal> user and + group. This is useful to securely detach the user and group databases used by the unit from the rest of the + system, and thus to create an effective sandbox environment. All files, directories, processes, IPC objects and + other resources owned by users/groups not equalling <literal>root</literal> or the unit's own will stay visible + from within the unit but appear owned by the <literal>nobody</literal> user and group. If this mode is enabled, + all unit processes are run without privileges in the host user namespace (regardless if the unit's own + user/group is <literal>root</literal> or not). Specifically this means that the process will have zero process + capabilities on the host's user namespace, but full capabilities within the service's user namespace. Settings + such as <varname>CapabilityBoundingSet=</varname> will affect only the latter, and there's no way to acquire + additional capabilities in the host's user namespace. Defaults to off.</para> + + <para>This setting is particularly useful in conjunction with <varname>RootDirectory=</varname>, as the need to + synchronize the user and group databases in the root directory and on the host is reduced, as the only users + and groups who need to be matched are <literal>root</literal>, <literal>nobody</literal> and the unit's own + user and group.</para></listitem> + </varlistentry> + + <varlistentry> <term><varname>ProtectSystem=</varname></term> <listitem><para>Takes a boolean argument or @@ -1421,7 +1464,7 @@ <listitem><para>Takes a boolean argument. If set, any attempts to enable realtime scheduling in a process of the unit are refused. This restricts access to realtime task scheduling policies such as <constant>SCHED_FIFO</constant>, <constant>SCHED_RR</constant> or <constant>SCHED_DEADLINE</constant>. See - <citerefentry><refentrytitle>sched</refentrytitle><manvolnum>7</manvolnum></citerefentry> for details about + <citerefentry project='man-pages'><refentrytitle>sched</refentrytitle><manvolnum>7</manvolnum></citerefentry> for details about these scheduling policies. Realtime scheduling policies may be used to monopolize CPU time for longer periods of time, and may hence be used to lock up or otherwise trigger Denial-of-Service situations on the system. It is hence recommended to restrict access to realtime scheduling to the few programs that actually require @@ -1574,6 +1617,118 @@ functions) if their standard output or standard error output is connected to the journal anyway, thus enabling delivery of structured metadata along with logged messages.</para></listitem> </varlistentry> + + <varlistentry> + <term><varname>$SERVICE_RESULT</varname></term> + + <listitem><para>Only defined for the service unit type, this environment variable is passed to all + <varname>ExecStop=</varname> and <varname>ExecStopPost=</varname> processes, and encodes the service + "result". Currently, the following values are defined: <literal>timeout</literal> (in case of an operation + timeout), <literal>exit-code</literal> (if a service process exited with a non-zero exit code; see + <varname>$EXIT_STATUS</varname> below for the actual exit status returned), <literal>signal</literal> (if a + service process was terminated abnormally by a signal; see <varname>$EXIT_STATUS</varname> below for the actual + signal used for the termination), <literal>core-dump</literal> (if a service process terminated abnormally and + dumped core), <literal>watchdog</literal> (if the watchdog keep-alive ping was enabled for the service but it + missed the deadline), or <literal>resources</literal> (a catch-all condition in case a system operation + failed).</para> + + <para>This environment variable is useful to monitor failure or successful termination of a service. Even + though this variable is available in both <varname>ExecStop=</varname> and <varname>ExecStopPost=</varname>, it + is usually a better choice to place monitoring tools in the latter, as the former is only invoked for services + that managed to start up correctly, and the latter covers both services that failed during their start-up and + those which failed during their runtime.</para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>$EXIT_CODE</varname></term> + <term><varname>$EXIT_STATUS</varname></term> + + <listitem><para>Only defined for the service unit type, these environment variables are passed to all + <varname>ExecStop=</varname>, <varname>ExecStopPost=</varname> processes and contain exit status/code + information of the main process of the service. For the precise definition of the exit code and status, see + <citerefentry><refentrytitle>wait</refentrytitle><manvolnum>2</manvolnum></citerefentry>. <varname>$EXIT_CODE</varname> + is one of <literal>exited</literal>, <literal>killed</literal>, + <literal>dumped</literal>. <varname>$EXIT_STATUS</varname> contains the numeric exit code formatted as string + if <varname>$EXIT_CODE</varname> is <literal>exited</literal>, and the signal name in all other cases. Note + that these environment variables are only set if the service manager succeeded to start and identify the main + process of the service.</para> + + <table> + <title>Summary of possible service result variable values</title> + <tgroup cols='3'> + <colspec colname='result' /> + <colspec colname='status' /> + <colspec colname='code' /> + <thead> + <row> + <entry><varname>$SERVICE_RESULT</varname></entry> + <entry><varname>$EXIT_STATUS</varname></entry> + <entry><varname>$EXIT_CODE</varname></entry> + </row> + </thead> + + <tbody> + <row> + <entry morerows="1" valign="top"><literal>timeout</literal></entry> + <entry valign="top"><literal>killed</literal></entry> + <entry><literal>TERM</literal><sbr/><literal>KILL</literal></entry> + </row> + + <row> + <entry valign="top"><literal>exited</literal></entry> + <entry><literal>0</literal><sbr/><literal>1</literal><sbr/><literal>2</literal><sbr/><literal + >3</literal><sbr/>…<sbr/><literal>255</literal></entry> + </row> + + <row> + <entry valign="top"><literal>exit-code</literal></entry> + <entry valign="top"><literal>exited</literal></entry> + <entry><literal>0</literal><sbr/><literal>1</literal><sbr/><literal>2</literal><sbr/><literal + >3</literal><sbr/>…<sbr/><literal>255</literal></entry> + </row> + + <row> + <entry valign="top"><literal>signal</literal></entry> + <entry valign="top"><literal>killed</literal></entry> + <entry><literal>HUP</literal><sbr/><literal>INT</literal><sbr/><literal>KILL</literal><sbr/>…</entry> + </row> + + <row> + <entry valign="top"><literal>core-dump</literal></entry> + <entry valign="top"><literal>dumped</literal></entry> + <entry><literal>ABRT</literal><sbr/><literal>SEGV</literal><sbr/><literal>QUIT</literal><sbr/>…</entry> + </row> + + <row> + <entry morerows="2" valign="top"><literal>watchdog</literal></entry> + <entry><literal>dumped</literal></entry> + <entry><literal>ABRT</literal></entry> + </row> + <row> + <entry><literal>killed</literal></entry> + <entry><literal>TERM</literal><sbr/><literal>KILL</literal></entry> + </row> + <row> + <entry><literal>exited</literal></entry> + <entry><literal>0</literal><sbr/><literal>1</literal><sbr/><literal>2</literal><sbr/><literal + >3</literal><sbr/>…<sbr/><literal>255</literal></entry> + </row> + + <row> + <entry><literal>resources</literal></entry> + <entry>any of the above</entry> + <entry>any of the above</entry> + </row> + + <row> + <entry namest="results" nameend="code">Note: the process may be also terminated by a signal not sent by systemd. In particular the process may send an arbitrary signal to itself in a handler for any of the non-maskable signals. Nevertheless, in the <literal>timeout</literal> and <literal>watchdog</literal> rows above only the signals that systemd sends have been included.</entry> + </row> + </tbody> + </tgroup> + </table> + + </listitem> + </varlistentry> </variablelist> <para>Additional variables may be configured by the following @@ -1609,4 +1764,5 @@ </para> </refsect1> + </refentry> diff --git a/man/systemd.mount.xml b/man/systemd.mount.xml index 66cddd72e0..a38165f9b9 100644 --- a/man/systemd.mount.xml +++ b/man/systemd.mount.xml @@ -160,7 +160,7 @@ for details about the conversion.</para> <para>The NFS mount option <option>bg</option> for NFS background mounts - as documented in <citerefentry><refentrytitle>nfs</refentrytitle><manvolnum>5</manvolnum></citerefentry> + as documented in <citerefentry project='man-pages'><refentrytitle>nfs</refentrytitle><manvolnum>5</manvolnum></citerefentry> is not supported in <filename>/etc/fstab</filename> entries. The systemd mount option <option>nofail</option> provides similar functionality and should be used instead.</para> diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml index a5c6f0fa40..e56708a648 100644 --- a/man/systemd.netdev.xml +++ b/man/systemd.netdev.xml @@ -343,8 +343,15 @@ </para> </listitem> </varlistentry> + <varlistentry> + <term><varname>STP=</varname></term> + <listitem> + <para>A boolean. This enables the bridge's Spanning Tree Protocol (STP). When unset, + the kernel's default setting applies. + </para> + </listitem> + </varlistentry> </variablelist> - </refsect1> <refsect1> diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 4541a55490..c332cd7bdc 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -212,6 +212,17 @@ below 1280 (the minimum MTU for IPv6) it will automatically be increased to this value.</para> </listitem> </varlistentry> + <varlistentry> + <term><varname>ARP=</varname></term> + <listitem> + <para> A boolean. Enables or disables the ARP (low-level Address Resolution Protocol) + for this interface. Defaults to unset, which means that the kernel default will be used.</para> + <para> For example, disabling ARP is useful when creating multiple MACVLAN or VLAN virtual + interfaces atop a single lower-level physical interface, which will then only serve as a + link/"bridge" device aggregating traffic to the same physical link and not participate in + the network otherwise.</para> + </listitem> + </varlistentry> </variablelist> </refsect1> diff --git a/man/systemd.offline-updates.xml b/man/systemd.offline-updates.xml index ae53b8552d..f404c8d72f 100644 --- a/man/systemd.offline-updates.xml +++ b/man/systemd.offline-updates.xml @@ -77,7 +77,7 @@ <listitem> <para>Very early in the new boot - <citerefentry><refentrytitle>systemd-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry> + <citerefentry><refentrytitle>systemd-system-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry> checks whether <filename>/system-update</filename> exists. If so, it (temporarily and for this boot only) redirects (i.e. symlinks) <filename>default.target</filename> to <filename>system-update.target</filename>, a special target that is pulls in the base system @@ -162,8 +162,8 @@ <ulink url="http://www.freedesktop.org/wiki/Software/systemd/SystemUpdates/">Implementing Offline System Updates</ulink>, <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>, - <citerefentry><refentrytitle>systemd-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>, - <citerefentry><refentrytitle>dnf.plugin.system-upgrade</refentrytitle><manvolnum>8</manvolnum></citerefentry> + <citerefentry><refentrytitle>systemd-system-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry project='mankier'><refentrytitle>dnf.plugin.system-upgrade</refentrytitle><manvolnum>8</manvolnum></citerefentry> </para> </refsect1> </refentry> diff --git a/man/systemd.preset.xml b/man/systemd.preset.xml index b7164014f0..d09167baaf 100644 --- a/man/systemd.preset.xml +++ b/man/systemd.preset.xml @@ -98,6 +98,10 @@ Empty lines and lines whose first non-whitespace character is # or ; are ignored.</para> + <para>Presets must refer to the "real" unit file, and not to any aliases. See + <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> + for a description of unit aliasing.</para> + <para>Two different directives are understood: <literal>enable</literal> may be used to enable units by default, <literal>disable</literal> to disable units by default.</para> diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml index bf44a68345..0e98ca78b8 100644 --- a/man/systemd.resource-control.xml +++ b/man/systemd.resource-control.xml @@ -99,9 +99,10 @@ <refsect1> <title>Unified and Legacy Control Group Hierarchies</title> - <para>The unified control group hierarchy is the new version of kernel control group interface. Depending on the - resource type, there are differences in resource control capabilities. Also, because of interface changes, some - resource types have a separate set of options on the unified hierarchy.</para> + <para>The unified control group hierarchy is the new version of kernel control group interface, see <ulink + url="https://www.kernel.org/doc/Documentation/cgroup-v2.txt">cgroup-v2.txt</ulink>. Depending on the resource type, + there are differences in resource control capabilities. Also, because of interface changes, some resource types + have separate set of options on the unified hierarchy.</para> <para> <variablelist> @@ -126,6 +127,13 @@ settings of a unit for a given resource type are for the other hierarchy type, the settings are translated and applied. If there are any valid settings for the hierarchy in use, all translations are disabled for the resource type. Mixing the two types of settings on a unit can lead to confusing results.</para> + + <para>Legacy control group hierarchy (see <ulink + url="https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt">cgroups.txt</ulink>), also called cgroup-v1, + doesn't allow safe delegation of controllers to unprivileged processes. If the system uses the legacy control group + hierarchy, resource control is disabled for systemd user instance, see + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>. + </para> </refsect1> <refsect1> diff --git a/man/systemd.service.xml b/man/systemd.service.xml index 875d368fcf..b58e887662 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -276,17 +276,12 @@ below (see section "Command Lines" below). </para> - <para>When <varname>Type=</varname> is not - <option>oneshot</option>, only one command may and must be - given. When <varname>Type=oneshot</varname> is used, zero or - more commands may be specified. This can be specified by - providing multiple command lines in the same directive, or - alternatively, this directive may be specified more than once - with the same effect. If the empty string is assigned to this - option, the list of commands to start is reset, prior - assignments of this option will have no effect. If no - <varname>ExecStart=</varname> is specified, then the service - must have <varname>RemainAfterExit=yes</varname> set.</para> + <para>Unless <varname>Type=</varname> is <option>oneshot</option>, exactly one command must be given. When + <varname>Type=oneshot</varname> is used, zero or more commands may be specified. Commands may be specified by + providing multiple command lines in the same directive, or alternatively, this directive may be specified more + than once with the same effect. If the empty string is assigned to this option, the list of commands to start + is reset, prior assignments of this option will have no effect. If no <varname>ExecStart=</varname> is + specified, then the service must have <varname>RemainAfterExit=yes</varname> set.</para> <para>For each of the specified commands, the first argument must be an absolute path to an executable. Optionally, if this file name is prefixed with <literal>@</literal>, the second token will be @@ -294,7 +289,7 @@ the absolute filename is prefixed with <literal>-</literal>, an exit code of the command normally considered a failure (i.e. non-zero exit status or abnormal exit due to signal) is ignored and considered success. If the absolute path is prefixed with <literal>+</literal> then it is executed with full - privileges. <literal>-</literal>, <literal>@</literal>, and <literal>+</literal> may be used together and they + privileges. <literal>@</literal>, <literal>-</literal>, and <literal>+</literal> may be used together and they can appear in any order.</para> <para>If more than one command is specified, the commands are @@ -429,7 +424,13 @@ service failed to start up correctly. Commands configured with this setting need to be able to operate even if the service failed starting up half-way and left incompletely initialized data around. As the service's processes have been terminated already when the commands specified with this setting are executed they should - not attempt to communicate with them.</para></listitem> + not attempt to communicate with them.</para> + + <para>Note that all commands that are configured with this setting are invoked with the result code of the + service, as well as the main process' exit code and status, set in the <varname>$SERVICE_RESULT</varname>, + <varname>$EXIT_CODE</varname> and <varname>$EXIT_STATUS</varname> environment variables, see + <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for + details.</para></listitem> </varlistentry> <varlistentry> diff --git a/man/systemd.socket.xml b/man/systemd.socket.xml index 5bf54d8ef3..26e5d3ce7b 100644 --- a/man/systemd.socket.xml +++ b/man/systemd.socket.xml @@ -443,6 +443,14 @@ </varlistentry> <varlistentry> + <term><varname>MaxConnectionsPerSource=</varname></term> + <listitem><para>The maximum number of connections for a service per source IP address. + This is is very similar to the <varname>MaxConnections=</varname> directive + above. Disabled by default.</para> + </listitem> + </varlistentry> + + <varlistentry> <term><varname>KeepAlive=</varname></term> <listitem><para>Takes a boolean argument. If true, the TCP/IP stack will send a keep alive message after 2h (depending on diff --git a/man/systemd.special.xml b/man/systemd.special.xml index 18ad8f92e5..d977298cd8 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -879,6 +879,70 @@ </refsect1> <refsect1> + <title>Special Passive User Units</title> + + <refsect2> + <title>graphical-session.target</title> + + <para>This target is active whenever any graphical session is running. It + is used to stop user services which only apply to a graphical (X, + Wayland, etc.) session when the session is terminated. Such services + should have <literal>PartOf=graphical-session.target</literal> in their + <literal>[Unit]</literal> section. A target for a particular session + (e. g. <filename>gnome-session.target</filename>) starts and stops + <literal>graphical-session.target</literal> with + <literal>BindsTo=graphical-session.target</literal>.</para> + + <para>Which services are started by a session target is determined by the + <literal>Wants=</literal> and <literal>Requires=</literal> dependencies. + For services that can be enabled independently, symlinks in + <literal>.wants/</literal> and <literal>.requires/</literal> should be + used, see + <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>. + Those symlinks should either be shipped in packages, or should be added + dynamically after installation, for example using <literal>systemctl add-wants</literal>, see + <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>. + </para> + + <example> + <title>Nautilus as part of a GNOME session</title> + + <para><literal>gnome-session.target</literal> pulls in Nautilus as + top-level service:</para> + + <programlisting>[Unit] +Description=User systemd services for GNOME graphical session +Wants=nautilus.service +BindsTo=graphical-session.target + </programlisting> + + <para><literal>nautilus.service</literal> gets stopped when the session stops:</para> + + <programlisting>[Unit] +Description=Render the desktop icons with Nautilus +PartOf=graphical-session.target + +[Service] +... + </programlisting> + </example> + </refsect2> + + <refsect2> + <title>graphical-session-pre.target</title> + + <para>This target contains services which set up the environment or + global configuration of a graphical session, such as SSH/GPG agents + (which need to export an environment variable into all desktop processes) + or migration of obsolete d-conf keys after an OS upgrade (which needs to + happen before starting any process that might use them). This target must + be started before starting a graphical session + like <filename>gnome-session.target</filename>.</para> + </refsect2> + + </refsect1> + + <refsect1> <title>Special Slice Units</title> <para>There are four <literal>.slice</literal> units which form diff --git a/man/systemd.time.xml b/man/systemd.time.xml index aae3accb6c..47229b4a4e 100644 --- a/man/systemd.time.xml +++ b/man/systemd.time.xml @@ -57,14 +57,13 @@ <refsect1> <title>Displaying Time Spans</title> - <para>Time spans refer to time durations. On display, systemd will - present time spans as a space-separated series of time values each - suffixed by a time unit.</para> + <para>Time spans refer to time durations. On display, systemd will present time spans as a space-separated series + of time values each suffixed by a time unit. Example:</para> <programlisting>2h 30min</programlisting> - <para>All specified time values are meant to be added up. The - above hence refers to 150 minutes.</para> + <para>All specified time values are meant to be added up. The above hence refers to 150 minutes. Display is + locale-independent, only English names for the time units are used.</para> </refsect1> <refsect1> @@ -83,13 +82,13 @@ <listitem><para>days, day, d</para></listitem> <listitem><para>weeks, week, w</para></listitem> <listitem><para>months, month, M (defined as 30.44 days)</para></listitem> - <listitem><para>years, year, y (define as 365.25 days)</para></listitem> + <listitem><para>years, year, y (defined as 365.25 days)</para></listitem> </itemizedlist> - <para>If no time unit is specified, generally seconds are assumed, - but some exceptions exist and are marked as such. In a few cases - <literal>ns</literal>, <literal>nsec</literal> is accepted too, - where the granularity of the time span allows for this.</para> + <para>If no time unit is specified, generally seconds are assumed, but some exceptions exist and are marked as + such. In a few cases <literal>ns</literal>, <literal>nsec</literal> is accepted too, where the granularity of the + time span permits this. Parsing is generally locale-independent, non-English names for the time units are not + accepted.</para> <para>Examples for valid time span specifications:</para> @@ -110,30 +109,29 @@ <programlisting>Fri 2012-11-23 23:02:15 CET</programlisting> - <para>The weekday is printed according to the locale choice of the - user.</para> + <para>The weekday is printed in the abbreviated English language form. The formatting is locale-independent.</para> + + <para>In some cases timestamps are shown in the UTC timezone instead of the local timezone, which is indicated via + the <literal>UTC</literal> timezone specifier in the output.</para> + + <para>In some cases timestamps are shown with microsecond granularity. In this case the sub-second remainder is + separated by a full stop from the seconds component.</para> </refsect1> <refsect1> <title>Parsing Timestamps</title> - <para>When parsing, systemd will accept a similar syntax, but - expects no timezone specification, unless it is given as the - literal string "UTC". In this case, the time is considered in UTC, - otherwise in the local timezone. The weekday specification is - optional, but when the weekday is specified, it must either be in - the abbreviated (<literal>Wed</literal>) or non-abbreviated - (<literal>Wednesday</literal>) English language form (case does - not matter), and is not subject to the locale choice of the user. - Either the date, or the time part may be omitted, in which case - the current date or 00:00:00, respectively, is assumed. The seconds - component of the time may also be omitted, in which case ":00" is - assumed. Year numbers may be specified in full or may be - abbreviated (omitting the century).</para> - - <para>A timestamp is considered invalid if a weekday is specified - and the date does not actually match the specified day of the - week.</para> + <para>When parsing, systemd will accept a similar syntax, but expects no timezone specification, unless it is given + as the literal string <literal>UTC</literal> (for the UTC timezone) or is specified to be the locally configured + timezone. Other timezones than the local and UTC are not supported. The weekday specification is optional, but when + the weekday is specified, it must either be in the abbreviated (<literal>Wed</literal>) or non-abbreviated + (<literal>Wednesday</literal>) English language form (case does not matter), and is not subject to the locale + choice of the user. Either the date, or the time part may be omitted, in which case the current date or 00:00:00, + respectively, is assumed. The seconds component of the time may also be omitted, in which case ":00" is + assumed. Year numbers may be specified in full or may be abbreviated (omitting the century).</para> + + <para>A timestamp is considered invalid if a weekday is specified and the date does not match the specified day of + the week.</para> <para>When parsing, systemd will also accept a few special placeholders instead of timestamps: <literal>now</literal> may be @@ -167,8 +165,6 @@ 2012-11-23 → Fri 2012-11-23 00:00:00 12-11-23 → Fri 2012-11-23 00:00:00 11:12:13 → Fri 2012-11-23 11:12:13 - 11:12:13.9900009 → Fri 2012-11-23 11:12:13 - format_timestamp_us: Fri 2012-11-23 11:12:13.990000 11:12 → Fri 2012-11-23 11:12:00 now → Fri 2012-11-23 18:15:22 today → Fri 2012-11-23 00:00:00 @@ -176,28 +172,25 @@ yesterday → Fri 2012-11-22 00:00:00 tomorrow → Fri 2012-11-24 00:00:00 +3h30min → Fri 2012-11-23 21:45:22 - +3h30min UTC → -EINVAL -5s → Fri 2012-11-23 18:15:17 11min ago → Fri 2012-11-23 18:04:22 - 11min ago UTC → -EINVAL @1395716396 → Tue 2014-03-25 03:59:56</programlisting> - <para>Note that timestamps printed by systemd will not be parsed - correctly by systemd, as the timezone specification is not - accepted, and printing timestamps is subject to locale settings - for the weekday, while parsing only accepts English weekday - names.</para> + <para>Note that timestamps displayed by remote systems with a non-matching timezone are usually not parsable + locally, as the timezone component is not understood (unless it happens to be <literal>UTC</literal>).</para> - <para>In some cases, systemd will display a relative timestamp - (relative to the current time, or the time of invocation of the - command) instead or in addition to an absolute timestamp as - described above. A relative timestamp is formatted as - follows:</para> + <para>Timestamps may also be specified with microsecond granularity. The sub-second remainder is expected separated + by a full stop from the seconds component. Example:</para> + + <programlisting>2014-03-25 03:59:56.654563</programlisting> + + <para>In some cases, systemd will display a relative timestamp (relative to the current time, or the time of + invocation of the command) instead of or in addition to an absolute timestamp as described above. A relative + timestamp is formatted as follows:</para> - <para>2 months 5 days ago</para> + <programlisting>2 months 5 days ago</programlisting> - <para>Note that any relative timestamp will also parse correctly - where a timestamp is expected. (see above)</para> + <para>Note that a relative timestamp is also accepted where a timestamp is expected (see above).</para> </refsect1> <refsect1> @@ -239,8 +232,9 @@ second component is not specified, <literal>:00</literal> is assumed.</para> - <para>A timezone specification is not expected, unless it is given - as the literal string "UTC", similarly to timestamps.</para> + <para>A timezone specification is not expected, unless it is given as the literal string <literal>UTC</literal>, or + the local timezone, similar to the supported syntax of timestamps (see above). Non-local timezones except for UTC + are not supported.</para> <para>The special expressions <literal>minutely</literal>, @@ -263,38 +257,38 @@ <para>Examples for valid timestamps and their normalized form:</para> -<programlisting> Sat,Thu,Mon..Wed,Sat..Sun → Mon..Thu,Sat,Sun *-*-* 00:00:00 - Mon,Sun 12-*-* 2,1:23 → Mon,Sun 2012-*-* 01,02:23:00 - Wed *-1 → Wed *-*-01 00:00:00 +<programlisting> Sat,Thu,Mon..Wed,Sat..Sun → Mon..Thu,Sat,Sun *-*-* 00:00:00 + Mon,Sun 12-*-* 2,1:23 → Mon,Sun 2012-*-* 01,02:23:00 + Wed *-1 → Wed *-*-01 00:00:00 Wed..Wed,Wed *-1 → Wed *-*-01 00:00:00 - Wed, 17:48 → Wed *-*-* 17:48:00 + Wed, 17:48 → Wed *-*-* 17:48:00 Wed..Sat,Tue 12-10-15 1:2:3 → Tue..Sat 2012-10-15 01:02:03 - *-*-7 0:0:0 → *-*-07 00:00:00 - 10-15 → *-10-15 00:00:00 - monday *-12-* 17:00 → Mon *-12-* 17:00:00 - Mon,Fri *-*-3,1,2 *:30:45 → Mon,Fri *-*-01,02,03 *:30:45 - 12,14,13,12:20,10,30 → *-*-* 12,13,14:10,20,30:00 - 12..14:10,20,30 → *-*-* 12,13,14:10,20,30:00 - mon,fri *-1/2-1,3 *:30:45 → Mon,Fri *-01/2-01,03 *:30:45 - 03-05 08:05:40 → *-03-05 08:05:40 - 08:05:40 → *-*-* 08:05:40 - 05:40 → *-*-* 05:40:00 - Sat,Sun 12-05 08:05:40 → Sat,Sun *-12-05 08:05:40 - Sat,Sun 08:05:40 → Sat,Sun *-*-* 08:05:40 - 2003-03-05 05:40 → 2003-03-05 05:40:00 -05:40:23.4200004/3.1700005 → 05:40:23.420000/3.170001 - 2003-02..04-05 → 2003-02,03,04-05 00:00:00 - 2003-03-05 05:40 UTC → 2003-03-05 05:40:00 UTC - 2003-03-05 → 2003-03-05 00:00:00 - 03-05 → *-03-05 00:00:00 - hourly → *-*-* *:00:00 - daily → *-*-* 00:00:00 - daily UTC → *-*-* 00:00:00 UTC - monthly → *-*-01 00:00:00 - weekly → Mon *-*-* 00:00:00 - yearly → *-01-01 00:00:00 - annually → *-01-01 00:00:00 - *:2/3 → *-*-* *:02/3:00</programlisting> + *-*-7 0:0:0 → *-*-07 00:00:00 + 10-15 → *-10-15 00:00:00 + monday *-12-* 17:00 → Mon *-12-* 17:00:00 + Mon,Fri *-*-3,1,2 *:30:45 → Mon,Fri *-*-01,02,03 *:30:45 + 12,14,13,12:20,10,30 → *-*-* 12,13,14:10,20,30:00 + 12..14:10,20,30 → *-*-* 12,13,14:10,20,30:00 + mon,fri *-1/2-1,3 *:30:45 → Mon,Fri *-01/2-01,03 *:30:45 + 03-05 08:05:40 → *-03-05 08:05:40 + 08:05:40 → *-*-* 08:05:40 + 05:40 → *-*-* 05:40:00 + Sat,Sun 12-05 08:05:40 → Sat,Sun *-12-05 08:05:40 + Sat,Sun 08:05:40 → Sat,Sun *-*-* 08:05:40 + 2003-03-05 05:40 → 2003-03-05 05:40:00 + 05:40:23.4200004/3.1700005 → 05:40:23.420000/3.170001 + 2003-02..04-05 → 2003-02,03,04-05 00:00:00 + 2003-03-05 05:40 UTC → 2003-03-05 05:40:00 UTC + 2003-03-05 → 2003-03-05 00:00:00 + 03-05 → *-03-05 00:00:00 + hourly → *-*-* *:00:00 + daily → *-*-* 00:00:00 + daily UTC → *-*-* 00:00:00 UTC + monthly → *-*-01 00:00:00 + weekly → Mon *-*-* 00:00:00 + yearly → *-01-01 00:00:00 + annually → *-01-01 00:00:00 + *:2/3 → *-*-* *:02/3:00</programlisting> <para>Calendar events are used by timer units, see <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry> diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 85a7b12d76..f818e772a9 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -144,21 +144,31 @@ <option>false</option> and <option>off</option> are equivalent.</para> - <para>Time span values encoded in unit files can be written in - various formats. A stand-alone number specifies a time in seconds. - If suffixed with a time unit, the unit is honored. A concatenation - of multiple values with units is supported, in which case the - values are added up. Example: "50" refers to 50 seconds; "2min - 200ms" refers to 2 minutes plus 200 milliseconds, i.e. 120200ms. - The following time units are understood: s, min, h, d, w, ms, us. - For details see + <para>Time span values encoded in unit files can be written in various formats. A stand-alone number specifies a + time in seconds. If suffixed with a time unit, the unit is honored. A concatenation of multiple values with units + is supported, in which case the values are added up. Example: <literal>50</literal> refers to 50 seconds; + <literal>2min 200ms</literal> refers to 2 minutes and 200 milliseconds, i.e. 120200 ms. The following time units + are understood: <literal>s</literal>, <literal>min</literal>, <literal>h</literal>, <literal>d</literal>, + <literal>w</literal>, <literal>ms</literal>, <literal>us</literal>. For details see <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para> - <para>Empty lines and lines starting with # or ; are - ignored. This may be used for commenting. Lines ending - in a backslash are concatenated with the following - line while reading and the backslash is replaced by a - space character. This may be used to wrap long lines.</para> + <para>Empty lines and lines starting with <literal>#</literal> or <literal>;</literal> are ignored. This may be + used for commenting. Lines ending in a backslash are concatenated with the following line while reading and the + backslash is replaced by a space character. This may be used to wrap long lines.</para> + + <para>Units can be aliased (have an alternative name), by creating a symlink from the new name to the existing name + in one of the unit search paths. For example, <filename>systemd-networkd.service</filename> has the alias + <filename>dbus-org.freedesktop.network1.service</filename>, created during installation as the symlink + <filename>/usr/lib/systemd/system/dbus-org.freedesktop.network1.service</filename>. In addition, unit files may + specify aliases through the <varname>Alias=</varname> directive in the [Install] section; those aliases are only + effective when the unit is enabled. When the unit is enabled, symlinks will be created for those names, and removed + when the unit is disabled. For example, <filename>reboot.target</filename> specifies + <varname>Alias=ctrl-alt-del.target</varname>, so when enabled it will be invoked whenever CTRL+ALT+DEL is + pressed. Alias names may be used in commands like <command>enable</command>, <command>disable</command>, + <command>start</command>, <command>stop</command>, <command>status</command>, …, and in unit dependency directives + <varname>Wants=</varname>, <varname>Requires=</varname>, <varname>Before=</varname>, <varname>After=</varname>, …, + with the limitation that aliases specified through <varname>Alias=</varname> are only effective when the unit is + enabled. Aliases cannot be used with the <command>preset</command> command.</para> <para>Along with a unit file <filename>foo.service</filename>, the directory <filename>foo.service.wants/</filename> may exist. All diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index 957475d2bd..75fb901102 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -626,7 +626,7 @@ <example> <title>Create directories with specific mode and ownership</title> <para> - <citerefentry><refentrytitle>screen</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry project='die-net'><refentrytitle>screen</refentrytitle><manvolnum>1</manvolnum></citerefentry>, needs two directories created at boot with specific modes and ownership:</para> <programlisting># /usr/lib/tmpfiles.d/screen.conf @@ -652,7 +652,7 @@ t /run/cups - - - - security.SMACK64=printing user.attr-with-spaces="foo bar" <example> <title>Create a directory and prevent its contents from cleanup</title> <para> - <citerefentry><refentrytitle>abrt</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry project='die-net'><refentrytitle>abrt</refentrytitle><manvolnum>1</manvolnum></citerefentry>, needs a directory created at boot with specific mode and ownership and its content should be preserved from the automatic cleanup applied to the contents of <filename>/var/tmp</filename>:</para> diff --git a/man/vconsole.conf.xml b/man/vconsole.conf.xml index 27196d44e9..fa30ca6569 100644 --- a/man/vconsole.conf.xml +++ b/man/vconsole.conf.xml @@ -55,8 +55,9 @@ <para>The <filename>/etc/vconsole.conf</filename> file configures the virtual console, i.e. keyboard mapping and console font. It is - applied at boot by - <citerefentry><refentrytitle>systemd-vconsole-setup.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> + applied at boot by udev using <filename>90-vconsole.rules</filename> file. + You can safely mask this file if you want to avoid this kind of initialization. + </para> <para>The basic file format of the <filename>vconsole.conf</filename> is a newline-separated list of @@ -68,10 +69,10 @@ <para>Note that the kernel command line options <varname>vconsole.keymap=</varname>, - <varname>vconsole.keymap.toggle=</varname>, + <varname>vconsole.keymap_toggle=</varname>, <varname>vconsole.font=</varname>, - <varname>vconsole.font.map=</varname>, - <varname>vconsole.font.unimap=</varname> may be used + <varname>vconsole.font_map=</varname>, + <varname>vconsole.font_unimap=</varname> may be used to override the console settings at boot.</para> <para>Depending on the operating system other configuration files @@ -90,12 +91,10 @@ <term><varname>KEYMAP=</varname></term> <term><varname>KEYMAP_TOGGLE=</varname></term> - <listitem><para>Configures the key mapping table for the - keyboard. <varname>KEYMAP=</varname> defaults to - <literal>us</literal> if not set. The - <varname>KEYMAP_TOGGLE=</varname> can be used to configure a - second toggle keymap and is by default - unset.</para></listitem> + <listitem><para>Configures the key mapping table for the keyboard. + <varname>KEYMAP=</varname> defaults to <literal>us</literal> if not set. The + <varname>KEYMAP_TOGGLE=</varname> can be used to configure a second toggle keymap and is by + default unset.</para></listitem> </varlistentry> <varlistentry> @@ -111,6 +110,32 @@ </refsect1> <refsect1> + <title>Kernel Command Line</title> + + <para>A few configuration parameters from <filename>vconsole.conf</filename> may be overridden + on the kernel command line:</para> + + <variablelist class='kernel-commandline-options'> + <varlistentry> + <term><varname>vconsole.keymap=</varname></term> + <term><varname>vconsole.keymap_toggle=</varname></term> + + <listitem><para>Overrides <varname>KEYMAP=</varname> and <varname>KEYMAP_TOGGLE=</varname>. + </para></listitem> + </varlistentry> + <varlistentry> + + <term><varname>vconsole.font=</varname></term> + <term><varname>vconsole.font_map=</varname></term> + <term><varname>vconsole.font_unimap=</varname></term> + + <listitem><para>Overrides <varname>FONT=</varname>, <varname>FONT_MAP=</varname>, and + <varname>FONT_UNIMAP=</varname>.</para></listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> <title>Example</title> <example> diff --git a/shell-completion/bash/journalctl b/shell-completion/bash/journalctl index 53bedcd92e..a999a10df1 100644 --- a/shell-completion/bash/journalctl +++ b/shell-completion/bash/journalctl @@ -65,7 +65,7 @@ _journalctl() { compopt -o filenames ;; --output|-o) - comps='short short-iso short-precise short-monotonic verbose export json json-pretty json-sse cat' + comps='short short-full short-iso short-precise short-monotonic short-unix verbose export json json-pretty json-sse cat' ;; --field|-F) comps=$(journalctl --fields | sort 2>/dev/null) diff --git a/shell-completion/bash/systemctl.in b/shell-completion/bash/systemctl.in index 6f2b3f122c..2a45dcbba0 100644 --- a/shell-completion/bash/systemctl.in +++ b/shell-completion/bash/systemctl.in @@ -145,7 +145,7 @@ _systemctl () { comps='full enable-only disable-only' ;; --output|-o) - comps='short short-iso short-precise short-monotonic verbose export json + comps='short short-full short-iso short-precise short-monotonic short-unix verbose export json json-pretty json-sse cat' ;; --machine|-M) diff --git a/shell-completion/zsh/_journalctl b/shell-completion/zsh/_journalctl index 2bee23b6d3..2271f7fa9c 100644 --- a/shell-completion/zsh/_journalctl +++ b/shell-completion/zsh/_journalctl @@ -23,7 +23,7 @@ _list_fields() { _journal_none() { local -a _commands _files _jrnl_none # Setting use-cache will slow this down considerably - _commands=( ${"$(_call_program commands "$service" -F _EXE 2>/dev/null)"} ) + _commands=( ${"$(_call_program commands "$service $_sys_service_mgr -F _EXE" 2>/dev/null)"} ) _jrnl_none='yes' _alternative : \ 'files:/dev files:_files -W /dev -P /dev/' \ @@ -51,6 +51,8 @@ _journal_boots() { "bootid:boot ids:compadd -a _bootid" } +local -a _modes; _modes=("--user" "--system") +local _sys_service_mgr=${${words:*_modes}[(R)(${(j.|.)_modes})]:---system} _arguments -s \ {-h,--help}'[Show this help]' \ '--version[Show package version]' \ diff --git a/shell-completion/zsh/_sd_outputmodes b/shell-completion/zsh/_sd_outputmodes index 3836f79b73..52617c6b7a 100644 --- a/shell-completion/zsh/_sd_outputmodes +++ b/shell-completion/zsh/_sd_outputmodes @@ -1,5 +1,5 @@ #autoload local -a _output_opts -_output_opts=(short short-iso short-precise short-monotonic verbose export json json-pretty json-sse cat) +_output_opts=(short short-full short-iso short-precise short-monotonic short-unix verbose export json json-pretty json-sse cat) _describe -t output 'output mode' _output_opts || compadd "$@" diff --git a/shell-completion/zsh/_systemctl.in b/shell-completion/zsh/_systemctl.in index 44c31b7833..69f643303d 100644 --- a/shell-completion/zsh/_systemctl.in +++ b/shell-completion/zsh/_systemctl.in @@ -126,7 +126,7 @@ _filter_units_by_property() { local property=$1 value=$2 ; shift ; shift local -a units ; units=($*) local props - for props in ${(ps:\n\n:)"$(_call_program units "$service show --no-pager --property="Id,$property" -- ${units} 2>/dev/null")"}; do + for props in ${(ps:\n\n:)"$(_call_program units "$service $_sys_service_mgr show --no-pager --property="Id,$property" -- ${units} 2>/dev/null")"}; do props=(${(f)props}) if [[ "${props[2]}" = "$property=$value" ]]; then echo -E - " ${props[1]#Id=}" diff --git a/src/basic/calendarspec.c b/src/basic/calendarspec.c index e4cfab364e..fda293fcb9 100644 --- a/src/basic/calendarspec.c +++ b/src/basic/calendarspec.c @@ -302,6 +302,17 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) { if (c->utc) fputs(" UTC", f); + else if (IN_SET(c->dst, 0, 1)) { + + /* If daylight saving is explicitly on or off, let's show the used timezone. */ + + tzset(); + + if (!isempty(tzname[c->dst])) { + fputc(' ', f); + fputs(tzname[c->dst], f); + } + } r = fflush_and_check(f); if (r < 0) { @@ -747,9 +758,9 @@ fail: } int calendar_spec_from_string(const char *p, CalendarSpec **spec) { + const char *utc; CalendarSpec *c; int r; - const char *utc; assert(p); assert(spec); @@ -760,11 +771,39 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) { c = new0(CalendarSpec, 1); if (!c) return -ENOMEM; + c->dst = -1; utc = endswith_no_case(p, " UTC"); if (utc) { c->utc = true; p = strndupa(p, utc - p); + } else { + const char *e = NULL; + int j; + + tzset(); + + /* Check if the local timezone was specified? */ + for (j = 0; j <= 1; j++) { + if (isempty(tzname[j])) + continue; + + e = endswith_no_case(p, tzname[j]); + if(!e) + continue; + if (e == p) + continue; + if (e[-1] != ' ') + continue; + + break; + } + + /* Found one of the two timezones specified? */ + if (IN_SET(j, 0, 1)) { + p = strndupa(p, e - p - 1); + c->dst = j; + } } if (strcaseeq(p, "minutely")) { @@ -1017,7 +1056,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { for (;;) { /* Normalize the current date */ (void) mktime_or_timegm(&c, spec->utc); - c.tm_isdst = -1; + c.tm_isdst = spec->dst; c.tm_year += 1900; r = find_matching_component(spec->year, &c.tm_year); diff --git a/src/basic/calendarspec.h b/src/basic/calendarspec.h index f6472c1244..c6087228fd 100644 --- a/src/basic/calendarspec.h +++ b/src/basic/calendarspec.h @@ -37,6 +37,7 @@ typedef struct CalendarComponent { typedef struct CalendarSpec { int weekdays_bits; bool utc; + int dst; CalendarComponent *year; CalendarComponent *month; diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 472e24b7a3..302b958d0d 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -134,6 +134,20 @@ int cg_read_event(const char *controller, const char *path, const char *event, return -ENOENT; } +bool cg_ns_supported(void) { + static thread_local int enabled = -1; + + if (enabled >= 0) + return enabled; + + if (access("/proc/self/ns/cgroup", F_OK) == 0) + enabled = 1; + else + enabled = 0; + + return enabled; +} + int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) { _cleanup_free_ char *fs = NULL; int r; diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h index 14ebde5fc9..ec5c715987 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -222,6 +222,8 @@ int cg_mask_supported(CGroupMask *ret); int cg_kernel_controllers(Set *controllers); +bool cg_ns_supported(void); + int cg_unified(void); void cg_unified_flush(void); diff --git a/src/basic/fileio.c b/src/basic/fileio.c index f183de4999..d642f3daea 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -47,6 +47,8 @@ #include "umask-util.h" #include "utf8.h" +#define READ_FULL_BYTES_MAX (4U*1024U*1024U) + int write_string_stream(FILE *f, const char *line, bool enforce_newline) { assert(f); @@ -230,7 +232,7 @@ int read_full_stream(FILE *f, char **contents, size_t *size) { if (S_ISREG(st.st_mode)) { /* Safety check */ - if (st.st_size > 4*1024*1024) + if (st.st_size > READ_FULL_BYTES_MAX) return -E2BIG; /* Start with the right file size, but be prepared for @@ -245,26 +247,31 @@ int read_full_stream(FILE *f, char **contents, size_t *size) { char *t; size_t k; - t = realloc(buf, n+1); + t = realloc(buf, n + 1); if (!t) return -ENOMEM; buf = t; k = fread(buf + l, 1, n - l, f); + if (k > 0) + l += k; - if (k <= 0) { - if (ferror(f)) - return -errno; + if (ferror(f)) + return -errno; + if (feof(f)) break; - } - l += k; - n *= 2; + /* We aren't expecting fread() to return a short read outside + * of (error && eof), assert buffer is full and enlarge buffer. + */ + assert(l == n); /* Safety check */ - if (n > 4*1024*1024) + if (n >= READ_FULL_BYTES_MAX) return -E2BIG; + + n = MIN(n * 2, READ_FULL_BYTES_MAX); } buf[l] = 0; @@ -1161,8 +1168,8 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) { char *t, *x; uint64_t u; unsigned i; + int r; - assert(p); assert(ret); /* Turns this: @@ -1171,6 +1178,12 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) { * /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0 */ + if (!p) { + r = tmp_dir(&p); + if (r < 0) + return r; + } + if (!extra) extra = ""; @@ -1257,10 +1270,13 @@ int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) int open_tmpfile_unlinkable(const char *directory, int flags) { char *p; - int fd; + int fd, r; - if (!directory) - directory = "/tmp"; + if (!directory) { + r = tmp_dir(&directory); + if (r < 0) + return r; + } /* Returns an unlinked temporary file that cannot be linked into the file system anymore */ diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index f0c6f3265e..ce87257bc1 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -496,34 +496,94 @@ int get_files_in_directory(const char *path, char ***list) { return n; } -int var_tmp(char **ret) { - const char *tmp_dir = NULL; - const char *env_tmp_dir = NULL; - char *c = NULL; - int r; +static int getenv_tmp_dir(const char **ret_path) { + const char *n; + int r, ret = 0; - assert(ret); + assert(ret_path); - env_tmp_dir = getenv("TMPDIR"); - if (env_tmp_dir != NULL) { - r = is_dir(env_tmp_dir, true); - if (r < 0 && r != -ENOENT) - return r; - if (r > 0) - tmp_dir = env_tmp_dir; + /* We use the same order of environment variables python uses in tempfile.gettempdir(): + * https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir */ + FOREACH_STRING(n, "TMPDIR", "TEMP", "TMP") { + const char *e; + + e = secure_getenv(n); + if (!e) + continue; + if (!path_is_absolute(e)) { + r = -ENOTDIR; + goto next; + } + if (!path_is_safe(e)) { + r = -EPERM; + goto next; + } + + r = is_dir(e, true); + if (r < 0) + goto next; + if (r == 0) { + r = -ENOTDIR; + goto next; + } + + *ret_path = e; + return 1; + + next: + /* Remember first error, to make this more debuggable */ + if (ret >= 0) + ret = r; } - if (!tmp_dir) - tmp_dir = "/var/tmp"; + if (ret < 0) + return ret; - c = strdup(tmp_dir); - if (!c) - return -ENOMEM; - *ret = c; + *ret_path = NULL; + return ret; +} +static int tmp_dir_internal(const char *def, const char **ret) { + const char *e; + int r, k; + + assert(def); + assert(ret); + + r = getenv_tmp_dir(&e); + if (r > 0) { + *ret = e; + return 0; + } + + k = is_dir(def, true); + if (k == 0) + k = -ENOTDIR; + if (k < 0) + return r < 0 ? r : k; + + *ret = def; return 0; } +int var_tmp_dir(const char **ret) { + + /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus + * even might survive a boot: /var/tmp. If $TMPDIR (or related environment variables) are set, its value is + * returned preferably however. Note that both this function and tmp_dir() below are affected by $TMPDIR, + * making it a variable that overrides all temporary file storage locations. */ + + return tmp_dir_internal("/var/tmp", ret); +} + +int tmp_dir(const char **ret) { + + /* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually + * backed by an in-memory file system: /tmp. */ + + return tmp_dir_internal("/tmp", ret); +} + int inotify_add_watch_fd(int fd, int what, uint32_t mask) { char path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; int r; diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 075e5942b1..2c3b9a1c74 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -61,7 +61,8 @@ int mkfifo_atomic(const char *path, mode_t mode); int get_files_in_directory(const char *path, char ***list); -int var_tmp(char **ret); +int tmp_dir(const char **ret); +int var_tmp_dir(const char **ret); #define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1) diff --git a/src/basic/missing.h b/src/basic/missing.h index b1272f8799..f8e096605e 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -445,6 +445,10 @@ struct btrfs_ioctl_quota_ctl_args { #define CGROUP2_SUPER_MAGIC 0x63677270 #endif +#ifndef CLONE_NEWCGROUP +#define CLONE_NEWCGROUP 0x02000000 +#endif + #ifndef TMPFS_MAGIC #define TMPFS_MAGIC 0x01021994 #endif diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index 503a895731..c98815b9bc 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -29,6 +29,7 @@ #include "extract-word.h" #include "macro.h" #include "parse-util.h" +#include "process-util.h" #include "string-util.h" int parse_boolean(const char *v) { @@ -533,7 +534,7 @@ int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) { return 0; } -int parse_percent(const char *p) { +int parse_percent_unbounded(const char *p) { const char *pc, *n; unsigned v; int r; @@ -546,8 +547,30 @@ int parse_percent(const char *p) { r = safe_atou(n, &v); if (r < 0) return r; + + return (int) v; +} + +int parse_percent(const char *p) { + int v; + + v = parse_percent_unbounded(p); if (v > 100) return -ERANGE; - return (int) v; + return v; +} + +int parse_nice(const char *p, int *ret) { + int n, r; + + r = safe_atoi(p, &n); + if (r < 0) + return r; + + if (!nice_is_valid(n)) + return -ERANGE; + + *ret = n; + return 0; } diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index 73441bb6fd..461e1cd4d8 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -106,4 +106,7 @@ int safe_atod(const char *s, double *ret_d); int parse_fractional_part_u(const char **s, size_t digits, unsigned *res); +int parse_percent_unbounded(const char *p); int parse_percent(const char *p); + +int parse_nice(const char *p, int *ret); diff --git a/src/basic/process-util.h b/src/basic/process-util.h index 9f75088796..2568e3834f 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -26,6 +26,7 @@ #include <stdio.h> #include <string.h> #include <sys/types.h> +#include <sys/resource.h> #include "formats-util.h" #include "macro.h" @@ -103,3 +104,7 @@ int sched_policy_from_string(const char *s); void valgrind_summary_hack(void); int pid_compare_func(const void *a, const void *b); + +static inline bool nice_is_valid(int n) { + return n >= PRIO_MIN && n < PRIO_MAX; +} diff --git a/src/basic/set.h b/src/basic/set.h index 12f64a8c57..a5f8beb0c4 100644 --- a/src/basic/set.h +++ b/src/basic/set.h @@ -23,8 +23,8 @@ #include "hashmap.h" #include "macro.h" -Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -#define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS) +Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS) static inline Set *set_free(Set *s) { internal_hashmap_free(HASHMAP_BASE(s)); @@ -42,8 +42,8 @@ static inline Set *set_copy(Set *s) { return (Set*) internal_hashmap_copy(HASHMAP_BASE(s)); } -int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -#define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) +int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) int set_put(Set *s, const void *key); /* no set_update */ diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 385c3e4df3..6093e47172 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -1046,3 +1046,17 @@ int flush_accept(int fd) { close(cfd); } } + +struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length) { + struct cmsghdr *cmsg; + + assert(mh); + + CMSG_FOREACH(cmsg, mh) + if (cmsg->cmsg_level == level && + cmsg->cmsg_type == type && + (length == (socklen_t) -1 || length == cmsg->cmsg_len)) + return cmsg; + + return NULL; +} diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index e9230e4a9f..2536b085f9 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -142,6 +142,8 @@ int flush_accept(int fd); #define CMSG_FOREACH(cmsg, mh) \ for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg))) +struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length); + /* Covers only file system and abstract AF_UNIX socket addresses, but not unnamed socket addresses. */ #define SOCKADDR_UN_LEN(sa) \ ({ \ diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 293a15f9c0..5d4510e1b3 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -22,6 +22,7 @@ #include <stdint.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include "alloc-util.h" #include "gunicode.h" @@ -323,6 +324,14 @@ char ascii_tolower(char x) { return x; } +char ascii_toupper(char x) { + + if (x >= 'a' && x <= 'z') + return x - 'a' + 'A'; + + return x; +} + char *ascii_strlower(char *t) { char *p; @@ -334,6 +343,17 @@ char *ascii_strlower(char *t) { return t; } +char *ascii_strupper(char *t) { + char *p; + + assert(t); + + for (p = t; *p; p++) + *p = ascii_toupper(*p); + + return t; +} + char *ascii_strlower_n(char *t, size_t n) { size_t i; @@ -803,25 +823,20 @@ int free_and_strdup(char **p, const char *s) { return 1; } -#pragma GCC push_options -#pragma GCC optimize("O0") +/* + * Pointer to memset is volatile so that compiler must de-reference + * the pointer and can't assume that it points to any function in + * particular (such as memset, which it then might further "optimize") + * This approach is inspired by openssl's crypto/mem_clr.c. + */ +typedef void *(*memset_t)(void *,int,size_t); -void* memory_erase(void *p, size_t l) { - volatile uint8_t* x = (volatile uint8_t*) p; +static volatile memset_t memset_func = memset; - /* This basically does what memset() does, but hopefully isn't - * optimized away by the compiler. One of those days, when - * glibc learns memset_s() we should replace this call by - * memset_s(), but until then this has to do. */ - - for (; l > 0; l--) - *(x++) = 'x'; - - return p; +void* memory_erase(void *p, size_t l) { + return memset_func(p, 'x', l); } -#pragma GCC pop_options - char* string_erase(char *x) { if (!x) diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 1209e1e2e1..b75aba63c2 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -137,6 +137,9 @@ char ascii_tolower(char x); char *ascii_strlower(char *s); char *ascii_strlower_n(char *s, size_t n); +char ascii_toupper(char x); +char *ascii_strupper(char *s); + int ascii_strcasecmp_n(const char *a, const char *b, size_t n); int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m); diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index df56d85317..f0a46c48cf 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -785,7 +785,7 @@ bool tty_is_vc_resolve(const char *tty) { } const char *default_term_for_tty(const char *tty) { - return tty && tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220"; + return tty && tty_is_vc_resolve(tty) ? "linux" : "vt220"; } int fd_columns(int fd) { diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 24e681bf85..0ef1f6393e 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -254,32 +254,95 @@ struct timeval *timeval_store(struct timeval *tv, usec_t u) { return tv; } -static char *format_timestamp_internal(char *buf, size_t l, usec_t t, - bool utc, bool us) { +static char *format_timestamp_internal( + char *buf, + size_t l, + usec_t t, + bool utc, + bool us) { + + /* The weekdays in non-localized (English) form. We use this instead of the localized form, so that our + * generated timestamps may be parsed with parse_timestamp(), and always read the same. */ + static const char * const weekdays[] = { + [0] = "Sun", + [1] = "Mon", + [2] = "Tue", + [3] = "Wed", + [4] = "Thu", + [5] = "Fri", + [6] = "Sat", + }; + struct tm tm; time_t sec; - int k; + size_t n; assert(buf); - assert(l > 0); + if (l < + 3 + /* week day */ + 1 + 10 + /* space and date */ + 1 + 8 + /* space and time */ + (us ? 1 + 6 : 0) + /* "." and microsecond part */ + 1 + 1 + /* space and shortest possible zone */ + 1) + return NULL; /* Not enough space even for the shortest form. */ if (t <= 0 || t == USEC_INFINITY) + return NULL; /* Timestamp is unset */ + + sec = (time_t) (t / USEC_PER_SEC); /* Round down */ + if ((usec_t) sec != (t / USEC_PER_SEC)) + return NULL; /* overflow? */ + + if (!localtime_or_gmtime_r(&sec, &tm, utc)) return NULL; - sec = (time_t) (t / USEC_PER_SEC); - localtime_or_gmtime_r(&sec, &tm, utc); + /* Start with the week day */ + assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays)); + memcpy(buf, weekdays[tm.tm_wday], 4); - if (us) - k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S", &tm); - else - k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", &tm); + /* Add the main components */ + if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0) + return NULL; /* Doesn't fit */ - if (k <= 0) - return NULL; + /* Append the microseconds part, if that's requested */ if (us) { - snprintf(buf + strlen(buf), l - strlen(buf), ".%06llu", (unsigned long long) (t % USEC_PER_SEC)); - if (strftime(buf + strlen(buf), l - strlen(buf), " %Z", &tm) <= 0) - return NULL; + n = strlen(buf); + if (n + 8 > l) + return NULL; /* Microseconds part doesn't fit. */ + + sprintf(buf + n, ".%06llu", (unsigned long long) (t % USEC_PER_SEC)); + } + + /* Append the timezone */ + n = strlen(buf); + if (utc) { + /* If this is UTC then let's explicitly use the "UTC" string here, because gmtime_r() normally uses the + * obsolete "GMT" instead. */ + if (n + 5 > l) + return NULL; /* "UTC" doesn't fit. */ + + strcpy(buf + n, " UTC"); + + } else if (!isempty(tm.tm_zone)) { + size_t tn; + + /* An explicit timezone is specified, let's use it, if it fits */ + tn = strlen(tm.tm_zone); + if (n + 1 + tn + 1 > l) { + /* The full time zone does not fit in. Yuck. */ + + if (n + 1 + _POSIX_TZNAME_MAX + 1 > l) + return NULL; /* Not even enough space for the POSIX minimum (of 6)? In that case, complain that it doesn't fit */ + + /* So the time zone doesn't fit in fully, but the caller passed enough space for the POSIX + * minimum time zone length. In this case suppress the timezone entirely, in order not to dump + * an overly long, hard to read string on the user. This should be safe, because the user will + * assume the local timezone anyway if none is shown. And so does parse_timestamp(). */ + } else { + buf[n++] = ' '; + strcpy(buf + n, tm.tm_zone); + } } return buf; @@ -539,12 +602,11 @@ int parse_timestamp(const char *t, usec_t *usec) { { "Sat", 6 }, }; - const char *k; - const char *utc; + const char *k, *utc, *tzn = NULL; struct tm tm, copy; time_t x; usec_t x_usec, plus = 0, minus = 0, ret; - int r, weekday = -1; + int r, weekday = -1, dst = -1; unsigned i; /* @@ -609,15 +671,55 @@ int parse_timestamp(const char *t, usec_t *usec) { goto finish; } + /* See if the timestamp is suffixed with UTC */ utc = endswith_no_case(t, " UTC"); if (utc) t = strndupa(t, utc - t); + else { + const char *e = NULL; + int j; + + tzset(); + + /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only + * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because + * there are no nice APIs available to cover this. By accepting the local time zone strings, we make + * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't + * support arbitrary timezone specifications. */ + + for (j = 0; j <= 1; j++) { + + if (isempty(tzname[j])) + continue; + + e = endswith_no_case(t, tzname[j]); + if (!e) + continue; + if (e == t) + continue; + if (e[-1] != ' ') + continue; + + break; + } - x = ret / USEC_PER_SEC; + if (IN_SET(j, 0, 1)) { + /* Found one of the two timezones specified. */ + t = strndupa(t, e - t - 1); + dst = j; + tzn = tzname[j]; + } + } + + x = (time_t) (ret / USEC_PER_SEC); x_usec = 0; - assert_se(localtime_or_gmtime_r(&x, &tm, utc)); - tm.tm_isdst = -1; + if (!localtime_or_gmtime_r(&x, &tm, utc)) + return -EINVAL; + + tm.tm_isdst = dst; + if (tzn) + tm.tm_zone = tzn; if (streq(t, "today")) { tm.tm_sec = tm.tm_min = tm.tm_hour = 0; @@ -634,7 +736,6 @@ int parse_timestamp(const char *t, usec_t *usec) { goto from_tm; } - for (i = 0; i < ELEMENTSOF(day_nr); i++) { size_t skip; @@ -727,7 +828,6 @@ parse_usec: return -EINVAL; x_usec = add; - } from_tm: diff --git a/src/basic/time-util.h b/src/basic/time-util.h index 1b058f0e49..99be5ce6ee 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -68,7 +68,9 @@ typedef struct triple_timestamp { #define USEC_PER_YEAR ((usec_t) (31557600ULL*USEC_PER_SEC)) #define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC)) -#define FORMAT_TIMESTAMP_MAX ((4*4+1)+11+9+4+1) /* weekdays can be unicode */ +/* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this + * to 6. Let's rely on that. */ +#define FORMAT_TIMESTAMP_MAX (3+1+10+1+8+1+6+1+6+1) #define FORMAT_TIMESTAMP_WIDTH 28 /* when outputting, assume this width */ #define FORMAT_TIMESTAMP_RELATIVE_MAX 256 #define FORMAT_TIMESPAN_MAX 64 diff --git a/src/basic/user-util.c b/src/basic/user-util.c index e9d668ddfc..122d9a0c7c 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -29,6 +29,7 @@ #include <string.h> #include <sys/stat.h> #include <unistd.h> +#include <utmp.h> #include "missing.h" #include "alloc-util.h" @@ -39,6 +40,7 @@ #include "path-util.h" #include "string-util.h" #include "user-util.h" +#include "utf8.h" bool uid_is_valid(uid_t uid) { @@ -479,3 +481,94 @@ int take_etc_passwd_lock(const char *root) { return fd; } + +bool valid_user_group_name(const char *u) { + const char *i; + long sz; + + /* Checks if the specified name is a valid user/group name. */ + + if (isempty(u)) + return false; + + if (!(u[0] >= 'a' && u[0] <= 'z') && + !(u[0] >= 'A' && u[0] <= 'Z') && + u[0] != '_') + return false; + + for (i = u+1; *i; i++) { + if (!(*i >= 'a' && *i <= 'z') && + !(*i >= 'A' && *i <= 'Z') && + !(*i >= '0' && *i <= '9') && + *i != '_' && + *i != '-') + return false; + } + + sz = sysconf(_SC_LOGIN_NAME_MAX); + assert_se(sz > 0); + + if ((size_t) (i-u) > (size_t) sz) + return false; + + if ((size_t) (i-u) > UT_NAMESIZE - 1) + return false; + + return true; +} + +bool valid_user_group_name_or_id(const char *u) { + + /* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the right + * range, and not the invalid user ids. */ + + if (isempty(u)) + return false; + + if (valid_user_group_name(u)) + return true; + + return parse_uid(u, NULL) >= 0; +} + +bool valid_gecos(const char *d) { + + if (!d) + return false; + + if (!utf8_is_valid(d)) + return false; + + if (string_has_cc(d, NULL)) + return false; + + /* Colons are used as field separators, and hence not OK */ + if (strchr(d, ':')) + return false; + + return true; +} + +bool valid_home(const char *p) { + + if (isempty(p)) + return false; + + if (!utf8_is_valid(p)) + return false; + + if (string_has_cc(p, NULL)) + return false; + + if (!path_is_absolute(p)) + return false; + + if (!path_is_safe(p)) + return false; + + /* Colons are used as field separators, and hence not OK */ + if (strchr(p, ':')) + return false; + + return true; +} diff --git a/src/basic/user-util.h b/src/basic/user-util.h index 8026eca3f4..36f71fb004 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -68,3 +68,8 @@ int take_etc_passwd_lock(const char *root); static inline bool userns_supported(void) { return access("/proc/self/uid_map", F_OK) >= 0; } + +bool valid_user_group_name(const char *u); +bool valid_user_group_name_or_id(const char *u); +bool valid_gecos(const char *d); +bool valid_home(const char *p); diff --git a/src/basic/util.h b/src/basic/util.h index 44497dcd78..bb2fc318ef 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -61,6 +61,10 @@ static inline const char* one_zero(bool b) { return b ? "1" : "0"; } +static inline const char* enable_disable(bool b) { + return b ? "enable" : "disable"; +} + void execute_directories(const char* const* directories, usec_t timeout, char *argv[]); bool plymouth_running(void); diff --git a/src/basic/virt.c b/src/basic/virt.c index dace1f4328..10a2043746 100644 --- a/src/basic/virt.c +++ b/src/basic/virt.c @@ -49,6 +49,8 @@ static int detect_vm_cpuid(void) { { "VMwareVMware", VIRTUALIZATION_VMWARE }, /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */ { "Microsoft Hv", VIRTUALIZATION_MICROSOFT }, + /* https://wiki.freebsd.org/bhyve */ + { "bhyve bhyve ", VIRTUALIZATION_BHYVE }, }; uint32_t eax, ecx; @@ -178,6 +180,8 @@ static int detect_vm_dmi(void) { { "Xen", VIRTUALIZATION_XEN }, { "Bochs", VIRTUALIZATION_BOCHS }, { "Parallels", VIRTUALIZATION_PARALLELS }, + /* https://wiki.freebsd.org/bhyve */ + { "BHYVE", VIRTUALIZATION_BHYVE }, }; unsigned i; int r; @@ -502,6 +506,7 @@ static const char *const virtualization_table[_VIRTUALIZATION_MAX] = { [VIRTUALIZATION_MICROSOFT] = "microsoft", [VIRTUALIZATION_ZVM] = "zvm", [VIRTUALIZATION_PARALLELS] = "parallels", + [VIRTUALIZATION_BHYVE] = "bhyve", [VIRTUALIZATION_VM_OTHER] = "vm-other", [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn", diff --git a/src/basic/virt.h b/src/basic/virt.h index a538f07f6b..bc5b3ae94d 100644 --- a/src/basic/virt.h +++ b/src/basic/virt.h @@ -37,6 +37,7 @@ enum { VIRTUALIZATION_MICROSOFT, VIRTUALIZATION_ZVM, VIRTUALIZATION_PARALLELS, + VIRTUALIZATION_BHYVE, VIRTUALIZATION_VM_OTHER, VIRTUALIZATION_VM_LAST = VIRTUALIZATION_VM_OTHER, diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 056a0790bd..e77ab3f850 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -26,6 +26,7 @@ #include <ftw.h> #include <getopt.h> #include <limits.h> +#include <linux/magic.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> @@ -42,22 +43,53 @@ #include "fd-util.h" #include "fileio.h" #include "locale-util.h" +#include "parse-util.h" #include "rm-rf.h" #include "string-util.h" +#include "strv.h" +#include "umask-util.h" #include "util.h" +#include "verbs.h" +#include "virt.h" +#include "stat-util.h" + +static char *arg_path = NULL; +static bool arg_touch_variables = true; + +static int verify_esp( + bool searching, + const char *p, + uint32_t *ret_part, + uint64_t *ret_pstart, + uint64_t *ret_psize, + sd_id128_t *ret_uuid) { -static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { - struct statfs sfs; - struct stat st, st2; - _cleanup_free_ char *t = NULL; _cleanup_blkid_free_probe_ blkid_probe b = NULL; - int r; + _cleanup_free_ char *t = NULL; + uint64_t pstart = 0, psize = 0; + struct stat st, st2; const char *v, *t2; + struct statfs sfs; + sd_id128_t uuid = SD_ID128_NULL; + uint32_t part = 0; + int r; + + assert(p); + + if (statfs(p, &sfs) < 0) { + + /* If we are searching for the mount point, don't generate a log message if we can't find the path */ + if (errno == ENOENT && searching) + return -ENOENT; - if (statfs(p, &sfs) < 0) return log_error_errno(errno, "Failed to check file system type of \"%s\": %m", p); + } + + if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) { + + if (searching) + return -EADDRNOTAVAIL; - if (sfs.f_type != 0x4d44) { log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p); return -ENODEV; } @@ -80,6 +112,11 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t return -ENODEV; } + /* In a container we don't have access to block devices, skip this part of the verification, we trust the + * container manager set everything up correctly on its own. */ + if (detect_container() > 0) + goto finish; + r = asprintf(&t, "/dev/block/%u:%u", major(st.st_dev), minor(st.st_dev)); if (r < 0) return log_oom(); @@ -117,7 +154,6 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe file system type \"%s\": %m", p); } - if (!streq(v, "vfat")) { log_error("File system \"%s\" is not FAT.", p); return -ENODEV; @@ -129,7 +165,6 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe partition scheme \"%s\": %m", p); } - if (!streq(v, "gpt")) { log_error("File system \"%s\" is not on a GPT partition table.", p); return -ENODEV; @@ -141,7 +176,6 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe partition type UUID \"%s\": %m", p); } - if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) { log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p); return -ENODEV; @@ -153,8 +187,7 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe partition entry UUID \"%s\": %m", p); } - - r = sd_id128_from_string(v, uuid); + r = sd_id128_from_string(v, &uuid); if (r < 0) { log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v); return -EIO; @@ -166,7 +199,9 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe partition number \"%s\": m", p); } - *part = strtoul(v, NULL, 10); + r = safe_atou32(v, &part); + if (r < 0) + return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field."); errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL); @@ -174,7 +209,9 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe partition offset \"%s\": %m", p); } - *pstart = strtoul(v, NULL, 10); + r = safe_atou64(v, &pstart); + if (r < 0) + return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field."); errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL); @@ -182,11 +219,50 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t r = errno ? -errno : -EIO; return log_error_errno(r, "Failed to probe partition size \"%s\": %m", p); } - *psize = strtoul(v, NULL, 10); + r = safe_atou64(v, &psize); + if (r < 0) + return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field."); + +finish: + if (ret_part) + *ret_part = part; + if (ret_pstart) + *ret_pstart = pstart; + if (ret_psize) + *ret_psize = psize; + if (ret_uuid) + *ret_uuid = uuid; return 0; } +static int find_esp(uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { + const char *path; + int r; + + if (arg_path) + return verify_esp(false, arg_path, part, pstart, psize, uuid); + + FOREACH_STRING(path, "/efi", "/boot", "/boot/efi") { + + r = verify_esp(true, path, part, pstart, psize, uuid); + if (IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */ + continue; + if (r < 0) + return r; + + arg_path = strdup(path); + if (!arg_path) + return log_oom(); + + log_info("Using EFI System Parition at %s.", path); + return 0; + } + + log_error("Couldn't find EFI system partition. It is recommended to mount it to /boot. Alternatively, use --path= to specify path to mount point."); + return -ENOENT; +} + /* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */ static int get_file_version(int fd, char **v) { struct stat st; @@ -199,14 +275,16 @@ static int get_file_version(int fd, char **v) { assert(v); if (fstat(fd, &st) < 0) - return -errno; + return log_error_errno(errno, "Failed to stat EFI binary: %m"); - if (st.st_size < 27) + if (st.st_size < 27) { + *v = NULL; return 0; + } buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (buf == MAP_FAILED) - return -errno; + return log_error_errno(errno, "Failed to memory map EFI binary: %m"); s = memmem(buf, st.st_size - 8, "#### LoaderInfo: ", 17); if (!s) @@ -228,7 +306,7 @@ static int get_file_version(int fd, char **v) { r = 1; finish: - munmap(buf, st.st_size); + (void) munmap(buf, st.st_size); *v = x; return r; } @@ -338,9 +416,10 @@ static int status_variables(void) { n_options = efi_get_boot_options(&options); if (n_options == -ENOENT) - return log_error_errno(ENOENT, "Failed to access EFI variables, efivarfs" + return log_error_errno(n_options, + "Failed to access EFI variables, efivarfs" " needs to be available at /sys/firmware/efi/efivars/."); - else if (n_options < 0) + if (n_options < 0) return log_error_errno(n_options, "Failed to read EFI boot entries: %m"); n_order = efi_get_boot_order(&order); @@ -360,11 +439,9 @@ static int status_variables(void) { for (j = 0; j < n_order; j++) if (options[i] == order[j]) - goto next; + continue; print_efi_option(options[i], false); - next: - continue; } return 0; @@ -523,15 +600,6 @@ error: return r; } -static char* strupper(char *s) { - char *p; - - for (p = s; *p; p++) - *p = toupper(*p); - - return s; -} - static int mkdir_one(const char *prefix, const char *suffix) { char *p; @@ -554,11 +622,11 @@ static const char *efi_subdirs[] = { }; static int create_dirs(const char *esp_path) { + const char **i; int r; - unsigned i; - for (i = 0; i < ELEMENTSOF(efi_subdirs); i++) { - r = mkdir_one(esp_path, efi_subdirs[i]); + STRV_FOREACH(i, efi_subdirs) { + r = mkdir_one(esp_path, *i); if (r < 0) return r; } @@ -580,7 +648,7 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) { /* Create the EFI default boot loader name (specified for removable devices) */ v = strjoina(esp_path, "/EFI/BOOT/BOOT", name + strlen("systemd-boot")); - strupper(strrchr(v, '/') + 1); + ascii_strupper(strrchr(v, '/') + 1); k = copy_file(p, v, force); if (k < 0 && r == 0) @@ -751,8 +819,8 @@ static int install_variables(const char *esp_path, if (access(p, F_OK) < 0) { if (errno == ENOENT) return 0; - else - return log_error_errno(errno, "Cannot access \"%s\": %m", p); + + return log_error_errno(errno, "Cannot access \"%s\": %m", p); } r = find_slot(uuid, path, &slot); @@ -762,7 +830,7 @@ static int install_variables(const char *esp_path, "Failed to access EFI variables. Is the \"efivarfs\" filesystem mounted?" : "Failed to determine current boot order: %m"); - if (first || r == false) { + if (first || r == 0) { r = efi_add_boot_option(slot, "Systemd Boot Manager", part, pstart, psize, uuid, path); @@ -872,46 +940,39 @@ static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { if (in_order) return remove_from_order(slot); - else - return 0; + + return 0; } static int install_loader_config(const char *esp_path) { - char *p; - char line[64]; - char *machine = NULL; - _cleanup_fclose_ FILE *f = NULL, *g = NULL; - f = fopen("/etc/machine-id", "re"); - if (!f) - return errno == ENOENT ? 0 : -errno; + _cleanup_fclose_ FILE *f = NULL; + char machine_string[SD_ID128_STRING_MAX]; + sd_id128_t machine_id; + const char *p; + int r; - if (fgets(line, sizeof(line), f) != NULL) { - char *s; + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return log_error_errno(r, "Failed to get machine did: %m"); - s = strchr(line, '\n'); - if (s) - s[0] = '\0'; - if (strlen(line) == 32) - machine = line; - } + p = strjoina(esp_path, "/loader/loader.conf"); + f = fopen(p, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to open loader.conf for writing: %m"); - if (!machine) - return -ESRCH; + fprintf(f, "#timeout 3\n"); + fprintf(f, "default %s-*\n", sd_id128_to_string(machine_id, machine_string)); - p = strjoina(esp_path, "/loader/loader.conf"); - g = fopen(p, "wxe"); - if (g) { - fprintf(g, "#timeout 3\n"); - fprintf(g, "default %s-*\n", machine); - if (ferror(g)) - return log_error_errno(EIO, "Failed to write \"%s\": %m", p); - } + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write \"%s\": %m", p); return 0; } -static int help(void) { +static int help(int argc, char *argv[], void *userdata) { + printf("%s [COMMAND] [OPTIONS...]\n" "\n" "Install, update or remove the systemd-boot EFI boot manager.\n\n" @@ -930,9 +991,6 @@ static int help(void) { return 0; } -static const char *arg_path = "/boot"; -static bool arg_touch_variables = true; - static int parse_argv(int argc, char *argv[]) { enum { ARG_PATH = 0x100, @@ -948,7 +1006,7 @@ static int parse_argv(int argc, char *argv[]) { { NULL, 0, NULL, 0 } }; - int c; + int c, r; assert(argc >= 0); assert(argv); @@ -957,14 +1015,16 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - help(); + help(0, NULL, NULL); return 0; case ARG_VERSION: return version(); case ARG_PATH: - arg_path = optarg; + r = free_and_strdup(&arg_path, optarg); + if (r < 0) + return log_oom(); break; case ARG_NO_VARIABLES: @@ -989,149 +1049,170 @@ static void read_loader_efi_var(const char *name, char **var) { log_warning_errno(r, "Failed to read EFI variable %s: %m", name); } -static int bootctl_main(int argc, char*argv[]) { - enum action { - ACTION_STATUS, - ACTION_INSTALL, - ACTION_UPDATE, - ACTION_REMOVE - } arg_action = ACTION_STATUS; - static const struct { - const char* verb; - enum action action; - } verbs[] = { - { "status", ACTION_STATUS }, - { "install", ACTION_INSTALL }, - { "update", ACTION_UPDATE }, - { "remove", ACTION_REMOVE }, - }; +static int must_be_root(void) { - sd_id128_t uuid = {}; - uint32_t part = 0; - uint64_t pstart = 0, psize = 0; - int r, q; + if (geteuid() == 0) + return 0; - if (argv[optind]) { - unsigned i; + log_error("Need to be root."); + return -EPERM; +} - for (i = 0; i < ELEMENTSOF(verbs); i++) { - if (!streq(argv[optind], verbs[i].verb)) - continue; - arg_action = verbs[i].action; - break; - } - if (i >= ELEMENTSOF(verbs)) { - log_error("Unknown operation \"%s\"", argv[optind]); - return -EINVAL; - } - } +static int verb_status(int argc, char *argv[], void *userdata) { - if (geteuid() != 0) - return log_error_errno(EPERM, "Need to be root."); + sd_id128_t uuid = SD_ID128_NULL; + int r; - r = verify_esp(arg_path, &part, &pstart, &psize, &uuid); - if (r == -ENODEV && !arg_path) - log_notice("You might want to use --path= to indicate the path to your ESP, in case it is not mounted on /boot."); + r = must_be_root(); if (r < 0) return r; - switch (arg_action) { - case ACTION_STATUS: { - _cleanup_free_ char *fw_type = NULL; - _cleanup_free_ char *fw_info = NULL; - _cleanup_free_ char *loader = NULL; - _cleanup_free_ char *loader_path = NULL; - sd_id128_t loader_part_uuid = {}; - - if (is_efi_boot()) { - read_loader_efi_var("LoaderFirmwareType", &fw_type); - read_loader_efi_var("LoaderFirmwareInfo", &fw_info); - read_loader_efi_var("LoaderInfo", &loader); - read_loader_efi_var("LoaderImageIdentifier", &loader_path); - if (loader_path) - efi_tilt_backslashes(loader_path); - r = efi_loader_get_device_part_uuid(&loader_part_uuid); - if (r < 0 && r == -ENOENT) - log_warning_errno(r, "Failed to read EFI variable LoaderDevicePartUUID: %m"); - - printf("System:\n"); - printf(" Firmware: %s (%s)\n", strna(fw_type), strna(fw_info)); - - r = is_efi_secure_boot(); - if (r < 0) - log_warning_errno(r, "Failed to query secure boot status: %m"); - else - printf(" Secure Boot: %s\n", r ? "enabled" : "disabled"); + r = find_esp(NULL, NULL, NULL, &uuid); + if (r < 0) + return r; - r = is_efi_secure_boot_setup_mode(); - if (r < 0) - log_warning_errno(r, "Failed to query secure boot mode: %m"); - else - printf(" Setup Mode: %s\n", r ? "setup" : "user"); - printf("\n"); - - printf("Loader:\n"); - printf(" Product: %s\n", strna(loader)); - if (!sd_id128_is_null(loader_part_uuid)) - printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", - SD_ID128_FORMAT_VAL(loader_part_uuid)); - else - printf(" Partition: n/a\n"); - printf(" File: %s%s\n", special_glyph(TREE_RIGHT), strna(loader_path)); - printf("\n"); - } else - printf("System:\n Not booted with EFI\n"); - - r = status_binaries(arg_path, uuid); + if (is_efi_boot()) { + _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL; + sd_id128_t loader_part_uuid = SD_ID128_NULL; + + read_loader_efi_var("LoaderFirmwareType", &fw_type); + read_loader_efi_var("LoaderFirmwareInfo", &fw_info); + read_loader_efi_var("LoaderInfo", &loader); + read_loader_efi_var("LoaderImageIdentifier", &loader_path); + + if (loader_path) + efi_tilt_backslashes(loader_path); + + r = efi_loader_get_device_part_uuid(&loader_part_uuid); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read EFI variable LoaderDevicePartUUID: %m"); + + printf("System:\n"); + printf(" Firmware: %s (%s)\n", strna(fw_type), strna(fw_info)); + + r = is_efi_secure_boot(); if (r < 0) - return r; + log_warning_errno(r, "Failed to query secure boot status: %m"); + else + printf(" Secure Boot: %sd\n", enable_disable(r)); - if (arg_touch_variables) - r = status_variables(); - break; - } + r = is_efi_secure_boot_setup_mode(); + if (r < 0) + log_warning_errno(r, "Failed to query secure boot mode: %m"); + else + printf(" Setup Mode: %s\n", r ? "setup" : "user"); + printf("\n"); + + printf("Loader:\n"); + printf(" Product: %s\n", strna(loader)); + if (!sd_id128_is_null(loader_part_uuid)) + printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", + SD_ID128_FORMAT_VAL(loader_part_uuid)); + else + printf(" Partition: n/a\n"); + printf(" File: %s%s\n", special_glyph(TREE_RIGHT), strna(loader_path)); + printf("\n"); + } else + printf("System:\n Not booted with EFI\n"); - case ACTION_INSTALL: - case ACTION_UPDATE: - umask(0002); + r = status_binaries(arg_path, uuid); + if (r < 0) + return r; + + if (arg_touch_variables) + r = status_variables(); - r = install_binaries(arg_path, arg_action == ACTION_INSTALL); + return r; +} + +static int verb_install(int argc, char *argv[], void *userdata) { + + sd_id128_t uuid = SD_ID128_NULL; + uint64_t pstart = 0, psize = 0; + uint32_t part = 0; + bool install; + int r; + + r = must_be_root(); + if (r < 0) + return r; + + r = find_esp(&part, &pstart, &psize, &uuid); + if (r < 0) + return r; + + install = streq(argv[0], "install"); + + RUN_WITH_UMASK(0002) { + r = install_binaries(arg_path, install); if (r < 0) return r; - if (arg_action == ACTION_INSTALL) { + if (install) { r = install_loader_config(arg_path); if (r < 0) return r; } + } - if (arg_touch_variables) - r = install_variables(arg_path, - part, pstart, psize, uuid, - "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", - arg_action == ACTION_INSTALL); - break; + if (arg_touch_variables) + r = install_variables(arg_path, + part, pstart, psize, uuid, + "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", + install); - case ACTION_REMOVE: - r = remove_binaries(arg_path); + return r; +} - if (arg_touch_variables) { - q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); - if (q < 0 && r == 0) - r = q; - } - break; +static int verb_remove(int argc, char *argv[], void *userdata) { + sd_id128_t uuid = SD_ID128_NULL; + int r; + + r = must_be_root(); + if (r < 0) + return r; + + r = find_esp(NULL, NULL, NULL, &uuid); + if (r < 0) + return r; + + r = remove_binaries(arg_path); + + if (arg_touch_variables) { + int q; + + q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); + if (q < 0 && r == 0) + r = q; } return r; } +static int bootctl_main(int argc, char *argv[]) { + + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, + { "install", VERB_ANY, 1, 0, verb_install }, + { "update", VERB_ANY, 1, 0, verb_install }, + { "remove", VERB_ANY, 1, 0, verb_remove }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + int main(int argc, char *argv[]) { int r; log_parse_environment(); log_open(); + /* If we run in a container, automatically turn of EFI file system access */ + if (detect_container() > 0) + arg_touch_variables = false; + r = parse_argv(argc, argv); if (r <= 0) goto finish; @@ -1139,5 +1220,6 @@ int main(int argc, char *argv[]) { r = bootctl_main(argc, argv); finish: + free(arg_path); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/core/automount.c b/src/core/automount.c index 4e9891569c..00295cf769 100644 --- a/src/core/automount.c +++ b/src/core/automount.c @@ -301,7 +301,7 @@ static void automount_dump(Unit *u, FILE *f, const char *prefix) { static void automount_enter_dead(Automount *a, AutomountResult f) { assert(a); - if (f != AUTOMOUNT_SUCCESS) + if (a->result == AUTOMOUNT_SUCCESS) a->result = f; automount_set_state(a, a->result != AUTOMOUNT_SUCCESS ? AUTOMOUNT_FAILED : AUTOMOUNT_DEAD); @@ -1105,6 +1105,9 @@ const UnitVTable automount_vtable = { .reset_failed = automount_reset_failed, .bus_vtable = bus_automount_vtable, + .bus_set_property = bus_automount_set_property, + + .can_transient = true, .shutdown = automount_shutdown, .supported = automount_supported, diff --git a/src/core/busname.c b/src/core/busname.c index 730be2ee14..7952cd31aa 100644 --- a/src/core/busname.c +++ b/src/core/busname.c @@ -442,7 +442,7 @@ fail: static void busname_enter_dead(BusName *n, BusNameResult f) { assert(n); - if (f != BUSNAME_SUCCESS) + if (n->result == BUSNAME_SUCCESS) n->result = f; busname_set_state(n, n->result != BUSNAME_SUCCESS ? BUSNAME_FAILED : BUSNAME_DEAD); @@ -454,7 +454,7 @@ static void busname_enter_signal(BusName *n, BusNameState state, BusNameResult f assert(n); - if (f != BUSNAME_SUCCESS) + if (n->result == BUSNAME_SUCCESS) n->result = f; kill_context_init(&kill_context); @@ -882,7 +882,7 @@ static void busname_sigchld_event(Unit *u, pid_t pid, int code, int status) { log_unit_full(u, f == BUSNAME_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, "Control process exited, code=%s status=%i", sigchld_code_to_string(code), status); - if (f != BUSNAME_SUCCESS) + if (n->result == BUSNAME_SUCCESS) n->result = f; switch (n->state) { diff --git a/src/core/dbus-automount.c b/src/core/dbus-automount.c index b2806ad86f..26212b3a95 100644 --- a/src/core/dbus-automount.c +++ b/src/core/dbus-automount.c @@ -32,3 +32,57 @@ const sd_bus_vtable bus_automount_vtable[] = { SD_BUS_PROPERTY("TimeoutIdleUSec", "t", bus_property_get_usec, offsetof(Automount, timeout_idle_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_VTABLE_END }; + +static int bus_automount_set_transient_property( + Automount *a, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(a); + assert(name); + assert(message); + + if (streq(name, "TimeoutIdleUSec")) { + usec_t timeout_idle_usec; + r = sd_bus_message_read(message, "t", &timeout_idle_usec); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + char time[FORMAT_TIMESPAN_MAX]; + + a->timeout_idle_usec = timeout_idle_usec; + unit_write_drop_in_format(UNIT(a), mode, name, "[Automount]\nTimeoutIdleSec=%s\n", + format_timespan(time, sizeof(time), timeout_idle_usec, USEC_PER_MSEC)); + } + } else + return 0; + + return 1; +} + +int bus_automount_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Automount *a = AUTOMOUNT(u); + int r = 0; + + assert(a); + assert(name); + assert(message); + + if (u->transient && u->load_state == UNIT_STUB) + /* This is a transient unit, let's load a little more */ + + r = bus_automount_set_transient_property(a, name, message, mode, error); + + return r; +} diff --git a/src/core/dbus-automount.h b/src/core/dbus-automount.h index 7b51eb973a..f41adda2a6 100644 --- a/src/core/dbus-automount.h +++ b/src/core/dbus-automount.h @@ -21,3 +21,5 @@ extern const sd_bus_vtable bus_automount_vtable[]; + +int bus_automount_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 307c3d8e7a..e35d3ccd2e 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -44,6 +44,7 @@ #endif #include "strv.h" #include "syslog-util.h" +#include "user-util.h" #include "utf8.h" BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput); @@ -693,6 +694,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ReadWriteDirectories", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), @@ -703,8 +705,9 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("InaccessiblePaths", "as", NULL, offsetof(ExecContext, inaccessible_paths), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MountFlags", "t", bus_property_get_ulong, offsetof(ExecContext, mount_flags), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PrivateTmp", "b", bus_property_get_bool, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PrivateNetwork", "b", bus_property_get_bool, offsetof(ExecContext, private_network), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PrivateDevices", "b", bus_property_get_bool, offsetof(ExecContext, private_devices), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PrivateNetwork", "b", bus_property_get_bool, offsetof(ExecContext, private_network), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PrivateUsers", "b", bus_property_get_bool, offsetof(ExecContext, private_users), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ProtectHome", "s", bus_property_get_protect_home, offsetof(ExecContext, protect_home), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ProtectSystem", "s", bus_property_get_protect_system, offsetof(ExecContext, protect_system), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SameProcessGroup", "b", bus_property_get_bool, offsetof(ExecContext, same_pgrp), SD_BUS_VTABLE_PROPERTY_CONST), @@ -840,6 +843,9 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; + if (!isempty(uu) && !valid_user_group_name_or_id(uu)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user name: %s", uu); + if (mode != UNIT_CHECK) { if (isempty(uu)) @@ -859,6 +865,9 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; + if (!isempty(gg) && !valid_user_group_name_or_id(gg)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group name: %s", gg); + if (mode != UNIT_CHECK) { if (isempty(gg)) @@ -927,7 +936,7 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; - if (n < PRIO_MIN || n >= PRIO_MAX) + if (!nice_is_valid(n)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Nice value out of range"); if (mode != UNIT_CHECK) { @@ -1060,8 +1069,9 @@ int bus_exec_context_set_transient_property( } else if (STR_IN_SET(name, "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", - "PrivateTmp", "PrivateDevices", "PrivateNetwork", - "NoNewPrivileges", "SyslogLevelPrefix", "MemoryDenyWriteExecute", "RestrictRealtime")) { + "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", + "NoNewPrivileges", "SyslogLevelPrefix", "MemoryDenyWriteExecute", + "RestrictRealtime", "DynamicUser")) { int b; r = sd_bus_message_read(message, "b", &b); @@ -1081,6 +1091,8 @@ int bus_exec_context_set_transient_property( c->private_devices = b; else if (streq(name, "PrivateNetwork")) c->private_network = b; + else if (streq(name, "PrivateUsers")) + c->private_users = b; else if (streq(name, "NoNewPrivileges")) c->no_new_privileges = b; else if (streq(name, "SyslogLevelPrefix")) @@ -1089,6 +1101,8 @@ int bus_exec_context_set_transient_property( c->memory_deny_write_execute = b; else if (streq(name, "RestrictRealtime")) c->restrict_realtime = b; + else if (streq(name, "DynamicUser")) + c->dynamic_user = b; unit_write_drop_in_private_format(u, mode, name, "%s=%s", name, yes_no(b)); } diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index d05968bd65..ef05a75a8b 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -43,6 +43,7 @@ #include "string-util.h" #include "strv.h" #include "syslog-util.h" +#include "user-util.h" #include "virt.h" #include "watchdog.h" @@ -1511,8 +1512,8 @@ static int method_unset_and_set_environment(sd_bus_message *message, void *userd } static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_error *error) { - uint8_t code; Manager *m = userdata; + uint8_t code; int r; assert(message); @@ -1534,6 +1535,61 @@ static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_ return sd_bus_reply_method_return(message, NULL); } +static int method_lookup_dynamic_user_by_name(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + uid_t uid; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read_basic(message, 's', &name); + if (r < 0) + return r; + + if (!MANAGER_IS_SYSTEM(m)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance."); + if (!valid_user_group_name(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name invalid: %s", name); + + r = dynamic_user_lookup_name(m, name, &uid); + if (r == -ESRCH) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DYNAMIC_USER, "Dynamic user %s does not exist.", name); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, "u", (uint32_t) uid); +} + +static int method_lookup_dynamic_user_by_uid(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *name = NULL; + Manager *m = userdata; + uid_t uid; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(uid) == sizeof(uint32_t)); + r = sd_bus_message_read_basic(message, 'u', &uid); + if (r < 0) + return r; + + if (!MANAGER_IS_SYSTEM(m)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance."); + if (!uid_is_valid(uid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User ID invalid: " UID_FMT, uid); + + r = dynamic_user_lookup_uid(m, uid, &name); + if (r == -ESRCH) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DYNAMIC_USER, "Dynamic user ID " UID_FMT " does not exist.", uid); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, "s", name); +} + static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; Manager *m = userdata; @@ -2199,6 +2255,8 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_METHOD("PresetAllUnitFiles", "sbb", "a(sss)", method_preset_all_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("AddDependencyUnitFiles", "asssbb", "a(sss)", method_add_dependency_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetExitCode", "y", NULL, method_set_exit_code, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("LookupDynamicUserByName", "s", "u", method_lookup_dynamic_user_by_name, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("LookupDynamicUserByUID", "u", "s", method_lookup_dynamic_user_by_uid, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL("UnitNew", "so", 0), SD_BUS_SIGNAL("UnitRemoved", "so", 0), diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c index 935db7c48b..b4bbee0648 100644 --- a/src/core/dbus-mount.c +++ b/src/core/dbus-mount.c @@ -157,6 +157,9 @@ static int bus_mount_set_transient_property( if (!p) return -ENOMEM; + unit_write_drop_in_format(UNIT(m), mode, name, "[Mount]\n%s=%s\n", + name, new_property); + free(*property); *property = p; } diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c index 961340608d..9a071a1355 100644 --- a/src/core/dbus-socket.c +++ b/src/core/dbus-socket.c @@ -137,6 +137,7 @@ const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_PROPERTY("Symlinks", "as", NULL, offsetof(Socket, symlinks), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Mark", "i", bus_property_get_int, offsetof(Socket, mark), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MaxConnections", "u", bus_property_get_unsigned, offsetof(Socket, max_connections), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MaxConnectionsPerSource", "u", bus_property_get_unsigned, offsetof(Socket, max_connections_per_source), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MessageQueueMaxMessages", "x", bus_property_get_long, offsetof(Socket, mq_maxmsg), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MessageQueueMessageSize", "x", bus_property_get_long, offsetof(Socket, mq_msgsize), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ReusePort", "b", bus_property_get_bool, offsetof(Socket, reuse_port), SD_BUS_VTABLE_PROPERTY_CONST), diff --git a/src/core/dynamic-user.c b/src/core/dynamic-user.c new file mode 100644 index 0000000000..8035bee231 --- /dev/null +++ b/src/core/dynamic-user.c @@ -0,0 +1,763 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <grp.h> +#include <pwd.h> +#include <sys/file.h> + +#include "dynamic-user.h" +#include "fd-util.h" +#include "fs-util.h" +#include "parse-util.h" +#include "random-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "user-util.h" +#include "fileio.h" + +/* Let's pick a UIDs within the 16bit range, so that we are compatible with containers using 16bit user namespacing. At + * least on Fedora normal users are allocated until UID 60000, hence do not allocate from below this. Also stay away + * from the upper end of the range as that is often used for overflow/nobody users. */ +#define UID_PICK_MIN ((uid_t) UINT32_C(0x0000EF00)) +#define UID_PICK_MAX ((uid_t) UINT32_C(0x0000FFEF)) + +/* Takes a value generated randomly or by hashing and turns it into a UID in the right range */ +#define UID_CLAMP_INTO_RANGE(rnd) (((uid_t) (rnd) % (UID_PICK_MAX - UID_PICK_MIN + 1)) + UID_PICK_MIN) + +static DynamicUser* dynamic_user_free(DynamicUser *d) { + if (!d) + return NULL; + + if (d->manager) + (void) hashmap_remove(d->manager->dynamic_users, d->name); + + safe_close_pair(d->storage_socket); + free(d); + + return NULL; +} + +static int dynamic_user_add(Manager *m, const char *name, int storage_socket[2], DynamicUser **ret) { + DynamicUser *d = NULL; + int r; + + assert(m); + assert(name); + assert(storage_socket); + + r = hashmap_ensure_allocated(&m->dynamic_users, &string_hash_ops); + if (r < 0) + return r; + + d = malloc0(offsetof(DynamicUser, name) + strlen(name) + 1); + if (!d) + return -ENOMEM; + + strcpy(d->name, name); + + d->storage_socket[0] = storage_socket[0]; + d->storage_socket[1] = storage_socket[1]; + + r = hashmap_put(m->dynamic_users, d->name, d); + if (r < 0) { + free(d); + return r; + } + + d->manager = m; + + if (ret) + *ret = d; + + return 0; +} + +int dynamic_user_acquire(Manager *m, const char *name, DynamicUser** ret) { + _cleanup_close_pair_ int storage_socket[2] = { -1, -1 }; + DynamicUser *d; + int r; + + assert(m); + assert(name); + + /* Return the DynamicUser structure for a specific user name. Note that this won't actually allocate a UID for + * it, but just prepare the data structure for it. The UID is allocated only on demand, when it's really + * needed, and in the child process we fork off, since allocation involves NSS checks which are not OK to do + * from PID 1. To allow the children and PID 1 share information about allocated UIDs we use an anonymous + * AF_UNIX/SOCK_DGRAM socket (called the "storage socket") that contains at most one datagram with the + * allocated UID number, plus an fd referencing the lock file for the UID + * (i.e. /run/systemd/dynamic-uid/$UID). Why involve the socket pair? So that PID 1 and all its children can + * share the same storage for the UID and lock fd, simply by inheriting the storage socket fds. The socket pair + * may exist in three different states: + * + * a) no datagram stored. This is the initial state. In this case the dynamic user was never realized. + * + * b) a datagram containing a UID stored, but no lock fd attached to it. In this case there was already a + * statically assigned UID by the same name, which we are reusing. + * + * c) a datagram containing a UID stored, and a lock fd is attached to it. In this case we allocated a dynamic + * UID and locked it in the file system, using the lock fd. + * + * As PID 1 and various children might access the socket pair simultaneously, and pop the datagram or push it + * back in any time, we also maintain a lock on the socket pair. Note one peculiarity regarding locking here: + * the UID lock on disk is protected via a BSD file lock (i.e. an fd-bound lock), so that the lock is kept in + * place as long as there's a reference to the fd open. The lock on the storage socket pair however is a POSIX + * file lock (i.e. a process-bound lock), as all users share the same fd of this (after all it is anonymous, + * nobody else could get any access to it except via our own fd) and we want to synchronize access between all + * processes that have access to it. */ + + d = hashmap_get(m->dynamic_users, name); + if (d) { + /* We already have a structure for the dynamic user, let's increase the ref count and reuse it */ + d->n_ref++; + *ret = d; + return 0; + } + + if (!valid_user_group_name_or_id(name)) + return -EINVAL; + + if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, storage_socket) < 0) + return -errno; + + r = dynamic_user_add(m, name, storage_socket, &d); + if (r < 0) + return r; + + storage_socket[0] = storage_socket[1] = -1; + + if (ret) { + d->n_ref++; + *ret = d; + } + + return 1; +} + +static int pick_uid(const char *name, uid_t *ret_uid) { + + static const uint8_t hash_key[] = { + 0x37, 0x53, 0x7e, 0x31, 0xcf, 0xce, 0x48, 0xf5, + 0x8a, 0xbb, 0x39, 0x57, 0x8d, 0xd9, 0xec, 0x59 + }; + + unsigned n_tries = 100; + uid_t candidate; + int r; + + /* A static user by this name does not exist yet. Let's find a free ID then, and use that. We start with a UID + * generated as hash from the user name. */ + candidate = UID_CLAMP_INTO_RANGE(siphash24(name, strlen(name), hash_key)); + + (void) mkdir("/run/systemd/dynamic-uid", 0755); + + for (;;) { + char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1]; + _cleanup_close_ int lock_fd = -1; + ssize_t l; + + if (--n_tries <= 0) /* Give up retrying eventually */ + return -EBUSY; + + if (candidate < UID_PICK_MIN || candidate > UID_PICK_MAX) + goto next; + + xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, candidate); + + for (;;) { + struct stat st; + + lock_fd = open(lock_path, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600); + if (lock_fd < 0) + return -errno; + + r = flock(lock_fd, LOCK_EX|LOCK_NB); /* Try to get a BSD file lock on the UID lock file */ + if (r < 0) { + if (errno == EBUSY || errno == EAGAIN) + goto next; /* already in use */ + + return -errno; + } + + if (fstat(lock_fd, &st) < 0) + return -errno; + if (st.st_nlink > 0) + break; + + /* Oh, bummer, we got got the lock, but the file was unlinked between the time we opened it and + * got the lock. Close it, and try again. */ + lock_fd = safe_close(lock_fd); + } + + /* Some superficial check whether this UID/GID might already be taken by some static user */ + if (getpwuid(candidate) || getgrgid((gid_t) candidate)) { + (void) unlink(lock_path); + goto next; + } + + /* Let's store the user name in the lock file, so that we can use it for looking up the username for a UID */ + l = pwritev(lock_fd, + (struct iovec[2]) { + { .iov_base = (char*) name, .iov_len = strlen(name) }, + { .iov_base = (char[1]) { '\n' }, .iov_len = 1 } + }, 2, 0); + if (l < 0) { + (void) unlink(lock_path); + return -errno; + } + + (void) ftruncate(lock_fd, l); + + *ret_uid = candidate; + r = lock_fd; + lock_fd = -1; + + return r; + + next: + /* Pick another random UID, and see if that works for us. */ + random_bytes(&candidate, sizeof(candidate)); + candidate = UID_CLAMP_INTO_RANGE(candidate); + } +} + +static int dynamic_user_pop(DynamicUser *d, uid_t *ret_uid, int *ret_lock_fd) { + uid_t uid = UID_INVALID; + struct iovec iov = { + .iov_base = &uid, + .iov_len = sizeof(uid), + }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + struct cmsghdr *cmsg; + + ssize_t k; + int lock_fd = -1; + + assert(d); + assert(ret_uid); + assert(ret_lock_fd); + + /* Read the UID and lock fd that is stored in the storage AF_UNIX socket. This should be called with the lock + * on the socket taken. */ + + k = recvmsg(d->storage_socket[0], &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); + if (k < 0) + return -errno; + + cmsg = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, CMSG_LEN(sizeof(int))); + if (cmsg) + lock_fd = *(int*) CMSG_DATA(cmsg); + else + cmsg_close_all(&mh); /* just in case... */ + + *ret_uid = uid; + *ret_lock_fd = lock_fd; + + return 0; +} + +static int dynamic_user_push(DynamicUser *d, uid_t uid, int lock_fd) { + struct iovec iov = { + .iov_base = &uid, + .iov_len = sizeof(uid), + }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + ssize_t k; + + assert(d); + + /* Store the UID and lock_fd in the storage socket. This should be called with the socket pair lock taken. */ + + if (lock_fd >= 0) { + struct cmsghdr *cmsg; + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &lock_fd, sizeof(int)); + + mh.msg_controllen = CMSG_SPACE(sizeof(int)); + } else { + mh.msg_control = NULL; + mh.msg_controllen = 0; + } + + k = sendmsg(d->storage_socket[1], &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + if (k < 0) + return -errno; + + return 0; +} + +static void unlink_uid_lock(int lock_fd, uid_t uid) { + char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1]; + + if (lock_fd < 0) + return; + + xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, uid); + (void) unlink_noerrno(lock_path); +} + +int dynamic_user_realize(DynamicUser *d, uid_t *ret) { + + _cleanup_close_ int etc_passwd_lock_fd = -1, uid_lock_fd = -1; + uid_t uid = UID_INVALID; + int r; + + assert(d); + + /* Acquire a UID for the user name. This will allocate a UID for the user name if the user doesn't exist + * yet. If it already exists its existing UID/GID will be reused. */ + + if (lockf(d->storage_socket[0], F_LOCK, 0) < 0) + return -errno; + + r = dynamic_user_pop(d, &uid, &uid_lock_fd); + if (r < 0) { + int new_uid_lock_fd; + uid_t new_uid; + + if (r != -EAGAIN) + goto finish; + + /* OK, nothing stored yet, let's try to find something useful. While we are working on this release the + * lock however, so that nobody else blocks on our NSS lookups. */ + (void) lockf(d->storage_socket[0], F_ULOCK, 0); + + /* Let's see if a proper, static user or group by this name exists. Try to take the lock on + * /etc/passwd, if that fails with EROFS then /etc is read-only. In that case it's fine if we don't + * take the lock, given that users can't be added there anyway in this case. */ + etc_passwd_lock_fd = take_etc_passwd_lock(NULL); + if (etc_passwd_lock_fd < 0 && etc_passwd_lock_fd != -EROFS) + return etc_passwd_lock_fd; + + /* First, let's parse this as numeric UID */ + r = parse_uid(d->name, &uid); + if (r < 0) { + struct passwd *p; + struct group *g; + + /* OK, this is not a numeric UID. Let's see if there's a user by this name */ + p = getpwnam(d->name); + if (p) + uid = p->pw_uid; + + /* Let's see if there's a group by this name */ + g = getgrnam(d->name); + if (g) { + /* If the UID/GID of the user/group of the same don't match, refuse operation */ + if (uid != UID_INVALID && uid != (uid_t) g->gr_gid) + return -EILSEQ; + + uid = (uid_t) g->gr_gid; + } + } + + if (uid == UID_INVALID) { + /* No static UID assigned yet, excellent. Let's pick a new dynamic one, and lock it. */ + + uid_lock_fd = pick_uid(d->name, &uid); + if (uid_lock_fd < 0) + return uid_lock_fd; + } + + /* So, we found a working UID/lock combination. Let's see if we actually still need it. */ + if (lockf(d->storage_socket[0], F_LOCK, 0) < 0) { + unlink_uid_lock(uid_lock_fd, uid); + return -errno; + } + + r = dynamic_user_pop(d, &new_uid, &new_uid_lock_fd); + if (r < 0) { + if (r != -EAGAIN) { + /* OK, something bad happened, let's get rid of the bits we acquired. */ + unlink_uid_lock(uid_lock_fd, uid); + goto finish; + } + + /* Great! Nothing is stored here, still. Store our newly acquired data. */ + } else { + /* Hmm, so as it appears there's now something stored in the storage socket. Throw away what we + * acquired, and use what's stored now. */ + + unlink_uid_lock(uid_lock_fd, uid); + safe_close(uid_lock_fd); + + uid = new_uid; + uid_lock_fd = new_uid_lock_fd; + } + } + + /* If the UID/GID was already allocated dynamically, push the data we popped out back in. If it was already + * allocated statically, push the UID back too, but do not push the lock fd in. If we allocated the UID + * dynamically right here, push that in along with the lock fd for it. */ + r = dynamic_user_push(d, uid, uid_lock_fd); + if (r < 0) + goto finish; + + *ret = uid; + r = 0; + +finish: + (void) lockf(d->storage_socket[0], F_ULOCK, 0); + return r; +} + +int dynamic_user_current(DynamicUser *d, uid_t *ret) { + _cleanup_close_ int lock_fd = -1; + uid_t uid; + int r; + + assert(d); + assert(ret); + + /* Get the currently assigned UID for the user, if there's any. This simply pops the data from the storage socket, and pushes it back in right-away. */ + + if (lockf(d->storage_socket[0], F_LOCK, 0) < 0) + return -errno; + + r = dynamic_user_pop(d, &uid, &lock_fd); + if (r < 0) + goto finish; + + r = dynamic_user_push(d, uid, lock_fd); + if (r < 0) + goto finish; + + *ret = uid; + r = 0; + +finish: + (void) lockf(d->storage_socket[0], F_ULOCK, 0); + return r; +} + +DynamicUser* dynamic_user_ref(DynamicUser *d) { + if (!d) + return NULL; + + assert(d->n_ref > 0); + d->n_ref++; + + return d; +} + +DynamicUser* dynamic_user_unref(DynamicUser *d) { + if (!d) + return NULL; + + /* Note that this doesn't actually release any resources itself. If a dynamic user should be fully destroyed + * and its UID released, use dynamic_user_destroy() instead. NB: the dynamic user table may contain entries + * with no references, which is commonly the case right before a daemon reload. */ + + assert(d->n_ref > 0); + d->n_ref--; + + return NULL; +} + +static int dynamic_user_close(DynamicUser *d) { + _cleanup_close_ int lock_fd = -1; + uid_t uid; + int r; + + /* Release the user ID, by releasing the lock on it, and emptying the storage socket. After this the user is + * unrealized again, much like it was after it the DynamicUser object was first allocated. */ + + if (lockf(d->storage_socket[0], F_LOCK, 0) < 0) + return -errno; + + r = dynamic_user_pop(d, &uid, &lock_fd); + if (r == -EAGAIN) { + /* User wasn't realized yet, nothing to do. */ + r = 0; + goto finish; + } + if (r < 0) + goto finish; + + /* This dynamic user was realized and dynamically allocated. In this case, let's remove the lock file. */ + unlink_uid_lock(lock_fd, uid); + r = 1; + +finish: + (void) lockf(d->storage_socket[0], F_ULOCK, 0); + return r; +} + +DynamicUser* dynamic_user_destroy(DynamicUser *d) { + if (!d) + return NULL; + + /* Drop a reference to a DynamicUser object, and destroy the user completely if this was the last + * reference. This is called whenever a service is shut down and wants its dynamic UID gone. Note that + * dynamic_user_unref() is what is called whenever a service is simply freed, for example during a reload + * cycle, where the dynamic users should not be destroyed, but our datastructures should. */ + + dynamic_user_unref(d); + + if (d->n_ref > 0) + return NULL; + + (void) dynamic_user_close(d); + return dynamic_user_free(d); +} + +int dynamic_user_serialize(Manager *m, FILE *f, FDSet *fds) { + DynamicUser *d; + Iterator i; + + assert(m); + assert(f); + assert(fds); + + /* Dump the dynamic user database into the manager serialization, to deal with daemon reloads. */ + + HASHMAP_FOREACH(d, m->dynamic_users, i) { + int copy0, copy1; + + copy0 = fdset_put_dup(fds, d->storage_socket[0]); + if (copy0 < 0) + return copy0; + + copy1 = fdset_put_dup(fds, d->storage_socket[1]); + if (copy1 < 0) + return copy1; + + fprintf(f, "dynamic-user=%s %i %i\n", d->name, copy0, copy1); + } + + return 0; +} + +void dynamic_user_deserialize_one(Manager *m, const char *value, FDSet *fds) { + _cleanup_free_ char *name = NULL, *s0 = NULL, *s1 = NULL; + int r, fd0, fd1; + + assert(m); + assert(value); + assert(fds); + + /* Parse the serialization again, after a daemon reload */ + + r = extract_many_words(&value, NULL, 0, &name, &s0, &s1, NULL); + if (r != 3 || !isempty(value)) { + log_debug("Unable to parse dynamic user line."); + return; + } + + if (safe_atoi(s0, &fd0) < 0 || !fdset_contains(fds, fd0)) { + log_debug("Unable to process dynamic user fd specification."); + return; + } + + if (safe_atoi(s1, &fd1) < 0 || !fdset_contains(fds, fd1)) { + log_debug("Unable to process dynamic user fd specification."); + return; + } + + r = dynamic_user_add(m, name, (int[]) { fd0, fd1 }, NULL); + if (r < 0) { + log_debug_errno(r, "Failed to add dynamic user: %m"); + return; + } + + (void) fdset_remove(fds, fd0); + (void) fdset_remove(fds, fd1); +} + +void dynamic_user_vacuum(Manager *m, bool close_user) { + DynamicUser *d; + Iterator i; + + assert(m); + + /* Empty the dynamic user database, optionally cleaning up orphaned dynamic users, i.e. destroy and free users + * to which no reference exist. This is called after a daemon reload finished, in order to destroy users which + * might not be referenced anymore. */ + + HASHMAP_FOREACH(d, m->dynamic_users, i) { + if (d->n_ref > 0) + continue; + + if (close_user) { + log_debug("Removing orphaned dynamic user %s", d->name); + (void) dynamic_user_close(d); + } + + dynamic_user_free(d); + } +} + +int dynamic_user_lookup_uid(Manager *m, uid_t uid, char **ret) { + char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1]; + _cleanup_free_ char *user = NULL; + uid_t check_uid; + int r; + + assert(m); + assert(ret); + + /* A friendly way to translate a dynamic user's UID into a his name. */ + + if (uid < UID_PICK_MIN) + return -ESRCH; + if (uid > UID_PICK_MAX) + return -ESRCH; + + xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, uid); + r = read_one_line_file(lock_path, &user); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; + + /* The lock file might be stale, hence let's verify the data before we return it */ + r = dynamic_user_lookup_name(m, user, &check_uid); + if (r < 0) + return r; + if (check_uid != uid) /* lock file doesn't match our own idea */ + return -ESRCH; + + *ret = user; + user = NULL; + + return 0; +} + +int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret) { + DynamicUser *d; + int r; + + assert(m); + assert(name); + assert(ret); + + /* A friendly call for translating a dynamic user's name into its UID */ + + d = hashmap_get(m->dynamic_users, name); + if (!d) + return -ESRCH; + + r = dynamic_user_current(d, ret); + if (r == -EAGAIN) /* not realized yet? */ + return -ESRCH; + + return r; +} + +int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group) { + bool acquired = false; + int r; + + assert(creds); + assert(m); + + /* A DynamicUser object encapsulates an allocation of both a UID and a GID for a specific name. However, some + * services use different user and groups. For cases like that there's DynamicCreds containing a pair of user + * and group. This call allocates a pair. */ + + if (!creds->user && user) { + r = dynamic_user_acquire(m, user, &creds->user); + if (r < 0) + return r; + + acquired = true; + } + + if (!creds->group) { + + if (creds->user && (!group || streq_ptr(user, group))) + creds->group = dynamic_user_ref(creds->user); + else { + r = dynamic_user_acquire(m, group, &creds->group); + if (r < 0) { + if (acquired) + creds->user = dynamic_user_unref(creds->user); + return r; + } + } + } + + return 0; +} + +int dynamic_creds_realize(DynamicCreds *creds, uid_t *uid, gid_t *gid) { + uid_t u = UID_INVALID; + gid_t g = GID_INVALID; + int r; + + assert(creds); + assert(uid); + assert(gid); + + /* Realize both the referenced user and group */ + + if (creds->user) { + r = dynamic_user_realize(creds->user, &u); + if (r < 0) + return r; + } + + if (creds->group && creds->group != creds->user) { + r = dynamic_user_realize(creds->group, &g); + if (r < 0) + return r; + } else + g = u; + + *uid = u; + *gid = g; + + return 0; +} + +void dynamic_creds_unref(DynamicCreds *creds) { + assert(creds); + + creds->user = dynamic_user_unref(creds->user); + creds->group = dynamic_user_unref(creds->group); +} + +void dynamic_creds_destroy(DynamicCreds *creds) { + assert(creds); + + creds->user = dynamic_user_destroy(creds->user); + creds->group = dynamic_user_destroy(creds->group); +} diff --git a/src/core/dynamic-user.h b/src/core/dynamic-user.h new file mode 100644 index 0000000000..0b8bce1a72 --- /dev/null +++ b/src/core/dynamic-user.h @@ -0,0 +1,66 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct DynamicUser DynamicUser; + +typedef struct DynamicCreds { + /* A combination of a dynamic user and group */ + DynamicUser *user; + DynamicUser *group; +} DynamicCreds; + +#include "manager.h" + +/* Note that this object always allocates a pair of user and group under the same name, even if one of them isn't + * used. This means, if you want to allocate a group and user pair, and they might have two different names, then you + * need to allocated two of these objects. DynamicCreds below makes that easy. */ +struct DynamicUser { + int n_ref; + Manager *manager; + + /* An AF_UNIX socket pair that contains a datagram containing both the numeric ID assigned, as well as a lock + * file fd locking the user ID we picked. */ + int storage_socket[2]; + + char name[]; +}; + +int dynamic_user_acquire(Manager *m, const char *name, DynamicUser **ret); + +int dynamic_user_realize(DynamicUser *d, uid_t *ret); +int dynamic_user_current(DynamicUser *d, uid_t *ret); + +DynamicUser* dynamic_user_ref(DynamicUser *d); +DynamicUser* dynamic_user_unref(DynamicUser *d); +DynamicUser* dynamic_user_destroy(DynamicUser *d); + +int dynamic_user_serialize(Manager *m, FILE *f, FDSet *fds); +void dynamic_user_deserialize_one(Manager *m, const char *value, FDSet *fds); +void dynamic_user_vacuum(Manager *m, bool close_user); + +int dynamic_user_lookup_uid(Manager *m, uid_t uid, char **ret); +int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret); + +int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group); +int dynamic_creds_realize(DynamicCreds *creds, uid_t *uid, gid_t *gid); + +void dynamic_creds_unref(DynamicCreds *creds); +void dynamic_creds_destroy(DynamicCreds *creds); diff --git a/src/core/execute.c b/src/core/execute.c index 7c178b97c3..6019df7ea6 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -25,6 +25,7 @@ #include <signal.h> #include <string.h> #include <sys/capability.h> +#include <sys/eventfd.h> #include <sys/mman.h> #include <sys/personality.h> #include <sys/prctl.h> @@ -219,12 +220,36 @@ static void exec_context_tty_reset(const ExecContext *context, const ExecParamet (void) vt_disallocate(path); } +static bool is_terminal_input(ExecInput i) { + return IN_SET(i, + EXEC_INPUT_TTY, + EXEC_INPUT_TTY_FORCE, + EXEC_INPUT_TTY_FAIL); +} + static bool is_terminal_output(ExecOutput o) { - return - o == EXEC_OUTPUT_TTY || - o == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || - o == EXEC_OUTPUT_KMSG_AND_CONSOLE || - o == EXEC_OUTPUT_JOURNAL_AND_CONSOLE; + return IN_SET(o, + EXEC_OUTPUT_TTY, + EXEC_OUTPUT_SYSLOG_AND_CONSOLE, + EXEC_OUTPUT_KMSG_AND_CONSOLE, + EXEC_OUTPUT_JOURNAL_AND_CONSOLE); +} + +static bool exec_context_needs_term(const ExecContext *c) { + assert(c); + + /* Return true if the execution context suggests we should set $TERM to something useful. */ + + if (is_terminal_input(c->std_input)) + return true; + + if (is_terminal_output(c->std_output)) + return true; + + if (is_terminal_output(c->std_error)) + return true; + + return !!c->tty_path; } static int open_null_as(int flags, int nfd) { @@ -363,13 +388,6 @@ static int open_terminal_as(const char *path, mode_t mode, int nfd) { return r; } -static bool is_terminal_input(ExecInput i) { - return - i == EXEC_INPUT_TTY || - i == EXEC_INPUT_TTY_FORCE || - i == EXEC_INPUT_TTY_FAIL; -} - static int fixup_input(ExecInput std_input, int socket_fd, bool apply_tty_stdin) { if (is_terminal_input(std_input) && !apply_tty_stdin) @@ -410,7 +428,7 @@ static int setup_input( return STDIN_FILENO; } - i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin); + i = fixup_input(context->std_input, socket_fd, params->flags & EXEC_APPLY_TTY_STDIN); switch (i) { @@ -485,7 +503,7 @@ static int setup_output( return STDERR_FILENO; } - i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin); + i = fixup_input(context->std_input, socket_fd, params->flags & EXEC_APPLY_TTY_STDIN); o = fixup_output(context->std_output, socket_fd); if (fileno == STDERR_FILENO) { @@ -1408,7 +1426,7 @@ static int build_environment( our_env[n_env++] = x; } - if (p->watchdog_usec > 0) { + if ((p->flags & EXEC_SET_WATCHDOG) && p->watchdog_usec > 0) { if (asprintf(&x, "WATCHDOG_PID="PID_FMT, getpid()) < 0) return -ENOMEM; our_env[n_env++] = x; @@ -1444,12 +1462,21 @@ static int build_environment( our_env[n_env++] = x; } - if (is_terminal_input(c->std_input) || - c->std_output == EXEC_OUTPUT_TTY || - c->std_error == EXEC_OUTPUT_TTY || - c->tty_path) { + if (exec_context_needs_term(c)) { + const char *tty_path, *term = NULL; + + tty_path = exec_context_tty_path(c); + + /* If we are forked off PID 1 and we are supposed to operate on /dev/console, then let's try to inherit + * the $TERM set for PID 1. This is useful for containers so that the $TERM the container manager + * passes to PID 1 ends up all the way in the console login shown. */ + + if (path_equal(tty_path, "/dev/console") && getppid() == 1) + term = getenv("TERM"); + if (!term) + term = default_term_for_tty(tty_path); - x = strdup(default_term_for_tty(exec_context_tty_path(c))); + x = strappend("TERM=", term); if (!x) return -ENOMEM; our_env[n_env++] = x; @@ -1526,14 +1553,181 @@ static bool exec_needs_mount_namespace( return false; } +static int setup_private_users(uid_t uid, gid_t gid) { + _cleanup_free_ char *uid_map = NULL, *gid_map = NULL; + _cleanup_close_pair_ int errno_pipe[2] = { -1, -1 }; + _cleanup_close_ int unshare_ready_fd = -1; + _cleanup_(sigkill_waitp) pid_t pid = 0; + uint64_t c = 1; + siginfo_t si; + ssize_t n; + int r; + + /* Set up a user namespace and map root to root, the selected UID/GID to itself, and everything else to + * nobody. In order to be able to write this mapping we need CAP_SETUID in the original user namespace, which + * we however lack after opening the user namespace. To work around this we fork() a temporary child process, + * which waits for the parent to create the new user namespace while staying in the original namespace. The + * child then writes the UID mapping, under full privileges. The parent waits for the child to finish and + * continues execution normally. */ + + if (uid != 0 && uid_is_valid(uid)) + asprintf(&uid_map, + "0 0 1\n" /* Map root → root */ + UID_FMT " " UID_FMT " 1\n", /* Map $UID → $UID */ + uid, uid); /* The case where the above is the same */ + else + uid_map = strdup("0 0 1\n"); + if (!uid_map) + return -ENOMEM; + + if (gid != 0 && gid_is_valid(gid)) + asprintf(&gid_map, + "0 0 1\n" /* Map root → root */ + GID_FMT " " GID_FMT " 1\n", /* Map $GID → $GID */ + gid, gid); + else + gid_map = strdup("0 0 1\n"); /* The case where the above is the same */ + if (!gid_map) + return -ENOMEM; + + /* Create a communication channel so that the parent can tell the child when it finished creating the user + * namespace. */ + unshare_ready_fd = eventfd(0, EFD_CLOEXEC); + if (unshare_ready_fd < 0) + return -errno; + + /* Create a communication channel so that the child can tell the parent a proper error code in case it + * failed. */ + if (pipe2(errno_pipe, O_CLOEXEC) < 0) + return -errno; + + pid = fork(); + if (pid < 0) + return -errno; + + if (pid == 0) { + _cleanup_close_ int fd = -1; + const char *a; + pid_t ppid; + + /* Child process, running in the original user namespace. Let's update the parent's UID/GID map from + * here, after the parent opened its own user namespace. */ + + ppid = getppid(); + errno_pipe[0] = safe_close(errno_pipe[0]); + + /* Wait until the parent unshared the user namespace */ + if (read(unshare_ready_fd, &c, sizeof(c)) < 0) { + r = -errno; + goto child_fail; + } + + /* Disable the setgroups() system call in the child user namespace, for good. */ + a = procfs_file_alloca(ppid, "setgroups"); + fd = open(a, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + if (errno != ENOENT) { + r = -errno; + goto child_fail; + } + + /* If the file is missing the kernel is too old, let's continue anyway. */ + } else { + if (write(fd, "deny\n", 5) < 0) { + r = -errno; + goto child_fail; + } + + fd = safe_close(fd); + } + + /* First write the GID map */ + a = procfs_file_alloca(ppid, "gid_map"); + fd = open(a, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + r = -errno; + goto child_fail; + } + if (write(fd, gid_map, strlen(gid_map)) < 0) { + r = -errno; + goto child_fail; + } + fd = safe_close(fd); + + /* The write the UID map */ + a = procfs_file_alloca(ppid, "uid_map"); + fd = open(a, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + r = -errno; + goto child_fail; + } + if (write(fd, uid_map, strlen(uid_map)) < 0) { + r = -errno; + goto child_fail; + } + + _exit(EXIT_SUCCESS); + + child_fail: + (void) write(errno_pipe[1], &r, sizeof(r)); + _exit(EXIT_FAILURE); + } + + errno_pipe[1] = safe_close(errno_pipe[1]); + + if (unshare(CLONE_NEWUSER) < 0) + return -errno; + + /* Let the child know that the namespace is ready now */ + if (write(unshare_ready_fd, &c, sizeof(c)) < 0) + return -errno; + + /* Try to read an error code from the child */ + n = read(errno_pipe[0], &r, sizeof(r)); + if (n < 0) + return -errno; + if (n == sizeof(r)) { /* an error code was sent to us */ + if (r < 0) + return r; + return -EIO; + } + if (n != 0) /* on success we should have read 0 bytes */ + return -EIO; + + r = wait_for_terminate(pid, &si); + if (r < 0) + return r; + pid = 0; + + /* If something strange happened with the child, let's consider this fatal, too */ + if (si.si_code != CLD_EXITED || si.si_status != 0) + return -EIO; + + return 0; +} + +static void append_socket_pair(int *array, unsigned *n, int pair[2]) { + assert(array); + assert(n); + + if (!pair) + return; + + if (pair[0] >= 0) + array[(*n)++] = pair[0]; + if (pair[1] >= 0) + array[(*n)++] = pair[1]; +} + static int close_remaining_fds( const ExecParameters *params, ExecRuntime *runtime, + DynamicCreds *dcreds, int socket_fd, int *fds, unsigned n_fds) { unsigned n_dont_close = 0; - int dont_close[n_fds + 7]; + int dont_close[n_fds + 11]; assert(params); @@ -1551,11 +1745,14 @@ static int close_remaining_fds( n_dont_close += n_fds; } - if (runtime) { - if (runtime->netns_storage_socket[0] >= 0) - dont_close[n_dont_close++] = runtime->netns_storage_socket[0]; - if (runtime->netns_storage_socket[1] >= 0) - dont_close[n_dont_close++] = runtime->netns_storage_socket[1]; + if (runtime) + append_socket_pair(dont_close, &n_dont_close, runtime->netns_storage_socket); + + if (dcreds) { + if (dcreds->user) + append_socket_pair(dont_close, &n_dont_close, dcreds->user->storage_socket); + if (dcreds->group) + append_socket_pair(dont_close, &n_dont_close, dcreds->group->storage_socket); } return close_all_fds(dont_close, n_dont_close); @@ -1567,6 +1764,7 @@ static int exec_child( const ExecContext *context, const ExecParameters *params, ExecRuntime *runtime, + DynamicCreds *dcreds, char **argv, int socket_fd, int *fds, unsigned n_fds, @@ -1617,7 +1815,7 @@ static int exec_child( log_forget_fds(); - r = close_remaining_fds(params, runtime, socket_fd, fds, n_fds); + r = close_remaining_fds(params, runtime, dcreds, socket_fd, fds, n_fds); if (r < 0) { *exit_status = EXIT_FDS; return r; @@ -1631,7 +1829,7 @@ static int exec_child( exec_context_tty_reset(context, params); - if (params->confirm_spawn) { + if (params->flags & EXEC_CONFIRM_SPAWN) { char response; r = ask_for_confirmation(&response, argv); @@ -1650,25 +1848,59 @@ static int exec_child( } } - if (context->user) { - username = context->user; - r = get_user_creds(&username, &uid, &gid, &home, &shell); + if (context->dynamic_user && dcreds) { + + /* Make sure we bypass our own NSS module for any NSS checks */ + if (putenv((char*) "SYSTEMD_NSS_DYNAMIC_BYPASS=1") != 0) { + *exit_status = EXIT_USER; + return -errno; + } + + r = dynamic_creds_realize(dcreds, &uid, &gid); if (r < 0) { *exit_status = EXIT_USER; return r; } - } - if (context->group) { - const char *g = context->group; + if (uid == UID_INVALID || gid == GID_INVALID) { + *exit_status = EXIT_USER; + return -ESRCH; + } + + if (dcreds->user) + username = dcreds->user->name; - r = get_group_creds(&g, &gid); - if (r < 0) { - *exit_status = EXIT_GROUP; - return r; + } else { + if (context->user) { + username = context->user; + r = get_user_creds(&username, &uid, &gid, &home, &shell); + if (r < 0) { + *exit_status = EXIT_USER; + return r; + } + + /* Don't set $HOME or $SHELL if they are are not particularly enlightening anyway. */ + if (isempty(home) || path_equal(home, "/")) + home = NULL; + + if (isempty(shell) || PATH_IN_SET(shell, + "/bin/nologin", + "/sbin/nologin", + "/usr/bin/nologin", + "/usr/sbin/nologin")) + shell = NULL; } - } + if (context->group) { + const char *g = context->group; + + r = get_group_creds(&g, &gid); + if (r < 0) { + *exit_status = EXIT_GROUP; + return r; + } + } + } /* If a socket is connected to STDIN/STDOUT/STDERR, we * must sure to drop O_NONBLOCK */ @@ -1862,7 +2094,7 @@ static int exec_child( umask(context->umask); - if (params->apply_permissions && !command->privileged) { + if ((params->flags & EXEC_APPLY_PERMISSIONS) && !command->privileged) { r = enforce_groups(context, username, gid); if (r < 0) { *exit_status = EXIT_GROUP; @@ -1932,7 +2164,7 @@ static int exec_child( } r = setup_namespace( - params->apply_chroot ? context->root_directory : NULL, + (params->flags & EXEC_APPLY_CHROOT) ? context->root_directory : NULL, context->read_write_paths, context->read_only_paths, context->inaccessible_paths, @@ -1963,7 +2195,7 @@ static int exec_child( else wd = "/"; - if (params->apply_chroot) { + if (params->flags & EXEC_APPLY_CHROOT) { if (!needs_mount_namespace && context->root_directory) if (chroot(context->root_directory) < 0) { *exit_status = EXIT_CHROOT; @@ -1987,7 +2219,12 @@ static int exec_child( } #ifdef HAVE_SELINUX - if (params->apply_permissions && mac_selinux_use() && params->selinux_context_net && socket_fd >= 0 && !command->privileged) { + if ((params->flags & EXEC_APPLY_PERMISSIONS) && + mac_selinux_use() && + params->selinux_context_net && + socket_fd >= 0 && + !command->privileged) { + r = mac_selinux_get_child_mls_label(socket_fd, command->path, context->selinux_context, &mac_selinux_context_net); if (r < 0) { *exit_status = EXIT_SELINUX_CONTEXT; @@ -1996,6 +2233,14 @@ static int exec_child( } #endif + if ((params->flags & EXEC_APPLY_PERMISSIONS) && context->private_users) { + r = setup_private_users(uid, gid); + if (r < 0) { + *exit_status = EXIT_USER; + return r; + } + } + /* We repeat the fd closing here, to make sure that * nothing is leaked from the PAM modules. Note that * we are more aggressive this time since socket_fd @@ -2012,7 +2257,7 @@ static int exec_child( return r; } - if (params->apply_permissions && !command->privileged) { + if ((params->flags & EXEC_APPLY_PERMISSIONS) && !command->privileged) { bool use_address_families = context->address_families_whitelist || !set_isempty(context->address_families); @@ -2192,6 +2437,7 @@ int exec_spawn(Unit *unit, const ExecContext *context, const ExecParameters *params, ExecRuntime *runtime, + DynamicCreds *dcreds, pid_t *ret) { _cleanup_strv_free_ char **files_env = NULL; @@ -2250,6 +2496,7 @@ int exec_spawn(Unit *unit, context, params, runtime, + dcreds, argv, socket_fd, fds, n_fds, @@ -2555,8 +2802,9 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { "%sRootDirectory: %s\n" "%sNonBlocking: %s\n" "%sPrivateTmp: %s\n" - "%sPrivateNetwork: %s\n" "%sPrivateDevices: %s\n" + "%sPrivateNetwork: %s\n" + "%sPrivateUsers: %s\n" "%sProtectHome: %s\n" "%sProtectSystem: %s\n" "%sIgnoreSIGPIPE: %s\n" @@ -2567,8 +2815,9 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { prefix, c->root_directory ? c->root_directory : "/", prefix, yes_no(c->non_blocking), prefix, yes_no(c->private_tmp), - prefix, yes_no(c->private_network), prefix, yes_no(c->private_devices), + prefix, yes_no(c->private_network), + prefix, yes_no(c->private_users), prefix, protect_home_to_string(c->protect_home), prefix, protect_system_to_string(c->protect_system), prefix, yes_no(c->ignore_sigpipe), @@ -2723,6 +2972,8 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { if (c->group) fprintf(f, "%sGroup: %s\n", prefix, c->group); + fprintf(f, "%sDynamicUser: %s\n", prefix, yes_no(c->dynamic_user)); + if (strv_length(c->supplementary_groups) > 0) { fprintf(f, "%sSupplementaryGroups:", prefix); strv_fprintf(f, c->supplementary_groups); @@ -2882,12 +3133,12 @@ void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) { "%sPID: "PID_FMT"\n", prefix, s->pid); - if (s->start_timestamp.realtime > 0) + if (dual_timestamp_is_set(&s->start_timestamp)) fprintf(f, "%sStart Timestamp: %s\n", prefix, format_timestamp(buf, sizeof(buf), s->start_timestamp.realtime)); - if (s->exit_timestamp.realtime > 0) + if (dual_timestamp_is_set(&s->exit_timestamp)) fprintf(f, "%sExit Timestamp: %s\n" "%sExit Code: %s\n" diff --git a/src/core/execute.h b/src/core/execute.h index 189c4d0999..106154f81a 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -92,6 +92,8 @@ struct ExecRuntime { char *tmp_dir; char *var_tmp_dir; + /* An AF_UNIX socket pair, that contains a datagram containing a file descriptor referring to the network + * namespace. */ int netns_storage_socket[2]; }; @@ -169,11 +171,14 @@ struct ExecContext { bool private_tmp; bool private_network; bool private_devices; + bool private_users; ProtectSystem protect_system; ProtectHome protect_home; bool no_new_privileges; + bool dynamic_user; + /* This is not exposed to the user but available * internally. We need it to make sure that whenever we spawn * /usr/bin/mount it is run in the same process group as us so @@ -204,6 +209,19 @@ struct ExecContext { bool no_new_privileges_set:1; }; +typedef enum ExecFlags { + EXEC_CONFIRM_SPAWN = 1U << 0, + EXEC_APPLY_PERMISSIONS = 1U << 1, + EXEC_APPLY_CHROOT = 1U << 2, + EXEC_APPLY_TTY_STDIN = 1U << 3, + + /* The following are not used by execute.c, but by consumers internally */ + EXEC_PASS_FDS = 1U << 4, + EXEC_IS_CONTROL = 1U << 5, + EXEC_SETENV_RESULT = 1U << 6, + EXEC_SET_WATCHDOG = 1U << 7, +} ExecFlags; + struct ExecParameters { char **argv; char **environment; @@ -212,11 +230,7 @@ struct ExecParameters { char **fd_names; unsigned n_fds; - bool apply_permissions:1; - bool apply_chroot:1; - bool apply_tty_stdin:1; - - bool confirm_spawn:1; + ExecFlags flags; bool selinux_context_net:1; bool cgroup_delegate:1; @@ -235,12 +249,14 @@ struct ExecParameters { }; #include "unit.h" +#include "dynamic-user.h" int exec_spawn(Unit *unit, ExecCommand *command, const ExecContext *context, const ExecParameters *exec_params, ExecRuntime *runtime, + DynamicCreds *dynamic_creds, pid_t *ret); void exec_command_done(ExecCommand *c); diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 6a5c16a000..251155b428 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -19,9 +19,9 @@ m4_dnl Define the context options only once m4_define(`EXEC_CONTEXT_CONFIG_ITEMS', `$1.WorkingDirectory, config_parse_working_directory, 0, offsetof($1, exec_context) $1.RootDirectory, config_parse_unit_path_printf, 0, offsetof($1, exec_context.root_directory) -$1.User, config_parse_unit_string_printf, 0, offsetof($1, exec_context.user) -$1.Group, config_parse_unit_string_printf, 0, offsetof($1, exec_context.group) -$1.SupplementaryGroups, config_parse_strv, 0, offsetof($1, exec_context.supplementary_groups) +$1.User, config_parse_user_group, 0, offsetof($1, exec_context.user) +$1.Group, config_parse_user_group, 0, offsetof($1, exec_context.group) +$1.SupplementaryGroups, config_parse_user_group_strv, 0, offsetof($1, exec_context.supplementary_groups) $1.Nice, config_parse_exec_nice, 0, offsetof($1, exec_context) $1.OOMScoreAdjust, config_parse_exec_oom_score_adjust, 0, offsetof($1, exec_context) $1.IOSchedulingClass, config_parse_exec_io_class, 0, offsetof($1, exec_context) @@ -34,6 +34,7 @@ $1.UMask, config_parse_mode, 0, $1.Environment, config_parse_environ, 0, offsetof($1, exec_context.environment) $1.EnvironmentFile, config_parse_unit_env_file, 0, offsetof($1, exec_context.environment_files) $1.PassEnvironment, config_parse_pass_environ, 0, offsetof($1, exec_context.pass_environment) +$1.DynamicUser, config_parse_bool, 0, offsetof($1, exec_context.dynamic_user) $1.StandardInput, config_parse_input, 0, offsetof($1, exec_context.std_input) $1.StandardOutput, config_parse_output, 0, offsetof($1, exec_context.std_output) $1.StandardError, config_parse_output, 0, offsetof($1, exec_context.std_error) @@ -87,8 +88,9 @@ $1.ReadWritePaths, config_parse_namespace_path_strv, 0, $1.ReadOnlyPaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_paths) $1.InaccessiblePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_paths) $1.PrivateTmp, config_parse_bool, 0, offsetof($1, exec_context.private_tmp) -$1.PrivateNetwork, config_parse_bool, 0, offsetof($1, exec_context.private_network) $1.PrivateDevices, config_parse_bool, 0, offsetof($1, exec_context.private_devices) +$1.PrivateNetwork, config_parse_bool, 0, offsetof($1, exec_context.private_network) +$1.PrivateUsers, config_parse_bool, 0, offsetof($1, exec_context.private_users) $1.ProtectSystem, config_parse_protect_system, 0, offsetof($1, exec_context) $1.ProtectHome, config_parse_protect_home, 0, offsetof($1, exec_context) $1.MountFlags, config_parse_exec_mount_flags, 0, offsetof($1, exec_context) @@ -285,13 +287,14 @@ Socket.ExecStartPost, config_parse_exec, SOCKET_EXEC Socket.ExecStopPre, config_parse_exec, SOCKET_EXEC_STOP_PRE, offsetof(Socket, exec_command) Socket.ExecStopPost, config_parse_exec, SOCKET_EXEC_STOP_POST, offsetof(Socket, exec_command) Socket.TimeoutSec, config_parse_sec, 0, offsetof(Socket, timeout_usec) -Socket.SocketUser, config_parse_unit_string_printf, 0, offsetof(Socket, user) -Socket.SocketGroup, config_parse_unit_string_printf, 0, offsetof(Socket, group) +Socket.SocketUser, config_parse_user_group, 0, offsetof(Socket, user) +Socket.SocketGroup, config_parse_user_group, 0, offsetof(Socket, group) Socket.SocketMode, config_parse_mode, 0, offsetof(Socket, socket_mode) Socket.DirectoryMode, config_parse_mode, 0, offsetof(Socket, directory_mode) Socket.Accept, config_parse_bool, 0, offsetof(Socket, accept) Socket.Writable, config_parse_bool, 0, offsetof(Socket, writable) Socket.MaxConnections, config_parse_unsigned, 0, offsetof(Socket, max_connections) +Socket.MaxConnectionsPerSource, config_parse_unsigned, 0, offsetof(Socket, max_connections_per_source) Socket.KeepAlive, config_parse_bool, 0, offsetof(Socket, keep_alive) Socket.KeepAliveTimeSec, config_parse_sec, 0, offsetof(Socket, keep_alive_time) Socket.KeepAliveIntervalSec, config_parse_sec, 0, offsetof(Socket, keep_alive_interval) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index a36953f766..420f368689 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -64,6 +64,7 @@ #include "unit-name.h" #include "unit-printf.h" #include "unit.h" +#include "user-util.h" #include "utf8.h" #include "web-util.h" @@ -490,16 +491,17 @@ int config_parse_socket_bind(const char *unit, return 0; } -int config_parse_exec_nice(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { +int config_parse_exec_nice( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { ExecContext *c = data; int priority, r; @@ -509,14 +511,13 @@ int config_parse_exec_nice(const char *unit, assert(rvalue); assert(data); - r = safe_atoi(rvalue, &priority); + r = parse_nice(rvalue, &priority); if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse nice priority, ignoring: %s", rvalue); - return 0; - } + if (r == -ERANGE) + log_syntax(unit, LOG_ERR, filename, line, r, "Nice priority out of range, ignoring: %s", rvalue); + else + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse nice priority, ignoring: %s", rvalue); - if (priority < PRIO_MIN || priority >= PRIO_MAX) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Nice priority out of range, ignoring: %s", rvalue); return 0; } @@ -1763,6 +1764,123 @@ int config_parse_sec_fix_0( return 0; } +int config_parse_user_group( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char **user = data, *n; + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + if (isempty(rvalue)) + n = NULL; + else { + _cleanup_free_ char *k = NULL; + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", rvalue); + return 0; + } + + if (!valid_user_group_name_or_id(k)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID, ignoring: %s", k); + return 0; + } + + n = k; + k = NULL; + } + + free(*user); + *user = n; + + return 0; +} + +int config_parse_user_group_strv( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char ***users = data; + Unit *u = userdata; + const char *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + if (isempty(rvalue)) { + char **empty; + + empty = new0(char*, 1); + if (!empty) + return log_oom(); + + strv_free(*users); + *users = empty; + + return 0; + } + + p = rvalue; + for (;;) { + _cleanup_free_ char *word = NULL, *k = NULL; + + r = extract_first_word(&p, &word, WHITESPACE, 0); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue); + break; + } + + r = unit_full_printf(u, word, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", word); + continue; + } + + if (!valid_user_group_name_or_id(k)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID, ignoring: %s", k); + continue; + } + + r = strv_push(users, k); + if (r < 0) + return log_oom(); + + k = NULL; + } + + return 0; +} + int config_parse_busname_service( const char *unit, const char *filename, @@ -2785,7 +2903,7 @@ int config_parse_cpu_quota( return 0; } - r = parse_percent(rvalue); + r = parse_percent_unbounded(rvalue); if (r <= 0) { log_syntax(unit, LOG_ERR, filename, line, r, "CPU quota '%s' invalid. Ignoring.", rvalue); return 0; diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index b36a2e3a02..213bce55a7 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -111,6 +111,8 @@ int config_parse_exec_utmp_mode(const char *unit, const char *filename, unsigned int config_parse_working_directory(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_fdname(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_sec_fix_0(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_user_group(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_user_group_strv(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length); diff --git a/src/core/main.c b/src/core/main.c index 33e22e37dc..35b0a43901 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -92,8 +92,7 @@ static enum { ACTION_HELP, ACTION_VERSION, ACTION_TEST, - ACTION_DUMP_CONFIGURATION_ITEMS, - ACTION_DONE + ACTION_DUMP_CONFIGURATION_ITEMS } arg_action = ACTION_RUN; static char *arg_default_unit = NULL; static bool arg_system = false; @@ -1319,7 +1318,7 @@ static int fixup_environment(void) { return r; if (r == 0) { - term = strdup(default_term_for_tty("/dev/console") + 5); + term = strdup(default_term_for_tty("/dev/console")); if (!term) return -ENOMEM; } @@ -1415,12 +1414,12 @@ int main(int argc, char *argv[]) { if (mac_selinux_setup(&loaded_policy) < 0) { error_message = "Failed to load SELinux policy"; goto finish; - } else if (ima_setup() < 0) { - error_message = "Failed to load IMA policy"; - goto finish; } else if (mac_smack_setup(&loaded_policy) < 0) { error_message = "Failed to load SMACK policy"; goto finish; + } else if (ima_setup() < 0) { + error_message = "Failed to load IMA policy"; + goto finish; } dual_timestamp_get(&security_finish_timestamp); } @@ -1615,12 +1614,10 @@ int main(int argc, char *argv[]) { retval = version(); goto finish; } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) { + pager_open(arg_no_pager, false); unit_dump_config_items(stdout); retval = EXIT_SUCCESS; goto finish; - } else if (arg_action == ACTION_DONE) { - retval = EXIT_SUCCESS; - goto finish; } if (!arg_system && diff --git a/src/core/manager.c b/src/core/manager.c index 85bf858992..bb2000d860 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -553,7 +553,6 @@ static int manager_default_environment(Manager *m) { return 0; } - int manager_new(UnitFileScope scope, bool test_run, Manager **_m) { Manager *m; int r; @@ -1004,6 +1003,9 @@ Manager* manager_free(Manager *m) { bus_done(m); + dynamic_user_vacuum(m, false); + hashmap_free(m->dynamic_users); + hashmap_free(m->units); hashmap_free(m->jobs); hashmap_free(m->watch_pids1); @@ -1227,6 +1229,9 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { /* Third, fire things up! */ manager_coldplug(m); + /* Release any dynamic users no longer referenced */ + dynamic_user_vacuum(m, true); + if (serialization) { assert(m->n_reloading > 0); m->n_reloading--; @@ -2409,6 +2414,10 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) { bus_track_serialize(m->subscribed, f); + r = dynamic_user_serialize(m, f, fds); + if (r < 0) + return r; + fputc('\n', f); HASHMAP_FOREACH_KEY(u, t, m->units, i) { @@ -2585,7 +2594,9 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { m->kdbus_fd = fdset_remove(fds, fd); } - } else { + } else if (startswith(l, "dynamic-user=")) + dynamic_user_deserialize_one(m, l + 13, fds); + else { int k; k = bus_track_deserialize_item(&m->deserialized_subscribed, l); @@ -2666,6 +2677,7 @@ int manager_reload(Manager *m) { manager_clear_jobs_and_units(m); lookup_paths_flush_generator(&m->lookup_paths); lookup_paths_free(&m->lookup_paths); + dynamic_user_vacuum(m, false); q = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL); if (q < 0 && r >= 0) @@ -2702,6 +2714,9 @@ int manager_reload(Manager *m) { /* Third, fire things up! */ manager_coldplug(m); + /* Release any dynamic users no longer referenced */ + dynamic_user_vacuum(m, true); + /* Sync current state of bus names with our set of listening units */ if (m->api_bus) manager_sync_bus_names(m, m->api_bus); diff --git a/src/core/manager.h b/src/core/manager.h index 6ed15c1a41..c681d5dc46 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -298,6 +298,9 @@ struct Manager { /* Used for processing polkit authorization responses */ Hashmap *polkit_registry; + /* Dynamic users/groups, indexed by their name */ + Hashmap *dynamic_users; + /* When the user hits C-A-D more than 7 times per 2s, reboot immediately... */ RateLimit ctrl_alt_del_ratelimit; diff --git a/src/core/mount.c b/src/core/mount.c index fda4d65d6f..f3ccf6d48a 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -245,6 +245,8 @@ static void mount_done(Unit *u) { exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); m->control_command = NULL; + dynamic_creds_unref(&m->dynamic_creds); + mount_unwatch_control_pid(m); m->timer_event_source = sd_event_source_unref(m->timer_event_source); @@ -482,6 +484,7 @@ static int mount_add_default_dependencies(Mount *m) { static int mount_verify(Mount *m) { _cleanup_free_ char *e = NULL; + MountParameters *p; int r; assert(m); @@ -506,7 +509,8 @@ static int mount_verify(Mount *m) { return -EINVAL; } - if (UNIT(m)->fragment_path && !m->parameters_fragment.what) { + p = get_mount_parameters_fragment(m); + if (p && !p->what) { log_unit_error(UNIT(m), "What= setting is missing. Refusing."); return -EBADMSG; } @@ -648,6 +652,9 @@ static int mount_coldplug(Unit *u) { return r; } + if (!IN_SET(new_state, MOUNT_DEAD, MOUNT_FAILED)) + (void) unit_setup_dynamic_creds(u); + mount_set_state(m, new_state); return 0; } @@ -694,12 +701,10 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { pid_t pid; int r; ExecParameters exec_params = { - .apply_permissions = true, - .apply_chroot = true, - .apply_tty_stdin = true, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, + .flags = EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, }; assert(m); @@ -716,12 +721,16 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { if (r < 0) return r; + r = unit_setup_dynamic_creds(UNIT(m)); + if (r < 0) + return r; + r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec)); if (r < 0) return r; exec_params.environment = UNIT(m)->manager->environment; - exec_params.confirm_spawn = UNIT(m)->manager->confirm_spawn; + exec_params.flags |= UNIT(m)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0; exec_params.cgroup_supported = UNIT(m)->manager->cgroup_supported; exec_params.cgroup_path = UNIT(m)->cgroup_path; exec_params.cgroup_delegate = m->cgroup_context.delegate; @@ -732,6 +741,7 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { &m->exec_context, &exec_params, m->exec_runtime, + &m->dynamic_creds, &pid); if (r < 0) return r; @@ -749,21 +759,23 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { static void mount_enter_dead(Mount *m, MountResult f) { assert(m); - if (f != MOUNT_SUCCESS) + if (m->result == MOUNT_SUCCESS) m->result = f; + mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD); + exec_runtime_destroy(m->exec_runtime); m->exec_runtime = exec_runtime_unref(m->exec_runtime); exec_context_destroy_runtime_directory(&m->exec_context, manager_get_runtime_prefix(UNIT(m)->manager)); - mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD); + dynamic_creds_destroy(&m->dynamic_creds); } static void mount_enter_mounted(Mount *m, MountResult f) { assert(m); - if (f != MOUNT_SUCCESS) + if (m->result == MOUNT_SUCCESS) m->result = f; mount_set_state(m, MOUNT_MOUNTED); @@ -774,7 +786,7 @@ static void mount_enter_signal(Mount *m, MountState state, MountResult f) { assert(m); - if (f != MOUNT_SUCCESS) + if (m->result == MOUNT_SUCCESS) m->result = f; r = unit_kill_context( @@ -850,11 +862,6 @@ fail: mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); } -static int mount_get_opts(Mount *m, char **ret) { - return fstab_filter_options(m->parameters_fragment.options, - "nofail\0" "noauto\0" "auto\0", NULL, NULL, ret); -} - static void mount_enter_mounting(Mount *m) { int r; MountParameters *p; @@ -877,19 +884,18 @@ static void mount_enter_mounting(Mount *m) { if (p && mount_is_bind(p)) (void) mkdir_p_label(p->what, m->directory_mode); - if (m->from_fragment) { + if (p) { _cleanup_free_ char *opts = NULL; - r = mount_get_opts(m, &opts); + r = fstab_filter_options(p->options, "nofail\0" "noauto\0" "auto\0", NULL, NULL, &opts); if (r < 0) goto fail; - r = exec_command_set(m->control_command, MOUNT_PATH, - m->parameters_fragment.what, m->where, NULL); + r = exec_command_set(m->control_command, MOUNT_PATH, p->what, m->where, NULL); if (r >= 0 && m->sloppy_options) r = exec_command_append(m->control_command, "-s", NULL); - if (r >= 0 && m->parameters_fragment.fstype) - r = exec_command_append(m->control_command, "-t", m->parameters_fragment.fstype, NULL); + if (r >= 0 && p->fstype) + r = exec_command_append(m->control_command, "-t", p->fstype, NULL); if (r >= 0 && !isempty(opts)) r = exec_command_append(m->control_command, "-o", opts, NULL); } else @@ -915,27 +921,29 @@ fail: static void mount_enter_remounting(Mount *m) { int r; + MountParameters *p; assert(m); m->control_command_id = MOUNT_EXEC_REMOUNT; m->control_command = m->exec_command + MOUNT_EXEC_REMOUNT; - if (m->from_fragment) { + p = get_mount_parameters_fragment(m); + if (p) { const char *o; - if (m->parameters_fragment.options) - o = strjoina("remount,", m->parameters_fragment.options); + if (p->options) + o = strjoina("remount,", p->options); else o = "remount"; r = exec_command_set(m->control_command, MOUNT_PATH, - m->parameters_fragment.what, m->where, + p->what, m->where, "-o", o, NULL); if (r >= 0 && m->sloppy_options) r = exec_command_append(m->control_command, "-s", NULL); - if (r >= 0 && m->parameters_fragment.fstype) - r = exec_command_append(m->control_command, "-t", m->parameters_fragment.fstype, NULL); + if (r >= 0 && p->fstype) + r = exec_command_append(m->control_command, "-t", p->fstype, NULL); } else r = -ENOENT; @@ -1150,7 +1158,7 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { else assert_not_reached("Unknown code"); - if (f != MOUNT_SUCCESS) + if (m->result == MOUNT_SUCCESS) m->result = f; if (m->control_command) { @@ -1817,6 +1825,7 @@ const UnitVTable mount_vtable = { .cgroup_context_offset = offsetof(Mount, cgroup_context), .kill_context_offset = offsetof(Mount, kill_context), .exec_runtime_offset = offsetof(Mount, exec_runtime), + .dynamic_creds_offset = offsetof(Mount, dynamic_creds), .sections = "Unit\0" diff --git a/src/core/mount.h b/src/core/mount.h index da529c44f4..ac27b518cc 100644 --- a/src/core/mount.h +++ b/src/core/mount.h @@ -21,8 +21,8 @@ typedef struct Mount Mount; -#include "execute.h" #include "kill.h" +#include "dynamic-user.h" typedef enum MountExecCommand { MOUNT_EXEC_MOUNT, @@ -85,6 +85,7 @@ struct Mount { CGroupContext cgroup_context; ExecRuntime *exec_runtime; + DynamicCreds dynamic_creds; MountState state, deserialized_state; diff --git a/src/core/org.freedesktop.systemd1.conf b/src/core/org.freedesktop.systemd1.conf index 3c64f20872..14f6aec029 100644 --- a/src/core/org.freedesktop.systemd1.conf +++ b/src/core/org.freedesktop.systemd1.conf @@ -108,6 +108,14 @@ send_interface="org.freedesktop.systemd1.Manager" send_member="GetDefaultTarget"/> + <allow send_destination="org.freedesktop.systemd1" + send_interface="org.freedesktop.systemd1.Manager" + send_member="LookupDynamicUserByName"/> + + <allow send_destination="org.freedesktop.systemd1" + send_interface="org.freedesktop.systemd1.Manager" + send_member="LookupDynamicUserByUID"/> + <!-- Managed via polkit or other criteria --> <allow send_destination="org.freedesktop.systemd1" diff --git a/src/core/path.c b/src/core/path.c index 0dd0d375d8..10f9b06974 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -454,7 +454,7 @@ static int path_coldplug(Unit *u) { static void path_enter_dead(Path *p, PathResult f) { assert(p); - if (f != PATH_SUCCESS) + if (p->result == PATH_SUCCESS) p->result = f; path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD); diff --git a/src/core/scope.c b/src/core/scope.c index b45e238974..b278aed3d6 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -221,7 +221,7 @@ static void scope_dump(Unit *u, FILE *f, const char *prefix) { static void scope_enter_dead(Scope *s, ScopeResult f) { assert(s); - if (f != SCOPE_SUCCESS) + if (s->result == SCOPE_SUCCESS) s->result = f; scope_set_state(s, s->result != SCOPE_SUCCESS ? SCOPE_FAILED : SCOPE_DEAD); @@ -233,7 +233,7 @@ static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) { assert(s); - if (f != SCOPE_SUCCESS) + if (s->result == SCOPE_SUCCESS) s->result = f; unit_watch_all_pids(UNIT(s)); diff --git a/src/core/service.c b/src/core/service.c index afb198507b..4a37702f52 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -322,6 +322,8 @@ static void service_done(Unit *u) { s->control_command = NULL; s->main_command = NULL; + dynamic_creds_unref(&s->dynamic_creds); + exit_status_set_free(&s->restart_prevent_status); exit_status_set_free(&s->restart_force_status); exit_status_set_free(&s->success_status); @@ -340,6 +342,7 @@ static void service_done(Unit *u) { s->bus_name_owner = mfree(s->bus_name_owner); service_close_socket_fd(s); + s->peer = socket_peer_unref(s->peer); unit_ref_unset(&s->accept_socket); @@ -758,6 +761,11 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { prefix, s->bus_name, prefix, yes_no(s->bus_name_good)); + if (UNIT_ISSET(s->accept_socket)) + fprintf(f, + "%sAccept Socket: %s\n", + prefix, UNIT_DEREF(s->accept_socket)->id); + kill_context_dump(&s->kill_context, f, prefix); exec_context_dump(&s->exec_context, f, prefix); @@ -1030,6 +1038,23 @@ static int service_coldplug(Unit *u) { if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) service_start_watchdog(s); + if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) + (void) unit_setup_dynamic_creds(u); + + if (UNIT_ISSET(s->accept_socket)) { + Socket* socket = SOCKET(UNIT_DEREF(s->accept_socket)); + + if (socket->max_connections_per_source > 0) { + SocketPeer *peer; + + /* Make a best-effort attempt at bumping the connection count */ + if (socket_acquire_peer(socket, s->socket_fd, &peer) > 0) { + socket_peer_unref(s->peer); + s->peer = peer; + } + } + } + service_set_state(s, s->deserialized_state); return 0; } @@ -1146,11 +1171,7 @@ static int service_spawn( Service *s, ExecCommand *c, usec_t timeout, - bool pass_fds, - bool apply_permissions, - bool apply_chroot, - bool apply_tty_stdin, - bool is_control, + ExecFlags flags, pid_t *_pid) { _cleanup_strv_free_ char **argv = NULL, **final_env = NULL, **our_env = NULL, **fd_names = NULL; @@ -1160,12 +1181,10 @@ static int service_spawn( pid_t pid; ExecParameters exec_params = { - .apply_permissions = apply_permissions, - .apply_chroot = apply_chroot, - .apply_tty_stdin = apply_tty_stdin, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, + .flags = flags, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, }; int r; @@ -1174,6 +1193,14 @@ static int service_spawn( assert(c); assert(_pid); + if (flags & EXEC_IS_CONTROL) { + /* If this is a control process, mask the permissions/chroot application if this is requested. */ + if (s->permissions_start_only) + exec_params.flags &= ~EXEC_APPLY_PERMISSIONS; + if (s->root_directory_start_only) + exec_params.flags &= ~EXEC_APPLY_CHROOT; + } + (void) unit_realize_cgroup(UNIT(s)); if (s->reset_cpu_usage) { (void) unit_reset_cpu_usage(UNIT(s)); @@ -1184,7 +1211,11 @@ static int service_spawn( if (r < 0) return r; - if (pass_fds || + r = unit_setup_dynamic_creds(UNIT(s)); + if (r < 0) + return r; + + if ((flags & EXEC_PASS_FDS) || s->exec_context.std_input == EXEC_INPUT_SOCKET || s->exec_context.std_output == EXEC_OUTPUT_SOCKET || s->exec_context.std_error == EXEC_OUTPUT_SOCKET) { @@ -1204,11 +1235,11 @@ static int service_spawn( if (r < 0) return r; - our_env = new0(char*, 6); + our_env = new0(char*, 9); if (!our_env) return -ENOMEM; - if (is_control ? s->notify_access == NOTIFY_ALL : s->notify_access != NOTIFY_NONE) + if ((flags & EXEC_IS_CONTROL) ? s->notify_access == NOTIFY_ALL : s->notify_access != NOTIFY_NONE) if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) return -ENOMEM; @@ -1216,7 +1247,7 @@ static int service_spawn( if (asprintf(our_env + n_env++, "MAINPID="PID_FMT, s->main_pid) < 0) return -ENOMEM; - if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) + if (MANAGER_IS_USER(UNIT(s)->manager)) if (asprintf(our_env + n_env++, "MANAGERPID="PID_FMT, getpid()) < 0) return -ENOMEM; @@ -1252,22 +1283,40 @@ static int service_spawn( } } + if (flags & EXEC_SETENV_RESULT) { + if (asprintf(our_env + n_env++, "SERVICE_RESULT=%s", service_result_to_string(s->result)) < 0) + return -ENOMEM; + + if (s->main_exec_status.pid > 0 && + dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) { + if (asprintf(our_env + n_env++, "EXIT_CODE=%s", sigchld_code_to_string(s->main_exec_status.code)) < 0) + return -ENOMEM; + + if (s->main_exec_status.code == CLD_EXITED) + r = asprintf(our_env + n_env++, "EXIT_STATUS=%i", s->main_exec_status.status); + else + r = asprintf(our_env + n_env++, "EXIT_STATUS=%s", signal_to_string(s->main_exec_status.status)); + if (r < 0) + return -ENOMEM; + } + } + final_env = strv_env_merge(2, UNIT(s)->manager->environment, our_env, NULL); if (!final_env) return -ENOMEM; - if (is_control && UNIT(s)->cgroup_path) { + if ((flags & EXEC_IS_CONTROL) && UNIT(s)->cgroup_path) { path = strjoina(UNIT(s)->cgroup_path, "/control"); (void) cg_create(SYSTEMD_CGROUP_CONTROLLER, path); } else path = UNIT(s)->cgroup_path; exec_params.argv = argv; + exec_params.environment = final_env; exec_params.fds = fds; exec_params.fd_names = fd_names; exec_params.n_fds = n_fds; - exec_params.environment = final_env; - exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; + exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0; exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; exec_params.cgroup_path = path; exec_params.cgroup_delegate = s->cgroup_context.delegate; @@ -1285,6 +1334,7 @@ static int service_spawn( &s->exec_context, &exec_params, s->exec_runtime, + &s->dynamic_creds, &pid); if (r < 0) return r; @@ -1392,7 +1442,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) int r; assert(s); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD); @@ -1418,9 +1468,12 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) exec_runtime_destroy(s->exec_runtime); s->exec_runtime = exec_runtime_unref(s->exec_runtime); - /* Also, remove the runtime directory in */ + /* Also, remove the runtime directory */ exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); + /* Release the user, and destroy it if we are the only remaining owner */ + dynamic_creds_destroy(&s->dynamic_creds); + /* Try to delete the pid file. At this point it will be * out-of-date, and some software might be confused by it, so * let's remove it. */ @@ -1438,7 +1491,7 @@ static void service_enter_stop_post(Service *s, ServiceResult f) { int r; assert(s); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; service_unwatch_control_pid(s); @@ -1451,11 +1504,7 @@ static void service_enter_stop_post(Service *s, ServiceResult f) { r = service_spawn(s, s->control_command, s->timeout_stop_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - true, - true, + EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_IS_CONTROL|EXEC_SETENV_RESULT, &s->control_pid); if (r < 0) goto fail; @@ -1495,7 +1544,7 @@ static void service_enter_signal(Service *s, ServiceState state, ServiceResult f assert(s); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; unit_watch_all_pids(UNIT(s)); @@ -1553,7 +1602,7 @@ static void service_enter_stop(Service *s, ServiceResult f) { assert(s); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; service_unwatch_control_pid(s); @@ -1566,11 +1615,7 @@ static void service_enter_stop(Service *s, ServiceResult f) { r = service_spawn(s, s->control_command, s->timeout_stop_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - true, + EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_SETENV_RESULT, &s->control_pid); if (r < 0) goto fail; @@ -1609,7 +1654,7 @@ static bool service_good(Service *s) { static void service_enter_running(Service *s, ServiceResult f) { assert(s); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; service_unwatch_control_pid(s); @@ -1647,11 +1692,7 @@ static void service_enter_start_post(Service *s) { r = service_spawn(s, s->control_command, s->timeout_start_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - true, + EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL, &s->control_pid); if (r < 0) goto fail; @@ -1721,11 +1762,7 @@ static void service_enter_start(Service *s) { r = service_spawn(s, c, timeout, - true, - true, - true, - true, - false, + EXEC_PASS_FDS|EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG, &pid); if (r < 0) goto fail; @@ -1784,11 +1821,7 @@ static void service_enter_start_pre(Service *s) { r = service_spawn(s, s->control_command, s->timeout_start_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - true, - true, + EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_APPLY_TTY_STDIN, &s->control_pid); if (r < 0) goto fail; @@ -1863,11 +1896,7 @@ static void service_enter_reload(Service *s) { r = service_spawn(s, s->control_command, s->timeout_start_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - true, + EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL, &s->control_pid); if (r < 0) goto fail; @@ -1905,12 +1934,9 @@ static void service_run_next_control(Service *s) { r = service_spawn(s, s->control_command, timeout, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - s->control_command_id == SERVICE_EXEC_START_PRE || - s->control_command_id == SERVICE_EXEC_STOP_POST, - true, + EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL| + (IN_SET(s->control_command_id, SERVICE_EXEC_START_PRE, SERVICE_EXEC_STOP_POST) ? EXEC_APPLY_TTY_STDIN : 0)| + (IN_SET(s->control_command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST) ? EXEC_SETENV_RESULT : 0), &s->control_pid); if (r < 0) goto fail; @@ -1948,11 +1974,7 @@ static void service_run_next_main(Service *s) { r = service_spawn(s, s->main_command, s->timeout_start_usec, - true, - true, - true, - true, - false, + EXEC_PASS_FDS|EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG, &pid); if (r < 0) goto fail; @@ -2116,6 +2138,12 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { if (r < 0) return r; + if (UNIT_ISSET(s->accept_socket)) { + r = unit_serialize_item(u, f, "accept-socket", UNIT_DEREF(s->accept_socket)->id); + if (r < 0) + return r; + } + r = unit_serialize_item_fd(u, f, fds, "socket-fd", s->socket_fd); if (r < 0) return r; @@ -2246,6 +2274,17 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, s->control_command_id = id; s->control_command = s->exec_command[id]; } + } else if (streq(key, "accept-socket")) { + Unit *socket; + + r = manager_load_unit(u->manager, value, NULL, NULL, &socket); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to load accept-socket unit: %s", value); + else { + unit_ref_set(&s->accept_socket, socket); + SOCKET(socket)->n_connections++; + } + } else if (streq(key, "socket-fd")) { int fd; @@ -2606,7 +2645,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { "EXIT_STATUS=%i", status, NULL); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; if (s->main_command && @@ -2687,7 +2726,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { "Control process exited, code=%s status=%i", sigchld_code_to_string(code), status); - if (f != SERVICE_SUCCESS) + if (s->result == SERVICE_SUCCESS) s->result = f; /* Immediately get rid of the cgroup, so that the @@ -3323,6 +3362,7 @@ const UnitVTable service_vtable = { .cgroup_context_offset = offsetof(Service, cgroup_context), .kill_context_offset = offsetof(Service, kill_context), .exec_runtime_offset = offsetof(Service, exec_runtime), + .dynamic_creds_offset = offsetof(Service, dynamic_creds), .sections = "Unit\0" diff --git a/src/core/service.h b/src/core/service.h index cfef375b03..888007cc0b 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -148,9 +148,11 @@ struct Service { /* Runtime data of the execution context */ ExecRuntime *exec_runtime; + DynamicCreds dynamic_creds; pid_t main_pid, control_pid; int socket_fd; + SocketPeer *peer; bool socket_fd_selinux_context_net; bool permissions_start_only; diff --git a/src/core/socket.c b/src/core/socket.c index e098055885..50872e8366 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -57,6 +57,14 @@ #include "unit-printf.h" #include "unit.h" #include "user-util.h" +#include "in-addr-util.h" + +struct SocketPeer { + unsigned n_ref; + + Socket *socket; + union sockaddr_union peer; +}; static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = { [SOCKET_DEAD] = UNIT_INACTIVE, @@ -141,15 +149,23 @@ void socket_free_ports(Socket *s) { static void socket_done(Unit *u) { Socket *s = SOCKET(u); + SocketPeer *p; assert(s); socket_free_ports(s); + while ((p = set_steal_first(s->peers_by_address))) + p->socket = NULL; + + s->peers_by_address = set_free(s->peers_by_address); + s->exec_runtime = exec_runtime_unref(s->exec_runtime); exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX); s->control_command = NULL; + dynamic_creds_unref(&s->dynamic_creds); + socket_unwatch_control_pid(s); unit_ref_unset(&s->service); @@ -466,6 +482,40 @@ static int socket_verify(Socket *s) { return 0; } +static void peer_address_hash_func(const void *p, struct siphash *state) { + const SocketPeer *s = p; + + assert(s); + assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6)); + + if (s->peer.sa.sa_family == AF_INET) + siphash24_compress(&s->peer.in.sin_addr, sizeof(s->peer.in.sin_addr), state); + else + siphash24_compress(&s->peer.in6.sin6_addr, sizeof(s->peer.in6.sin6_addr), state); +} + +static int peer_address_compare_func(const void *a, const void *b) { + const SocketPeer *x = a, *y = b; + + if (x->peer.sa.sa_family < y->peer.sa.sa_family) + return -1; + if (x->peer.sa.sa_family > y->peer.sa.sa_family) + return 1; + + switch(x->peer.sa.sa_family) { + case AF_INET: + return memcmp(&x->peer.in.sin_addr, &y->peer.in.sin_addr, sizeof(x->peer.in.sin_addr)); + case AF_INET6: + return memcmp(&x->peer.in6.sin6_addr, &y->peer.in6.sin6_addr, sizeof(x->peer.in6.sin6_addr)); + } + assert_not_reached("Black sheep in the family!"); +} + +const struct hash_ops peer_address_hash_ops = { + .hash = peer_address_hash_func, + .compare = peer_address_compare_func +}; + static int socket_load(Unit *u) { Socket *s = SOCKET(u); int r; @@ -473,6 +523,10 @@ static int socket_load(Unit *u) { assert(u); assert(u->load_state == UNIT_STUB); + r = set_ensure_allocated(&s->peers_by_address, &peer_address_hash_ops); + if (r < 0) + return r; + r = unit_load_fragment_and_dropin(u); if (r < 0) return r; @@ -487,6 +541,87 @@ static int socket_load(Unit *u) { return socket_verify(s); } +static SocketPeer *socket_peer_new(void) { + SocketPeer *p; + + p = new0(SocketPeer, 1); + if (!p) + return NULL; + + p->n_ref = 1; + + return p; +} + +SocketPeer *socket_peer_ref(SocketPeer *p) { + if (!p) + return NULL; + + assert(p->n_ref > 0); + p->n_ref++; + + return p; +} + +SocketPeer *socket_peer_unref(SocketPeer *p) { + if (!p) + return NULL; + + assert(p->n_ref > 0); + + p->n_ref--; + + if (p->n_ref > 0) + return NULL; + + if (p->socket) + set_remove(p->socket->peers_by_address, p); + + return mfree(p); +} + +int socket_acquire_peer(Socket *s, int fd, SocketPeer **p) { + _cleanup_(socket_peer_unrefp) SocketPeer *remote = NULL; + SocketPeer sa = {}, *i; + socklen_t salen = sizeof(sa.peer); + int r; + + assert(fd >= 0); + assert(s); + + r = getpeername(fd, &sa.peer.sa, &salen); + if (r < 0) + return log_error_errno(errno, "getpeername failed: %m"); + + if (!IN_SET(sa.peer.sa.sa_family, AF_INET, AF_INET6)) { + *p = NULL; + return 0; + } + + i = set_get(s->peers_by_address, &sa); + if (i) { + *p = socket_peer_ref(i); + return 1; + } + + remote = socket_peer_new(); + if (!remote) + return log_oom(); + + remote->peer = sa.peer; + + r = set_put(s->peers_by_address, remote); + if (r < 0) + return r; + + remote->socket = s; + + *p = remote; + remote = NULL; + + return 1; +} + _const_ static const char* listen_lookup(int family, int type) { if (family == AF_NETLINK) @@ -1602,6 +1737,9 @@ static int socket_coldplug(Unit *u) { return r; } + if (!IN_SET(s->deserialized_state, SOCKET_DEAD, SOCKET_FAILED)) + (void) unit_setup_dynamic_creds(u); + socket_set_state(s, s->deserialized_state); return 0; } @@ -1611,12 +1749,10 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { pid_t pid; int r; ExecParameters exec_params = { - .apply_permissions = true, - .apply_chroot = true, - .apply_tty_stdin = true, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, + .flags = EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, }; assert(s); @@ -1633,6 +1769,10 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { if (r < 0) return r; + r = unit_setup_dynamic_creds(UNIT(s)); + if (r < 0) + return r; + r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); if (r < 0) return r; @@ -1643,7 +1783,7 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { exec_params.argv = argv; exec_params.environment = UNIT(s)->manager->environment; - exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; + exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0; exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; exec_params.cgroup_path = UNIT(s)->cgroup_path; exec_params.cgroup_delegate = s->cgroup_context.delegate; @@ -1654,6 +1794,7 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { &s->exec_context, &exec_params, s->exec_runtime, + &s->dynamic_creds, &pid); if (r < 0) return r; @@ -1754,15 +1895,17 @@ fail: static void socket_enter_dead(Socket *s, SocketResult f) { assert(s); - if (f != SOCKET_SUCCESS) + if (s->result == SOCKET_SUCCESS) s->result = f; + socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD); + exec_runtime_destroy(s->exec_runtime); s->exec_runtime = exec_runtime_unref(s->exec_runtime); exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); - socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD); + dynamic_creds_destroy(&s->dynamic_creds); } static void socket_enter_signal(Socket *s, SocketState state, SocketResult f); @@ -1771,7 +1914,7 @@ static void socket_enter_stop_post(Socket *s, SocketResult f) { int r; assert(s); - if (f != SOCKET_SUCCESS) + if (s->result == SOCKET_SUCCESS) s->result = f; socket_unwatch_control_pid(s); @@ -1799,7 +1942,7 @@ static void socket_enter_signal(Socket *s, SocketState state, SocketResult f) { assert(s); - if (f != SOCKET_SUCCESS) + if (s->result == SOCKET_SUCCESS) s->result = f; r = unit_kill_context( @@ -1843,7 +1986,7 @@ static void socket_enter_stop_pre(Socket *s, SocketResult f) { int r; assert(s); - if (f != SOCKET_SUCCESS) + if (s->result == SOCKET_SUCCESS) s->result = f; socket_unwatch_control_pid(s); @@ -2038,14 +2181,34 @@ static void socket_enter_running(Socket *s, int cfd) { socket_set_state(s, SOCKET_RUNNING); } else { _cleanup_free_ char *prefix = NULL, *instance = NULL, *name = NULL; + _cleanup_(socket_peer_unrefp) SocketPeer *p = NULL; Service *service; if (s->n_connections >= s->max_connections) { - log_unit_warning(UNIT(s), "Too many incoming connections (%u), refusing connection attempt.", s->n_connections); + log_unit_warning(UNIT(s), "Too many incoming connections (%u), dropping connection.", + s->n_connections); safe_close(cfd); return; } + if (s->max_connections_per_source > 0) { + r = socket_acquire_peer(s, cfd, &p); + if (r < 0) { + safe_close(cfd); + return; + } else if (r > 0 && p->n_ref > s->max_connections_per_source) { + _cleanup_free_ char *t = NULL; + + sockaddr_pretty(&p->peer.sa, FAMILY_ADDRESS_SIZE(p->peer.sa.sa_family), true, false, &t); + + log_unit_warning(UNIT(s), + "Too many incoming connections (%u) from source %s, dropping connection.", + p->n_ref, strnull(t)); + safe_close(cfd); + return; + } + } + r = socket_instantiate_service(s); if (r < 0) goto fail; @@ -2087,6 +2250,9 @@ static void socket_enter_running(Socket *s, int cfd) { cfd = -1; /* We passed ownership of the fd to the service now. Forget it here. */ s->n_connections++; + service->peer = p; /* Pass ownership of the peer reference */ + p = NULL; + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, &error, NULL); if (r < 0) { /* We failed to activate the new service, but it still exists. Let's make sure the service @@ -2286,6 +2452,11 @@ static int socket_serialize(Unit *u, FILE *f, FDSet *fds) { return 0; } +static void socket_port_take_fd(SocketPort *p, FDSet *fds, int fd) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); +} + static int socket_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { Socket *s = SOCKET(u); @@ -2340,18 +2511,13 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse fifo value: %s", value); - else { - + else LIST_FOREACH(port, p, s->ports) if (p->type == SOCKET_FIFO && - path_equal_or_files_same(p->path, value+skip)) + path_equal_or_files_same(p->path, value+skip)) { + socket_port_take_fd(p, fds, fd); break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } + } } else if (streq(key, "special")) { int fd, skip = 0; @@ -2359,18 +2525,13 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse special value: %s", value); - else { - + else LIST_FOREACH(port, p, s->ports) if (p->type == SOCKET_SPECIAL && - path_equal_or_files_same(p->path, value+skip)) + path_equal_or_files_same(p->path, value+skip)) { + socket_port_take_fd(p, fds, fd); break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } + } } else if (streq(key, "mqueue")) { int fd, skip = 0; @@ -2378,18 +2539,13 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse mqueue value: %s", value); - else { - + else LIST_FOREACH(port, p, s->ports) if (p->type == SOCKET_MQUEUE && - streq(p->path, value+skip)) + streq(p->path, value+skip)) { + socket_port_take_fd(p, fds, fd); break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } + } } else if (streq(key, "socket")) { int fd, type, skip = 0; @@ -2397,17 +2553,12 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, if (sscanf(value, "%i %i %n", &fd, &type, &skip) < 2 || fd < 0 || type < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse socket value: %s", value); - else { - + else LIST_FOREACH(port, p, s->ports) - if (socket_address_is(&p->address, value+skip, type)) + if (socket_address_is(&p->address, value+skip, type)) { + socket_port_take_fd(p, fds, fd); break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } + } } else if (streq(key, "netlink")) { int fd, skip = 0; @@ -2415,17 +2566,12 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse socket value: %s", value); - else { - + else LIST_FOREACH(port, p, s->ports) - if (socket_address_is_netlink(&p->address, value+skip)) + if (socket_address_is_netlink(&p->address, value+skip)) { + socket_port_take_fd(p, fds, fd); break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } + } } else if (streq(key, "ffs")) { int fd, skip = 0; @@ -2433,18 +2579,13 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse ffs value: %s", value); - else { - + else LIST_FOREACH(port, p, s->ports) if (p->type == SOCKET_USB_FUNCTION && - path_equal_or_files_same(p->path, value+skip)) + path_equal_or_files_same(p->path, value+skip)) { + socket_port_take_fd(p, fds, fd); break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } + } } else log_unit_debug(UNIT(s), "Unknown serialization key: %s", key); @@ -2627,7 +2768,7 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) { "Control process exited, code=%s status=%i", sigchld_code_to_string(code), status); - if (f != SOCKET_SUCCESS) + if (s->result == SOCKET_SUCCESS) s->result = f; if (s->control_command && @@ -2930,6 +3071,7 @@ const UnitVTable socket_vtable = { .cgroup_context_offset = offsetof(Socket, cgroup_context), .kill_context_offset = offsetof(Socket, kill_context), .exec_runtime_offset = offsetof(Socket, exec_runtime), + .dynamic_creds_offset = offsetof(Socket, dynamic_creds), .sections = "Unit\0" diff --git a/src/core/socket.h b/src/core/socket.h index 0f1ac69c6f..89f4664510 100644 --- a/src/core/socket.h +++ b/src/core/socket.h @@ -20,6 +20,7 @@ ***/ typedef struct Socket Socket; +typedef struct SocketPeer SocketPeer; #include "mount.h" #include "service.h" @@ -79,9 +80,12 @@ struct Socket { LIST_HEAD(SocketPort, ports); + Set *peers_by_address; + unsigned n_accepted; unsigned n_connections; unsigned max_connections; + unsigned max_connections_per_source; unsigned backlog; unsigned keep_alive_cnt; @@ -94,7 +98,9 @@ struct Socket { ExecContext exec_context; KillContext kill_context; CGroupContext cgroup_context; + ExecRuntime *exec_runtime; + DynamicCreds dynamic_creds; /* For Accept=no sockets refers to the one service we'll activate. For Accept=yes sockets is either NULL, or filled @@ -162,6 +168,12 @@ struct Socket { RateLimit trigger_limit; }; +SocketPeer *socket_peer_ref(SocketPeer *p); +SocketPeer *socket_peer_unref(SocketPeer *p); +int socket_acquire_peer(Socket *s, int fd, SocketPeer **p); + +DEFINE_TRIVIAL_CLEANUP_FUNC(SocketPeer*, socket_peer_unref); + /* Called from the service code when collecting fds */ int socket_collect_fds(Socket *s, int **fds); diff --git a/src/core/swap.c b/src/core/swap.c index a532b15be8..2c802da3b5 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -153,6 +153,8 @@ static void swap_done(Unit *u) { exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX); s->control_command = NULL; + dynamic_creds_unref(&s->dynamic_creds); + swap_unwatch_control_pid(s); s->timer_event_source = sd_event_source_unref(s->timer_event_source); @@ -553,6 +555,9 @@ static int swap_coldplug(Unit *u) { return r; } + if (!IN_SET(new_state, SWAP_DEAD, SWAP_FAILED)) + (void) unit_setup_dynamic_creds(u); + swap_set_state(s, new_state); return 0; } @@ -606,12 +611,10 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { pid_t pid; int r; ExecParameters exec_params = { - .apply_permissions = true, - .apply_chroot = true, - .apply_tty_stdin = true, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, + .flags = EXEC_APPLY_PERMISSIONS|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, }; assert(s); @@ -628,12 +631,16 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { if (r < 0) goto fail; + r = unit_setup_dynamic_creds(UNIT(s)); + if (r < 0) + return r; + r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); if (r < 0) goto fail; exec_params.environment = UNIT(s)->manager->environment; - exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; + exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0; exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; exec_params.cgroup_path = UNIT(s)->cgroup_path; exec_params.cgroup_delegate = s->cgroup_context.delegate; @@ -644,6 +651,7 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { &s->exec_context, &exec_params, s->exec_runtime, + &s->dynamic_creds, &pid); if (r < 0) goto fail; @@ -665,21 +673,23 @@ fail: static void swap_enter_dead(Swap *s, SwapResult f) { assert(s); - if (f != SWAP_SUCCESS) + if (s->result == SWAP_SUCCESS) s->result = f; + swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD); + exec_runtime_destroy(s->exec_runtime); s->exec_runtime = exec_runtime_unref(s->exec_runtime); exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); - swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD); + dynamic_creds_destroy(&s->dynamic_creds); } static void swap_enter_active(Swap *s, SwapResult f) { assert(s); - if (f != SWAP_SUCCESS) + if (s->result == SWAP_SUCCESS) s->result = f; swap_set_state(s, SWAP_ACTIVE); @@ -690,7 +700,7 @@ static void swap_enter_signal(Swap *s, SwapState state, SwapResult f) { assert(s); - if (f != SWAP_SUCCESS) + if (s->result == SWAP_SUCCESS) s->result = f; r = unit_kill_context( @@ -987,7 +997,7 @@ static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) { else assert_not_reached("Unknown code"); - if (f != SWAP_SUCCESS) + if (s->result == SWAP_SUCCESS) s->result = f; if (s->control_command) { @@ -1466,6 +1476,7 @@ const UnitVTable swap_vtable = { .cgroup_context_offset = offsetof(Swap, cgroup_context), .kill_context_offset = offsetof(Swap, kill_context), .exec_runtime_offset = offsetof(Swap, exec_runtime), + .dynamic_creds_offset = offsetof(Swap, dynamic_creds), .sections = "Unit\0" diff --git a/src/core/swap.h b/src/core/swap.h index fbf66debdc..b0ef50f1e8 100644 --- a/src/core/swap.h +++ b/src/core/swap.h @@ -82,6 +82,7 @@ struct Swap { CGroupContext cgroup_context; ExecRuntime *exec_runtime; + DynamicCreds dynamic_creds; SwapState state, deserialized_state; diff --git a/src/core/timer.c b/src/core/timer.c index 3206296f09..e2b43f02f8 100644 --- a/src/core/timer.c +++ b/src/core/timer.c @@ -291,7 +291,7 @@ static int timer_coldplug(Unit *u) { static void timer_enter_dead(Timer *t, TimerResult f) { assert(t); - if (f != TIMER_SUCCESS) + if (t->result == TIMER_SUCCESS) t->result = f; timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD); diff --git a/src/core/unit.c b/src/core/unit.c index 4934a0e56f..ff7c562fba 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -3224,6 +3224,33 @@ void unit_ref_unset(UnitRef *ref) { ref->unit = NULL; } +static int user_from_unit_name(Unit *u, char **ret) { + + static const uint8_t hash_key[] = { + 0x58, 0x1a, 0xaf, 0xe6, 0x28, 0x58, 0x4e, 0x96, + 0xb4, 0x4e, 0xf5, 0x3b, 0x8c, 0x92, 0x07, 0xec + }; + + _cleanup_free_ char *n = NULL; + int r; + + r = unit_name_to_prefix(u->id, &n); + if (r < 0) + return r; + + if (valid_user_group_name(n)) { + *ret = n; + n = NULL; + return 0; + } + + /* If we can't use the unit name as a user name, then let's hash it and use that */ + if (asprintf(ret, "_du%016" PRIx64, siphash24(n, strlen(n), hash_key)) < 0) + return -ENOMEM; + + return 0; +} + int unit_patch_contexts(Unit *u) { CGroupContext *cc; ExecContext *ec; @@ -3268,6 +3295,22 @@ int unit_patch_contexts(Unit *u) { if (ec->private_devices) ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD); + + if (ec->dynamic_user) { + if (!ec->user) { + r = user_from_unit_name(u, &ec->user); + if (r < 0) + return r; + } + + if (!ec->group) { + ec->group = strdup(ec->user); + if (!ec->group) + return -ENOMEM; + } + + ec->private_tmp = true; + } } cc = unit_get_cgroup_context(u); @@ -3776,6 +3819,26 @@ int unit_setup_exec_runtime(Unit *u) { return exec_runtime_make(rt, unit_get_exec_context(u), u->id); } +int unit_setup_dynamic_creds(Unit *u) { + ExecContext *ec; + DynamicCreds *dcreds; + size_t offset; + + assert(u); + + offset = UNIT_VTABLE(u)->dynamic_creds_offset; + assert(offset > 0); + dcreds = (DynamicCreds*) ((uint8_t*) u + offset); + + ec = unit_get_exec_context(u); + assert(ec); + + if (!ec->dynamic_user) + return 0; + + return dynamic_creds_acquire(dcreds, u->manager, ec->user, ec->group); +} + bool unit_type_supported(UnitType t) { if (_unlikely_(t < 0)) return false; diff --git a/src/core/unit.h b/src/core/unit.h index 1eabfa51e2..47eb8d50a6 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -291,6 +291,10 @@ struct UnitVTable { * that */ size_t exec_runtime_offset; + /* If greater than 0, the offset into the object where the pointer to DynamicCreds is found, if the unit type + * has that. */ + size_t dynamic_creds_offset; + /* The name of the configuration file section with the private settings of this unit */ const char *private_section; @@ -589,6 +593,7 @@ CGroupContext *unit_get_cgroup_context(Unit *u) _pure_; ExecRuntime *unit_get_exec_runtime(Unit *u) _pure_; int unit_setup_exec_runtime(Unit *u); +int unit_setup_dynamic_creds(Unit *u); int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data); int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) _printf_(4,5); diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index dcc09fcc6d..be724aed4e 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -327,9 +327,11 @@ static int save_external_coredump( r = safe_atou64(context[CONTEXT_RLIMIT], &rlimit); if (r < 0) return log_error_errno(r, "Failed to parse resource limit: %s", context[CONTEXT_RLIMIT]); - if (rlimit <= 0) { - /* Is coredumping disabled? Then don't bother saving/processing the coredump */ - log_info("Core Dumping has been disabled for process %s (%s).", context[CONTEXT_PID], context[CONTEXT_COMM]); + if (rlimit < page_size()) { + /* Is coredumping disabled? Then don't bother saving/processing the coredump. + * Anything below PAGE_SIZE cannot give a readable coredump (the kernel uses + * ELF_EXEC_PAGESIZE which is not easily accessible, but is usually the same as PAGE_SIZE. */ + log_info("Core dumping has been disabled for process %s (%s).", context[CONTEXT_PID], context[CONTEXT_COMM]); return -EBADSLT; } @@ -558,6 +560,89 @@ static int compose_open_fds(pid_t pid, char **open_fds) { return 0; } +static int get_process_ns(pid_t pid, const char *namespace, ino_t *ns) { + const char *p; + struct stat stbuf; + _cleanup_close_ int proc_ns_dir_fd; + + p = procfs_file_alloca(pid, "ns"); + + proc_ns_dir_fd = open(p, O_DIRECTORY | O_CLOEXEC | O_RDONLY); + if (proc_ns_dir_fd < 0) + return -errno; + + if (fstatat(proc_ns_dir_fd, namespace, &stbuf, /* flags */0) < 0) + return -errno; + + *ns = stbuf.st_ino; + return 0; +} + +static int get_mount_namespace_leader(pid_t pid, pid_t *container_pid) { + pid_t cpid = pid, ppid = 0; + ino_t proc_mntns; + int r = 0; + + r = get_process_ns(pid, "mnt", &proc_mntns); + if (r < 0) + return r; + + while (1) { + ino_t parent_mntns; + + r = get_process_ppid(cpid, &ppid); + if (r < 0) + return r; + + r = get_process_ns(ppid, "mnt", &parent_mntns); + if (r < 0) + return r; + + if (proc_mntns != parent_mntns) + break; + + if (ppid == 1) + return -ENOENT; + + cpid = ppid; + } + + *container_pid = ppid; + return 0; +} + +/* Returns 1 if the parent was found. + * Returns 0 if there is not a process we can call the pid's + * container parent (the pid's process isn't 'containerized'). + * Returns a negative number on errors. + */ +static int get_process_container_parent_cmdline(pid_t pid, char** cmdline) { + int r = 0; + pid_t container_pid; + const char *proc_root_path; + struct stat root_stat, proc_root_stat; + + /* To compare inodes of / and /proc/[pid]/root */ + if (stat("/", &root_stat) < 0) + return -errno; + + proc_root_path = procfs_file_alloca(pid, "root"); + if (stat(proc_root_path, &proc_root_stat) < 0) + return -errno; + + /* The process uses system root. */ + if (proc_root_stat.st_ino == root_stat.st_ino) { + *cmdline = NULL; + return 0; + } + + r = get_mount_namespace_leader(pid, &container_pid); + if (r < 0) + return r; + + return get_process_cmdline(container_pid, 0, false, cmdline); +} + static int change_uid_gid(const char *context[]) { uid_t uid; gid_t gid; @@ -933,11 +1018,13 @@ static int process_kernel(int argc, char* argv[]) { /* The larger ones we allocate on the heap */ _cleanup_free_ char *core_owner_uid = NULL, *core_open_fds = NULL, *core_proc_status = NULL, - *core_proc_maps = NULL, *core_proc_limits = NULL, *core_proc_cgroup = NULL, *core_environ = NULL; + *core_proc_maps = NULL, *core_proc_limits = NULL, *core_proc_cgroup = NULL, *core_environ = NULL, + *core_proc_mountinfo = NULL, *core_container_cmdline = NULL; _cleanup_free_ char *exe = NULL, *comm = NULL; const char *context[_CONTEXT_MAX]; - struct iovec iovec[25]; + bool proc_self_root_is_slash; + struct iovec iovec[27]; size_t n_iovec = 0; uid_t owner_uid; const char *p; @@ -1110,6 +1197,15 @@ static int process_kernel(int argc, char* argv[]) { IOVEC_SET_STRING(iovec[n_iovec++], core_proc_cgroup); } + p = procfs_file_alloca(pid, "mountinfo"); + if (read_full_file(p, &t, NULL) >=0) { + core_proc_mountinfo = strappend("COREDUMP_PROC_MOUNTINFO=", t); + free(t); + + if (core_proc_mountinfo) + IOVEC_SET_STRING(iovec[n_iovec++], core_proc_mountinfo); + } + if (get_process_cwd(pid, &t) >= 0) { core_cwd = strjoina("COREDUMP_CWD=", t); free(t); @@ -1119,9 +1215,20 @@ static int process_kernel(int argc, char* argv[]) { if (get_process_root(pid, &t) >= 0) { core_root = strjoina("COREDUMP_ROOT=", t); - free(t); IOVEC_SET_STRING(iovec[n_iovec++], core_root); + + /* If the process' root is "/", then there is a chance it has + * mounted own root and hence being containerized. */ + proc_self_root_is_slash = strcmp(t, "/") == 0; + free(t); + if (proc_self_root_is_slash && get_process_container_parent_cmdline(pid, &t) > 0) { + core_container_cmdline = strappend("COREDUMP_CONTAINER_CMDLINE=", t); + free(t); + + if (core_container_cmdline) + IOVEC_SET_STRING(iovec[n_iovec++], core_container_cmdline); + } } if (get_process_environ(pid, &t) >= 0) { diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 27b1e0fb3f..bbf8793e57 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -30,6 +30,7 @@ #include "compress.h" #include "fd-util.h" #include "fileio.h" +#include "fs-util.h" #include "journal-internal.h" #include "log.h" #include "macro.h" @@ -609,7 +610,13 @@ static int save_core(sd_journal *j, int fd, char **path, bool *unlink_temp) { char *temp = NULL; if (fd < 0) { - temp = strdup("/var/tmp/coredump-XXXXXX"); + const char *vt; + + r = var_tmp_dir(&vt); + if (r < 0) + return log_error_errno(r, "Failed to acquire temporary directory path: %m"); + + temp = strjoin(vt, "/coredump-XXXXXX", NULL); if (!temp) return log_oom(); diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 39355de953..6cc1aad705 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -450,99 +450,101 @@ static int add_automount( } static int add_boot(const char *what) { - _cleanup_blkid_free_probe_ blkid_probe b = NULL; - const char *fstype = NULL, *uuid = NULL; - sd_id128_t id, type_id; + const char *esp; int r; assert(what); - if (!is_efi_boot()) { - log_debug("Not an EFI boot, ignoring /boot."); - return 0; - } - if (in_initrd()) { - log_debug("In initrd, ignoring /boot."); + log_debug("In initrd, ignoring the ESP."); return 0; } if (detect_container() > 0) { - log_debug("In a container, ignoring /boot."); + log_debug("In a container, ignoring the ESP."); return 0; } + /* If /efi exists we'll use that. Otherwise we'll use /boot, as that's usually the better choice */ + esp = access("/efi/", F_OK) >= 0 ? "/efi" : "/boot"; + /* We create an .automount which is not overridden by the .mount from the fstab generator. */ - if (fstab_is_mount_point("/boot")) { - log_debug("/boot specified in fstab, ignoring."); + if (fstab_is_mount_point(esp)) { + log_debug("%s specified in fstab, ignoring.", esp); return 0; } - if (path_is_busy("/boot")) { - log_debug("/boot already populated, ignoring."); + if (path_is_busy(esp)) { + log_debug("%s already populated, ignoring.", esp); return 0; } - r = efi_loader_get_device_part_uuid(&id); - if (r == -ENOENT) { - log_debug("EFI loader partition unknown."); - return 0; - } + if (is_efi_boot()) { + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + const char *fstype = NULL, *uuid_string = NULL; + sd_id128_t loader_uuid, part_uuid; - if (r < 0) - return log_error_errno(r, "Failed to read ESP partition UUID: %m"); + /* If this is an EFI boot, be extra careful, and only mount the ESP if it was the ESP used for booting. */ - errno = 0; - b = blkid_new_probe_from_filename(what); - if (!b) { - if (errno == 0) - return log_oom(); - return log_error_errno(errno, "Failed to allocate prober: %m"); - } - - blkid_probe_enable_partitions(b, 1); - blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + r = efi_loader_get_device_part_uuid(&loader_uuid); + if (r == -ENOENT) { + log_debug("EFI loader partition unknown."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to read ESP partition UUID: %m"); - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -2 || r == 1) /* no result or uncertain */ - return 0; - else if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what); + errno = 0; + b = blkid_new_probe_from_filename(what); + if (!b) { + if (errno == 0) + return log_oom(); + return log_error_errno(errno, "Failed to allocate prober: %m"); + } - (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); - if (!streq_ptr(fstype, "vfat")) { - log_debug("Partition for /boot is not a FAT filesystem, ignoring."); - return 0; - } + blkid_probe_enable_partitions(b, 1); + blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &uuid, NULL); - if (r != 0) { - log_debug_errno(errno, "Partition for /boot does not have a UUID, ignoring."); - return 0; - } + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -2 || r == 1) /* no result or uncertain */ + return 0; + else if (r != 0) + return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what); - if (sd_id128_from_string(uuid, &type_id) < 0) { - log_debug("Partition for /boot does not have a valid UUID, ignoring."); - return 0; - } + (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); + if (!streq_ptr(fstype, "vfat")) { + log_debug("Partition for %s is not a FAT filesystem, ignoring.", esp); + return 0; + } - if (!sd_id128_equal(type_id, id)) { - log_debug("Partition for /boot does not appear to be the partition we are booted from."); - return 0; - } + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &uuid_string, NULL); + if (r != 0) { + log_debug_errno(errno, "Partition for %s does not have a UUID, ignoring.", esp); + return 0; + } - r = add_automount("boot", - what, - "/boot", - "vfat", - true, - "umask=0077", - "EFI System Partition Automount", - 120 * USEC_PER_SEC); + if (sd_id128_from_string(uuid_string, &part_uuid) < 0) { + log_debug("Partition for %s does not have a valid UUID, ignoring.", esp); + return 0; + } - return r; + if (!sd_id128_equal(part_uuid, loader_uuid)) { + log_debug("Partition for %s does not appear to be the partition we are booted from.", esp); + return 0; + } + } else + log_debug("Not an EFI boot, skipping ESP check."); + + return add_automount("boot", + what, + esp, + "vfat", + true, + "umask=0077", + "EFI System Partition Automount", + 120 * USEC_PER_SEC); } #else static int add_boot(const char *what) { diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index e265027a04..1b3942b3dc 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -19,9 +19,6 @@ #include <fcntl.h> #include <getopt.h> -#ifdef HAVE_GNUTLS -#include <gnutls/gnutls.h> -#endif #include <microhttpd.h> #include <stdlib.h> #include <string.h> @@ -48,6 +45,7 @@ static char *arg_key_pem = NULL; static char *arg_cert_pem = NULL; static char *arg_trust_pem = NULL; +static char *arg_directory = NULL; typedef struct RequestMeta { sd_journal *journal; @@ -118,7 +116,10 @@ static int open_journal(RequestMeta *m) { if (m->journal) return 0; - return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM); + if (arg_directory) + return sd_journal_open_directory(&m->journal, arg_directory, 0); + else + return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM); } static int request_meta_ensure_tmp(RequestMeta *m) { @@ -239,6 +240,9 @@ static ssize_t request_reader_entries( m->size = (uint64_t) sz; } + if (m->tmp == NULL && m->follow) + return 0; + if (fseeko(m->tmp, pos, SEEK_SET) < 0) { log_error_errno(errno, "Failed to seek to position: %m"); return MHD_CONTENT_READER_END_WITH_ERROR; @@ -881,7 +885,8 @@ static void help(void) { " --version Show package version\n" " --cert=CERT.PEM Server certificate in PEM format\n" " --key=KEY.PEM Server key in PEM format\n" - " --trust=CERT.PEM Certificat authority certificate in PEM format\n", + " --trust=CERT.PEM Certificate authority certificate in PEM format\n" + " -D --directory=PATH Serve journal files in directory\n", program_invocation_short_name); } @@ -896,11 +901,12 @@ static int parse_argv(int argc, char *argv[]) { int r, c; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "key", required_argument, NULL, ARG_KEY }, + { "cert", required_argument, NULL, ARG_CERT }, + { "trust", required_argument, NULL, ARG_TRUST }, + { "directory", required_argument, NULL, 'D' }, {} }; @@ -954,6 +960,9 @@ static int parse_argv(int argc, char *argv[]) { #else log_error("Option --trust is not available."); #endif + case 'D': + arg_directory = optarg; + break; case '?': return -EINVAL; diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index 35a1e55f9e..80e2adb100 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -27,10 +27,6 @@ #include <sys/socket.h> #include <unistd.h> -#ifdef HAVE_GNUTLS -#include <gnutls/gnutls.h> -#endif - #include "sd-daemon.h" #include "alloc-util.h" @@ -1564,7 +1560,7 @@ int main(int argc, char **argv) { if (r < 0) log_error_errno(r, "Failed to enable watchdog: %m"); else - log_debug("Watchdog is %s.", r > 0 ? "enabled" : "disabled"); + log_debug("Watchdog is %sd.", enable_disable(r > 0)); log_debug("%s running as pid "PID_FMT, program_invocation_short_name, getpid()); diff --git a/src/journal/.gitignore b/src/journal/.gitignore index 04d5852547..b93a9462fa 100644 --- a/src/journal/.gitignore +++ b/src/journal/.gitignore @@ -1,4 +1,3 @@ /journald-gperf.c -/libsystemd-journal.pc /audit_type-list.txt /audit_type-*-name.* diff --git a/src/journal/journal-verify.c b/src/journal/journal-verify.c index f61f158e8a..4105abfccc 100644 --- a/src/journal/journal-verify.c +++ b/src/journal/journal-verify.c @@ -826,7 +826,7 @@ int journal_file_verify( int data_fd = -1, entry_fd = -1, entry_array_fd = -1; unsigned i; bool found_last = false; - _cleanup_free_ char *tmp_dir = NULL; + const char *tmp_dir = NULL; #ifdef HAVE_GCRYPT uint64_t last_tag = 0; @@ -846,7 +846,7 @@ int journal_file_verify( } else if (f->seal) return -ENOKEY; - r = var_tmp(&tmp_dir); + r = var_tmp_dir(&tmp_dir); if (r < 0) { log_error_errno(r, "Failed to determine temporary directory: %m"); goto fail; diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 53c6180864..6f841efb69 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -297,9 +297,9 @@ static void help(void) { " -n --lines[=INTEGER] Number of journal entries to show\n" " --no-tail Show all lines, even in follow mode\n" " -r --reverse Show the newest entries first\n" - " -o --output=STRING Change journal output mode (short, short-iso,\n" - " short-precise, short-monotonic, verbose,\n" - " export, json, json-pretty, json-sse, cat)\n" + " -o --output=STRING Change journal output mode (short, short-precise,\n" + " short-iso, short-full, short-monotonic, short-unix,\n" + " verbose, export, json, json-pretty, json-sse, cat)\n" " --utc Express time in Coordinated Universal Time (UTC)\n" " -x --catalog Add message explanations where available\n" " --no-full Ellipsize fields\n" diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h index e025a4cf90..d2a32ab422 100644 --- a/src/journal/journald-server.h +++ b/src/journal/journald-server.h @@ -43,7 +43,7 @@ typedef enum Storage { typedef enum SplitMode { SPLIT_UID, - SPLIT_LOGIN, + SPLIT_LOGIN, /* deprecated */ SPLIT_NONE, _SPLIT_MAX, _SPLIT_INVALID = -1 diff --git a/src/journal/lookup3.c b/src/journal/lookup3.c index 3d791234f4..d8f1a4977d 100644 --- a/src/journal/lookup3.c +++ b/src/journal/lookup3.c @@ -317,7 +317,7 @@ uint32_t jenkins_hashlittle( const void *key, size_t length, uint32_t initval) * still catch it and complain. The masking trick does make the hash * noticeably faster for short strings (like English words). */ -#ifndef VALGRIND +#if !defined(VALGRIND) && !defined(__SANITIZE_ADDRESS__) switch(length) { @@ -503,7 +503,7 @@ void jenkins_hashlittle2( * still catch it and complain. The masking trick does make the hash * noticeably faster for short strings (like English words). */ -#ifndef VALGRIND +#if !defined(VALGRIND) && !defined(__SANITIZE_ADDRESS__) switch(length) { @@ -681,7 +681,7 @@ uint32_t jenkins_hashbig( const void *key, size_t length, uint32_t initval) * still catch it and complain. The masking trick does make the hash * noticeably faster for short strings (like English words). */ -#ifndef VALGRIND +#if !defined(VALGRIND) && !defined(__SANITIZE_ADDRESS__) switch(length) { diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c index 75a0ffb49b..2a3824d0e8 100644 --- a/src/journal/sd-journal.c +++ b/src/journal/sd-journal.c @@ -2290,6 +2290,8 @@ _public_ int sd_journal_get_fd(sd_journal *j) { * inotify */ if (j->no_new_files) r = add_current_paths(j); + else if (j->flags & SD_JOURNAL_OS_ROOT) + r = add_search_paths(j); else if (j->toplevel_fd >= 0) r = add_root_directory(j, NULL, false); else if (j->path) diff --git a/src/kernel-install/kernel-install b/src/kernel-install/kernel-install index 1159dc384d..c66bcfc092 100644 --- a/src/kernel-install/kernel-install +++ b/src/kernel-install/kernel-install @@ -86,10 +86,15 @@ if [[ ! $COMMAND ]] || [[ ! $KERNEL_VERSION ]]; then exit 1 fi -if [[ -d /boot/loader/entries ]] || [[ -d /boot/$MACHINE_ID ]]; then +if [[ -d /efi/loader/entries ]] || [[ -d /efi/$MACHINE_ID ]]; then + BOOT_DIR_ABS="/efi/$MACHINE_ID/$KERNEL_VERSION" +elif [[ -d /boot/loader/entries ]] || [[ -d /boot/$MACHINE_ID ]]; then BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION" -elif [[ -d /boot/efi/loader/entries ]] || [[ -d /boot/efi/$MACHINE_ID ]] \ - || mountpoint -q /boot/efi; then +elif [[ -d /boot/efi/loader/entries ]] || [[ -d /boot/efi/$MACHINE_ID ]]; then + BOOT_DIR_ABS="/boot/efi/$MACHINE_ID/$KERNEL_VERSION" +elif mountpoint -q /efi; then + BOOT_DIR_ABS="/efi/$MACHINE_ID/$KERNEL_VERSION" +elif mountpoint -q /boot/efi; then BOOT_DIR_ABS="/boot/efi/$MACHINE_ID/$KERNEL_VERSION" else BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION" diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 02e3bf904c..32be3cdc38 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -44,6 +44,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_NO_ISOLATION, EPERM), SD_BUS_ERROR_MAP(BUS_ERROR_SHUTTING_DOWN, ECANCELED), SD_BUS_ERROR_MAP(BUS_ERROR_SCOPE_NOT_RUNNING, EHOSTDOWN), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DYNAMIC_USER, ESRCH), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_MACHINE, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE, ENOENT), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index c8f369cb78..befb6fbfe0 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -40,6 +40,7 @@ #define BUS_ERROR_NO_ISOLATION "org.freedesktop.systemd1.NoIsolation" #define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown" #define BUS_ERROR_SCOPE_NOT_RUNNING "org.freedesktop.systemd1.ScopeNotRunning" +#define BUS_ERROR_NO_SUCH_DYNAMIC_USER "org.freedesktop.systemd1.NoSuchDynamicUser" #define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine" #define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage" diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 348e396292..63363035e7 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -893,7 +893,7 @@ int config_parse_user_tasks_max( /* First, try to parse as percentage */ r = parse_percent(rvalue); - if (r > 0 && r < 100) + if (r >= 0) k = system_tasks_max_scale(r, 100U); else { diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index ddec6cb4d6..c78ca7ad76 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -185,7 +185,7 @@ static int list_machines(int argc, char *argv[], void *userdata) { qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info); - if (arg_legend) + if (arg_legend && n_machines > 0) printf("%-*s %-*s %-*s\n", (int) max_name, "MACHINE", (int) max_class, "CLASS", @@ -197,8 +197,10 @@ static int list_machines(int argc, char *argv[], void *userdata) { (int) max_class, machines[j].class, (int) max_service, machines[j].service); - if (arg_legend) + if (arg_legend && n_machines > 0) printf("\n%zu machines listed.\n", n_machines); + else + printf("No machines.\n"); return 0; } @@ -305,7 +307,7 @@ static int list_images(int argc, char *argv[], void *userdata) { qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info); - if (arg_legend) + if (arg_legend && n_images > 0) printf("%-*s %-*s %-3s %-*s %-*s %-*s\n", (int) max_name, "NAME", (int) max_type, "TYPE", @@ -326,8 +328,10 @@ static int list_images(int argc, char *argv[], void *userdata) { (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime))); } - if (arg_legend) + if (arg_legend && n_images > 0) printf("\n%zu images listed.\n", n_images); + else + printf("No images.\n"); return 0; } @@ -2314,7 +2318,7 @@ static int list_transfers(int argc, char *argv[], void *userdata) { qsort_safe(transfers, n_transfers, sizeof(TransferInfo), compare_transfer_info); - if (arg_legend) + if (arg_legend && n_transfers > 0) printf("%-*s %-*s %-*s %-*s %-*s\n", (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID", (int) 7, "PERCENT", @@ -2330,8 +2334,10 @@ static int list_transfers(int argc, char *argv[], void *userdata) { (int) max_local, transfers[j].local, (int) max_remote, transfers[j].remote); - if (arg_legend) + if (arg_legend && n_transfers > 0) printf("\n%zu transfers listed.\n", n_transfers); + else + printf("No transfers.\n"); return 0; } diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 1923e8b971..5e2462cba2 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -954,7 +954,7 @@ static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_err /* Create a temporary file we can dump information about deleted images into. We use a temporary file for this * instead of a pipe or so, since this might grow quit large in theory and we don't want to process this * continuously */ - result_fd = open_tmpfile_unlinkable("/tmp/", O_RDWR|O_CLOEXEC); + result_fd = open_tmpfile_unlinkable(NULL, O_RDWR|O_CLOEXEC); if (result_fd < 0) return -errno; diff --git a/src/network/networkd-brvlan.c b/src/network/networkd-brvlan.c index 8bc330ebae..18ecd86858 100644 --- a/src/network/networkd-brvlan.c +++ b/src/network/networkd-brvlan.c @@ -257,6 +257,24 @@ static int parse_vid_range(const char *rvalue, uint16_t *vid, uint16_t *vid_end) return r; } +int config_parse_brvlan_pvid(const char *unit, const char *filename, + unsigned line, const char *section, + unsigned section_line, const char *lvalue, + int ltype, const char *rvalue, void *data, + void *userdata) { + Network *network = userdata; + int r; + uint16_t pvid; + r = parse_vlanid(rvalue, &pvid); + if (r < 0) + return r; + + network->pvid = pvid; + network->use_br_vlan = true; + + return 0; +} + int config_parse_brvlan_vlan(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, @@ -288,6 +306,7 @@ int config_parse_brvlan_vlan(const char *unit, const char *filename, for (; vid <= vid_end; vid++) set_bit(vid, network->br_vid_bitmap); } + network->use_br_vlan = true; return 0; } @@ -325,5 +344,6 @@ int config_parse_brvlan_untagged(const char *unit, const char *filename, set_bit(vid, network->br_untagged_bitmap); } } + network->use_br_vlan = true; return 0; } diff --git a/src/network/networkd-brvlan.h b/src/network/networkd-brvlan.h index 6aa6883bfc..b37633f94f 100644 --- a/src/network/networkd-brvlan.h +++ b/src/network/networkd-brvlan.h @@ -25,5 +25,6 @@ typedef struct Link Link; int br_vlan_configure(Link *link, uint16_t pvid, uint32_t *br_vid_bitmap, uint32_t *br_untagged_bitmap); +int config_parse_brvlan_pvid(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_brvlan_vlan(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_brvlan_untagged(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 82f56158be..69ee7424ce 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -256,12 +256,8 @@ static int link_enable_ipv6(Link *link) { r = write_string_file(p, one_zero(disabled), WRITE_STRING_FILE_VERIFY_ON_FAILURE); if (r < 0) log_link_warning_errno(link, r, "Cannot %s IPv6 for interface %s: %m", disabled ? "disable" : "enable", link->ifname); - else { - if (disabled) - log_link_info(link, "IPv6 disabled for interface: %m"); - else - log_link_info(link, "IPv6 enabled for interface: %m"); - } + else + log_link_info(link, "IPv6 %sd for interface: %m", enable_disable(!disabled)); return 0; } @@ -1318,6 +1314,65 @@ int link_set_mtu(Link *link, uint32_t mtu) { return 0; } +static int set_flags_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + _cleanup_link_unref_ Link *link = userdata; + int r; + + assert(m); + assert(link); + assert(link->ifname); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + log_link_warning_errno(link, r, "Could not set link flags: %m"); + + return 1; +} + +static int link_set_flags(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + unsigned ifi_change = 0; + unsigned ifi_flags = 0; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + + if (link->flags & IFF_LOOPBACK) + return 0; + + if (!link->network) + return 0; + + if (link->network->arp < 0) + return 0; + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + if (link->network->arp >= 0) { + ifi_change |= IFF_NOARP; + ifi_flags |= link->network->arp ? 0 : IFF_NOARP; + } + + r = sd_rtnl_message_link_set_flags(req, ifi_flags, ifi_change); + if (r < 0) + return log_link_error_errno(link, r, "Could not set link flags: %m"); + + r = sd_netlink_call_async(link->manager->rtnl, req, set_flags_handler, link, 0, NULL); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + static int link_set_bridge(Link *link) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; int r; @@ -2005,7 +2060,8 @@ static int link_joined(Link *link) { log_link_error_errno(link, r, "Could not set bridge message: %m"); } - if (link->network->bridge || streq_ptr("bridge", link->kind)) { + if (link->network->use_br_vlan && + (link->network->bridge || streq_ptr("bridge", link->kind))) { r = link_set_bridge_vlan(link); if (r < 0) log_link_error_errno(link, r, "Could not set bridge vlan: %m"); @@ -2318,6 +2374,35 @@ static int link_drop_foreign_config(Link *link) { return 0; } +static int link_drop_config(Link *link) { + Address *address; + Route *route; + Iterator i; + int r; + + SET_FOREACH(address, link->addresses, i) { + /* we consider IPv6LL addresses to be managed by the kernel */ + if (address->family == AF_INET6 && in_addr_is_link_local(AF_INET6, &address->in_addr) == 1) + continue; + + r = address_remove(address, link, link_address_remove_handler); + if (r < 0) + return r; + } + + SET_FOREACH(route, link->routes, i) { + /* do not touch routes managed by the kernel */ + if (route->protocol == RTPROT_KERNEL) + continue; + + r = route_remove(route, link, link_route_remove_handler); + if (r < 0) + return r; + } + + return 0; +} + static int link_update_lldp(Link *link) { int r; @@ -2386,6 +2471,10 @@ static int link_configure(Link *link) { if (r < 0) return r; + r = link_set_flags(link); + if (r < 0) + return r; + if (link_ipv4ll_enabled(link)) { r = ipv4ll_configure(link); if (r < 0) @@ -2864,6 +2953,14 @@ static int link_carrier_lost(Link *link) { return r; } + r = link_drop_config(link); + if (r < 0) + return r; + + r = link_drop_foreign_config(link); + if (r < 0) + return r; + r = link_handle_bound_by_list(link); if (r < 0) return r; diff --git a/src/network/networkd-netdev-bridge.c b/src/network/networkd-netdev-bridge.c index a5085d2b19..12b0fe972f 100644 --- a/src/network/networkd-netdev-bridge.c +++ b/src/network/networkd-netdev-bridge.c @@ -108,6 +108,12 @@ static int netdev_bridge_post_create(NetDev *netdev, Link *link, sd_netlink_mess return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_VLAN_FILTERING attribute: %m"); } + if (b->stp >= 0) { + r = sd_netlink_message_append_u32(req, IFLA_BR_STP_STATE, b->stp); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_STP_STATE attribute: %m"); + } + r = sd_netlink_message_close_container(req); if (r < 0) return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m"); @@ -135,6 +141,7 @@ static void bridge_init(NetDev *n) { b->mcast_querier = -1; b->mcast_snooping = -1; b->vlan_filtering = -1; + b->stp = -1; } const NetDevVTable bridge_vtable = { diff --git a/src/network/networkd-netdev-bridge.h b/src/network/networkd-netdev-bridge.h index a637aea0a3..4ce0fbb6f9 100644 --- a/src/network/networkd-netdev-bridge.h +++ b/src/network/networkd-netdev-bridge.h @@ -27,6 +27,7 @@ typedef struct Bridge { int mcast_querier; int mcast_snooping; int vlan_filtering; + int stp; usec_t forward_delay; usec_t hello_time; diff --git a/src/network/networkd-netdev-gperf.gperf b/src/network/networkd-netdev-gperf.gperf index 9d69f61376..a1ca1a3d4e 100644 --- a/src/network/networkd-netdev-gperf.gperf +++ b/src/network/networkd-netdev-gperf.gperf @@ -106,4 +106,5 @@ Bridge.ForwardDelaySec, config_parse_sec, 0, Bridge.MulticastQuerier, config_parse_tristate, 0, offsetof(Bridge, mcast_querier) Bridge.MulticastSnooping, config_parse_tristate, 0, offsetof(Bridge, mcast_snooping) Bridge.VLANFiltering, config_parse_tristate, 0, offsetof(Bridge, vlan_filtering) +Bridge.STP, config_parse_tristate, 0, offsetof(Bridge, stp) VRF.TableId, config_parse_uint32, 0, offsetof(Vrf, table_id) diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 5172a7b5e9..b96f0b7210 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -28,6 +28,7 @@ Match.KernelCommandLine, config_parse_net_condition, Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, match_arch) Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac) Link.MTUBytes, config_parse_iec_size, 0, offsetof(Network, mtu) +Link.ARP, config_parse_tristate, 0, offsetof(Network, arp) Network.Description, config_parse_string, 0, offsetof(Network, description) Network.Bridge, config_parse_netdev, 0, offsetof(Network, bridge) Network.Bond, config_parse_netdev, 0, offsetof(Network, bond) @@ -114,7 +115,7 @@ Bridge.AllowPortToBeRoot, config_parse_bool, Bridge.UnicastFlood, config_parse_bool, 0, offsetof(Network, unicast_flood) BridgeFDB.MACAddress, config_parse_fdb_hwaddr, 0, 0 BridgeFDB.VLANId, config_parse_fdb_vlan_id, 0, 0 -BridgeVLAN.PVID, config_parse_vlanid, 0, offsetof(Network, pvid) +BridgeVLAN.PVID, config_parse_brvlan_pvid, 0, 0 BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0 BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0 /* backwards compatibility: do not add new entries to this section */ diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 2b764d4f24..49faba5b12 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -134,6 +134,7 @@ static int network_load_one(Manager *manager, const char *filename) { network->ipv6_hop_limit = -1; network->duid.type = _DUID_TYPE_INVALID; network->proxy_arp = -1; + network->arp = -1; network->ipv6_accept_ra_use_dns = true; r = config_parse(NULL, filename, file, @@ -394,10 +395,8 @@ int network_apply(Manager *manager, Network *network, Link *link) { if (!strv_isempty(network->dns) || !strv_isempty(network->ntp) || !strv_isempty(network->search_domains) || - !strv_isempty(network->route_domains)) { - manager_dirty(manager); + !strv_isempty(network->route_domains)) link_dirty(link); - } return 0; } diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 08ee939faa..5460eb4d1c 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -151,6 +151,7 @@ struct Network { bool unicast_flood; unsigned cost; + bool use_br_vlan; uint16_t pvid; uint32_t br_vid_bitmap[BRIDGE_VLAN_BITMAP_LEN]; uint32_t br_untagged_bitmap[BRIDGE_VLAN_BITMAP_LEN]; @@ -171,6 +172,7 @@ struct Network { struct ether_addr *mac; unsigned mtu; + int arp; uint32_t iaid; DUID duid; diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index 85e2c943e3..803caef3dd 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -23,6 +23,8 @@ #include "alloc-util.h" #include "cgroup-util.h" #include "escape.h" +#include "fd-util.h" +#include "fileio.h" #include "fs-util.h" #include "label.h" #include "mkdir.h" @@ -181,13 +183,15 @@ int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s) { static int tmpfs_patch_options( const char *options, - bool userns, uid_t uid_shift, uid_t uid_range, + bool userns, + uid_t uid_shift, uid_t uid_range, + bool patch_ids, const char *selinux_apifs_context, char **ret) { char *buf = NULL; - if (userns && uid_shift != 0) { + if ((userns && uid_shift != 0) || patch_ids) { assert(uid_shift != UID_INVALID); if (options) @@ -218,7 +222,13 @@ static int tmpfs_patch_options( } #endif + if (!buf && options) { + buf = strdup(options); + if (!buf) + return -ENOMEM; + } *ret = buf; + return !!buf; } @@ -271,7 +281,15 @@ int mount_sysfs(const char *dest) { return log_error_errno(errno, "Failed to remove %s: %m", full); x = prefix_roota(top, "/fs/kdbus"); - (void) mkdir(x, 0755); + (void) mkdir_p(x, 0755); + + /* Create mountpoint for cgroups. Otherwise we are not allowed since we + * remount /sys read-only. + */ + if (cg_ns_supported()) { + x = prefix_roota(top, "/fs/cgroup"); + (void) mkdir_p(x, 0755); + } if (mount(NULL, top, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL) < 0) return log_error_errno(errno, "Failed to make %s read-only: %m", top); @@ -349,7 +367,7 @@ int mount_all(const char *dest, o = mount_table[k].options; if (streq_ptr(mount_table[k].type, "tmpfs")) { - r = tmpfs_patch_options(o, use_userns, uid_shift, uid_range, selinux_apifs_context, &options); + r = tmpfs_patch_options(o, use_userns, uid_shift, uid_range, false, selinux_apifs_context, &options); if (r < 0) return log_oom(); if (r > 0) @@ -486,7 +504,7 @@ static int mount_tmpfs( if (r < 0 && r != -EEXIST) return log_error_errno(r, "Creating mount point for tmpfs %s failed: %m", where); - r = tmpfs_patch_options(m->options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); + r = tmpfs_patch_options(m->options, userns, uid_shift, uid_range, false, selinux_apifs_context, &buf); if (r < 0) return log_oom(); options = r > 0 ? buf : m->options; @@ -601,6 +619,48 @@ int mount_custom( return 0; } +/* Retrieve existing subsystems. This function is called in a new cgroup + * namespace. + */ +static int get_controllers(Set *subsystems) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + + assert(subsystems); + + f = fopen("/proc/self/cgroup", "re"); + if (!f) + return errno == ENOENT ? -ESRCH : -errno; + + FOREACH_LINE(line, f, return -errno) { + int r; + char *e, *l, *p; + + truncate_nl(line); + + l = strchr(line, ':'); + if (!l) + continue; + + l++; + e = strchr(l, ':'); + if (!e) + continue; + + *e = 0; + + if (streq(l, "") || streq(l, "name=systemd")) + continue; + + p = strdup(l); + r = set_consume(subsystems, p); + if (r < 0) + return r; + } + + return 0; +} + static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controller, const char *hierarchy, bool read_only) { char *to; int r; @@ -629,11 +689,107 @@ static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controlle return 1; } -static int mount_legacy_cgroups( - const char *dest, +/* Mount a legacy cgroup hierarchy when cgroup namespaces are supported. */ +static int mount_legacy_cgns_supported( bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context) { + _cleanup_set_free_free_ Set *controllers = NULL; + const char *cgroup_root = "/sys/fs/cgroup", *c; + int r; + + (void) mkdir_p(cgroup_root, 0755); + + /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */ + r = path_is_mount_point(cgroup_root, AT_SYMLINK_FOLLOW); + if (r < 0) + return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m"); + if (r == 0) { + _cleanup_free_ char *options = NULL; + + /* When cgroup namespaces are enabled and user namespaces are + * used then the mount of the cgroupfs is done *inside* the new + * user namespace. We're root in the new user namespace and the + * kernel will happily translate our uid/gid to the correct + * uid/gid as seen from e.g. /proc/1/mountinfo. So we simply + * pass uid 0 and not uid_shift to tmpfs_patch_options(). + */ + r = tmpfs_patch_options("mode=755", userns, 0, uid_range, true, selinux_apifs_context, &options); + if (r < 0) + return log_oom(); + if (mount("tmpfs", cgroup_root, "tmpfs", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, options) < 0) + return log_error_errno(errno, "Failed to mount /sys/fs/cgroup: %m"); + } + + if (cg_unified() > 0) + goto skip_controllers; + + controllers = set_new(&string_hash_ops); + if (!controllers) + return log_oom(); + + r = get_controllers(controllers); + if (r < 0) + return log_error_errno(r, "Failed to determine cgroup controllers: %m"); + + for (;;) { + _cleanup_free_ const char *controller = NULL; + + controller = set_steal_first(controllers); + if (!controller) + break; + + r = mount_legacy_cgroup_hierarchy("", controller, controller, !userns); + if (r < 0) + return r; + + /* When multiple hierarchies are co-mounted, make their + * constituting individual hierarchies a symlink to the + * co-mount. + */ + c = controller; + for (;;) { + _cleanup_free_ char *target = NULL, *tok = NULL; + + r = extract_first_word(&c, &tok, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to extract co-mounted cgroup controller: %m"); + if (r == 0) + break; + + target = prefix_root("/sys/fs/cgroup", tok); + if (!target) + return log_oom(); + + if (streq(controller, tok)) + break; + + r = symlink_idempotent(controller, target); + if (r == -EINVAL) + return log_error_errno(r, "Invalid existing symlink for combined hierarchy: %m"); + if (r < 0) + return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m"); + } + } + +skip_controllers: + r = mount_legacy_cgroup_hierarchy("", "none,name=systemd,xattr", "systemd", false); + if (r < 0) + return r; + + if (!userns) { + if (mount(NULL, cgroup_root, NULL, MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755") < 0) + return log_error_errno(errno, "Failed to remount %s read-only: %m", cgroup_root); + } + + return 0; +} + +/* Mount legacy cgroup hierarchy when cgroup namespaces are unsupported. */ +static int mount_legacy_cgns_unsupported( + const char *dest, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { _cleanup_set_free_free_ Set *controllers = NULL; const char *cgroup_root; int r; @@ -649,7 +805,7 @@ static int mount_legacy_cgroups( if (r == 0) { _cleanup_free_ char *options = NULL; - r = tmpfs_patch_options("mode=755", userns, uid_shift, uid_range, selinux_apifs_context, &options); + r = tmpfs_patch_options("mode=755", userns, uid_shift, uid_range, false, selinux_apifs_context, &options); if (r < 0) return log_oom(); @@ -708,10 +864,8 @@ static int mount_legacy_cgroups( return r; r = symlink_idempotent(combined, target); - if (r == -EINVAL) { - log_error("Invalid existing symlink for combined hierarchy"); - return r; - } + if (r == -EINVAL) + return log_error_errno(r, "Invalid existing symlink for combined hierarchy: %m"); if (r < 0) return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m"); } @@ -762,12 +916,15 @@ int mount_cgroups( const char *dest, bool unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { + const char *selinux_apifs_context, + bool use_cgns) { if (unified_requested) return mount_unified_cgroups(dest); - else - return mount_legacy_cgroups(dest, userns, uid_shift, uid_range, selinux_apifs_context); + else if (use_cgns && cg_ns_supported()) + return mount_legacy_cgns_supported(userns, uid_shift, uid_range, selinux_apifs_context); + + return mount_legacy_cgns_unsupported(dest, userns, uid_shift, uid_range, selinux_apifs_context); } int mount_systemd_cgroup_writable( @@ -835,7 +992,7 @@ int setup_volatile_state( return log_error_errno(errno, "Failed to create %s: %m", directory); options = "mode=755"; - r = tmpfs_patch_options(options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); + r = tmpfs_patch_options(options, userns, uid_shift, uid_range, false, selinux_apifs_context, &buf); if (r < 0) return log_oom(); if (r > 0) @@ -871,7 +1028,7 @@ int setup_volatile( return log_error_errno(errno, "Failed to create temporary directory: %m"); options = "mode=755"; - r = tmpfs_patch_options(options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); + r = tmpfs_patch_options(options, userns, uid_shift, uid_range, false, selinux_apifs_context, &buf); if (r < 0) return log_oom(); if (r > 0) diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h index 0daf145412..0eff8e1006 100644 --- a/src/nspawn/nspawn-mount.h +++ b/src/nspawn/nspawn-mount.h @@ -58,7 +58,7 @@ int custom_mount_compare(const void *a, const void *b); int mount_all(const char *dest, bool use_userns, bool in_userns, bool use_netns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); int mount_sysfs(const char *dest); -int mount_cgroups(const char *dest, bool unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); +int mount_cgroups(const char *dest, bool unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context, bool use_cgns); int mount_systemd_cgroup_writable(const char *dest, bool unified_requested); int mount_custom(const char *dest, CustomMount *mounts, unsigned n, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index b1c012a9e4..fcf14bba4c 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -194,6 +194,7 @@ static int arg_settings_trusted = -1; static char **arg_parameters = NULL; static const char *arg_container_service_name = "systemd-nspawn"; static bool arg_notify_ready = false; +static bool arg_use_cgns = true; static void help(void) { printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" @@ -215,10 +216,10 @@ static void help(void) { " --uuid=UUID Set a specific machine UUID for the container\n" " -S --slice=SLICE Place the container in the specified slice\n" " --property=NAME=VALUE Set scope unit property\n" - " -U --private-users=pick Run within user namespace, pick UID/GID range automatically\n" + " -U --private-users=pick Run within user namespace, autoselect UID/GID range\n" " --private-users[=UIDBASE[:NUIDS]]\n" - " Run within user namespace, user configured UID/GID range\n" - " --private-user-chown Adjust OS tree file ownership for private UID/GID range\n" + " Similar, but with user configured UID/GID range\n" + " --private-user-chown Adjust OS tree ownership to private UID/GID range\n" " --private-network Disable network in container\n" " --network-interface=INTERFACE\n" " Assign an existing network interface to the\n" @@ -235,11 +236,10 @@ static void help(void) { " Add an additional virtual Ethernet link between\n" " host and container\n" " --network-bridge=INTERFACE\n" - " Add a virtual Ethernet connection between host\n" - " and container and add it to an existing bridge on\n" - " the host\n" - " --network-zone=NAME Add a virtual Ethernet connection to the container,\n" - " and add it to an automatically managed bridge interface\n" + " Add a virtual Ethernet connection to the container\n" + " and attach it to an existing bridge on the host\n" + " --network-zone=NAME Similar, but attach the new interface to an\n" + " an automatically managed bridge interface\n" " -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n" " Expose a container IP port on the host\n" " -Z --selinux-context=SECLABEL\n" @@ -268,14 +268,12 @@ static void help(void) { " --overlay-ro=PATH[:PATH...]:PATH\n" " Similar, but creates a read-only overlay mount\n" " -E --setenv=NAME=VALUE Pass an environment variable to PID 1\n" - " --share-system Share system namespaces with host\n" " --register=BOOLEAN Register container as machine\n" " --keep-unit Do not register a scope for the machine, reuse\n" " the service unit nspawn is running in\n" " --volatile[=MODE] Run the system in volatile mode\n" " --settings=BOOLEAN Load additional settings from .nspawn file\n" - " --notify-ready=BOOLEAN Receive notifications from the container's init process,\n" - " accepted values: yes and no\n" + " --notify-ready=BOOLEAN Receive notifications from the child init process\n" , program_invocation_short_name); } @@ -404,7 +402,7 @@ static int parse_argv(int argc, char *argv[]) { { "selinux-context", required_argument, NULL, 'Z' }, { "selinux-apifs-context", required_argument, NULL, 'L' }, { "quiet", no_argument, NULL, 'q' }, - { "share-system", no_argument, NULL, ARG_SHARE_SYSTEM }, + { "share-system", no_argument, NULL, ARG_SHARE_SYSTEM }, /* not documented */ { "register", required_argument, NULL, ARG_REGISTER }, { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, { "network-interface", required_argument, NULL, ARG_NETWORK_INTERFACE }, @@ -813,6 +811,8 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_SHARE_SYSTEM: + /* We don't officially support this anymore, except for compat reasons. People should use the + * $SYSTEMD_NSPAWN_SHARE_SYSTEM environment variable instead. */ arg_share_system = true; break; @@ -1017,6 +1017,9 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached("Unhandled option"); } + if (getenv_bool("SYSTEMD_NSPAWN_SHARE_SYSTEM") > 0) + arg_share_system = true; + if (arg_share_system) arg_register = false; @@ -1024,7 +1027,7 @@ static int parse_argv(int argc, char *argv[]) { arg_userns_chown = true; if (arg_start_mode != START_PID1 && arg_share_system) { - log_error("--boot and --share-system may not be combined."); + log_error("--boot and SYSTEMD_NSPAWN_SHARE_SYSTEM=1 may not be combined."); return -EINVAL; } @@ -1104,6 +1107,12 @@ static int parse_argv(int argc, char *argv[]) { if (e) arg_container_service_name = e; + r = getenv_bool("SYSTEMD_NSPAWN_USE_CGNS"); + if (r < 0) + arg_use_cgns = cg_ns_supported(); + else + arg_use_cgns = r; + return 1; } @@ -1247,24 +1256,39 @@ static int setup_resolv_conf(const char *dest) { /* Fix resolv.conf, if possible */ where = prefix_roota(dest, "/etc/resolv.conf"); + if (access("/usr/lib/systemd/resolv.conf", F_OK) >= 0) { + /* resolved is enabled on the host. In this, case bind mount its static resolv.conf file into the + * container, so that the container can use the host's resolver. Given that network namespacing is + * disabled it's only natural of the container also uses the host's resolver. It also has the big + * advantage that the container will be able to follow the host's DNS server configuration changes + * transparently. */ + + if (mount("/usr/lib/systemd/resolv.conf", where, NULL, MS_BIND, NULL) < 0) + log_warning_errno(errno, "Failed to mount /etc/resolv.conf in the container, ignoring: %m"); + else { + if (mount(NULL, where, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL) < 0) + return log_error_errno(errno, "Failed to remount /etc/resolv.conf read-only: %m"); + + return 0; + } + } + + /* If that didn't work, let's copy the file */ r = copy_file("/etc/resolv.conf", where, O_TRUNC|O_NOFOLLOW, 0644, 0); if (r < 0) { - /* If the file already exists as symlink, let's - * suppress the warning, under the assumption that - * resolved or something similar runs inside and the - * symlink points there. + /* If the file already exists as symlink, let's suppress the warning, under the assumption that + * resolved or something similar runs inside and the symlink points there. * - * If the disk image is read-only, there's also no - * point in complaining. + * If the disk image is read-only, there's also no point in complaining. */ log_full_errno(IN_SET(r, -ELOOP, -EROFS) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to copy /etc/resolv.conf to %s: %m", where); + "Failed to copy /etc/resolv.conf to %s, ignoring: %m", where); return 0; } r = userns_lchown(where, 0, 0); if (r < 0) - log_warning_errno(r, "Failed to chown /etc/resolv.conf: %m"); + log_warning_errno(r, "Failed to chown /etc/resolv.conf, ignoring: %m"); return 0; } @@ -1294,7 +1318,7 @@ static int setup_boot_id(const char *dest) { if (mount(from, to, NULL, MS_BIND, NULL) < 0) r = log_error_errno(errno, "Failed to bind mount boot id: %m"); else if (mount(NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL) < 0) - log_warning_errno(errno, "Failed to make boot id read-only, ignoring: %m"); + r = log_error_errno(errno, "Failed to make boot id read-only: %m"); (void) unlink(from); return r; @@ -1794,17 +1818,18 @@ static int dissect_image( char **root_device, bool *root_device_rw, char **home_device, bool *home_device_rw, char **srv_device, bool *srv_device_rw, + char **esp_device, bool *secondary) { #ifdef HAVE_BLKID - int home_nr = -1, srv_nr = -1; + int home_nr = -1, srv_nr = -1, esp_nr = -1; #ifdef GPT_ROOT_NATIVE int root_nr = -1; #endif #ifdef GPT_ROOT_SECONDARY int secondary_root_nr = -1; #endif - _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL, *generic = NULL; + _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL, *esp = NULL, *generic = NULL; _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; _cleanup_udev_device_unref_ struct udev_device *d = NULL; _cleanup_blkid_free_probe_ blkid_probe b = NULL; @@ -1822,6 +1847,7 @@ static int dissect_image( assert(root_device); assert(home_device); assert(srv_device); + assert(esp_device); assert(secondary); assert(arg_image); @@ -2035,6 +2061,16 @@ static int dissect_image( r = free_and_strdup(&srv, node); if (r < 0) return log_oom(); + } else if (sd_id128_equal(type_id, GPT_ESP)) { + + if (esp && nr >= esp_nr) + continue; + + esp_nr = nr; + + r = free_and_strdup(&esp, node); + if (r < 0) + return log_oom(); } #ifdef GPT_ROOT_NATIVE else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) { @@ -2152,6 +2188,11 @@ static int dissect_image( *srv_device_rw = srv_rw; } + if (esp) { + *esp_device = esp; + esp = NULL; + } + return 0; #else log_error("--image= is not supported, compiled without blkid support."); @@ -2284,7 +2325,8 @@ static int mount_devices( const char *where, const char *root_device, bool root_device_rw, const char *home_device, bool home_device_rw, - const char *srv_device, bool srv_device_rw) { + const char *srv_device, bool srv_device_rw, + const char *esp_device) { int r; assert(where); @@ -2307,6 +2349,27 @@ static int mount_devices( return log_error_errno(r, "Failed to mount server data directory: %m"); } + if (esp_device) { + const char *mp, *x; + + /* Mount the ESP to /efi if it exists and is empty. If it doesn't exist, use /boot instead. */ + + mp = "/efi"; + x = strjoina(arg_directory, mp); + r = dir_is_empty(x); + if (r == -ENOENT) { + mp = "/boot"; + x = strjoina(arg_directory, mp); + r = dir_is_empty(x); + } + + if (r > 0) { + r = mount_device(esp_device, arg_directory, mp, true); + if (r < 0) + return log_error_errno(r, "Failed to mount ESP: %m"); + } + } + return 0; } @@ -2589,9 +2652,25 @@ static int inner_child( return -ESRCH; } - r = mount_systemd_cgroup_writable("", arg_unified_cgroup_hierarchy); - if (r < 0) - return r; + if (arg_use_cgns && cg_ns_supported()) { + r = unshare(CLONE_NEWCGROUP); + if (r < 0) + return log_error_errno(errno, "Failed to unshare cgroup namespace"); + r = mount_cgroups( + "", + arg_unified_cgroup_hierarchy, + arg_userns_mode != USER_NAMESPACE_NO, + arg_uid_shift, + arg_uid_range, + arg_selinux_apifs_context, + arg_use_cgns); + if (r < 0) + return r; + } else { + r = mount_systemd_cgroup_writable("", arg_unified_cgroup_hierarchy); + if (r < 0) + return r; + } r = reset_uid_gid(); if (r < 0) @@ -2780,6 +2859,7 @@ static int outer_child( const char *root_device, bool root_device_rw, const char *home_device, bool home_device_rw, const char *srv_device, bool srv_device_rw, + const char *esp_device, bool interactive, bool secondary, int pid_socket, @@ -2841,7 +2921,8 @@ static int outer_child( r = mount_devices(directory, root_device, root_device_rw, home_device, home_device_rw, - srv_device, srv_device_rw); + srv_device, srv_device_rw, + esp_device); if (r < 0) return r; @@ -2973,15 +3054,18 @@ static int outer_child( if (r < 0) return r; - r = mount_cgroups( - directory, - arg_unified_cgroup_hierarchy, - arg_userns_mode != USER_NAMESPACE_NO, - arg_uid_shift, - arg_uid_range, - arg_selinux_apifs_context); - if (r < 0) - return r; + if (!arg_use_cgns || !cg_ns_supported()) { + r = mount_cgroups( + directory, + arg_unified_cgroup_hierarchy, + arg_userns_mode != USER_NAMESPACE_NO, + arg_uid_shift, + arg_uid_range, + arg_selinux_apifs_context, + arg_use_cgns); + if (r < 0) + return r; + } r = mount_move_root(directory); if (r < 0) @@ -3444,7 +3528,7 @@ static int load_settings(void) { int main(int argc, char *argv[]) { - _cleanup_free_ char *device_path = NULL, *root_device = NULL, *home_device = NULL, *srv_device = NULL, *console = NULL; + _cleanup_free_ char *device_path = NULL, *root_device = NULL, *home_device = NULL, *srv_device = NULL, *esp_device = NULL, *console = NULL; bool root_device_rw = true, home_device_rw = true, srv_device_rw = true; _cleanup_close_ int master = -1, image_fd = -1; _cleanup_fdset_free_ FDSet *fds = NULL; @@ -3626,6 +3710,7 @@ int main(int argc, char *argv[]) { &root_device, &root_device_rw, &home_device, &home_device_rw, &srv_device, &srv_device_rw, + &esp_device, &secondary); if (r < 0) goto finish; @@ -3800,6 +3885,7 @@ int main(int argc, char *argv[]) { root_device, root_device_rw, home_device, home_device_rw, srv_device, srv_device_rw, + esp_device, interactive, secondary, pid_socket_pair[1], diff --git a/src/nss-systemd/Makefile b/src/nss-systemd/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/nss-systemd/Makefile @@ -0,0 +1 @@ +../Makefile
\ No newline at end of file diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c new file mode 100644 index 0000000000..7078c0c50c --- /dev/null +++ b/src/nss-systemd/nss-systemd.c @@ -0,0 +1,433 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <nss.h> + +#include "sd-bus.h" + +#include "bus-common-errors.h" +#include "env-util.h" +#include "macro.h" +#include "nss-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "user-util.h" +#include "util.h" + +#ifndef NOBODY_USER_NAME +#define NOBODY_USER_NAME "nobody" +#endif + +#ifndef NOBODY_GROUP_NAME +#define NOBODY_GROUP_NAME "nobody" +#endif + +static const struct passwd root_passwd = { + .pw_name = (char*) "root", + .pw_passwd = (char*) "x", /* see shadow file */ + .pw_uid = 0, + .pw_gid = 0, + .pw_gecos = (char*) "Super User", + .pw_dir = (char*) "/root", + .pw_shell = (char*) "/bin/sh", +}; + +static const struct passwd nobody_passwd = { + .pw_name = (char*) NOBODY_USER_NAME, + .pw_passwd = (char*) "*", /* locked */ + .pw_uid = 65534, + .pw_gid = 65534, + .pw_gecos = (char*) "User Nobody", + .pw_dir = (char*) "/", + .pw_shell = (char*) "/sbin/nologin", +}; + +static const struct group root_group = { + .gr_name = (char*) "root", + .gr_gid = 0, + .gr_passwd = (char*) "x", /* see shadow file */ + .gr_mem = (char*[]) { NULL }, +}; + +static const struct group nobody_group = { + .gr_name = (char*) NOBODY_GROUP_NAME, + .gr_gid = 65534, + .gr_passwd = (char*) "*", /* locked */ + .gr_mem = (char*[]) { NULL }, +}; + +NSS_GETPW_PROTOTYPES(systemd); +NSS_GETGR_PROTOTYPES(systemd); + +enum nss_status _nss_systemd_getpwnam_r( + const char *name, + struct passwd *pwd, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + uint32_t translated; + size_t l; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(pwd); + + if (!valid_user_group_name(name)) { + r = -EINVAL; + goto fail; + } + + /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */ + if (streq(name, root_passwd.pw_name)) { + *pwd = root_passwd; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + if (streq(name, nobody_passwd.pw_name)) { + *pwd = nobody_passwd; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + + /* Make sure that we don't go in circles when allocating a dynamic UID by checking our own database */ + if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LookupDynamicUserByName", + &error, + &reply, + "s", + name); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "u", &translated); + if (r < 0) + goto fail; + + l = strlen(name); + if (buflen < l+1) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memcpy(buffer, name, l+1); + + pwd->pw_name = buffer; + pwd->pw_uid = (uid_t) translated; + pwd->pw_gid = (uid_t) translated; + pwd->pw_gecos = (char*) "Dynamic User"; + pwd->pw_passwd = (char*) "*"; /* locked */ + pwd->pw_dir = (char*) "/"; + pwd->pw_shell = (char*) "/sbin/nologin"; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_systemd_getpwuid_r( + uid_t uid, + struct passwd *pwd, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + const char *translated; + size_t l; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + if (!uid_is_valid(uid)) { + r = -EINVAL; + goto fail; + } + + /* Synthesize data for the root user and for nobody in case they are missing from /etc/passwd */ + if (uid == root_passwd.pw_uid) { + *pwd = root_passwd; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + if (uid == nobody_passwd.pw_uid) { + *pwd = nobody_passwd; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + + if (uid <= SYSTEM_UID_MAX) + goto not_found; + + if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LookupDynamicUserByUID", + &error, + &reply, + "u", + (uint32_t) uid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "s", &translated); + if (r < 0) + goto fail; + + l = strlen(translated) + 1; + if (buflen < l) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memcpy(buffer, translated, l); + + pwd->pw_name = buffer; + pwd->pw_uid = uid; + pwd->pw_gid = uid; + pwd->pw_gecos = (char*) "Dynamic User"; + pwd->pw_passwd = (char*) "*"; /* locked */ + pwd->pw_dir = (char*) "/"; + pwd->pw_shell = (char*) "/sbin/nologin"; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_systemd_getgrnam_r( + const char *name, + struct group *gr, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + uint32_t translated; + size_t l; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(gr); + + if (!valid_user_group_name(name)) { + r = -EINVAL; + goto fail; + } + + /* Synthesize records for root and nobody, in case they are missing form /etc/group */ + if (streq(name, root_group.gr_name)) { + *gr = root_group; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + if (streq(name, nobody_group.gr_name)) { + *gr = nobody_group; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + + if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LookupDynamicUserByName", + &error, + &reply, + "s", + name); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "u", &translated); + if (r < 0) + goto fail; + + l = sizeof(char*) + strlen(name) + 1; + if (buflen < l) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memzero(buffer, sizeof(char*)); + strcpy(buffer + sizeof(char*), name); + + gr->gr_name = buffer + sizeof(char*); + gr->gr_gid = (gid_t) translated; + gr->gr_passwd = (char*) "*"; /* locked */ + gr->gr_mem = (char**) buffer; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_systemd_getgrgid_r( + gid_t gid, + struct group *gr, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + const char *translated; + size_t l; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + if (!gid_is_valid(gid)) { + r = -EINVAL; + goto fail; + } + + /* Synthesize records for root and nobody, in case they are missing from /etc/group */ + if (gid == root_group.gr_gid) { + *gr = root_group; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + if (gid == nobody_group.gr_gid) { + *gr = nobody_group; + *errnop = 0; + return NSS_STATUS_SUCCESS; + } + + if (gid <= SYSTEM_GID_MAX) + goto not_found; + + if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LookupDynamicUserByUID", + &error, + &reply, + "u", + (uint32_t) gid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "s", &translated); + if (r < 0) + goto fail; + + l = sizeof(char*) + strlen(translated) + 1; + if (buflen < l) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memzero(buffer, sizeof(char*)); + strcpy(buffer + sizeof(char*), translated); + + gr->gr_name = buffer + sizeof(char*); + gr->gr_gid = gid; + gr->gr_passwd = (char*) "*"; /* locked */ + gr->gr_mem = (char**) buffer; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} diff --git a/src/nss-systemd/nss-systemd.sym b/src/nss-systemd/nss-systemd.sym new file mode 100644 index 0000000000..955078788a --- /dev/null +++ b/src/nss-systemd/nss-systemd.sym @@ -0,0 +1,17 @@ +/*** + This file is part of systemd. + + 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. +***/ + +{ +global: + _nss_systemd_getpwnam_r; + _nss_systemd_getpwuid_r; + _nss_systemd_getgrnam_r; + _nss_systemd_getgrgid_r; +local: *; +}; diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c index 6ae3750417..07e4cd7d1d 100644 --- a/src/resolve/resolve-tool.c +++ b/src/resolve/resolve-tool.c @@ -1542,7 +1542,7 @@ static void help(void) { "%1$s [OPTIONS...] --statistics\n" "%1$s [OPTIONS...] --reset-statistics\n" "\n" - "Resolve domain names, IPv4 and IPv6 addresses, DNS resource records, and services.\n\n" + "Resolve domain names, IPv4 and IPv6 addresses, DNS records, and services.\n\n" " -h --help Show this help\n" " --version Show package version\n" " --no-pager Do not pipe output into a pager\n" diff --git a/src/run/run.c b/src/run/run.c index 58fa49a4d1..1917ffd857 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -257,11 +257,9 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_NICE: - r = safe_atoi(optarg, &arg_nice); - if (r < 0 || arg_nice < PRIO_MIN || arg_nice >= PRIO_MAX) { - log_error("Failed to parse nice value"); - return -EINVAL; - } + r = parse_nice(optarg, &arg_nice); + if (r < 0) + return log_error_errno(r, "Failed to parse nice value: %s", optarg); arg_nice_set = true; break; diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index ea020b517b..7774d607c7 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -84,7 +84,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen if (isempty(eq)) r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY); else { - r = parse_percent(eq); + r = parse_percent_unbounded(eq); if (r <= 0) { log_error_errno(r, "CPU quota '%s' invalid.", eq); return -EINVAL; @@ -199,11 +199,12 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur); } else if (STR_IN_SET(field, - "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting", - "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies", - "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit", - "PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges", - "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute")) { + "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting", + "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies", + "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit", + "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", "NoNewPrivileges", + "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute", + "RestrictRealtime", "DynamicUser")) { r = parse_boolean(eq); if (r < 0) @@ -365,15 +366,13 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen } } else if (streq(field, "Nice")) { - int32_t i; + int n; - r = safe_atoi32(eq, &i); - if (r < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } + r = parse_nice(eq, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse nice value: %s", eq); - r = sd_bus_message_append(m, "v", "i", i); + r = sd_bus_message_append(m, "v", "i", (int32_t) n); } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) { const char *p; diff --git a/src/shared/install.c b/src/shared/install.c index 7b49e1ece9..6c7eb9b2ef 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -393,19 +393,40 @@ void unit_file_dump_changes(int r, const char *verb, const UnitFileChange *chang log_error_errno(r, "Failed to %s: %m.", verb); } +/** + * Checks if two paths or symlinks from wd are the same, when root is the root of the filesystem. + * wc should be the full path in the host file system. + */ +static bool chroot_symlinks_same(const char *root, const char *wd, const char *a, const char *b) { + assert(path_is_absolute(wd)); + + /* This will give incorrect results if the paths are relative and go outside + * of the chroot. False negatives are possible. */ + + a = strjoina(path_is_absolute(a) ? root : wd, "/", a); + b = strjoina(path_is_absolute(b) ? root : wd, "/", b); + return path_equal_or_files_same(a, b); +} + static int create_symlink( + const LookupPaths *paths, const char *old_path, const char *new_path, bool force, UnitFileChange **changes, unsigned *n_changes) { - _cleanup_free_ char *dest = NULL; + _cleanup_free_ char *dest = NULL, *dirname = NULL; + const char *rp; int r; assert(old_path); assert(new_path); + rp = skip_root(paths, old_path); + if (rp) + old_path = rp; + /* Actually create a symlink, and remember that we did. Is * smart enough to check if there's already a valid symlink in * place. @@ -436,7 +457,11 @@ static int create_symlink( return r; } - if (path_equal(dest, old_path)) + dirname = dirname_malloc(new_path); + if (!dirname) + return -ENOMEM; + + if (chroot_symlinks_same(paths->root_dir, dirname, dest, old_path)) return 1; if (!force) { @@ -620,7 +645,7 @@ static int remove_marked_symlinks( fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); if (fd < 0) - return -errno; + return errno == ENOENT ? 0 : -errno; do { int q, cfd; @@ -777,7 +802,7 @@ static int find_symlinks( assert(config_path); assert(same_name_link); - fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC); if (fd < 0) { if (IN_SET(errno, ENOENT, ENOTDIR, EACCES)) return 0; @@ -903,6 +928,10 @@ static int install_info_may_process( return 0; } +/** + * Adds a new UnitFileInstallInfo entry under name in the InstallContext.will_process + * hashmap, or retrieves the existing one if already present. + */ static int install_info_add( InstallContext *c, const char *name, @@ -1334,9 +1363,8 @@ static int install_info_follow( } /** - * Search for the unit file. If the unit name is a symlink, - * follow the symlink to the target, maybe more than once. - * Propagate the instance name if present. + * Search for the unit file. If the unit name is a symlink, follow the symlink to the + * target, maybe more than once. Propagate the instance name if present. */ static int install_info_traverse( UnitFileScope scope, @@ -1421,6 +1449,10 @@ static int install_info_traverse( return 0; } +/** + * Call install_info_add() with name_or_path as the path (if name_or_path starts with "/") + * or the name (otherwise). root_dir is prepended to the path. + */ static int install_info_add_auto( InstallContext *c, const LookupPaths *paths, @@ -1479,7 +1511,6 @@ static int install_info_symlink_alias( STRV_FOREACH(s, i->aliases) { _cleanup_free_ char *alias_path = NULL, *dst = NULL; - const char *rp; q = install_full_printf(i, *s, &dst); if (q < 0) @@ -1489,9 +1520,7 @@ static int install_info_symlink_alias( if (!alias_path) return -ENOMEM; - rp = skip_root(paths, i->path); - - q = create_symlink(rp ?: i->path, alias_path, force, changes, n_changes); + q = create_symlink(paths, i->path, alias_path, force, changes, n_changes); if (r == 0) r = q; } @@ -1535,7 +1564,6 @@ static int install_info_symlink_wants( STRV_FOREACH(s, list) { _cleanup_free_ char *path = NULL, *dst = NULL; - const char *rp; q = install_full_printf(i, *s, &dst); if (q < 0) @@ -1550,9 +1578,7 @@ static int install_info_symlink_wants( if (!path) return -ENOMEM; - rp = skip_root(paths, i->path); - - q = create_symlink(rp ?: i->path, path, true, changes, n_changes); + q = create_symlink(paths, i->path, path, true, changes, n_changes); if (r == 0) r = q; } @@ -1569,7 +1595,6 @@ static int install_info_symlink_link( unsigned *n_changes) { _cleanup_free_ char *path = NULL; - const char *rp; int r; assert(i); @@ -1587,9 +1612,7 @@ static int install_info_symlink_link( if (!path) return -ENOMEM; - rp = skip_root(paths, i->path); - - return create_symlink(rp ?: i->path, path, force, changes, n_changes); + return create_symlink(paths, i->path, path, force, changes, n_changes); } static int install_info_apply( @@ -1765,7 +1788,7 @@ int unit_file_mask( if (!path) return -ENOMEM; - q = create_symlink("/dev/null", path, force, changes, n_changes); + q = create_symlink(&paths, "/dev/null", path, force, changes, n_changes); if (q < 0 && r >= 0) r = q; } @@ -1925,14 +1948,12 @@ int unit_file_link( r = 0; STRV_FOREACH(i, todo) { _cleanup_free_ char *new_path = NULL; - const char *old_path; - old_path = skip_root(&paths, *i); new_path = path_make_absolute(basename(*i), config_path); if (!new_path) return -ENOMEM; - q = create_symlink(old_path ?: *i, new_path, force, changes, n_changes); + q = create_symlink(&paths, *i, new_path, force, changes, n_changes); if (q < 0 && r >= 0) r = q; } @@ -1967,7 +1988,6 @@ int unit_file_revert( unsigned *n_changes) { _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - /* _cleanup_(install_context_done) InstallContext c = {}; */ _cleanup_lookup_paths_free_ LookupPaths paths = {}; _cleanup_strv_free_ char **todo = NULL; size_t n_todo = 0, n_allocated = 0; @@ -2312,7 +2332,7 @@ int unit_file_set_default( _cleanup_lookup_paths_free_ LookupPaths paths = {}; _cleanup_(install_context_done) InstallContext c = {}; UnitFileInstallInfo *i; - const char *new_path, *old_path; + const char *new_path; int r; assert(scope >= 0); @@ -2335,10 +2355,8 @@ int unit_file_set_default( if (r < 0) return r; - old_path = skip_root(&paths, i->path); new_path = strjoina(paths.persistent_config, "/" SPECIAL_DEFAULT_TARGET); - - return create_symlink(old_path ?: i->path, new_path, force, changes, n_changes); + return create_symlink(&paths, i->path, new_path, force, changes, n_changes); } int unit_file_get_default( @@ -2685,19 +2703,26 @@ static int preset_prepare_one( InstallContext *plus, InstallContext *minus, LookupPaths *paths, - UnitFilePresetMode mode, const char *name, Presets presets, UnitFileChange **changes, unsigned *n_changes) { + _cleanup_(install_context_done) InstallContext tmp = {}; UnitFileInstallInfo *i; int r; - if (install_info_find(plus, name) || - install_info_find(minus, name)) + if (install_info_find(plus, name) || install_info_find(minus, name)) return 0; + r = install_info_discover(scope, &tmp, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + if (r < 0) + return r; + if (!streq(name, i->name)) { + log_debug("Skipping %s because is an alias for %s", name, i->name); + return 0; + } + r = query_presets(name, presets); if (r < 0) return r; @@ -2748,7 +2773,7 @@ int unit_file_preset( return r; STRV_FOREACH(i, files) { - r = preset_prepare_one(scope, &plus, &minus, &paths, mode, *i, presets, changes, n_changes); + r = preset_prepare_one(scope, &plus, &minus, &paths, *i, presets, changes, n_changes); if (r < 0) return r; } @@ -2809,7 +2834,7 @@ int unit_file_preset_all( continue; /* we don't pass changes[] in, because we want to handle errors on our own */ - r = preset_prepare_one(scope, &plus, &minus, &paths, mode, de->d_name, presets, NULL, 0); + r = preset_prepare_one(scope, &plus, &minus, &paths, de->d_name, presets, NULL, 0); if (r == -ERFKILL) r = unit_file_changes_add(changes, n_changes, UNIT_FILE_IS_MASKED, de->d_name, NULL); diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index d04728f505..f9d9c4ed62 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -45,6 +45,7 @@ #include "parse-util.h" #include "process-util.h" #include "sparse-endian.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "terminal-util.h" @@ -206,6 +207,108 @@ static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, Output return ellipsized; } +static int output_timestamp_monotonic(FILE *f, sd_journal *j, const char *monotonic) { + sd_id128_t boot_id; + uint64_t t; + int r; + + assert(f); + assert(j); + + r = -ENXIO; + if (monotonic) + r = safe_atou64(monotonic, &t); + if (r < 0) + r = sd_journal_get_monotonic_usec(j, &t, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + fprintf(f, "[%5llu.%06llu]", + (unsigned long long) (t / USEC_PER_SEC), + (unsigned long long) (t % USEC_PER_SEC)); + + return 1 + 5 + 1 + 6 + 1; +} + +static int output_timestamp_realtime(FILE *f, sd_journal *j, OutputMode mode, OutputFlags flags, const char *realtime) { + char buf[MAX(FORMAT_TIMESTAMP_MAX, 64)]; + struct tm *(*gettime_r)(const time_t *, struct tm *); + struct tm tm; + uint64_t x; + time_t t; + int r; + + assert(f); + assert(j); + + r = -ENXIO; + if (realtime) + r = safe_atou64(realtime, &x); + if (r < 0) + r = sd_journal_get_realtime_usec(j, &x); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + if (mode == OUTPUT_SHORT_FULL) { + const char *k; + + if (flags & OUTPUT_UTC) + k = format_timestamp_utc(buf, sizeof(buf), x); + else + k = format_timestamp(buf, sizeof(buf), x); + if (!k) { + log_error("Failed to format timestamp."); + return -EINVAL; + } + + } else { + gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r; + t = (time_t) (x / USEC_PER_SEC); + + switch (mode) { + + case OUTPUT_SHORT_UNIX: + xsprintf(buf, "%10llu.%06llu", (unsigned long long) t, (unsigned long long) (x % USEC_PER_SEC)); + break; + + case OUTPUT_SHORT_ISO: + if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm)) <= 0) { + log_error("Failed for format ISO time"); + return -EINVAL; + } + break; + + case OUTPUT_SHORT: + case OUTPUT_SHORT_PRECISE: + + if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)) <= 0) { + log_error("Failed to format syslog time"); + return -EINVAL; + } + + if (mode == OUTPUT_SHORT_PRECISE) { + size_t k; + + assert(sizeof(buf) > strlen(buf)); + k = sizeof(buf) - strlen(buf); + + r = snprintf(buf + strlen(buf), k, ".%06llu", (unsigned long long) (x % USEC_PER_SEC)); + if (r <= 0 || (size_t) r >= k) { /* too long? */ + log_error("Failed to format precise time"); + return -EINVAL; + } + } + break; + + default: + assert_not_reached("Unknown time format"); + } + } + + fputs(buf, f); + return (int) strlen(buf); +} + static int output_short( FILE *f, sd_journal *j, @@ -305,78 +408,15 @@ static int output_short( if (priority_len == 1 && *priority >= '0' && *priority <= '7') p = *priority - '0'; - if (mode == OUTPUT_SHORT_MONOTONIC) { - uint64_t t; - sd_id128_t boot_id; - - r = -ENOENT; - - if (monotonic) - r = safe_atou64(monotonic, &t); - - if (r < 0) - r = sd_journal_get_monotonic_usec(j, &t, &boot_id); - - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - fprintf(f, "[%5llu.%06llu]", - (unsigned long long) (t / USEC_PER_SEC), - (unsigned long long) (t % USEC_PER_SEC)); - - n += 1 + 5 + 1 + 6 + 1; - - } else { - char buf[64]; - uint64_t x; - time_t t; - struct tm tm; - struct tm *(*gettime_r)(const time_t *, struct tm *); - - r = -ENOENT; - gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r; - - if (realtime) - r = safe_atou64(realtime, &x); - - if (r < 0) - r = sd_journal_get_realtime_usec(j, &x); - - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); - - t = (time_t) (x / USEC_PER_SEC); - - switch (mode) { - - case OUTPUT_SHORT_UNIX: - r = snprintf(buf, sizeof(buf), "%10llu.%06llu", (unsigned long long) t, (unsigned long long) (x % USEC_PER_SEC)); - break; - - case OUTPUT_SHORT_ISO: - r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm)); - break; - - case OUTPUT_SHORT_PRECISE: - r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)); - if (r > 0) - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%06llu", (unsigned long long) (x % USEC_PER_SEC)); - break; - - default: - r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)); - } - - if (r <= 0) { - log_error("Failed to format time."); - return -EINVAL; - } - - fputs(buf, f); - n += strlen(buf); - } + if (mode == OUTPUT_SHORT_MONOTONIC) + r = output_timestamp_monotonic(f, j, monotonic); + else + r = output_timestamp_realtime(f, j, mode, flags, realtime); + if (r < 0) + return r; + n += r; - if (hostname && (flags & OUTPUT_NO_HOSTNAME)) { + if (flags & OUTPUT_NO_HOSTNAME) { /* Suppress display of the hostname if this is requested. */ hostname = NULL; hostname_len = 0; @@ -910,6 +950,7 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])( [OUTPUT_SHORT_PRECISE] = output_short, [OUTPUT_SHORT_MONOTONIC] = output_short, [OUTPUT_SHORT_UNIX] = output_short, + [OUTPUT_SHORT_FULL] = output_short, [OUTPUT_VERBOSE] = output_verbose, [OUTPUT_EXPORT] = output_export, [OUTPUT_JSON] = output_json, diff --git a/src/shared/output-mode.c b/src/shared/output-mode.c index bec53ee0ae..67d8208ad2 100644 --- a/src/shared/output-mode.c +++ b/src/shared/output-mode.c @@ -22,6 +22,7 @@ static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { [OUTPUT_SHORT] = "short", + [OUTPUT_SHORT_FULL] = "short-full", [OUTPUT_SHORT_ISO] = "short-iso", [OUTPUT_SHORT_PRECISE] = "short-precise", [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h index f37189e57f..ff29dafcb5 100644 --- a/src/shared/output-mode.h +++ b/src/shared/output-mode.h @@ -23,6 +23,7 @@ typedef enum OutputMode { OUTPUT_SHORT, + OUTPUT_SHORT_FULL, OUTPUT_SHORT_ISO, OUTPUT_SHORT_PRECISE, OUTPUT_SHORT_MONOTONIC, diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 114c4f1703..ebedb11b14 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -4770,7 +4770,7 @@ static int show_one( else if (streq(verb, "status")) { print_status_info(bus, &info, ellipsized); - if (info.active_state && STR_IN_SET(info.active_state, "inactive", "failed")) + if (info.active_state && !STR_IN_SET(info.active_state, "active", "reloading")) r = EXIT_PROGRAM_NOT_RUNNING; else r = EXIT_PROGRAM_RUNNING_OR_SERVICE_OK; @@ -5569,10 +5569,12 @@ static int enable_sysv_units(const char *verb, char **args) { if (!found_sysv) continue; - if (found_native) - log_info("Synchronizing state of %s with SysV service script with %s.", name, argv[0]); - else - log_info("%s is not a native service, redirecting to systemd-sysv-install.", name); + if (!arg_quiet) { + if (found_native) + log_info("Synchronizing state of %s with SysV service script with %s.", name, argv[0]); + else + log_info("%s is not a native service, redirecting to systemd-sysv-install.", name); + } if (!isempty(arg_root)) argv[c++] = q = strappend("--root=", arg_root); @@ -5673,6 +5675,29 @@ static int mangle_names(char **original_names, char ***mangled_names) { return 0; } +static int normalize_names(char **names, bool warn_if_path) { + char **u; + bool was_path = false; + + STRV_FOREACH(u, names) { + int r; + + if (!is_path(*u)) + continue; + + r = free_and_strdup(u, basename(*u)); + if (r < 0) + return log_error_errno(r, "Failed to normalize unit file path: %m"); + + was_path = true; + } + + if (warn_if_path && was_path) + log_warning("Warning: Can't execute disable on the unit file path. Proceeding with the unit name."); + + return 0; +} + static int unit_exists(const char *unit) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -5740,6 +5765,12 @@ static int enable_unit(int argc, char *argv[], void *userdata) { return daemon_reload(argc, argv, userdata); } + if (streq(verb, "disable")) { + r = normalize_names(names, true); + if (r < 0) + return r; + } + if (install_client_side()) { if (streq(verb, "enable")) { r = unit_file_enable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); @@ -6563,9 +6594,9 @@ static void systemctl_help(void) { " --preset-mode= Apply only enable, only disable, or all presets\n" " --root=PATH Enable unit files in the specified root directory\n" " -n --lines=INTEGER Number of journal entries to show\n" - " -o --output=STRING Change journal output mode (short, short-iso,\n" - " short-precise, short-monotonic, verbose,\n" - " export, json, json-pretty, json-sse, cat)\n" + " -o --output=STRING Change journal output mode (short, short-precise,\n" + " short-iso, short-full, short-monotonic, short-unix,\n" + " verbose, export, json, json-pretty, json-sse, cat)\n" " --firmware-setup Tell the firmware to show the setup menu on next boot\n" " --plain Print unit dependencies as a list instead of a tree\n\n" "Unit Commands:\n" diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 787d68a009..5d72493725 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -1299,81 +1299,6 @@ static bool item_equal(Item *a, Item *b) { return true; } -static bool valid_user_group_name(const char *u) { - const char *i; - long sz; - - if (isempty(u)) - return false; - - if (!(u[0] >= 'a' && u[0] <= 'z') && - !(u[0] >= 'A' && u[0] <= 'Z') && - u[0] != '_') - return false; - - for (i = u+1; *i; i++) { - if (!(*i >= 'a' && *i <= 'z') && - !(*i >= 'A' && *i <= 'Z') && - !(*i >= '0' && *i <= '9') && - *i != '_' && - *i != '-') - return false; - } - - sz = sysconf(_SC_LOGIN_NAME_MAX); - assert_se(sz > 0); - - if ((size_t) (i-u) > (size_t) sz) - return false; - - if ((size_t) (i-u) > UT_NAMESIZE - 1) - return false; - - return true; -} - -static bool valid_gecos(const char *d) { - - if (!d) - return false; - - if (!utf8_is_valid(d)) - return false; - - if (string_has_cc(d, NULL)) - return false; - - /* Colons are used as field separators, and hence not OK */ - if (strchr(d, ':')) - return false; - - return true; -} - -static bool valid_home(const char *p) { - - if (isempty(p)) - return false; - - if (!utf8_is_valid(p)) - return false; - - if (string_has_cc(p, NULL)) - return false; - - if (!path_is_absolute(p)) - return false; - - if (!path_is_safe(p)) - return false; - - /* Colons are used as field separators, and hence not OK */ - if (strchr(p, ':')) - return false; - - return true; -} - static int parse_line(const char *fname, unsigned line, const char *buffer) { static const Specifier specifier_table[] = { diff --git a/src/test/test-calendarspec.c b/src/test/test-calendarspec.c index 4a2b93de59..57d9da4855 100644 --- a/src/test/test-calendarspec.c +++ b/src/test/test-calendarspec.c @@ -88,6 +88,27 @@ static void test_next(const char *input, const char *new_tz, usec_t after, usec_ tzset(); } +static void test_timestamp(void) { + char buf[FORMAT_TIMESTAMP_MAX]; + _cleanup_free_ char *t = NULL; + CalendarSpec *c; + usec_t x, y; + + /* Ensure that a timestamp is also a valid calendar specification. Convert forth and back */ + + x = now(CLOCK_REALTIME); + + assert_se(format_timestamp_us(buf, sizeof(buf), x)); + printf("%s\n", buf); + assert_se(calendar_spec_from_string(buf, &c) >= 0); + assert_se(calendar_spec_to_string(c, &t) >= 0); + calendar_spec_free(c); + printf("%s\n", t); + + assert_se(parse_timestamp(t, &y) >= 0); + assert_se(y == x); +} + int main(int argc, char* argv[]) { CalendarSpec *c; @@ -155,5 +176,7 @@ int main(int argc, char* argv[]) { assert_se(calendar_spec_from_string("00:00:00/0.00000001", &c) < 0); assert_se(calendar_spec_from_string("00:00:00.0..00.9", &c) < 0); + test_timestamp(); + return 0; } diff --git a/src/test/test-condition.c b/src/test/test-condition.c index 987862f1c6..66003aa6bd 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -25,6 +25,7 @@ #include "audit-util.h" #include "condition.h" #include "hostname-util.h" +#include "id128-util.h" #include "ima-util.h" #include "log.h" #include "macro.h" @@ -142,9 +143,14 @@ static void test_condition_test_host(void) { hostname = gethostname_malloc(); assert_se(hostname); - condition = condition_new(CONDITION_HOST, hostname, false, false); - assert_se(condition_test(condition)); - condition_free(condition); + /* if hostname looks like an id128 then skip testing it */ + if (id128_is_valid(hostname)) + log_notice("hostname is an id128, skipping test"); + else { + condition = condition_new(CONDITION_HOST, hostname, false, false); + assert_se(condition_test(condition)); + condition_free(condition); + } } static void test_condition_test_architecture(void) { diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 77ef4e8b2a..1d24115b5c 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -33,6 +33,7 @@ #include "test-helper.h" #include "unit.h" #include "util.h" +#include "virt.h" typedef void (*test_function_t)(Manager *m); @@ -91,6 +92,16 @@ static void test_exec_personality(Manager *m) { #elif defined(__s390__) test(m, "exec-personality-s390.service", 0, CLD_EXITED); +#elif defined(__powerpc64__) +# if __BYTE_ORDER == __BIG_ENDIAN + test(m, "exec-personality-ppc64.service", 0, CLD_EXITED); +# else + test(m, "exec-personality-ppc64le.service", 0, CLD_EXITED); +# endif + +#elif defined(__aarch64__) + test(m, "exec-personality-aarch64.service", 0, CLD_EXITED); + #elif defined(__i386__) test(m, "exec-personality-x86.service", 0, CLD_EXITED); #endif @@ -111,6 +122,10 @@ static void test_exec_privatetmp(Manager *m) { } static void test_exec_privatedevices(Manager *m) { + if (detect_container() > 0) { + log_notice("testing in container, skipping private device tests"); + return; + } test(m, "exec-privatedevices-yes.service", 0, CLD_EXITED); test(m, "exec-privatedevices-no.service", 0, CLD_EXITED); } diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index e0c040f39b..93eec3ef9c 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -83,47 +83,35 @@ static void test_get_files_in_directory(void) { } static void test_var_tmp(void) { - char *tmp_dir = NULL; - char *tmpdir_backup = NULL; - const char *default_var_tmp = NULL; - const char *var_name; - bool do_overwrite = true; - - default_var_tmp = "/var/tmp"; - var_name = "TMPDIR"; - - if (getenv(var_name) != NULL) { - tmpdir_backup = strdup(getenv(var_name)); - assert_se(tmpdir_backup != NULL); - } - - unsetenv(var_name); + _cleanup_free_ char *tmpdir_backup = NULL; + const char *tmp_dir = NULL, *t; - var_tmp(&tmp_dir); - assert_se(!strcmp(tmp_dir, default_var_tmp)); - - free(tmp_dir); + t = getenv("TMPDIR"); + if (t) { + tmpdir_backup = strdup(t); + assert_se(tmpdir_backup); + } - setenv(var_name, "/tmp", do_overwrite); - assert_se(!strcmp(getenv(var_name), "/tmp")); + assert(unsetenv("TMPDIR") >= 0); - var_tmp(&tmp_dir); - assert_se(!strcmp(tmp_dir, "/tmp")); + assert_se(var_tmp_dir(&tmp_dir) >= 0); + assert_se(streq(tmp_dir, "/var/tmp")); - free(tmp_dir); + assert_se(setenv("TMPDIR", "/tmp", true) >= 0); + assert_se(streq(getenv("TMPDIR"), "/tmp")); - setenv(var_name, "/88_does_not_exist_88", do_overwrite); - assert_se(!strcmp(getenv(var_name), "/88_does_not_exist_88")); + assert_se(var_tmp_dir(&tmp_dir) >= 0); + assert_se(streq(tmp_dir, "/tmp")); - var_tmp(&tmp_dir); - assert_se(!strcmp(tmp_dir, default_var_tmp)); + assert_se(setenv("TMPDIR", "/88_does_not_exist_88", true) >= 0); + assert_se(streq(getenv("TMPDIR"), "/88_does_not_exist_88")); - free(tmp_dir); + assert_se(var_tmp_dir(&tmp_dir) >= 0); + assert_se(streq(tmp_dir, "/var/tmp")); - if (tmpdir_backup != NULL) { - setenv(var_name, tmpdir_backup, do_overwrite); - assert_se(!strcmp(getenv(var_name), tmpdir_backup)); - free(tmpdir_backup); + if (tmpdir_backup) { + assert_se(setenv("TMPDIR", tmpdir_backup, true) >= 0); + assert_se(streq(getenv("TMPDIR"), tmpdir_backup)); } } diff --git a/src/test/test-id128.c b/src/test/test-id128.c index f01fbdd6b2..1c8e5549da 100644 --- a/src/test/test-id128.c +++ b/src/test/test-id128.c @@ -144,7 +144,7 @@ int main(int argc, char *argv[]) { assert_se(ftruncate(fd, 0) >= 0); assert_se(sd_id128_randomize(&id) >= 0); - assert_se(write(fd, id128_to_uuid_string(id, t), 36) == 36); + assert_se(write(fd, id128_to_uuid_string(id, q), 36) == 36); assert_se(lseek(fd, 0, SEEK_SET) == 0); assert_se(id128_read_fd(fd, ID128_PLAIN, &id2) == -EINVAL); diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c index 0a76308f72..d08014100b 100644 --- a/src/test/test-parse-util.c +++ b/src/test/test-parse-util.c @@ -493,6 +493,39 @@ static void test_parse_percent(void) { assert_se(parse_percent("1%%") == -EINVAL); } +static void test_parse_percent_unbounded(void) { + assert_se(parse_percent_unbounded("101%") == 101); + assert_se(parse_percent_unbounded("400%") == 400); +} + +static void test_parse_nice(void) { + int n; + + assert_se(parse_nice("0", &n) >= 0 && n == 0); + assert_se(parse_nice("+0", &n) >= 0 && n == 0); + assert_se(parse_nice("-1", &n) >= 0 && n == -1); + assert_se(parse_nice("-2", &n) >= 0 && n == -2); + assert_se(parse_nice("1", &n) >= 0 && n == 1); + assert_se(parse_nice("2", &n) >= 0 && n == 2); + assert_se(parse_nice("+1", &n) >= 0 && n == 1); + assert_se(parse_nice("+2", &n) >= 0 && n == 2); + assert_se(parse_nice("-20", &n) >= 0 && n == -20); + assert_se(parse_nice("19", &n) >= 0 && n == 19); + assert_se(parse_nice("+19", &n) >= 0 && n == 19); + + + assert_se(parse_nice("", &n) == -EINVAL); + assert_se(parse_nice("-", &n) == -EINVAL); + assert_se(parse_nice("+", &n) == -EINVAL); + assert_se(parse_nice("xx", &n) == -EINVAL); + assert_se(parse_nice("-50", &n) == -ERANGE); + assert_se(parse_nice("50", &n) == -ERANGE); + assert_se(parse_nice("+50", &n) == -ERANGE); + assert_se(parse_nice("-21", &n) == -ERANGE); + assert_se(parse_nice("20", &n) == -ERANGE); + assert_se(parse_nice("+20", &n) == -ERANGE); +} + int main(int argc, char *argv[]) { log_parse_environment(); log_open(); @@ -507,6 +540,8 @@ int main(int argc, char *argv[]) { test_safe_atoi16(); test_safe_atod(); test_parse_percent(); + test_parse_percent_unbounded(); + test_parse_nice(); return 0; } diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 6094d4c3e5..164a10d8a8 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -114,7 +114,8 @@ static void test_find_binary(const char *self) { assert_se(find_binary(self, &p) == 0); puts(p); - assert_se(endswith(p, "/lt-test-path-util")); + /* libtool might prefix the binary name with "lt-" */ + assert_se(endswith(p, "/lt-test-path-util") || endswith(p, "/test-path-util")); assert_se(path_is_absolute(p)); free(p); diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index 562ad4acb8..9ada46b1e9 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -40,6 +40,7 @@ #include "stdio-util.h" #include "string-util.h" #include "terminal-util.h" +#include "test-helper.h" #include "util.h" #include "virt.h" @@ -357,7 +358,7 @@ int main(int argc, char *argv[]) { (void) parse_pid(argv[1], &pid); test_get_process_comm(pid); } else { - test_get_process_comm(1); + TEST_REQ_RUNNING_SYSTEMD(test_get_process_comm(1)); test_get_process_comm(getpid()); } diff --git a/src/test/test-sigbus.c b/src/test/test-sigbus.c index 17b81747be..02b8e24308 100644 --- a/src/test/test-sigbus.c +++ b/src/test/test-sigbus.c @@ -29,6 +29,9 @@ int main(int argc, char *argv[]) { void *addr = NULL; uint8_t *p; +#ifdef __SANITIZE_ADDRESS__ + return EXIT_TEST_SKIP; +#endif sigbus_install(); assert_se(sigbus_pop(&addr) == 0); diff --git a/src/test/test-time.c b/src/test/test-time.c index ee7d55c5ab..7078a0374d 100644 --- a/src/test/test-time.c +++ b/src/test/test-time.c @@ -19,6 +19,7 @@ #include "strv.h" #include "time-util.h" +#include "random-util.h" static void test_parse_sec(void) { usec_t u; @@ -201,6 +202,48 @@ static void test_usec_sub(void) { assert_se(usec_sub(USEC_INFINITY, 5) == USEC_INFINITY); } +static void test_format_timestamp(void) { + unsigned i; + + for (i = 0; i < 100; i++) { + char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)]; + usec_t x, y; + + random_bytes(&x, sizeof(x)); + x = x % (2147483600 * USEC_PER_SEC) + 1; + + assert_se(format_timestamp(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); + + assert_se(format_timestamp_utc(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); + + assert_se(format_timestamp_us(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x == y); + + assert_se(format_timestamp_us_utc(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x == y); + + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + + /* The two calls above will run with a slightly different local time. Make sure we are in the same + * range however, but give enough leeway that this is unlikely to explode. And of course, + * format_timestamp_relative() scales the accuracy with the distance from the current time up to one + * month, cover for that too. */ + assert_se(y > x ? y - x : x - y <= USEC_PER_MONTH + USEC_PER_DAY); + } +} + int main(int argc, char *argv[]) { uintmax_t x; @@ -214,6 +257,7 @@ int main(int argc, char *argv[]) { test_get_timezones(); test_usec_add(); test_usec_sub(); + test_format_timestamp(); /* Ensure time_t is signed */ assert_cc((time_t) -1 < (time_t) 1); diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c index 8d1ec19f17..2a344a9f93 100644 --- a/src/test/test-user-util.c +++ b/src/test/test-user-util.c @@ -61,6 +61,88 @@ static void test_uid_ptr(void) { assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000); } +static void test_valid_user_group_name(void) { + assert_se(!valid_user_group_name(NULL)); + assert_se(!valid_user_group_name("")); + assert_se(!valid_user_group_name("1")); + assert_se(!valid_user_group_name("65535")); + assert_se(!valid_user_group_name("-1")); + assert_se(!valid_user_group_name("-kkk")); + assert_se(!valid_user_group_name("rööt")); + assert_se(!valid_user_group_name(".")); + assert_se(!valid_user_group_name("eff.eff")); + assert_se(!valid_user_group_name("foo\nbar")); + assert_se(!valid_user_group_name("0123456789012345678901234567890123456789")); + assert_se(!valid_user_group_name_or_id("aaa:bbb")); + + assert_se(valid_user_group_name("root")); + assert_se(valid_user_group_name("lennart")); + assert_se(valid_user_group_name("LENNART")); + assert_se(valid_user_group_name("_kkk")); + assert_se(valid_user_group_name("kkk-")); + assert_se(valid_user_group_name("kk-k")); + + assert_se(valid_user_group_name("some5")); + assert_se(!valid_user_group_name("5some")); + assert_se(valid_user_group_name("INNER5NUMBER")); +} + +static void test_valid_user_group_name_or_id(void) { + assert_se(!valid_user_group_name_or_id(NULL)); + assert_se(!valid_user_group_name_or_id("")); + assert_se(valid_user_group_name_or_id("0")); + assert_se(valid_user_group_name_or_id("1")); + assert_se(valid_user_group_name_or_id("65534")); + assert_se(!valid_user_group_name_or_id("65535")); + assert_se(valid_user_group_name_or_id("65536")); + assert_se(!valid_user_group_name_or_id("-1")); + assert_se(!valid_user_group_name_or_id("-kkk")); + assert_se(!valid_user_group_name_or_id("rööt")); + assert_se(!valid_user_group_name_or_id(".")); + assert_se(!valid_user_group_name_or_id("eff.eff")); + assert_se(!valid_user_group_name_or_id("foo\nbar")); + assert_se(!valid_user_group_name_or_id("0123456789012345678901234567890123456789")); + assert_se(!valid_user_group_name_or_id("aaa:bbb")); + + assert_se(valid_user_group_name_or_id("root")); + assert_se(valid_user_group_name_or_id("lennart")); + assert_se(valid_user_group_name_or_id("LENNART")); + assert_se(valid_user_group_name_or_id("_kkk")); + assert_se(valid_user_group_name_or_id("kkk-")); + assert_se(valid_user_group_name_or_id("kk-k")); + + assert_se(valid_user_group_name_or_id("some5")); + assert_se(!valid_user_group_name_or_id("5some")); + assert_se(valid_user_group_name_or_id("INNER5NUMBER")); +} + +static void test_valid_gecos(void) { + + assert_se(!valid_gecos(NULL)); + assert_se(valid_gecos("")); + assert_se(valid_gecos("test")); + assert_se(valid_gecos("Ümläüt")); + assert_se(!valid_gecos("In\nvalid")); + assert_se(!valid_gecos("In:valid")); +} + +static void test_valid_home(void) { + + assert_se(!valid_home(NULL)); + assert_se(!valid_home("")); + assert_se(!valid_home(".")); + assert_se(!valid_home("/home/..")); + assert_se(!valid_home("/home/../")); + assert_se(!valid_home("/home\n/foo")); + assert_se(!valid_home("./piep")); + assert_se(!valid_home("piep")); + assert_se(!valid_home("/home/user:lennart")); + + assert_se(valid_home("/")); + assert_se(valid_home("/home")); + assert_se(valid_home("/home/foo")); +} + int main(int argc, char*argv[]) { test_uid_to_name_one(0, "root"); @@ -75,5 +157,10 @@ int main(int argc, char*argv[]) { test_parse_uid(); test_uid_ptr(); + test_valid_user_group_name(); + test_valid_user_group_name_or_id(); + test_valid_gecos(); + test_valid_home(); + return 0; } diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c index ffec609c69..490929e93b 100644 --- a/src/timedate/timedated.c +++ b/src/timedate/timedated.c @@ -637,7 +637,7 @@ static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error return r; c->use_ntp = enabled; - log_info("Set NTP to %s", enabled ? "enabled" : "disabled"); + log_info("Set NTP to %sd", enable_disable(enabled)); (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL); diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index 1118118450..c0d76f9685 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -2,6 +2,7 @@ This file is part of systemd. Copyright 2010 Kay Sievers + Copyright 2016 Michal Soltys <soltys@ziu.info> 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 @@ -27,6 +28,7 @@ #include <stdio.h> #include <stdlib.h> #include <sys/ioctl.h> +#include <termios.h> #include <unistd.h> #include "alloc-util.h" @@ -50,67 +52,85 @@ static bool is_vconsole(int fd) { return ioctl(fd, TIOCLINUX, data) >= 0; } -static int disable_utf8(int fd) { - int r = 0, k; +static bool is_allocated(unsigned int idx) { + char vcname[strlen("/dev/vcs") + DECIMAL_STR_MAX(int)]; - if (ioctl(fd, KDSKBMODE, K_XLATE) < 0) - r = -errno; - - k = loop_write(fd, "\033%@", 3, false); - if (k < 0) - r = k; + xsprintf(vcname, "/dev/vcs%i", idx); + return access(vcname, F_OK) == 0; +} - k = write_string_file("/sys/module/vt/parameters/default_utf8", "0", 0); - if (k < 0) - r = k; +static bool is_allocated_byfd(int fd) { + struct vt_stat vcs = {}; - if (r < 0) - log_warning_errno(r, "Failed to disable UTF-8: %m"); + if (ioctl(fd, VT_GETSTATE, &vcs) < 0) { + log_warning_errno(errno, "VT_GETSTATE failed: %m"); + return false; + } + return is_allocated(vcs.v_active); +} - return r; +static bool is_settable(int fd) { + int r, curr_mode; + + r = ioctl(fd, KDGKBMODE, &curr_mode); + /* + * Make sure we only adjust consoles in K_XLATE or K_UNICODE mode. + * Oterwise we would (likely) interfere with X11's processing of the + * key events. + * + * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html + */ + return r == 0 && IN_SET(curr_mode, K_XLATE, K_UNICODE); } -static int enable_utf8(int fd) { - int r = 0, k; - long current = 0; - - if (ioctl(fd, KDGKBMODE, ¤t) < 0 || current == K_XLATE) { - /* - * Change the current keyboard to unicode, unless it - * is currently in raw or off mode anyway. We - * shouldn't interfere with X11's processing of the - * key events. - * - * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html - * - */ - - if (ioctl(fd, KDSKBMODE, K_UNICODE) < 0) - r = -errno; +static int toggle_utf8(const char *name, int fd, bool utf8) { + int r; + struct termios tc = {}; + + assert(name); + + r = ioctl(fd, KDSKBMODE, utf8 ? K_UNICODE : K_XLATE); + if (r < 0) + return log_warning_errno(errno, "Failed to %s UTF-8 kbdmode on %s: %m", enable_disable(utf8), name); + + r = loop_write(fd, utf8 ? "\033%G" : "\033%@", 3, false); + if (r < 0) + return log_warning_errno(r, "Failed to %s UTF-8 term processing on %s: %m", enable_disable(utf8), name); + + r = tcgetattr(fd, &tc); + if (r >= 0) { + if (utf8) + tc.c_iflag |= IUTF8; + else + tc.c_iflag &= ~IUTF8; + r = tcsetattr(fd, TCSANOW, &tc); } + if (r < 0) + return log_warning_errno(errno, "Failed to %s iutf8 flag on %s: %m", enable_disable(utf8), name); - k = loop_write(fd, "\033%G", 3, false); - if (k < 0) - r = k; + log_debug("UTF-8 kbdmode %sd on %s", enable_disable(utf8), name); + return 0; +} - k = write_string_file("/sys/module/vt/parameters/default_utf8", "1", 0); - if (k < 0) - r = k; +static int toggle_utf8_sysfs(bool utf8) { + int r; + r = write_string_file("/sys/module/vt/parameters/default_utf8", one_zero(utf8), 0); if (r < 0) - log_warning_errno(r, "Failed to enable UTF-8: %m"); + return log_warning_errno(r, "Failed to %s sysfs UTF-8 flag: %m", enable_disable(utf8)); - return r; + log_debug("Sysfs UTF-8 flag %sd", enable_disable(utf8)); + return 0; } static int keyboard_load_and_wait(const char *vc, const char *map, const char *map_toggle, bool utf8) { const char *args[8]; - int i = 0, r; + int i = 0; pid_t pid; /* An empty map means kernel map */ if (isempty(map)) - return 1; + return 0; args[i++] = KBD_LOADKEYS; args[i++] = "-q"; @@ -135,34 +155,31 @@ static int keyboard_load_and_wait(const char *vc, const char *map, const char *m _exit(EXIT_FAILURE); } - r = wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true); - if (r < 0) - return r; - - return r == 0; + return wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true); } static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) { const char *args[9]; - int i = 0, r; + int i = 0; pid_t pid; - /* An empty font means kernel font */ - if (isempty(font)) - return 1; + /* Any part can be set independently */ + if (isempty(font) && isempty(map) && isempty(unimap)) + return 0; args[i++] = KBD_SETFONT; args[i++] = "-C"; args[i++] = vc; - args[i++] = font; - if (map) { + if (!isempty(map)) { args[i++] = "-m"; args[i++] = map; } - if (unimap) { + if (!isempty(unimap)) { args[i++] = "-u"; args[i++] = unimap; } + if (!isempty(font)) + args[i++] = font; args[i++] = NULL; pid = fork(); @@ -177,11 +194,7 @@ static int font_load_and_wait(const char *vc, const char *font, const char *map, _exit(EXIT_FAILURE); } - r = wait_for_terminate_and_warn(KBD_SETFONT, pid, true); - if (r < 0) - return r; - - return r == 0; + return wait_for_terminate_and_warn(KBD_SETFONT, pid, true); } /* @@ -189,13 +202,21 @@ static int font_load_and_wait(const char *vc, const char *font, const char *map, * we update all possibly already allocated VTs with the configured * font. It also allows to restart systemd-vconsole-setup.service, * to apply a new font to all VTs. + * + * We also setup per-console utf8 related stuff: kbdmode, term + * processing, stty iutf8. */ -static void font_copy_to_all_vcs(int fd) { +static void setup_remaining_vcs(int fd, bool utf8) { + struct console_font_op cfo = { + .op = KD_FONT_OP_GET, .flags = 0, + .width = 32, .height = 32, + .charcount = 512, + }; struct vt_stat vcs = {}; - unsigned char map8[E_TABSZ]; - unsigned short map16[E_TABSZ]; + struct unimapinit adv = {}; struct unimapdesc unimapd; _cleanup_free_ struct unipair* unipairs = NULL; + _cleanup_free_ void *fontbuf = NULL; int i, r; unipairs = new(struct unipair, USHRT_MAX); @@ -204,55 +225,79 @@ static void font_copy_to_all_vcs(int fd) { return; } + fontbuf = malloc(cfo.width * cfo.height * cfo.charcount / 8); + if (!fontbuf) { + log_oom(); + return; + } + /* get active, and 16 bit mask of used VT numbers */ r = ioctl(fd, VT_GETSTATE, &vcs); if (r < 0) { - log_debug_errno(errno, "VT_GETSTATE failed, ignoring: %m"); + log_warning_errno(errno, "VT_GETSTATE failed, ignoring remaining consoles: %m"); return; } - for (i = 1; i <= 15; i++) { - char vcname[strlen("/dev/vcs") + DECIMAL_STR_MAX(int)]; - _cleanup_close_ int vcfd = -1; - struct console_font_op cfo = {}; + /* get fonts from source console */ + cfo.data = fontbuf; + r = ioctl(fd, KDFONTOP, &cfo); + if (r < 0) + log_warning_errno(errno, "KD_FONT_OP_GET failed, fonts will not be copied: %m"); + else { + unimapd.entries = unipairs; + unimapd.entry_ct = USHRT_MAX; + r = ioctl(fd, GIO_UNIMAP, &unimapd); + if (r < 0) + log_warning_errno(errno, "GIO_UNIMAP failed, fonts will not be copied: %m"); + else + cfo.op = KD_FONT_OP_SET; + } + + for (i = 1; i <= 63; i++) { + char ttyname[strlen("/dev/tty") + DECIMAL_STR_MAX(int)]; + _cleanup_close_ int fd_d = -1; - if (i == vcs.v_active) + if (i == vcs.v_active || !is_allocated(i)) continue; - /* skip non-allocated ttys */ - xsprintf(vcname, "/dev/vcs%i", i); - if (access(vcname, F_OK) < 0) + /* try to open terminal */ + xsprintf(ttyname, "/dev/tty%i", i); + fd_d = open_terminal(ttyname, O_RDWR|O_CLOEXEC); + if (fd_d < 0) { + log_warning_errno(fd_d, "Unable to open tty%i, fonts will not be copied: %m", i); continue; + } - xsprintf(vcname, "/dev/tty%i", i); - vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC); - if (vcfd < 0) + if (!is_settable(fd_d)) continue; - /* copy font from active VT, where the font was uploaded to */ - cfo.op = KD_FONT_OP_COPY; - cfo.height = vcs.v_active-1; /* tty1 == index 0 */ - (void) ioctl(vcfd, KDFONTOP, &cfo); + toggle_utf8(ttyname, fd_d, utf8); - /* copy map of 8bit chars */ - if (ioctl(fd, GIO_SCRNMAP, map8) >= 0) - (void) ioctl(vcfd, PIO_SCRNMAP, map8); + if (cfo.op != KD_FONT_OP_SET) + continue; - /* copy map of 8bit chars -> 16bit Unicode values */ - if (ioctl(fd, GIO_UNISCRNMAP, map16) >= 0) - (void) ioctl(vcfd, PIO_UNISCRNMAP, map16); + r = ioctl(fd_d, KDFONTOP, &cfo); + if (r < 0) { + log_warning_errno(errno, "KD_FONT_OP_SET failed, fonts will not be copied to tty%i: %m", i); + continue; + } /* copy unicode translation table */ /* unimapd is a ushort count and a pointer to an array of struct unipair { ushort, ushort } */ - unimapd.entries = unipairs; - unimapd.entry_ct = USHRT_MAX; - if (ioctl(fd, GIO_UNIMAP, &unimapd) >= 0) { - struct unimapinit adv = { 0, 0, 0 }; + r = ioctl(fd_d, PIO_UNIMAPCLR, &adv); + if (r < 0) { + log_warning_errno(errno, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%i: %m", i); + continue; + } - (void) ioctl(vcfd, PIO_UNIMAPCLR, &adv); - (void) ioctl(vcfd, PIO_UNIMAP, &unimapd); + r = ioctl(fd_d, PIO_UNIMAP, &unimapd); + if (r < 0) { + log_warning_errno(errno, "PIO_UNIMAP failed, unimaps might be incorrect for tty%i: %m", i); + continue; } + + log_debug("Font and unimap successfully copied to %s", ttyname); } } @@ -289,6 +334,16 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } + if (!is_allocated_byfd(fd)) { + log_error("Virtual console %s is not allocated.", vc); + return EXIT_FAILURE; + } + + if (!is_settable(fd)) { + log_error("Virtual console %s is not in K_XLATE or K_UNICODE.", vc); + return EXIT_FAILURE; + } + utf8 = is_locale_utf8(); r = parse_env_file("/etc/vconsole.conf", NEWLINE, @@ -306,8 +361,12 @@ int main(int argc, char **argv) { if (detect_container() <= 0) { r = parse_env_file("/proc/cmdline", WHITESPACE, "vconsole.keymap", &vc_keymap, - "vconsole.keymap.toggle", &vc_keymap_toggle, + "vconsole.keymap_toggle", &vc_keymap_toggle, "vconsole.font", &vc_font, + "vconsole.font_map", &vc_font_map, + "vconsole.font_unimap", &vc_font_unimap, + /* compatibility with obsolete multiple-dot scheme */ + "vconsole.keymap.toggle", &vc_keymap_toggle, "vconsole.font.map", &vc_font_map, "vconsole.font.unimap", &vc_font_unimap, NULL); @@ -316,17 +375,17 @@ int main(int argc, char **argv) { log_warning_errno(r, "Failed to read /proc/cmdline: %m"); } - if (utf8) - (void) enable_utf8(fd); - else - (void) disable_utf8(fd); - - font_ok = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap) > 0; - keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) > 0; + toggle_utf8_sysfs(utf8); + toggle_utf8(vc, fd, utf8); + font_ok = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap) == 0; + keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) == 0; - /* Only copy the font when we executed setfont successfully */ - if (font_copy && font_ok) - (void) font_copy_to_all_vcs(fd); + if (font_copy) { + if (font_ok) + setup_remaining_vcs(fd, utf8); + else + log_warning("Setting source virtual console failed, ignoring remaining ones"); + } return font_ok && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/system-preset/90-journal-remote.preset b/system-preset/90-journal-remote.preset new file mode 100644 index 0000000000..f5917b2b84 --- /dev/null +++ b/system-preset/90-journal-remote.preset @@ -0,0 +1,13 @@ +# This file is part of systemd. +# +# 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. + +# These ones should be enabled by default, even if distributions +# generally follow a default-off policy. + +disable systemd-journal-gatewayd.* +disable systemd-journal-remote.* +disable systemd-journal-upload.* diff --git a/system-preset/90-journald.preset b/system-preset/90-journald.preset index 6a8c17b1fa..1022f0e86f 100644 --- a/system-preset/90-journald.preset +++ b/system-preset/90-journald.preset @@ -5,6 +5,7 @@ # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. -disable syslog.socket +# These ones should be enabled by default, even if distributions +# generally follow a default-off policy. -disable systemd-journal-gatewayd.* +disable syslog.socket diff --git a/system-preset/90-networkd.preset b/system-preset/90-networkd.preset index a053f7a4b1..609edafe23 100644 --- a/system-preset/90-networkd.preset +++ b/system-preset/90-networkd.preset @@ -5,6 +5,8 @@ # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. -enable systemd-networkd.service +# These ones should be enabled by default, even if distributions +# generally follow a default-off policy. -disable systemd-networkd-wait-online.service +enable systemd-networkd.service +enable systemd-networkd-wait-online.service diff --git a/system-preset/90-timesyncd.preset b/system-preset/90-timesyncd.preset index d40b418358..b42460ef5e 100644 --- a/system-preset/90-timesyncd.preset +++ b/system-preset/90-timesyncd.preset @@ -5,4 +5,7 @@ # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. +# These ones should be enabled by default, even if distributions +# generally follow a default-off policy. + enable systemd-timesyncd.service 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/udev-test.pl b/test/udev-test.pl index da0a4e1f6b..129af854f1 100755 --- a/test/udev-test.pl +++ b/test/udev-test.pl @@ -1535,11 +1535,18 @@ if (!($<==0)) { 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); + print "Running in a container, skipping the test.\n"; + exit($EXIT_TEST_SKIP); } udev_setup(); diff --git a/units/systemd-ask-password-console.path b/units/systemd-ask-password-console.path index 2949635fea..7899ae788f 100644 --- a/units/systemd-ask-password-console.path +++ b/units/systemd-ask-password-console.path @@ -11,7 +11,7 @@ Documentation=man:systemd-ask-password-console.service(8) DefaultDependencies=no Conflicts=shutdown.target After=plymouth-start.service -Before=paths.target shutdown.target +Before=paths.target shutdown.target cryptsetup.target ConditionPathExists=!/run/plymouth/pid [Path] diff --git a/units/systemd-ask-password-wall.path b/units/systemd-ask-password-wall.path index 95ec9bc8a0..a3ca617256 100644 --- a/units/systemd-ask-password-wall.path +++ b/units/systemd-ask-password-wall.path @@ -10,7 +10,7 @@ Description=Forward Password Requests to Wall Directory Watch Documentation=man:systemd-ask-password-console.service(8) DefaultDependencies=no Conflicts=shutdown.target -Before=paths.target shutdown.target +Before=paths.target shutdown.target cryptsetup.target [Path] DirectoryNotEmpty=/run/systemd/ask-password diff --git a/units/systemd-vconsole-setup.service.in b/units/systemd-vconsole-setup.service.in index 6160361871..2bd1fd1a5d 100644 --- a/units/systemd-vconsole-setup.service.in +++ b/units/systemd-vconsole-setup.service.in @@ -9,11 +9,8 @@ Description=Setup Virtual Console Documentation=man:systemd-vconsole-setup.service(8) man:vconsole.conf(5) DefaultDependencies=no -Conflicts=shutdown.target -Before=sysinit.target shutdown.target ConditionPathExists=/dev/tty0 [Service] Type=oneshot -RemainAfterExit=yes ExecStart=@rootlibexecdir@/systemd-vconsole-setup diff --git a/units/user/graphical-session-pre.target b/units/user/graphical-session-pre.target new file mode 100644 index 0000000000..86d15aff33 --- /dev/null +++ b/units/user/graphical-session-pre.target @@ -0,0 +1,14 @@ +# This file is part of systemd. +# +# 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. + +[Unit] +Description=Session services which should run early before the graphical session is brought up +Documentation=man:systemd.special(7) +Requires=basic.target +Before=graphical-session.target +RefuseManualStart=yes +StopWhenUnneeded=yes diff --git a/units/user/graphical-session.target b/units/user/graphical-session.target new file mode 100644 index 0000000000..00d16230b7 --- /dev/null +++ b/units/user/graphical-session.target @@ -0,0 +1,13 @@ +# This file is part of systemd. +# +# 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. + +[Unit] +Description=Current graphical user session +Documentation=man:systemd.special(7) +Requires=basic.target +RefuseManualStart=yes +StopWhenUnneeded=yes |