From: Kay Sievers Date: Tue, 3 Apr 2012 19:08:04 +0000 (+0200) Subject: import udev repository X-Git-Tag: v183~456 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=19c5f19d69bb5f520fa7213239490c55de06d99d;hp=4db539b27021dcaa716828cbb689f591adb5af23;p=elogind.git import udev repository --- diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 000000000..9d9f8cd17 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,7 @@ +; Sets emacs variables based on mode. +; A list of (major-mode . ((var1 . value1) (var2 . value2))) +; Mode can be nil, which gives default values. + +((nil . ((indent-tabs-mode . nil) + (tab-width . 8))) +) diff --git a/.gitignore b/.gitignore index fa3500ba9..f36dd8a81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,40 +1,109 @@ +/test-journal-send +/systemd-multi-seat-x +/systemd-cgtop +/systemd-coredump +/systemd-cat +/systemd-rc-local-generator +/libsystemd-id128.pc +journalctl +systemd-journald +test-id128 +test-journal +test-install +org.freedesktop.hostname1.xml +org.freedesktop.locale1.xml +libsystemd-daemon.pc +libsystemd-login.pc +test-login +loginctl +systemd-localed +systemd-timedated +org.freedesktop.timedate1.xml +systemd-logind +systemd-uaccess +systemd-hostnamed +systemd-binfmt +systemd-getty-generator +systemd-nspawn +systemd-stdio-bridge +systemd-machine-id-setup +systemd-detect-virt +systemd-sysctl +test-strv +systemd-ac-power +systemd-timestamp +systemd-cryptsetup +systemd-cryptsetup-generator +systemd-tty-ask-password-agent +systemd-fsck +systemd-quotacheck +systemd-user-sessions +systemd-shutdown +systemd-tmpfiles +systemd-readahead-collect +systemd-readahead-replay +systemd-reply-password +systemd-gnome-ask-password-agent +systemd-ask-password +systemd-kmsg-syslogd +systemd-remount-api-vfs +test-hostname +systemd-modules-load +systemd-vconsole-setup +systemd-shutdownd +systemd-random-seed +systemd-update-utmp +test-env-replace +systemd-cgls +systemd.pc +test-cgroup +.libs/ +systemd-notify +test-daemon +systemd-install +org.freedesktop.systemd1.*.xml +test-ns +test-loopback +systemd-cgroups-agent +systemd-initctl +/systemd +test-engine +test-job-type +systemd-stdout-syslog-bridge +systemctl +systemadm +.dirstamp +*.1 +*.3 +*.5 +*.7 +*.8 +*.html *~ *.o -*.a *.lo +*.a *.la -.libs -.deps -.dirstamp -Makefile +.deps/ Makefile.in -/aclocal.m4 -/autom4te.cache -/config.h -/config.h.in -/config.log -/config.status -/config.guess -/config.sub -/libtool -/ltmain.sh -/install-sh -/missing -/configure -/stamp-h1 -/depcomp -/gtk-doc.make -/build-aux -/udev-test-install -/udevd -/udevadm -/test-udev -/test-libudev -/accelerometer -/ata_id -/cdrom_id -/collect -/mtd_probe -/v4l_id -/keymap -/scsi_id +aclocal.m4 +*.cache +compile +config.guess +config.h +config.h.in +config.log +config.status +config.sub +configure +depcomp +install-sh +missing +stamp-* +*.stamp +/Makefile +ltmain.sh +*.tar.xz +*.tar.gz +*.tar.bz2 +libtool diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..04b1c6cba --- /dev/null +++ b/.mailmap @@ -0,0 +1,4 @@ +Kay Sievers +Robert Gerus Robert "arachnist" Gerus +Zbigniew Jędrzejewski-Szmek Zbyszek Szmek +Fabiano Fidêncio Fabiano Fidencio diff --git a/CODING_STYLE b/CODING_STYLE new file mode 100644 index 000000000..9341b48d4 --- /dev/null +++ b/CODING_STYLE @@ -0,0 +1,27 @@ + +- 8ch indent, no tabs + +- structs in MixedCase, variables, functions in lower_case + +- the destructors always unregister the object from the next bigger + object, not the other way around + +- to minimize strict aliasing violations we prefer unions over casting + +- for robustness reasons destructors should be able to destruct + half-initialized objects, too + +- error codes are returned as negative Exxx. i.e. return -EINVAL. There + are some exceptions: for constructors its is OK to return NULL on + OOM. For lookup functions NULL is fine too for "not found". + +- Do not issue NSS requests (that includes user name and host name + lookups) from the main daemon as this might trigger deadlocks when + we those lookups involve synchronously talking to services that we + would need to start up. + +- Do not access any directories outside of /etc/, /dev, /lib from the + init daemon to avoid deadlocks with the automounter. + +- Don't synchronously talk to any other service, due to risk of + deadlocks. diff --git a/DISTRO_PORTING b/DISTRO_PORTING new file mode 100644 index 000000000..2b08bf811 --- /dev/null +++ b/DISTRO_PORTING @@ -0,0 +1,58 @@ +Porting systemd To New Distributions + +HOWTO: + You need to make the follow changes to adapt systemd to your + distribution: + + 0) Make your distribution recognized via the autoconf checks + in configure.ac. Grep for the word "fedora" (case + insensitively) and you should be able to find the places where + you need to add/change things. + + 1) Patch src/hostname-setup.c so that systemd knows where to + read your host name from. You might also want to update + status_welcome() in util.c. + + 2) Check the unit files in units/ if they match your + distribution. Most likely you will have to make additions to + units/*.m4 and create a copy of units/fedora/ with changes for + your distribution. + + 3) Adjust Makefile.am to register the unit files you added in + step 2. Also you might need to update the m4 invocation in + Makefile.am. Grep for the word "fedora" (case insensitively) + and you should be able to find the places where you need to + add/change things. + + 4) Try it out. Play around with 'systemd --test --system' for + a test run of systemd without booting. This will read the unit + files and print the initial transaction it would execute + during boot-up. This will also inform you about ordering loops + and suchlike. + +CONTRIBUTING UPSTREAM: + We are interested in merging your changes upstream, if they + are for a big, and well-known distribution. Unfortunately we + don't have the time and resources to maintain + distribution-specific patches for all distributions on the + planet, hence please do not send us patches that add systemd + support for non-mainstream or niche distributions. + + Thank you for understanding. + +BE CONSIDERATE: + We'd like to keep differences between the distributions + minimal. This both simplifies our maintenance work, as well + as it helps administrators to move from one distribution to + another. + + Hence we'd like to ask you to keep your changes minimal, and + not rename any units without a very good reason (if you need a + particular name for compatibility reasons, consider using + alias names via symlinks). Before you make changes that change + semantics from upstream, please talk to us! + + In SysV almost every distribution uses a different + nomenclature and different locations for the boot-up + scripts. We'd like to avoid chaos like that with systemd right + from the beginning. So please, be considerate! diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..d511905c1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makefile.am b/Makefile.am index 1c7f86b08..219d8ded8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,712 +1,2444 @@ -# Copyright (C) 2008-2012 Kay Sievers -# Copyright (C) 2009 Diego Elio 'Flameeyes' Pettenò +# This file is part of systemd. +# +# Copyright 2011 Lennart Poettering +# Copyright 2011 Kay Sievers +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with systemd; If not, see . + +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = po + +LIBSYSTEMD_LOGIN_CURRENT=2 +LIBSYSTEMD_LOGIN_REVISION=1 +LIBSYSTEMD_LOGIN_AGE=2 + +LIBSYSTEMD_DAEMON_CURRENT=0 +LIBSYSTEMD_DAEMON_REVISION=1 +LIBSYSTEMD_DAEMON_AGE=0 + +LIBSYSTEMD_ID128_CURRENT=0 +LIBSYSTEMD_ID128_REVISION=3 +LIBSYSTEMD_ID128_AGE=0 + +LIBSYSTEMD_JOURNAL_CURRENT=0 +LIBSYSTEMD_JOURNAL_REVISION=3 +LIBSYSTEMD_JOURNAL_AGE=0 + +# Dirs of external packages +dbuspolicydir=@dbuspolicydir@ +dbussessionservicedir=@dbussessionservicedir@ +dbussystemservicedir=@dbussystemservicedir@ +dbusinterfacedir=@dbusinterfacedir@ +udevrulesdir=@udevrulesdir@ +pamlibdir=@pamlibdir@ +pkgconfigdatadir=$(datadir)/pkgconfig +pkgconfiglibdir=$(libdir)/pkgconfig +polkitpolicydir=$(datadir)/polkit-1/actions +bashcompletiondir=$(sysconfdir)/bash_completion.d + +# Our own, non-special dirs +pkgsysconfdir=$(sysconfdir)/systemd +userunitdir=$(prefix)/lib/systemd/user +tmpfilesdir=$(prefix)/lib/tmpfiles.d +sysctldir=$(prefix)/lib/sysctl.d +usergeneratordir=$(pkglibexecdir)/user-generators +pkgincludedir=$(includedir)/systemd + +# And these are the special ones for / +rootprefix=@rootprefix@ +rootbindir=$(rootprefix)/bin +rootlibexecdir=$(rootprefix)/lib/systemd +systemgeneratordir=$(rootlibexecdir)/system-generators +systemshutdowndir=$(rootlibexecdir)/system-shutdown +systemunitdir=$(rootprefix)/lib/systemd/system -SUBDIRS = . +CLEANFILES = +EXTRA_DIST = +INSTALL_EXEC_HOOKS = +UNINSTALL_EXEC_HOOKS = +INSTALL_DATA_HOOKS = +pkginclude_HEADERS = +lib_LTLIBRARIES = +pkgconfiglib_DATA = +polkitpolicy_in_files = +dist_udevrules_DATA = -ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} +AM_CPPFLAGS = \ + -include $(top_builddir)/config.h \ + -DSYSTEM_CONFIG_FILE=\"$(pkgsysconfdir)/system.conf\" \ + -DSYSTEM_CONFIG_UNIT_PATH=\"$(pkgsysconfdir)/system\" \ + -DSYSTEM_DATA_UNIT_PATH=\"$(systemunitdir)\" \ + -DSYSTEM_SYSVINIT_PATH=\"$(SYSTEM_SYSVINIT_PATH)\" \ + -DSYSTEM_SYSVRCND_PATH=\"$(SYSTEM_SYSVRCND_PATH)\" \ + -DUSER_CONFIG_FILE=\"$(pkgsysconfdir)/user.conf\" \ + -DUSER_CONFIG_UNIT_PATH=\"$(pkgsysconfdir)/user\" \ + -DUSER_DATA_UNIT_PATH=\"$(userunitdir)\" \ + -DSYSTEMD_CGROUP_AGENT_PATH=\"$(rootlibexecdir)/systemd-cgroups-agent\" \ + -DSYSTEMD_BINARY_PATH=\"$(rootlibexecdir)/systemd\" \ + -DSYSTEMD_SHUTDOWN_BINARY_PATH=\"$(rootlibexecdir)/systemd-shutdown\" \ + -DSYSTEMCTL_BINARY_PATH=\"$(rootbindir)/systemctl\" \ + -DSYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH=\"$(rootbindir)/systemd-tty-ask-password-agent\" \ + -DSYSTEMD_STDIO_BRIDGE_BINARY_PATH=\"$(bindir)/systemd-stdio-bridge\" \ + -DROOTPREFIX=\"$(rootprefix)\" \ + -DRUNTIME_DIR=\"/run\" \ + -DRANDOM_SEED=\"$(localstatedir)/lib/random-seed\" \ + -DSYSTEMD_CRYPTSETUP_PATH=\"$(rootlibexecdir)/systemd-cryptsetup\" \ + -DSYSTEM_GENERATOR_PATH=\"$(systemgeneratordir)\" \ + -DUSER_GENERATOR_PATH=\"$(usergeneratordir)\" \ + -DSYSTEM_SHUTDOWN_PATH=\"$(systemshutdowndir)\" \ + -DSYSTEMD_KBD_MODEL_MAP=\"$(pkgdatadir)/kbd-model-map\" \ + -DX_SERVER=\"$(bindir)/X\" \ + -I $(top_srcdir)/src \ + -I $(top_srcdir)/src/readahead \ + -I $(top_srcdir)/src/login \ + -I $(top_srcdir)/src/journal \ + -I $(top_srcdir)/src/systemd + +AM_CFLAGS = $(WARNINGFLAGS) +AM_LDFLAGS = $(GCLDFLAGS) + +if TARGET_GENTOO +AM_CPPFLAGS += \ + -DKBD_LOADKEYS=\"/usr/bin/loadkeys\" \ + -DKBD_SETFONT=\"/usr/bin/setfont\" \ + -DDEFAULT_FONT=\"LatArCyrHeb-16\" +else +if TARGET_ARCH +AM_CPPFLAGS += \ + -DKBD_LOADKEYS=\"/usr/bin/loadkeys\" \ + -DKBD_SETFONT=\"/usr/bin/setfont\" \ + -DDEFAULT_FONT=\"LatArCyrHeb-16\" +else +if TARGET_FRUGALWARE +AM_CPPFLAGS += \ + -DKBD_LOADKEYS=\"/usr/bin/loadkeys\" \ + -DKBD_SETFONT=\"/usr/bin/setfont\" \ + -DDEFAULT_FONT=\"LatArCyrHeb-16\" +else +if TARGET_MANDRIVA +AM_CPPFLAGS += \ + -DKBD_LOADKEYS=\"/bin/loadkeys\" \ + -DKBD_SETFONT=\"/bin/setfont\" \ + -DDEFAULT_FONT=\"LatArCyrHeb-16\" +else +if TARGET_MEEGO +AM_CPPFLAGS += \ + -DKBD_LOADKEYS=\"/bin/loadkeys\" \ + -DKBD_SETFONT=\"/bin/setfont\" \ + -DDEFAULT_FONT=\"LatArCyrHeb-16\" +else +if TARGET_ANGSTROM +AM_CPPFLAGS += \ + -DKBD_LOADKEYS=\"/usr/bin/loadkeys\" \ + -DKBD_SETFONT=\"/usr/bin/setfont\" \ + -DDEFAULT_FONT=\"LatArCyrHeb-16\" +else +if TARGET_MAGEIA +AM_CPPFLAGS += \ + -DKBD_LOADKEYS=\"/bin/loadkeys\" \ + -DKBD_SETFONT=\"/bin/setfont\" \ + -DDEFAULT_FONT=\"LatArCyrHeb-16\" +else +AM_CPPFLAGS += \ + -DKBD_LOADKEYS=\"/bin/loadkeys\" \ + -DKBD_SETFONT=\"/bin/setfont\" \ + -DDEFAULT_FONT=\"latarcyrheb-sun16\" +endif +endif +endif +endif +endif +endif +endif -AM_MAKEFLAGS = --no-print-directory +rootbin_PROGRAMS = \ + systemctl \ + systemd-notify \ + systemd-ask-password \ + systemd-tty-ask-password-agent \ + systemd-tmpfiles \ + systemd-machine-id-setup -LIBUDEV_CURRENT=13 -LIBUDEV_REVISION=2 -LIBUDEV_AGE=13 +bin_PROGRAMS = \ + systemd-cgls \ + systemd-cgtop \ + systemd-stdio-bridge \ + systemd-nspawn + +dist_bin_SCRIPTS = \ + src/systemd-analyze + +rootlibexec_PROGRAMS = \ + systemd \ + systemd-cgroups-agent \ + systemd-initctl \ + systemd-update-utmp \ + systemd-shutdownd \ + systemd-shutdown \ + systemd-modules-load \ + systemd-remount-api-vfs \ + systemd-reply-password \ + systemd-fsck \ + systemd-timestamp \ + systemd-ac-power \ + systemd-detect-virt \ + systemd-sysctl + +systemgenerator_PROGRAMS = \ + systemd-getty-generator + +noinst_PROGRAMS = \ + test-engine \ + test-job-type \ + test-ns \ + test-loopback \ + test-hostname \ + test-daemon \ + test-cgroup \ + test-env-replace \ + test-strv \ + test-install + +dist_pkgsysconf_DATA = \ + src/system.conf \ + src/user.conf + +dist_dbuspolicy_DATA = \ + src/org.freedesktop.systemd1.conf + +dist_dbussystemservice_DATA = \ + src/org.freedesktop.systemd1.service + +nodist_udevrules_DATA = \ + src/99-systemd.rules + +dbusinterface_DATA = \ + org.freedesktop.systemd1.Manager.xml \ + org.freedesktop.systemd1.Job.xml \ + org.freedesktop.systemd1.Unit.xml \ + org.freedesktop.systemd1.Service.xml \ + org.freedesktop.systemd1.Socket.xml \ + org.freedesktop.systemd1.Timer.xml \ + org.freedesktop.systemd1.Target.xml \ + org.freedesktop.systemd1.Device.xml \ + org.freedesktop.systemd1.Mount.xml \ + org.freedesktop.systemd1.Automount.xml \ + org.freedesktop.systemd1.Snapshot.xml \ + org.freedesktop.systemd1.Swap.xml \ + org.freedesktop.systemd1.Path.xml + +dist_bashcompletion_DATA = \ + src/systemd-bash-completion.sh + +dist_tmpfiles_DATA = \ + tmpfiles.d/systemd.conf \ + tmpfiles.d/tmp.conf \ + tmpfiles.d/x11.conf + +if HAVE_SYSV_COMPAT +dist_tmpfiles_DATA += \ + tmpfiles.d/legacy.conf +endif -LIBGUDEV_CURRENT=1 -LIBGUDEV_REVISION=1 -LIBGUDEV_AGE=1 +dist_systemunit_DATA = \ + units/graphical.target \ + units/multi-user.target \ + units/emergency.service \ + units/emergency.target \ + units/sysinit.target \ + units/basic.target \ + units/getty.target \ + units/halt.target \ + units/kexec.target \ + units/local-fs.target \ + units/local-fs-pre.target \ + units/remote-fs.target \ + units/remote-fs-pre.target \ + units/network.target \ + units/nss-lookup.target \ + units/mail-transfer-agent.target \ + units/http-daemon.target \ + units/poweroff.target \ + units/reboot.target \ + units/rescue.target \ + units/rpcbind.target \ + units/time-sync.target \ + units/shutdown.target \ + units/final.target \ + units/umount.target \ + units/sigpwr.target \ + units/sockets.target \ + units/swap.target \ + units/systemd-initctl.socket \ + units/systemd-shutdownd.socket \ + units/syslog.socket \ + units/dev-hugepages.mount \ + units/dev-mqueue.mount \ + units/sys-kernel-config.mount \ + units/sys-kernel-debug.mount \ + units/sys-fs-fuse-connections.mount \ + units/tmp.mount \ + units/remount-rootfs.service \ + units/printer.target \ + units/sound.target \ + units/bluetooth.target \ + units/smartcard.target \ + units/systemd-tmpfiles-clean.timer \ + units/quotaon.service \ + units/systemd-ask-password-wall.path \ + units/systemd-ask-password-console.path \ + units/syslog.target + +nodist_systemunit_DATA = \ + units/getty@.service \ + units/serial-getty@.service \ + units/console-shell.service \ + units/systemd-initctl.service \ + units/systemd-shutdownd.service \ + units/systemd-modules-load.service \ + units/systemd-remount-api-vfs.service \ + units/systemd-update-utmp-runlevel.service \ + units/systemd-update-utmp-shutdown.service \ + units/systemd-tmpfiles-setup.service \ + units/systemd-tmpfiles-clean.service \ + units/systemd-ask-password-wall.service \ + units/systemd-ask-password-console.service \ + units/systemd-sysctl.service \ + units/halt.service \ + units/poweroff.service \ + units/reboot.service \ + units/kexec.service \ + units/fsck@.service \ + units/fsck-root.service \ + units/rescue.service \ + units/user@.service + +dist_userunit_DATA = \ + units/user/default.target \ + units/user/exit.target + +nodist_userunit_DATA = \ + units/user/exit.service -AM_CPPFLAGS = \ - -include $(top_builddir)/config.h \ - -I$(top_srcdir)/src \ - -DSYSCONFDIR=\""$(sysconfdir)"\" \ - -DPKGLIBEXECDIR=\""$(libexecdir)/udev"\" +EXTRA_DIST += \ + units/getty@.service.m4 \ + units/serial-getty@.service.m4 \ + units/console-shell.service.m4 \ + units/rescue.service.m4 \ + units/systemd-initctl.service.in \ + units/systemd-shutdownd.service.in \ + units/systemd-modules-load.service.in \ + units/systemd-remount-api-vfs.service.in \ + units/systemd-update-utmp-runlevel.service.in \ + units/systemd-update-utmp-shutdown.service.in \ + units/systemd-tmpfiles-setup.service.in \ + units/systemd-tmpfiles-clean.service.in \ + units/systemd-ask-password-wall.service.in \ + units/systemd-ask-password-console.service.in \ + units/systemd-sysctl.service.in \ + units/halt.service.in \ + units/poweroff.service.in \ + units/reboot.service.in \ + units/kexec.service.in \ + units/user/exit.service.in \ + units/fsck@.service.in \ + units/fsck-root.service.in \ + units/user@.service.in \ + src/systemd.pc.in \ + introspect.awk \ + src/99-systemd.rules.in \ + man/custom-html.xsl + +if TARGET_FEDORA +dist_systemunit_DATA += \ + units/fedora/prefdm.service \ + units/fedora/rc-local.service \ + units/fedora/halt-local.service +systemgenerator_PROGRAMS += \ + systemd-rc-local-generator +endif -AM_CFLAGS = \ - ${my_CFLAGS} \ - -fvisibility=hidden \ - -ffunction-sections \ - -fdata-sections +if TARGET_MANDRIVA +dist_systemunit_DATA += \ + units/mandriva/prefdm.service \ + units/fedora/rc-local.service \ + units/fedora/halt-local.service +systemgenerator_PROGRAMS += \ + systemd-rc-local-generator +endif -AM_LDFLAGS = \ - -Wl,--gc-sections \ - -Wl,--as-needed +if TARGET_FRUGALWARE +dist_systemunit_DATA += \ + units/frugalware/display-manager.service +endif -DISTCHECK_CONFIGURE_FLAGS = \ - --enable-debug \ - --enable-rule_generator \ - --enable-floppy \ - --with-selinux \ - --enable-gtk-doc \ - --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) - -BUILT_SOURCES = -EXTRA_DIST = -CLEANFILES = -INSTALL_EXEC_HOOKS = -INSTALL_DATA_HOOKS = -UNINSTALL_EXEC_HOOKS = -DISTCHECK_HOOKS = -DISTCLEAN_LOCAL_HOOKS = +if TARGET_SUSE +dist_systemunit_DATA += \ + units/suse/rc-local.service \ + units/suse/halt-local.service +systemgenerator_PROGRAMS += \ + systemd-rc-local-generator +endif -udevhomedir = $(libexecdir)/udev -udevhome_SCRIPTS = -dist_udevhome_SCRIPTS = -dist_udevhome_DATA = -dist_man_MANS = +if TARGET_MAGEIA +dist_systemunit_DATA += \ + units/mageia/prefdm.service \ + units/fedora/rc-local.service \ + units/fedora/halt-local.service +systemgenerator_PROGRAMS += \ + systemd-rc-local-generator +endif -SED_PROCESS = \ - $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(SED) \ - -e 's,@VERSION\@,$(VERSION),g' \ - -e 's,@prefix\@,$(prefix),g' \ - -e 's,@rootprefix\@,$(rootprefix),g' \ - -e 's,@exec_prefix\@,$(exec_prefix),g' \ - -e 's,@libdir\@,$(libdir),g' \ - -e 's,@includedir\@,$(includedir),g' \ - -e 's,@bindir\@,$(bindir),g' \ - -e 's,@pkglibexecdir\@,$(libexecdir)/udev,g' \ - < $< > $@ || rm $@ +if HAVE_PLYMOUTH +dist_systemunit_DATA += \ + units/plymouth-start.service \ + units/plymouth-read-write.service \ + units/plymouth-quit.service \ + units/plymouth-quit-wait.service \ + units/plymouth-reboot.service \ + units/plymouth-kexec.service \ + units/plymouth-poweroff.service \ + units/plymouth-halt.service \ + units/systemd-ask-password-plymouth.path + +nodist_systemunit_DATA += \ + units/systemd-ask-password-plymouth.service -%.pc: %.pc.in Makefile - $(SED_PROCESS) +EXTRA_DIST += \ + units/systemd-ask-password-plymouth.service.in +endif -%.rules: %.rules.in Makefile - $(SED_PROCESS) +dist_doc_DATA = \ + README \ + NEWS \ + LICENSE \ + DISTRO_PORTING -%.service: %.service.in Makefile - $(SED_PROCESS) +pkgconfigdata_DATA = \ + src/systemd.pc -%.sh: %.sh.in Makefile - $(SED_PROCESS) - $(AM_V_GEN)chmod +x $@ +# First passed through sed, followed by intltool +polkitpolicy_in_in_files = \ + src/org.freedesktop.systemd1.policy.in.in -%.pl: %.pl.in Makefile - $(SED_PROCESS) - $(AM_V_GEN)chmod +x $@ +nodist_polkitpolicy_DATA = \ + $(polkitpolicy_in_files:.policy.in=.policy) \ + $(polkitpolicy_in_in_files:.policy.in.in=.policy) + +EXTRA_DIST += \ + $(polkitpolicy_in_files) \ + $(polkitpolicy_in_in_files) + +@INTLTOOL_POLICY_RULE@ + +noinst_LTLIBRARIES = \ + libsystemd-basic.la \ + libsystemd-core.la + +libsystemd_basic_la_SOURCES = \ + src/util.c \ + src/virt.c \ + src/label.c \ + src/hashmap.c \ + src/set.c \ + src/strv.c \ + src/conf-parser.c \ + src/socket-util.c \ + src/log.c \ + src/ratelimit.c \ + src/exit-status.c \ + src/utf8.c + +libsystemd_basic_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(SELINUX_CFLAGS) + +libsystemd_basic_la_LIBADD = \ + $(SELINUX_LIBS) \ + $(CAP_LIBS) + +libsystemd_core_la_SOURCES = \ + src/unit.c \ + src/job.c \ + src/manager.c \ + src/path-lookup.c \ + src/load-fragment.c \ + src/service.c \ + src/automount.c \ + src/mount.c \ + src/swap.c \ + src/device.c \ + src/target.c \ + src/snapshot.c \ + src/socket.c \ + src/timer.c \ + src/path.c \ + src/load-dropin.c \ + src/execute.c \ + src/utmp-wtmp.c \ + src/dbus.c \ + src/dbus-manager.c \ + src/dbus-unit.c \ + src/dbus-job.c \ + src/dbus-service.c \ + src/dbus-socket.c \ + src/dbus-timer.c \ + src/dbus-target.c \ + src/dbus-mount.c \ + src/dbus-automount.c \ + src/dbus-swap.c \ + src/dbus-snapshot.c \ + src/dbus-device.c \ + src/dbus-execute.c \ + src/dbus-path.c \ + src/cgroup.c \ + src/mount-setup.c \ + src/hostname-setup.c \ + src/selinux-setup.c \ + src/ima-setup.c \ + src/loopback-setup.c \ + src/kmod-setup.c \ + src/locale-setup.c \ + src/machine-id-setup.c \ + src/specifier.c \ + src/unit-name.c \ + src/fdset.c \ + src/namespace.c \ + src/tcpwrap.c \ + src/cgroup-util.c \ + src/condition.c \ + src/dbus-common.c \ + src/sd-daemon.c \ + src/install.c \ + src/cgroup-attr.c \ + src/sd-id128.c + +nodist_libsystemd_core_la_SOURCES = \ + src/load-fragment-gperf.c \ + src/load-fragment-gperf-nulstr.c + +EXTRA_DIST += \ + src/load-fragment-gperf.gperf.m4 + +libsystemd_core_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(UDEV_CFLAGS) \ + $(LIBWRAP_CFLAGS) \ + $(PAM_CFLAGS) \ + $(AUDIT_CFLAGS) \ + $(KMOD_CFLAGS) + +libsystemd_core_la_LIBADD = \ + libsystemd-basic.la \ + $(DBUS_LIBS) \ + $(UDEV_LIBS) \ + $(LIBWRAP_LIBS) \ + $(PAM_LIBS) \ + $(AUDIT_LIBS) \ + $(CAP_LIBS) \ + $(KMOD_LIBS) + +# This is needed because automake is buggy in how it generates the +# rules for C programs, but not Vala programs. We therefore can't +# list the .h files as dependencies if we want make dist to work. + +EXTRA_DIST += \ + src/util.h \ + src/virt.h \ + src/label.h \ + src/hashmap.h \ + src/set.h \ + src/strv.h \ + src/conf-parser.h \ + src/socket-util.h \ + src/log.h \ + src/ratelimit.h \ + src/exit-status.h \ + src/unit.h \ + src/job.h \ + src/manager.h \ + src/path-lookup.h \ + src/load-fragment.h \ + src/service.h \ + src/automount.h \ + src/mount.h \ + src/swap.h \ + src/device.h \ + src/target.h \ + src/snapshot.h \ + src/socket.h \ + src/timer.h \ + src/path.h \ + src/load-dropin.h \ + src/execute.h \ + src/utmp-wtmp.h \ + src/dbus.h \ + src/dbus-manager.h \ + src/dbus-unit.h \ + src/dbus-job.h \ + src/dbus-service.h \ + src/dbus-socket.h \ + src/dbus-timer.h \ + src/dbus-target.h \ + src/dbus-mount.h \ + src/dbus-automount.h \ + src/dbus-swap.h \ + src/dbus-snapshot.h \ + src/dbus-device.h \ + src/dbus-execute.h \ + src/dbus-path.h \ + src/cgroup.h \ + src/mount-setup.h \ + src/hostname-setup.h \ + src/selinux-setup.h \ + src/loopback-setup.h \ + src/kmod-setup.h \ + src/locale-setup.h \ + src/machine-id-setup.h \ + src/specifier.h \ + src/unit-name.h \ + src/fdset.h \ + src/namespace.h \ + src/tcpwrap.h \ + src/cgroup-util.h \ + src/condition.h \ + src/dbus-common.h \ + src/install.h \ + src/cgroup-attr.h \ + src/macro.h \ + src/def.h \ + src/ioprio.h \ + src/missing.h \ + src/list.h \ + src/securebits.h \ + src/linux/auto_dev-ioctl.h \ + src/linux/fanotify.h \ + src/initreq.h \ + src/special.h \ + src/dbus-common.h \ + src/bus-errors.h \ + src/cgroup-show.h \ + src/build.h \ + src/shutdownd.h \ + src/umount.h \ + src/ask-password-api.h \ + src/pager.h \ + src/sysfs-show.h \ + src/polkit.h \ + src/dbus-loop.h \ + src/spawn-agent.h \ + src/acl-util.h \ + src/logs-show.h \ + src/utf8.h \ + src/journal/sparse-endian.h \ + src/ima-setup.h + +MANPAGES = \ + man/systemd.1 \ + man/systemctl.1 \ + man/systemd-cgls.1 \ + man/systemd-cgtop.1 \ + man/systemd-nspawn.1 \ + man/systemd-tmpfiles.8 \ + man/systemd-notify.1 \ + man/systemd.unit.5 \ + man/systemd.service.5 \ + man/systemd.socket.5 \ + man/systemd.mount.5 \ + man/systemd.automount.5 \ + man/systemd.swap.5 \ + man/systemd.timer.5 \ + man/systemd.path.5 \ + man/systemd.target.5 \ + man/systemd.device.5 \ + man/systemd.snapshot.5 \ + man/systemd.exec.5 \ + man/systemd.special.7 \ + man/daemon.7 \ + man/runlevel.8 \ + man/telinit.8 \ + man/halt.8 \ + man/shutdown.8 \ + man/pam_systemd.8 \ + man/systemd.conf.5 \ + man/tmpfiles.d.5 \ + man/hostname.5 \ + man/timezone.5 \ + man/machine-id.5 \ + man/locale.conf.5 \ + man/os-release.5 \ + man/machine-info.5 \ + man/modules-load.d.5 \ + man/sysctl.d.5 \ + man/systemd-ask-password.1 \ + man/systemd-cat.1 \ + man/systemd-machine-id-setup.1 \ + man/journald.conf.5 \ + man/journalctl.1 + +MANPAGES_ALIAS = \ + man/reboot.8 \ + man/poweroff.8 \ + man/init.1 + +man/reboot.8: man/halt.8 +man/poweroff.8: man/halt.8 +man/init.1: man/systemd.1 + +XML_FILES = \ + ${patsubst %.1,%.xml,${patsubst %.3,%.xml,${patsubst %.5,%.xml,${patsubst %.7,%.xml,${patsubst %.8,%.xml,$(MANPAGES)}}}}} + +if ENABLE_MANPAGES +man_MANS = \ + $(MANPAGES) \ + $(MANPAGES_ALIAS) + +noinst_DATA = \ + ${XML_FILES:.xml=.html} +endif + +EXTRA_DIST += \ + $(XML_FILES) \ + ${XML_FILES:.xml=.html} \ + $(MANPAGES) \ + $(MANPAGES_ALIAS) + +systemd_SOURCES = \ + src/main.c + +systemd_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(UDEV_CFLAGS) + +systemd_LDADD = \ + libsystemd-core.la + +test_engine_SOURCES = \ + src/test-engine.c + +test_engine_CFLAGS = $(systemd_CFLAGS) +test_engine_LDADD = $(systemd_LDADD) + +test_job_type_SOURCES = \ + src/test-job-type.c + +test_job_type_CFLAGS = $(systemd_CFLAGS) +test_job_type_LDADD = $(systemd_LDADD) + +test_ns_SOURCES = \ + src/test-ns.c + +test_ns_CFLAGS = $(systemd_CFLAGS) +test_ns_LDADD = $(systemd_LDADD) + +test_loopback_SOURCES = \ + src/test-loopback.c \ + src/loopback-setup.c + +test_loopback_LDADD = \ + libsystemd-basic.la + +test_hostname_SOURCES = \ + src/test-hostname.c \ + src/hostname-setup.c + +test_hostname_LDADD = \ + libsystemd-basic.la + +test_daemon_SOURCES = \ + src/test-daemon.c + +test_daemon_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la + +test_cgroup_SOURCES = \ + src/test-cgroup.c \ + src/cgroup-util.c + +test_cgroup_LDADD = \ + libsystemd-basic.la + +test_env_replace_SOURCES = \ + src/test-env-replace.c + +test_env_replace_LDADD = \ + libsystemd-basic.la + +test_strv_SOURCES = \ + src/test-strv.c \ + src/specifier.c + +test_strv_LDADD = \ + libsystemd-basic.la + +test_install_SOURCES = \ + src/test-install.c \ + src/install.c \ + src/path-lookup.c \ + src/unit-name.c + +test_install_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) + +test_install_LDADD = \ + libsystemd-basic.la + +systemd_initctl_SOURCES = \ + src/initctl.c \ + src/dbus-common.c + +systemd_initctl_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) + +systemd_initctl_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la \ + $(DBUS_LIBS) + +systemd_update_utmp_SOURCES = \ + src/update-utmp.c \ + src/dbus-common.c \ + src/utmp-wtmp.c + +systemd_update_utmp_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(AUDIT_CFLAGS) + +systemd_update_utmp_LDADD = \ + libsystemd-basic.la \ + $(DBUS_LIBS) \ + $(AUDIT_LIBS) + +systemd_shutdownd_SOURCES = \ + src/utmp-wtmp.c \ + src/shutdownd.c + +systemd_shutdownd_CFLAGS = \ + $(AM_CFLAGS) + +systemd_shutdownd_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la + +systemd_shutdown_SOURCES = \ + src/mount-setup.c \ + src/umount.c \ + src/shutdown.c + +systemd_shutdown_CFLAGS = \ + $(AM_CFLAGS) \ + $(UDEV_CFLAGS) + +systemd_shutdown_LDADD = \ + libsystemd-basic.la \ + $(UDEV_LIBS) + +systemd_modules_load_SOURCES = \ + src/modules-load.c + +systemd_modules_load_CFLAGS = \ + $(KMOD_CFLAGS) + +systemd_modules_load_LDADD = \ + libsystemd-basic.la \ + $(KMOD_LIBS) + +systemd_tmpfiles_SOURCES = \ + src/tmpfiles.c + +systemd_tmpfiles_LDADD = \ + libsystemd-basic.la + +systemd_machine_id_setup_SOURCES = \ + src/machine-id-setup.c \ + src/machine-id-main.c \ + src/sd-id128.c + +systemd_machine_id_setup_LDADD = \ + libsystemd-basic.la + +systemd_sysctl_SOURCES = \ + src/sysctl.c + +systemd_sysctl_LDADD = \ + libsystemd-basic.la + +systemd_fsck_SOURCES = \ + src/fsck.c \ + src/dbus-common.c + +systemd_fsck_CFLAGS = \ + $(AM_CFLAGS) \ + $(UDEV_CFLAGS) \ + $(DBUS_CFLAGS) + +systemd_fsck_LDADD = \ + libsystemd-basic.la \ + $(UDEV_LIBS) \ + $(DBUS_LIBS) + +systemd_timestamp_SOURCES = \ + src/timestamp.c + +systemd_timestamp_LDADD = \ + libsystemd-basic.la + +systemd_ac_power_SOURCES = \ + src/ac-power.c + +systemd_ac_power_CFLAGS = \ + $(AM_CFLAGS) \ + $(UDEV_CFLAGS) + +systemd_ac_power_LDADD = \ + libsystemd-basic.la \ + $(UDEV_LIBS) + +systemd_detect_virt_SOURCES = \ + src/detect-virt.c + +systemd_detect_virt_LDADD = \ + libsystemd-basic.la + +systemd_getty_generator_SOURCES = \ + src/getty-generator.c \ + src/unit-name.c + +systemd_getty_generator_LDADD = \ + libsystemd-basic.la + +systemd_rc_local_generator_SOURCES = \ + src/rc-local-generator.c + +systemd_rc_local_generator_LDADD = \ + libsystemd-basic.la + +systemd_remount_api_vfs_SOURCES = \ + src/remount-api-vfs.c \ + src/mount-setup.c \ + src/exit-status.c + +systemd_remount_api_vfs_LDADD = \ + libsystemd-basic.la + +systemd_cgroups_agent_SOURCES = \ + src/cgroups-agent.c \ + src/dbus-common.c + +systemd_cgroups_agent_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) + +systemd_cgroups_agent_LDADD = \ + libsystemd-basic.la \ + $(DBUS_LIBS) + +systemctl_SOURCES = \ + src/systemctl.c \ + src/utmp-wtmp.c \ + src/dbus-common.c \ + src/path-lookup.c \ + src/cgroup-show.c \ + src/cgroup-util.c \ + src/exit-status.c \ + src/unit-name.c \ + src/pager.c \ + src/install.c \ + src/spawn-agent.c \ + src/logs-show.c + +systemctl_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) + +systemctl_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la \ + libsystemd-journal.la \ + libsystemd-id128.la \ + $(DBUS_LIBS) + +systemd_notify_SOURCES = \ + src/notify.c \ + src/readahead/sd-readahead.c + +systemd_notify_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la + +systemd_ask_password_SOURCES = \ + src/ask-password.c \ + src/ask-password-api.c + +systemd_ask_password_LDADD = \ + libsystemd-basic.la + +systemd_reply_password_SOURCES = \ + src/reply-password.c + +systemd_reply_password_LDADD = \ + libsystemd-basic.la + +systemd_cgls_SOURCES = \ + src/cgls.c \ + src/cgroup-show.c \ + src/cgroup-util.c \ + src/pager.c + +systemd_cgls_LDADD = \ + libsystemd-basic.la + +systemd_cgtop_SOURCES = \ + src/cgtop.c \ + src/cgroup-util.c + +systemd_cgtop_LDADD = \ + libsystemd-basic.la + +systemd_nspawn_SOURCES = \ + src/nspawn.c \ + src/cgroup-util.c \ + src/loopback-setup.c + +systemd_nspawn_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la + +systemd_stdio_bridge_SOURCES = \ + src/bridge.c + +systemd_stdio_bridge_LDADD = \ + libsystemd-basic.la + +systemd_tty_ask_password_agent_SOURCES = \ + src/tty-ask-password-agent.c \ + src/ask-password-api.c \ + src/utmp-wtmp.c + +systemd_tty_ask_password_agent_LDADD = \ + libsystemd-basic.la # ------------------------------------------------------------------------------ -SUBDIRS += src/docs - -include_HEADERS = src/libudev.h -lib_LTLIBRARIES = libudev.la -noinst_LTLIBRARIES = libudev-private.la - -libudev_la_SOURCES =\ - src/libudev-private.h \ - src/libudev.c \ - src/libudev-list.c \ - src/libudev-util.c \ - src/libudev-device.c \ - src/libudev-enumerate.c \ - src/libudev-monitor.c \ - src/libudev-queue.c - -libudev_la_LDFLAGS = \ - $(AM_LDFLAGS) \ - -version-info $(LIBUDEV_CURRENT):$(LIBUDEV_REVISION):$(LIBUDEV_AGE) - -libudev_private_la_SOURCES =\ - $(libudev_la_SOURCES) \ - src/libudev-util-private.c \ - src/libudev-device-private.c \ - src/libudev-queue-private.c - -if WITH_SELINUX -libudev_private_la_SOURCES += src/libudev-selinux-private.c -libudev_private_la_LIBADD = $(SELINUX_LIBS) -endif - -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = src/libudev.pc -EXTRA_DIST += src/libudev.pc.in -CLEANFILES += src/libudev.pc - -EXTRA_DIST += src/COPYING -# move lib from $(libdir) to $(rootlib_execdir) and update devel link, if needed -libudev-install-move-hook: - if test "$(libdir)" != "$(rootlib_execdir)"; then \ - mkdir -p $(DESTDIR)$(rootlib_execdir) && \ - so_img_name=$$(readlink $(DESTDIR)$(libdir)/libudev.so) && \ +libsystemd_daemon_la_SOURCES = \ + src/sd-daemon.c + +libsystemd_daemon_la_CFLAGS = \ + $(AM_CFLAGS) \ + -fvisibility=hidden \ + -DSD_EXPORT_SYMBOLS + +libsystemd_daemon_la_LDFLAGS = \ + -shared \ + -version-info $(LIBSYSTEMD_DAEMON_CURRENT):$(LIBSYSTEMD_DAEMON_REVISION):$(LIBSYSTEMD_DAEMON_AGE) \ + -Wl,--version-script=$(top_srcdir)/src/libsystemd-daemon.sym + +pkginclude_HEADERS += \ + src/systemd/sd-daemon.h + +# move lib from $(libdir) to $(rootlibdir) and update devel link, if needed +libsystemd-daemon-install-hook: + if test "$(libdir)" != "$(rootlibdir)"; then \ + mkdir -p $(DESTDIR)$(rootlibdir) && \ + so_img_name=$$(readlink $(DESTDIR)$(libdir)/libsystemd-daemon.so) && \ so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \ - ln -sf $$so_img_rel_target_prefix$(rootlib_execdir)/$$so_img_name $(DESTDIR)$(libdir)/libudev.so && \ - mv $(DESTDIR)$(libdir)/libudev.so.* $(DESTDIR)$(rootlib_execdir); \ + ln -sf $$so_img_rel_target_prefix$(rootlibdir)/$$so_img_name $(DESTDIR)$(libdir)/libsystemd-daemon.so && \ + mv $(DESTDIR)$(libdir)/libsystemd-daemon.so.* $(DESTDIR)$(rootlibdir); \ fi -libudev-uninstall-move-hook: - rm -f $(DESTDIR)$(rootlib_execdir)/libudev.so* +INSTALL_EXEC_HOOKS += \ + libsystemd-daemon-install-hook + +libsystemd-daemon-uninstall-hook: + rm -f $(DESTDIR)$(rootlibdir)/libsystemd-daemon.so* + +UNINSTALL_EXEC_HOOKS += \ + libsystemd-daemon-uninstall-hook + +lib_LTLIBRARIES += \ + libsystemd-daemon.la -INSTALL_EXEC_HOOKS += libudev-install-move-hook -UNINSTALL_EXEC_HOOKS += libudev-uninstall-move-hook +pkgconfiglib_DATA += \ + src/libsystemd-daemon.pc + +MANPAGES += \ + man/sd-daemon.7 \ + man/sd_notify.3 \ + man/sd_listen_fds.3 \ + man/sd_is_fifo.3 \ + man/sd_booted.3 + +MANPAGES_ALIAS += \ + man/sd_is_socket.3 \ + man/sd_is_socket_unix.3 \ + man/sd_is_socket_inet.3 \ + man/sd_is_mq.3 \ + man/sd_notifyf.3 + +man/sd_is_socket.3: man/sd_is_fifo.3 +man/sd_is_socket_unix.3: man/sd_is_fifo.3 +man/sd_is_socket_inet.3: man/sd_is_fifo.3 +man/sd_is_mq.3: man/sd_is_fifo.3 +man/sd_notifyf.3: man/sd_notify.3 + +EXTRA_DIST += \ + src/libsystemd-daemon.pc.in \ + src/libsystemd-daemon.sym # ------------------------------------------------------------------------------ -udev-confdirs: - -mkdir -p $(DESTDIR)$(sysconfdir)/udev/rules.d - -mkdir -p $(DESTDIR)$(libexecdir)/udev/devices - -INSTALL_DATA_HOOKS += udev-confdirs - -udevrulesdir = $(libexecdir)/udev/rules.d -dist_udevrules_DATA = \ - rules/42-usb-hid-pm.rules \ - rules/50-udev-default.rules \ - rules/60-persistent-storage-tape.rules \ - rules/60-persistent-serial.rules \ - rules/60-persistent-input.rules \ - rules/60-persistent-alsa.rules \ - rules/60-persistent-storage.rules \ - rules/75-net-description.rules \ - rules/75-tty-description.rules \ - rules/78-sound-card.rules \ - rules/80-drivers.rules \ - rules/95-udev-late.rules - -udevconfdir = $(sysconfdir)/udev -dist_udevconf_DATA = src/udev.conf - -sharepkgconfigdir = $(datadir)/pkgconfig -sharepkgconfig_DATA = src/udev.pc -EXTRA_DIST += src/udev.pc.in -CLEANFILES += src/udev.pc - -if WITH_SYSTEMD -dist_systemdsystemunit_DATA = \ - src/udev-control.socket \ - src/udev-kernel.socket - -systemdsystemunit_DATA = \ - src/udev.service \ - src/udev-trigger.service \ - src/udev-settle.service +libsystemd_id128_la_SOURCES = \ + src/sd-id128.c + +libsystemd_id128_la_CFLAGS = \ + $(AM_CFLAGS) \ + -fvisibility=hidden + +libsystemd_id128_la_LDFLAGS = \ + -shared \ + -version-info $(LIBSYSTEMD_ID128_CURRENT):$(LIBSYSTEMD_ID128_REVISION):$(LIBSYSTEMD_ID128_AGE) \ + -Wl,--version-script=$(top_srcdir)/src/libsystemd-id128.sym + +libsystemd_id128_la_LIBADD = \ + libsystemd-basic.la + +test_id128_SOURCES = \ + src/test-id128.c \ + src/sd-id128.c + +test_id128_LDADD = \ + libsystemd-basic.la + +noinst_PROGRAMS += \ + test-id128 + +pkginclude_HEADERS += \ + src/systemd/sd-id128.h + +lib_LTLIBRARIES += \ + libsystemd-id128.la + +pkgconfiglib_DATA += \ + src/libsystemd-id128.pc + +# move lib from $(libdir) to $(rootlibdir) and update devel link, if needed +libsystemd-id128-install-hook: + if test "$(libdir)" != "$(rootlibdir)"; then \ + mkdir -p $(DESTDIR)$(rootlibdir) && \ + so_img_name=$$(readlink $(DESTDIR)$(libdir)/libsystemd-id128.so) && \ + so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \ + ln -sf $$so_img_rel_target_prefix$(rootlibdir)/$$so_img_name $(DESTDIR)$(libdir)/libsystemd-id128.so && \ + mv $(DESTDIR)$(libdir)/libsystemd-id128.so.* $(DESTDIR)$(rootlibdir); \ + fi + +INSTALL_EXEC_HOOKS += \ + libsystemd-id128-install-hook + +libsystemd-id128-uninstall-hook: + rm -f $(DESTDIR)$(rootlibdir)/libsystemd-id128.so* + +UNINSTALL_EXEC_HOOKS += \ + libsystemd-id128-uninstall-hook EXTRA_DIST += \ - src/udev.service.in \ - src/udev-trigger.service.in \ - src/udev-settle.service.in + src/libsystemd-id128.pc.in \ + src/libsystemd-id128.sym -CLEANFILES += \ - src/udev.service \ - src/udev-trigger.service \ - src/udev-settle.service +# ------------------------------------------------------------------------------ +systemd_journald_SOURCES = \ + src/journal/journald.c \ + src/journal/sd-journal.c \ + src/journal/journal-file.c \ + src/journal/lookup3.c \ + src/journal/journal-rate-limit.c \ + src/sd-id128.c \ + src/cgroup-util.c + +if HAVE_ACL +systemd_journald_SOURCES += \ + src/acl-util.c +endif -systemd-install-hook: - mkdir -p $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants - ln -sf ../udev-control.socket $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants/udev-control.socket - ln -sf ../udev-kernel.socket $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants/udev-kernel.socket - mkdir -p $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants - ln -sf ../udev.service $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants/udev.service - ln -sf ../udev-trigger.service $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants/udev-trigger.service +nodist_systemd_journald_SOURCES = \ + src/journal/journald-gperf.c + +systemd_journald_CFLAGS = \ + $(AM_CFLAGS) \ + $(ACL_CFLAGS) + +systemd_journald_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la \ + libsystemd-login.la \ + $(ACL_LIBS) + +if HAVE_XZ +systemd_journald_SOURCES += \ + src/journal/compress.c +systemd_journald_CFLAGS += \ + $(XZ_CFLAGS) +systemd_journald_LDADD += \ + $(XZ_LIBS) +endif -INSTALL_DATA_HOOKS += systemd-install-hook +systemd_cat_SOURCES = \ + src/journal/cat.c + +systemd_cat_LDADD = \ + libsystemd-basic.la \ + libsystemd-journal.la + +journalctl_SOURCES = \ + src/journal/journalctl.c \ + src/pager.c \ + src/logs-show.c + +journalctl_LDADD = \ + libsystemd-basic.la \ + libsystemd-journal.la \ + libsystemd-id128.la + +if HAVE_XZ +journalctl_SOURCES += \ + src/journal/compress.c +journalctl_CFLAGS = \ + $(AM_CFLAGS) \ + $(XZ_CFLAGS) +journalctl_LDADD += \ + $(XZ_LIBS) endif -bin_PROGRAMS = \ - udevadm - -pkglibexec_PROGRAMS = \ - udevd - -udev_common_sources = \ - src/udev.h \ - src/udev-event.c \ - src/udev-watch.c \ - src/udev-node.c \ - src/udev-rules.c \ - src/udev-ctrl.c \ - src/udev-builtin.c \ - src/udev-builtin-blkid.c \ - src/udev-builtin-firmware.c \ - src/udev-builtin-hwdb.c \ - src/udev-builtin-input_id.c \ - src/udev-builtin-kmod.c \ - src/udev-builtin-path_id.c \ - src/udev-builtin-usb_id.c - -udev_common_CFLAGS = \ - $(BLKID_CFLAGS) \ - $(KMOD_CFLAGS) +test_journal_SOURCES = \ + src/journal/test-journal.c \ + src/journal/sd-journal.c \ + src/journal/journal-file.c \ + src/journal/lookup3.c \ + src/journal/journal-send.c \ + src/sd-id128.c -udev_common_LDADD = \ - libudev-private.la \ - $(BLKID_LIBS) \ - $(KMOD_LIBS) +test_journal_LDADD = \ + libsystemd-basic.la -udev_common_CPPFLAGS = \ - $(AM_CPPFLAGS) \ - -DFIRMWARE_PATH="$(FIRMWARE_PATH)" \ - -DUSB_DATABASE=\"$(USB_DATABASE)\" -DPCI_DATABASE=\"$(PCI_DATABASE)\" +if HAVE_XZ +test_journal_SOURCES += \ + src/journal/compress.c -udevd_SOURCES = \ - $(udev_common_sources) \ - src/udevd.c \ - src/sd-daemon.h \ - src/sd-daemon.c -udevd_CFLAGS = $(udev_common_CFLAGS) -udevd_LDADD = $(udev_common_LDADD) -udevd_CPPFLAGS = $(udev_common_CPPFLAGS) - -udevadm_SOURCES = \ - $(udev_common_sources) \ - src/udevadm.c \ - src/udevadm-info.c \ - src/udevadm-control.c \ - src/udevadm-monitor.c \ - src/udevadm-settle.c \ - src/udevadm-trigger.c \ - src/udevadm-test.c \ - src/udevadm-test-builtin.c -udevadm_CFLAGS = $(udev_common_CFLAGS) -udevadm_LDADD = $(udev_common_LDADD) -udevadm_CPPFLAGS = $(udev_common_CPPFLAGS) +test_journal_CFLAGS = \ + $(AM_CFLAGS) \ + $(XZ_CFLAGS) -# ------------------------------------------------------------------------------ -if ENABLE_MANPAGES -dist_man_MANS += \ - src/udev.7 \ - src/udevadm.8 \ - src/udevd.8 +test_journal_LDADD += \ + $(XZ_LIBS) +endif + +test_journal_send_SOURCES = \ + src/journal/test-journal-send.c + +test_journal_send_LDADD = \ + libsystemd-basic.la \ + libsystemd-journal.la + +libsystemd_journal_la_SOURCES = \ + src/journal/sd-journal.c \ + src/journal/journal-file.c \ + src/journal/lookup3.c \ + src/journal/journal-send.c + +libsystemd_journal_la_CFLAGS = \ + $(AM_CFLAGS) \ + -fvisibility=hidden + +libsystemd_journal_la_LDFLAGS = \ + -shared \ + -version-info $(LIBSYSTEMD_JOURNAL_CURRENT):$(LIBSYSTEMD_JOURNAL_REVISION):$(LIBSYSTEMD_JOURNAL_AGE) \ + -Wl,--version-script=$(top_srcdir)/src/journal/libsystemd-journal.sym + +libsystemd_journal_la_LIBADD = \ + libsystemd-basic.la \ + libsystemd-id128.la + +if HAVE_XZ +libsystemd_journal_la_SOURCES += \ + src/journal/compress.c + +libsystemd_journal_la_CFLAGS += \ + $(XZ_CFLAGS) + +libsystemd_journal_la_LIBADD += \ + $(XZ_LIBS) endif +# move lib from $(libdir) to $(rootlibdir) and update devel link, if needed +libsystemd-journal-install-hook: + if test "$(libdir)" != "$(rootlibdir)"; then \ + mkdir -p $(DESTDIR)$(rootlibdir) && \ + so_img_name=$$(readlink $(DESTDIR)$(libdir)/libsystemd-journal.so) && \ + so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \ + ln -sf $$so_img_rel_target_prefix$(rootlibdir)/$$so_img_name $(DESTDIR)$(libdir)/libsystemd-journal.so && \ + mv $(DESTDIR)$(libdir)/libsystemd-journal.so.* $(DESTDIR)$(rootlibdir); \ + fi + +INSTALL_EXEC_HOOKS += \ + libsystemd-journal-install-hook + +libsystemd-journal-uninstall-hook: + rm -f $(DESTDIR)$(rootlibdir)/libsystemd-journal.so* + +UNINSTALL_EXEC_HOOKS += \ + libsystemd-journal-uninstall-hook + +noinst_PROGRAMS += \ + test-journal \ + test-journal-send + +pkginclude_HEADERS += \ + src/systemd/sd-journal.h \ + src/systemd/sd-messages.h + +lib_LTLIBRARIES += \ + libsystemd-journal.la + +rootlibexec_PROGRAMS += \ + systemd-journald + +rootbin_PROGRAMS += \ + journalctl + +bin_PROGRAMS += \ + systemd-cat + +dist_systemunit_DATA += \ + units/systemd-journald.socket + +nodist_systemunit_DATA += \ + units/systemd-journald.service + +dist_pkgsysconf_DATA += \ + src/journal/journald.conf + +pkgconfiglib_DATA += \ + src/journal/libsystemd-journal.pc + +journal-install-data-hook: + $(MKDIR_P) -m 0755 \ + $(DESTDIR)$(systemunitdir)/sockets.target.wants \ + $(DESTDIR)$(systemunitdir)/sysinit.target.wants + ( cd $(DESTDIR)$(systemunitdir)/sockets.target.wants && \ + rm -f systemd-journald.socket && \ + $(LN_S) ../systemd-journald.socket ) + ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \ + rm -f systemd-journald.service && \ + $(LN_S) ../systemd-journald.service ) + +INSTALL_DATA_HOOKS += \ + journal-install-data-hook + EXTRA_DIST += \ - src/udev.xml \ - src/udevadm.xml \ - src/udevd.xml + src/journal/journald.h \ + src/journal/journal-def.h \ + src/journal/journal-internal.h \ + src/journal/journal-file.h \ + src/journal/lookup3.h \ + src/journal/compress.h \ + src/journal/journal-rate-limit.h \ + src/journal/libsystemd-journal.pc.in \ + src/journal/libsystemd-journal.sym \ + units/systemd-journald.service.in \ + src/journal/journald-gperf.gperf -if HAVE_XSLTPROC -dist_noinst_DATA = \ - src/udev.html \ - src/udevadm.html \ - src/udevd.html +CLEANFILES += \ + src/journal/journald-gperf.c + +# ------------------------------------------------------------------------------ +if ENABLE_COREDUMP +systemd_coredump_SOURCES = \ + src/journal/coredump.c + +systemd_coredump_LDADD = \ + libsystemd-basic.la \ + libsystemd-journal.la \ + libsystemd-login.la + +rootlibexec_PROGRAMS += \ + systemd-coredump -src/%.7 src/%.8 : src/%.xml - $(AM_V_GEN)$(XSLTPROC) -o $@ -nonet http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< +sysctl_DATA = \ + sysctl.d/coredump.conf -src/%.html : src/%.xml - $(AM_V_GEN)$(XSLTPROC) -o $@ -nonet http://docbook.sourceforge.net/release/xsl/current/xhtml-1_1/docbook.xsl $< +EXTRA_DIST += \ + sysctl.d/coredump.conf.in + +CLEANFILES += \ + sysctl.d/coredump.conf endif # ------------------------------------------------------------------------------ -TESTS = \ - test/udev-test.pl \ - test/rules-test.sh +if ENABLE_BINFMT +systemd_binfmt_SOURCES = \ + src/binfmt/binfmt.c -check_PROGRAMS = \ - test-libudev \ - test-udev +systemd_binfmt_LDADD = \ + libsystemd-basic.la -test_libudev_SOURCES = src/test-libudev.c -test_libudev_LDADD = libudev.la +rootlibexec_PROGRAMS += \ + systemd-binfmt -test_udev_SOURCES = \ - $(udev_common_sources) \ - src/test-udev.c -test_udev_CFLAGS = $(udev_common_CFLAGS) -test_udev_LDADD = $(udev_common_LDADD) -test_udev_CPPFLAGS = $(udev_common_CPPFLAGS) -test_udev_DEPENDENCIES = test/sys +dist_systemunit_DATA += \ + units/proc-sys-fs-binfmt_misc.automount \ + units/proc-sys-fs-binfmt_misc.mount -# packed sysfs test tree -test/sys: - $(AM_V_GEN)mkdir -p test && tar -C test/ -xJf $(top_srcdir)/test/sys.tar.xz +nodist_systemunit_DATA += \ + units/systemd-binfmt.service -test-sys-distclean: - -rm -rf test/sys -DISTCLEAN_LOCAL_HOOKS += test-sys-distclean +binfmt-install-data-hook: + $(MKDIR_P) -m 0755 \ + $(DESTDIR)$(prefix)/lib/binfmt.d \ + $(DESTDIR)$(sysconfdir)/binfmt.d \ + $(DESTDIR)$(systemunitdir)/sysinit.target.wants + ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \ + rm -f systemd-binfmt.service \ + proc-sys-fs-binfmt_misc.automount && \ + $(LN_S) ../systemd-binfmt.service systemd-binfmt.service && \ + $(LN_S) ../proc-sys-fs-binfmt_misc.automount proc-sys-fs-binfmt_misc.automount ) -EXTRA_DIST += test/sys.tar.xz +INSTALL_DATA_HOOKS += \ + binfmt-install-data-hook + +MANPAGES += \ + man/binfmt.d.5 + +EXTRA_DIST += \ + units/systemd-binfmt.service.in +endif # ------------------------------------------------------------------------------ -ata_id_SOURCES = src/ata_id/ata_id.c -ata_id_LDADD = libudev-private.la -pkglibexec_PROGRAMS += ata_id +if ENABLE_VCONSOLE +systemd_vconsole_setup_SOURCES = \ + src/vconsole/vconsole-setup.c + +systemd_vconsole_setup_LDADD = \ + libsystemd-basic.la + +rootlibexec_PROGRAMS += \ + systemd-vconsole-setup + +nodist_systemunit_DATA += \ + units/systemd-vconsole-setup.service + +vconsole-install-data-hook: + $(MKDIR_P) -m 0755 \ + $(DESTDIR)$(systemunitdir)/sysinit.target.wants + ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \ + rm -f systemd-vconsole-setup.service && \ + $(LN_S) ../systemd-vconsole-setup.service systemd-vconsole-setup.service ) + +INSTALL_DATA_HOOKS += \ + vconsole-install-data-hook + +MANPAGES += \ + man/vconsole.conf.5 + +EXTRA_DIST += \ + units/systemd-vconsole-setup.service.in +endif # ------------------------------------------------------------------------------ -cdrom_id_SOURCES = src/cdrom_id/cdrom_id.c -cdrom_id_LDADD = libudev-private.la -pkglibexec_PROGRAMS += cdrom_id -dist_udevrules_DATA += src/cdrom_id/60-cdrom_id.rules +if ENABLE_READAHEAD +systemd_readahead_collect_SOURCES = \ + src/readahead/readahead-collect.c \ + src/readahead/readahead-common.c + +systemd_readahead_collect_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la \ + $(UDEV_LIBS) + +systemd_readahead_collect_CFLAGS = \ + $(AM_CFLAGS) \ + $(UDEV_CFLAGS) + +systemd_readahead_replay_SOURCES = \ + src/readahead/readahead-replay.c \ + src/readahead/readahead-common.c + +systemd_readahead_replay_CFLAGS = \ + $(AM_CFLAGS) \ + $(UDEV_CFLAGS) + +systemd_readahead_replay_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la \ + $(UDEV_LIBS) + +rootlibexec_PROGRAMS += \ + systemd-readahead-collect \ + systemd-readahead-replay + +dist_systemunit_DATA += \ + units/systemd-readahead-done.timer + +nodist_systemunit_DATA += \ + units/systemd-readahead-collect.service \ + units/systemd-readahead-replay.service \ + units/systemd-readahead-done.service + +EXTRA_DIST += \ + src/systemd/sd-readahead.h \ + src/readahead/readahead-common.h \ + units/systemd-readahead-collect.service.in \ + units/systemd-readahead-replay.service.in \ + units/systemd-readahead-done.service.in + +MANPAGES += \ + man/sd_readahead.3 \ + man/sd-readahead.7 +endif # ------------------------------------------------------------------------------ -collect_SOURCES = src/collect/collect.c -collect_LDADD = libudev-private.la -pkglibexec_PROGRAMS += collect +if ENABLE_QUOTACHECK +rootlibexec_PROGRAMS += \ + systemd-quotacheck + +nodist_systemunit_DATA += \ + units/quotacheck.service + +EXTRA_DIST += \ + units/quotacheck.service.in + +systemd_quotacheck_SOURCES = \ + src/quotacheck.c + +systemd_quotacheck_LDADD = \ + libsystemd-basic.la +endif # ------------------------------------------------------------------------------ -scsi_id_SOURCES =\ - src/scsi_id/scsi_id.c \ - src/scsi_id/scsi_serial.c \ - src/scsi_id/scsi.h \ - src/scsi_id/scsi_id.h -scsi_id_LDADD = libudev-private.la -pkglibexec_PROGRAMS += scsi_id -dist_man_MANS += src/scsi_id/scsi_id.8 -EXTRA_DIST += src/scsi_id/README +if ENABLE_RANDOMSEED +rootlibexec_PROGRAMS += \ + systemd-random-seed + +nodist_systemunit_DATA += \ + units/systemd-random-seed-save.service \ + units/systemd-random-seed-load.service + +EXTRA_DIST += \ + units/systemd-random-seed-save.service.in \ + units/systemd-random-seed-load.service.in + +systemd_random_seed_SOURCES = \ + src/random-seed.c + +systemd_random_seed_LDADD = \ + libsystemd-basic.la + +randomseed-install-data-hook: + $(MKDIR_P) -m 0755 \ + $(DESTDIR)$(systemunitdir)/shutdown.target.wants \ + $(DESTDIR)$(systemunitdir)/sysinit.target.wants + ( cd $(DESTDIR)$(systemunitdir)/shutdown.target.wants && \ + rm -f systemd-random-seed-save.service && \ + $(LN_S) ../systemd-random-seed-save.service systemd-random-seed-save.service ) + ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \ + rm -f systemd-random-seed-load.service && \ + $(LN_S) ../systemd-random-seed-load.service systemd-random-seed-load.service ) + +INSTALL_DATA_HOOKS += \ + randomseed-install-data-hook +endif + +# ------------------------------------------------------------------------------ +if HAVE_LIBCRYPTSETUP +rootlibexec_PROGRAMS += \ + systemd-cryptsetup + +systemgenerator_PROGRAMS += \ + systemd-cryptsetup-generator + +dist_systemunit_DATA += \ + units/cryptsetup.target + +systemd_cryptsetup_SOURCES = \ + src/cryptsetup/cryptsetup.c \ + src/ask-password-api.c + +systemd_cryptsetup_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBCRYPTSETUP_CFLAGS) \ + $(UDEV_CFLAGS) + +systemd_cryptsetup_LDADD = \ + $(LIBCRYPTSETUP_LIBS) \ + $(UDEV_LIBS) \ + libsystemd-basic.la + +systemd_cryptsetup_generator_SOURCES = \ + src/cryptsetup/cryptsetup-generator.c \ + src/unit-name.c + +systemd_cryptsetup_generator_LDADD = \ + libsystemd-basic.la + +cryptsetup-install-data-hook: + $(MKDIR_P) -m 0755 \ + $(DESTDIR)$(systemunitdir)/sysinit.target.wants + ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \ + rm -f cryptsetup.target && \ + $(LN_S) ../cryptsetup.target cryptsetup.target ) + +INSTALL_DATA_HOOKS += \ + cryptsetup-install-data-hook +endif # ------------------------------------------------------------------------------ -v4l_id_SOURCES = src/v4l_id/v4l_id.c -v4l_id_LDADD = libudev-private.la -pkglibexec_PROGRAMS += v4l_id -dist_udevrules_DATA += src/v4l_id/60-persistent-v4l.rules +if ENABLE_HOSTNAMED +systemd_hostnamed_SOURCES = \ + src/hostname/hostnamed.c \ + src/dbus-common.c \ + src/polkit.c + +systemd_hostnamed_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) + +systemd_hostnamed_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la \ + $(DBUS_LIBS) + +rootlibexec_PROGRAMS += \ + systemd-hostnamed + +nodist_systemunit_DATA += \ + units/systemd-hostnamed.service + +dist_dbuspolicy_DATA += \ + src/hostname/org.freedesktop.hostname1.conf + +dist_dbussystemservice_DATA += \ + src/hostname/org.freedesktop.hostname1.service + +polkitpolicy_in_files += \ + src/hostname/org.freedesktop.hostname1.policy.in + +dbusinterface_DATA += \ + org.freedesktop.hostname1.xml + +org.freedesktop.hostname1.xml: systemd-hostnamed + $(AM_V_GEN)$(LIBTOOL) --mode=execute $(OBJCOPY) -O binary -j introspect.hostname1 $< $@.tmp && \ + $(STRINGS) $@.tmp | $(AWK) -f $(srcdir)/introspect.awk | \ + $(DBUS_PREPROCESS) -o $@ - && rm $@.tmp + +hostnamed-install-data-hook: + ( cd $(DESTDIR)$(systemunitdir) && \ + rm -f dbus-org.freedesktop.hostname1.service && \ + $(LN_S) systemd-hostnamed.service dbus-org.freedesktop.hostname1.service ) + +INSTALL_DATA_HOOKS += \ + hostnamed-install-data-hook + +EXTRA_DIST += \ + units/systemd-hostnamed.service.in +endif # ------------------------------------------------------------------------------ -accelerometer_SOURCES = src/accelerometer/accelerometer.c -accelerometer_LDADD = libudev-private.la -lm -pkglibexec_PROGRAMS += accelerometer -dist_udevrules_DATA += src/accelerometer/61-accelerometer.rules +if ENABLE_LOCALED +systemd_localed_SOURCES = \ + src/locale/localed.c \ + src/dbus-common.c \ + src/polkit.c + +systemd_localed_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) + +systemd_localed_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la \ + $(DBUS_LIBS) + +nodist_systemunit_DATA += \ + units/systemd-localed.service + +rootlibexec_PROGRAMS += \ + systemd-localed + +dist_dbuspolicy_DATA += \ + src/locale/org.freedesktop.locale1.conf + +dist_dbussystemservice_DATA += \ + src/locale/org.freedesktop.locale1.service + +polkitpolicy_in_files += \ + src/locale/org.freedesktop.locale1.policy.in + +dbusinterface_DATA += \ + org.freedesktop.locale1.xml + +org.freedesktop.locale1.xml: systemd-localed + $(AM_V_GEN)$(LIBTOOL) --mode=execute $(OBJCOPY) -O binary -j introspect.locale1 $< $@.tmp && \ + $(STRINGS) $@.tmp | $(AWK) -f $(srcdir)/introspect.awk | \ + $(DBUS_PREPROCESS) -o $@ - && rm $@.tmp + +localed-install-data-hook: + ( cd $(DESTDIR)$(systemunitdir) && \ + rm -f dbus-org.freedesktop.locale1.service && \ + $(LN_S) systemd-localed.service dbus-org.freedesktop.locale1.service ) + +INSTALL_DATA_HOOKS += \ + localed-install-data-hook + +EXTRA_DIST += \ + units/systemd-localed.service.in + +dist_pkgdata_DATA = \ + src/locale/kbd-model-map + +dist_noinst_SCRIPT = \ + src/locale/generate-kbd-model-map + +update-kbd-model-map: + src/locale/generate-kbd-model-map > src/locale/kbd-model-map + +endif # ------------------------------------------------------------------------------ -if ENABLE_GUDEV -SUBDIRS += src/gudev/docs - -libgudev_includedir=$(includedir)/gudev-1.0/gudev -libgudev_include_HEADERS = \ - src/gudev/gudev.h \ - src/gudev/gudevenums.h \ - src/gudev/gudevenumtypes.h \ - src/gudev/gudevtypes.h \ - src/gudev/gudevclient.h \ - src/gudev/gudevdevice.h \ - src/gudev/gudevenumerator.h - -lib_LTLIBRARIES += libgudev-1.0.la - -pkgconfig_DATA += src/gudev/gudev-1.0.pc -EXTRA_DIST += src/gudev/gudev-1.0.pc.in -CLEANFILES += src/gudev/gudev-1.0.pc - -libgudev_1_0_la_SOURCES = \ - src/gudev/gudevenums.h \ - src/gudev/gudevenumtypes.h \ - src/gudev/gudevenumtypes.h\ - src/gudev/gudevtypes.h \ - src/gudev/gudevclient.h \ - src/gudev/gudevclient.c \ - src/gudev/gudevdevice.h \ - src/gudev/gudevdevice.c \ - src/gudev/gudevenumerator.h \ - src/gudev/gudevenumerator.c \ - src/gudev/gudevprivate.h - -nodist_libgudev_1_0_la_SOURCES = \ - src/gudev/gudevmarshal.h \ - src/gudev/gudevmarshal.c \ - src/gudev/gudevenumtypes.h \ - src/gudev/gudevenumtypes.c -BUILT_SOURCES += $(nodist_libgudev_1_0_la_SOURCES) - -libgudev_1_0_la_CPPFLAGS = \ - $(AM_CPPFLAGS) \ - -I$(top_builddir)/src\ - -I$(top_srcdir)/src\ - -I$(top_builddir)/src/gudev \ - -I$(top_srcdir)/src/gudev \ - -D_POSIX_PTHREAD_SEMANTICS -D_REENTRANT \ - -D_GUDEV_COMPILATION \ - -DG_LOG_DOMAIN=\"GUdev\" - -libgudev_1_0_la_CFLAGS = \ - -fvisibility=default \ - $(GLIB_CFLAGS) - -libgudev_1_0_la_LIBADD = libudev.la $(GLIB_LIBS) - -libgudev_1_0_la_LDFLAGS = \ - -version-info $(LIBGUDEV_CURRENT):$(LIBGUDEV_REVISION):$(LIBGUDEV_AGE) \ - -export-dynamic -no-undefined \ - -export-symbols-regex '^g_udev_.*' +if ENABLE_TIMEDATED +systemd_timedated_SOURCES = \ + src/timedate/timedated.c \ + src/dbus-common.c \ + src/polkit.c + +systemd_timedated_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) + +systemd_timedated_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la \ + $(DBUS_LIBS) + +rootlibexec_PROGRAMS += \ + systemd-timedated + +dist_dbussystemservice_DATA += \ + src/timedate/org.freedesktop.timedate1.service + +dist_dbuspolicy_DATA += \ + src/timedate/org.freedesktop.timedate1.conf + +nodist_systemunit_DATA += \ + units/systemd-timedated.service + +polkitpolicy_in_files += \ + src/timedate/org.freedesktop.timedate1.policy.in + +org.freedesktop.timedate1.xml: systemd-timedated + $(AM_V_GEN)$(LIBTOOL) --mode=execute $(OBJCOPY) -O binary -j introspect.timedate1 $< $@.tmp && \ + $(STRINGS) $@.tmp | $(AWK) -f $(srcdir)/introspect.awk | \ + $(DBUS_PREPROCESS) -o $@ - && rm $@.tmp + +dbusinterface_DATA += \ + org.freedesktop.timedate1.xml + +timedated-install-data-hook: + ( cd $(DESTDIR)$(systemunitdir) && \ + rm -f dbus-org.freedesktop.timedate1.service && \ + $(LN_S) systemd-timedated.service dbus-org.freedesktop.timedate1.service ) + +INSTALL_DATA_HOOKS += \ + timedated-install-data-hook EXTRA_DIST += \ - src/gudev/COPYING \ - src/gudev/gudevmarshal.list \ - src/gudev/gudevenumtypes.h.template \ - src/gudev/gudevenumtypes.c.template \ - src/gudev/gjs-example.js \ - src/gudev/seed-example-enum.js \ - src/gudev/seed-example.js - -src/gudev/gudevmarshal.h: src/gudev/gudevmarshal.list - $(AM_V_GEN)glib-genmarshal $< --prefix=g_udev_marshal --header > $@ - -src/gudev/gudevmarshal.c: src/gudev/gudevmarshal.list - $(AM_V_GEN)echo "#include \"gudevmarshal.h\"" > $@ && \ - glib-genmarshal $< --prefix=g_udev_marshal --body >> $@ - -src/gudev/gudevenumtypes.h: src/gudev/gudevenumtypes.h.template src/gudev/gudevenums.h - $(AM_V_GEN)glib-mkenums --template $^ > \ - $@.tmp && mv $@.tmp $@ - -src/gudev/gudevenumtypes.c: src/gudev/gudevenumtypes.c.template src/gudev/gudevenums.h - $(AM_V_GEN)glib-mkenums --template $^ > \ - $@.tmp && mv $@.tmp $@ - -if ENABLE_INTROSPECTION -src/gudev/GUdev-1.0.gir: libgudev-1.0.la $(G_IR_SCANNER) - $(AM_V_GEN)$(G_IR_SCANNER) -v \ - --warn-all \ - --namespace GUdev \ - --nsversion=1.0 \ - --include=GObject-2.0 \ - --library=gudev-1.0 \ - --library-path=$(top_builddir)/src \ - --library-path=$(top_builddir)/src/gudev \ - --output $@ \ - --pkg=glib-2.0 \ - --pkg=gobject-2.0 \ - --pkg-export=gudev-1.0 \ - --c-include=gudev/gudev.h \ - -I$(top_srcdir)/src/\ - -I$(top_builddir)/src/\ - -D_GUDEV_COMPILATION \ - -D_GUDEV_WORK_AROUND_DEV_T_BUG \ - $(top_srcdir)/src/gudev/gudev.h \ - $(top_srcdir)/src/gudev/gudevtypes.h \ - $(top_srcdir)/src/gudev/gudevenums.h \ - $(or $(wildcard $(top_builddir)/src/gudev/gudevenumtypes.h),$(top_srcdir)/src/gudev/gudevenumtypes.h) \ - $(top_srcdir)/src/gudev/gudevclient.h \ - $(top_srcdir)/src/gudev/gudevdevice.h \ - $(top_srcdir)/src/gudev/gudevenumerator.h \ - $(top_srcdir)/src/gudev/gudevclient.c \ - $(top_srcdir)/src/gudev/gudevdevice.c \ - $(top_srcdir)/src/gudev/gudevenumerator.c - -src/gudev/GUdev-1.0.typelib: src/gudev/GUdev-1.0.gir $(G_IR_COMPILER) - $(AM_V_GEN)g-ir-compiler $< -o $@ - -girdir = $(GIRDIR) -gir_DATA = src/gudev/GUdev-1.0.gir - -typelibsdir = $(GIRTYPELIBDIR) -typelibs_DATA = src/gudev/GUdev-1.0.typelib - -CLEANFILES += $(gir_DATA) $(typelibs_DATA) -endif # ENABLE_INTROSPECTION - -# move lib from $(libdir) to $(rootlib_execdir) and update devel link, if needed -libgudev-install-move-hook: - if test "$(libdir)" != "$(rootlib_execdir)"; then \ - mkdir -p $(DESTDIR)$(rootlib_execdir) && \ - so_img_name=$$(readlink $(DESTDIR)$(libdir)/libgudev-1.0.so) && \ + units/systemd-timedated.service.in +endif + +# ------------------------------------------------------------------------------ +if ENABLE_LOGIND +systemd_logind_SOURCES = \ + src/login/logind.c \ + src/login/logind-dbus.c \ + src/login/logind-device.c \ + src/login/logind-seat.c \ + src/login/logind-seat-dbus.c \ + src/login/logind-session.c \ + src/login/logind-session-dbus.c \ + src/login/logind-user.c \ + src/login/logind-user-dbus.c \ + src/dbus-common.c \ + src/dbus-loop.c \ + src/cgroup-util.c \ + src/polkit.c + +nodist_systemd_logind_SOURCES = \ + src/login/logind-gperf.c + +if HAVE_ACL +systemd_logind_SOURCES += \ + src/login/logind-acl.c \ + src/acl-util.c +endif + +systemd_logind_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(UDEV_CFLAGS) \ + $(ACL_CFLAGS) + +systemd_logind_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la \ + $(DBUS_LIBS) \ + $(UDEV_LIBS) \ + $(ACL_LIBS) + +systemd_user_sessions_SOURCES = \ + src/login/user-sessions.c \ + src/cgroup-util.c + +systemd_user_sessions_LDADD = \ + libsystemd-basic.la + +rootlibexec_PROGRAMS += \ + systemd-logind \ + systemd-user-sessions + +loginctl_SOURCES = \ + src/login/loginctl.c \ + src/login/sysfs-show.c \ + src/dbus-common.c \ + src/cgroup-show.c \ + src/cgroup-util.c \ + src/pager.c + +loginctl_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(UDEV_CFLAGS) + +loginctl_LDADD = \ + libsystemd-basic.la \ + $(DBUS_LIBS) \ + $(UDEV_LIBS) + +rootbin_PROGRAMS += \ + loginctl + +test_login_SOURCES = \ + src/login/test-login.c + +test_login_LDADD = \ + libsystemd-basic.la \ + libsystemd-login.la + +noinst_PROGRAMS += \ + test-login + +libsystemd_login_la_SOURCES = \ + src/login/sd-login.c \ + src/cgroup-util.c + +libsystemd_login_la_CFLAGS = \ + $(AM_CFLAGS) \ + -fvisibility=hidden + +libsystemd_login_la_LDFLAGS = \ + -shared \ + -version-info $(LIBSYSTEMD_LOGIN_CURRENT):$(LIBSYSTEMD_LOGIN_REVISION):$(LIBSYSTEMD_LOGIN_AGE) \ + -Wl,--version-script=$(top_srcdir)/src/login/libsystemd-login.sym + +libsystemd_login_la_LIBADD = \ + libsystemd-basic.la + +if HAVE_PAM +pam_systemd_la_SOURCES = \ + src/login/pam-module.c \ + src/dbus-common.c + +pam_systemd_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(PAM_CFLAGS) \ + $(DBUS_CFLAGS) \ + -fvisibility=hidden + +pam_systemd_la_LDFLAGS = \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -export-symbols-regex '^pam_sm_.*' + +pam_systemd_la_LIBADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la \ + $(PAM_LIBS) \ + $(DBUS_LIBS) + +pamlib_LTLIBRARIES = \ + pam_systemd.la +endif + +# move lib from $(libdir) to $(rootlibdir) and update devel link, if needed +libsystemd-login-install-hook: + if test "$(libdir)" != "$(rootlibdir)"; then \ + mkdir -p $(DESTDIR)$(rootlibdir) && \ + so_img_name=$$(readlink $(DESTDIR)$(libdir)/libsystemd-login.so) && \ so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \ - ln -sf $$so_img_rel_target_prefix$(rootlib_execdir)/$$so_img_name $(DESTDIR)$(libdir)/libgudev-1.0.so && \ - mv $(DESTDIR)$(libdir)/libgudev-1.0.so.* $(DESTDIR)$(rootlib_execdir); \ + ln -sf $$so_img_rel_target_prefix$(rootlibdir)/$$so_img_name $(DESTDIR)$(libdir)/libsystemd-login.so && \ + mv $(DESTDIR)$(libdir)/libsystemd-login.so.* $(DESTDIR)$(rootlibdir); \ fi -libgudev-uninstall-move-hook: - rm -f $(DESTDIR)$(rootlib_execdir)/libgudev-1.0.so* +INSTALL_EXEC_HOOKS += \ + libsystemd-login-install-hook + +libsystemd-login-uninstall-hook: + rm -f $(DESTDIR)$(rootlibdir)/libsystemd-login.so* + +UNINSTALL_EXEC_HOOKS += \ + libsystemd-login-uninstall-hook + +nodist_systemunit_DATA += \ + units/systemd-logind.service \ + units/systemd-user-sessions.service -INSTALL_EXEC_HOOKS += libgudev-install-move-hook -UNINSTALL_EXEC_HOOKS += libgudev-uninstall-move-hook +dist_dbussystemservice_DATA += \ + src/login/org.freedesktop.login1.service + +dist_dbuspolicy_DATA += \ + src/login/org.freedesktop.login1.conf + +dist_pkgsysconf_DATA += \ + src/login/logind.conf + +pkginclude_HEADERS += \ + src/systemd/sd-login.h + +lib_LTLIBRARIES += \ + libsystemd-login.la + +pkgconfiglib_DATA += \ + src/login/libsystemd-login.pc + +polkitpolicy_in_files += \ + src/login/org.freedesktop.login1.policy.in + +logind-install-data-hook: + $(MKDIR_P) -m 0755 \ + $(DESTDIR)$(systemunitdir)/multi-user.target.wants \ + $(DESTDIR)$(localstatedir)/lib/systemd + ( cd $(DESTDIR)$(systemunitdir) && \ + rm -f dbus-org.freedesktop.login1.service && \ + $(LN_S) systemd-logind.service dbus-org.freedesktop.login1.service) + ( cd $(DESTDIR)$(systemunitdir)/multi-user.target.wants && \ + rm -f systemd-logind.service systemd-user-sessions.service && \ + $(LN_S) ../systemd-logind.service systemd-logind.service && \ + $(LN_S) ../systemd-user-sessions.service systemd-user-sessions.service ) + +INSTALL_DATA_HOOKS += \ + logind-install-data-hook + +systemd_multi_seat_x_SOURCES = \ + src/login/multi-seat-x.c + +systemd_multi_seat_x_CFLAGS = \ + $(AM_CFLAGS) \ + $(UDEV_CFLAGS) + +systemd_multi_seat_x_LDADD = \ + libsystemd-basic.la \ + $(UDEV_LIBS) + +rootlibexec_PROGRAMS += \ + systemd-multi-seat-x + +systemd_uaccess_SOURCES = \ + src/login/uaccess.c + +if HAVE_ACL +systemd_uaccess_SOURCES += \ + src/login/logind-acl.c \ + src/acl-util.c endif -# ------------------------------------------------------------------------------ -if ENABLE_KEYMAP -keymap_SOURCES = src/keymap/keymap.c -keymap_CPPFLAGS = $(AM_CPPFLAGS) -I src/keymap -nodist_keymap_SOURCES = \ - src/keymap/keys-from-name.h \ - src/keymap/keys-to-name.h -BUILT_SOURCES += $(nodist_keymap_SOURCES) +systemd_uaccess_CFLAGS = \ + $(AM_CFLAGS) \ + $(UDEV_CFLAGS) \ + $(ACL_CFLAGS) -pkglibexec_PROGRAMS += keymap -dist_doc_DATA = src/keymap/README.keymap.txt +systemd_uaccess_LDADD = \ + libsystemd-basic.la \ + libsystemd-daemon.la \ + libsystemd-login.la \ + $(UDEV_LIBS) \ + $(ACL_LIBS) + +rootlibexec_PROGRAMS += \ + systemd-uaccess dist_udevrules_DATA += \ - src/keymap/95-keymap.rules \ - src/keymap/95-keyboard-force-release.rules + src/login/70-uaccess.rules -dist_udevhome_SCRIPTS += src/keymap/findkeyboards -udevhome_SCRIPTS += src/keymap/keyboard-force-release.sh +dist_udevrules_DATA += \ + src/login/71-seat.rules + +nodist_udevrules_DATA += \ + src/login/73-seat-late.rules + +MANPAGES += \ + man/logind.conf.5 \ + man/sd-login.7 \ + man/loginctl.1 \ + man/sd_login_monitor_new.3 \ + man/sd_pid_get_session.3 \ + man/sd_uid_get_state.3 \ + man/sd_session_is_active.3 \ + man/sd_seat_get_active.3 \ + man/sd_get_seats.3 + +MANPAGES_ALIAS += \ + man/sd_login_monitor_unref.3 \ + man/sd_login_monitor_flush.3 \ + man/sd_login_monitor_get_fd.3 \ + man/sd_session_get_uid.3 \ + man/sd_session_get_seat.3 \ + man/sd_session_get_service.3 \ + man/sd_session_get_type.3 \ + man/sd_session_get_class.3 \ + man/sd_session_get_display.3 \ + man/sd_pid_get_owner_uid.3 \ + man/sd_pid_get_unit.3 \ + man/sd_uid_is_on_seat.3 \ + man/sd_uid_get_sessions.3 \ + man/sd_uid_get_seats.3 \ + man/sd_seat_get_sessions.3 \ + man/sd_seat_can_multi_session.3 \ + man/sd_get_sessions.3 \ + man/sd_get_uids.3 + +man/sd_login_monitor_unref.3: man/sd_login_monitor_new.3 +man/sd_login_monitor_flush.3: man/sd_login_monitor_new.3 +man/sd_login_monitor_get_fd.3: man/sd_login_monitor_new.3 +man/sd_session_get_uid.3: man/sd_session_is_active.3 +man/sd_session_get_seat.3: man/sd_session_is_active.3 +man/sd_session_get_service.3: man/sd_session_is_active.3 +man/sd_session_get_type.3: man/sd_session_is_active.3 +man/sd_session_get_class.3: man/sd_session_is_active.3 +man/sd_session_get_display.3: man/sd_session_is_active.3 +man/sd_pid_get_owner_uid.3: man/sd_pid_get_session.3 +man/sd_pid_get_unit.3: man/sd_pid_get_session.3 +man/sd_uid_is_on_seat.3: man/sd_uid_get_state.3 +man/sd_uid_get_sessions.3: man/sd_uid_get_state.3 +man/sd_uid_get_seats.3: man/sd_uid_get_state.3 +man/sd_seat_get_sessions.3: man/sd_seat_get_active.3 +man/sd_seat_can_multi_session.3: man/sd_seat_get_active.3 +man/sd_get_sessions.3: man/sd_get_seats.3 +man/sd_get_uids.3: man/sd_get_seats.3 EXTRA_DIST += \ - src/keymap/check-keymaps.sh \ - src/keymap/keyboard-force-release.sh.in + src/login/logind-gperf.gperf \ + src/login/libsystemd-login.pc.in \ + src/login/libsystemd-login.sym \ + src/login/logind.h \ + src/login/logind-device.h \ + src/login/logind-seat.h \ + src/login/logind-session.h \ + src/login/logind-user.h \ + src/login/logind-acl.h \ + src/login/73-seat-late.rules.in \ + units/systemd-logind.service.in \ + units/systemd-user-sessions.service.in CLEANFILES += \ - src/keymap/keys.txt \ - src/keymap/keys-from-name.gperf \ - src/keymap/keyboard-force-release.sh - -udevkeymapdir = $(libexecdir)/udev/keymaps -dist_udevkeymap_DATA = \ - src/keymap/keymaps/acer \ - src/keymap/keymaps/acer-aspire_5720 \ - src/keymap/keymaps/acer-aspire_8930 \ - src/keymap/keymaps/acer-aspire_5920g \ - src/keymap/keymaps/acer-aspire_6920 \ - src/keymap/keymaps/acer-travelmate_c300 \ - src/keymap/keymaps/asus \ - src/keymap/keymaps/compaq-e_evo \ - src/keymap/keymaps/dell \ - src/keymap/keymaps/dell-latitude-xt2 \ - src/keymap/keymaps/everex-xt5000 \ - src/keymap/keymaps/fujitsu-amilo_li_2732 \ - src/keymap/keymaps/fujitsu-amilo_pa_2548 \ - src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 \ - src/keymap/keymaps/fujitsu-amilo_pro_v3205 \ - src/keymap/keymaps/fujitsu-amilo_si_1520 \ - src/keymap/keymaps/fujitsu-esprimo_mobile_v5 \ - src/keymap/keymaps/fujitsu-esprimo_mobile_v6 \ - src/keymap/keymaps/genius-slimstar-320 \ - src/keymap/keymaps/hewlett-packard \ - src/keymap/keymaps/hewlett-packard-2510p_2530p \ - src/keymap/keymaps/hewlett-packard-compaq_elitebook \ - src/keymap/keymaps/hewlett-packard-pavilion \ - src/keymap/keymaps/hewlett-packard-presario-2100 \ - src/keymap/keymaps/hewlett-packard-tablet \ - src/keymap/keymaps/hewlett-packard-tx2 \ - src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint \ - src/keymap/keymaps/inventec-symphony_6.0_7.0 \ - src/keymap/keymaps/lenovo-3000 \ - src/keymap/keymaps/lenovo-ideapad \ - src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint \ - src/keymap/keymaps/lenovo-thinkpad_x6_tablet \ - src/keymap/keymaps/lenovo-thinkpad_x200_tablet \ - src/keymap/keymaps/lg-x110 \ - src/keymap/keymaps/logitech-wave \ - src/keymap/keymaps/logitech-wave-cordless \ - src/keymap/keymaps/logitech-wave-pro-cordless \ - src/keymap/keymaps/maxdata-pro_7000 \ - src/keymap/keymaps/medion-fid2060 \ - src/keymap/keymaps/medionnb-a555 \ - src/keymap/keymaps/micro-star \ - src/keymap/keymaps/module-asus-w3j \ - src/keymap/keymaps/module-ibm \ - src/keymap/keymaps/module-lenovo \ - src/keymap/keymaps/module-sony \ - src/keymap/keymaps/module-sony-old \ - src/keymap/keymaps/module-sony-vgn \ - src/keymap/keymaps/olpc-xo \ - src/keymap/keymaps/onkyo \ - src/keymap/keymaps/oqo-model2 \ - src/keymap/keymaps/samsung-other \ - src/keymap/keymaps/samsung-90x3a \ - src/keymap/keymaps/samsung-sq1us \ - src/keymap/keymaps/samsung-sx20s \ - src/keymap/keymaps/toshiba-satellite_a100 \ - src/keymap/keymaps/toshiba-satellite_a110 \ - src/keymap/keymaps/toshiba-satellite_m30x \ - src/keymap/keymaps/zepto-znote - -udevkeymapforcereldir = $(libexecdir)/udev/keymaps/force-release -dist_udevkeymapforcerel_DATA = \ - src/keymap/force-release-maps/dell-touchpad \ - src/keymap/force-release-maps/hp-other \ - src/keymap/force-release-maps/samsung-other \ - src/keymap/force-release-maps/samsung-90x3a \ - src/keymap/force-release-maps/common-volume-keys - -src/keymap/keys.txt: $(INCLUDE_PREFIX)/linux/input.h - $(AM_V_at)mkdir -p src/keymap - $(AM_V_GEN)$(AWK) '/^#define.*KEY_[^ ]+[ \t]+[0-9]/ { if ($$2 != "KEY_MAX") { print $$2 } }' < $< | sed 's/^KEY_COFFEE$$/KEY_SCREENLOCK/' > $@ - -src/keymap/keys-from-name.gperf: src/keymap/keys.txt - $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct key { const char* name; unsigned short id; };"; print "%null-strings"; print "%%";} { print $$1 ", " $$1 }' < $< > $@ - -src/keymap/keys-from-name.h: src/keymap/keys-from-name.gperf Makefile - $(AM_V_GEN)$(GPERF) -L ANSI-C -t --ignore-case -N lookup_key -H hash_key_name -p -C < $< > $@ - -src/keymap/keys-to-name.h: src/keymap/keys.txt Makefile - $(AM_V_GEN)$(AWK) 'BEGIN{ print "const char* const key_names[KEY_CNT] = { "} { print "[" $$1 "] = \"" $$1 "\"," } END{print "};"}' < $< > $@ - -keymaps-distcheck-hook: src/keymap/keys.txt - $(top_srcdir)/src/keymap/check-keymaps.sh $(top_srcdir) $^ -DISTCHECK_HOOKS += keymaps-distcheck-hook -endif - -if ENABLE_MTD_PROBE + src/login/logind-gperf.c \ + src/login/73-seat-late.rules +endif # ------------------------------------------------------------------------------ -mtd_probe_SOURCES = \ - src/mtd_probe/mtd_probe.c \ - src/mtd_probe/mtd_probe.h \ - src/mtd_probe/probe_smartmedia.c -mtd_probe_CPPFLAGS = $(AM_CPPFLAGS) -dist_udevrules_DATA += src/mtd_probe/75-probe_mtd.rules -pkglibexec_PROGRAMS += mtd_probe + +SED_PROCESS = \ + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + $(SED) -e 's,@rootlibexecdir\@,$(rootlibexecdir),g' \ + -e 's,@rootbindir\@,$(rootbindir),g' \ + -e 's,@bindir\@,$(bindir),g' \ + -e 's,@SYSTEMCTL\@,$(rootbindir)/systemctl,g' \ + -e 's,@SYSTEMD_NOTIFY\@,$(rootbindir)/systemd-notify,g' \ + -e 's,@pkgsysconfdir\@,$(pkgsysconfdir),g' \ + -e 's,@pkgdatadir\@,$(pkgdatadir),g' \ + -e 's,@pkglibexecdir\@,$(pkglibexecdir),g' \ + -e 's,@systemunitdir\@,$(systemunitdir),g' \ + -e 's,@userunitdir\@,$(userunitdir),g' \ + -e 's,@PACKAGE_VERSION\@,$(PACKAGE_VERSION),g' \ + -e 's,@PACKAGE_NAME\@,$(PACKAGE_NAME),g' \ + -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' \ + -e 's,@prefix\@,$(prefix),g' \ + -e 's,@exec_prefix\@,$(exec_prefix),g' \ + -e 's,@libdir\@,$(libdir),g' \ + -e 's,@includedir\@,$(includedir),g' \ + < $< > $@ || rm $@ + +units/%: units/%.in Makefile + $(SED_PROCESS) + +man/%: man/%.in Makefile + $(SED_PROCESS) + +sysctl.d/%: sysctl.d/%.in Makefile + $(SED_PROCESS) + +%.pc: %.pc.in Makefile + $(SED_PROCESS) + +src/%.policy.in: src/%.policy.in.in Makefile + $(SED_PROCESS) + +src/%.rules: src/%.rules.in Makefile + $(SED_PROCESS) + +src/%.c: src/%.gperf + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + $(GPERF) < $< > $@ + +src/%: src/%.m4 + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + $(M4) -P $(M4_DEFINES) < $< > $@ || rm $@ + +src/load-fragment-gperf-nulstr.c: src/load-fragment-gperf.gperf + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + $(AWK) 'BEGIN{ keywords=0 ; FS="," ; print "extern const char load_fragment_gperf_nulstr[];" ; print "const char load_fragment_gperf_nulstr[] ="} ; keyword==1 { print "\"" $$1 "\\0\"" } ; /%%/ { keyword=1} ; END { print ";" }' < $< > $@ || rm $@ + +M4_PROCESS_SYSTEM = \ + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + $(M4) -P $(M4_DEFINES) -DFOR_SYSTEM=1 < $< > $@ || rm $@ + +M4_PROCESS_USER = \ + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + $(M4) -P $(M4_DEFINES) -DFOR_USER=1 < $< > $@ || rm $@ + +units/%: units/%.m4 Makefile + $(M4_PROCESS_SYSTEM) + +units/user/%: units/%.m4 Makefile + $(M4_PROCESS_USER) + +CLEANFILES += \ + $(nodist_systemunit_DATA) \ + $(nodist_userunit_DATA) \ + $(nodist_man_MANS) \ + $(pkgconfigdata_DATA) \ + $(pkgconfiglib_DATA) \ + $(nodist_polkitpolicy_DATA) \ + src/load-fragment-gperf.gperf \ + src/load-fragment-gperf.c \ + src/load-fragment-gperf-nulstr.c \ + src/99-systemd.rules + +if HAVE_XSLTPROC +XSLTPROC_FLAGS = \ + --nonet \ + --stringparam funcsynopsis.style ansi + +XSLTPROC_PROCESS_MAN = \ + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< + +XSLTPROC_PROCESS_HTML = \ + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ + $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(srcdir)/man/custom-html.xsl $< + +man/%.1: man/%.xml + $(XSLTPROC_PROCESS_MAN) + +man/%.3: man/%.xml + $(XSLTPROC_PROCESS_MAN) + +man/%.5: man/%.xml + $(XSLTPROC_PROCESS_MAN) + +man/%.7: man/%.xml + $(XSLTPROC_PROCESS_MAN) + +man/%.8: man/%.xml + $(XSLTPROC_PROCESS_MAN) + +man/%.html: man/%.xml + $(XSLTPROC_PROCESS_HTML) + +CLEANFILES += \ + $(dist_man_MANS) \ + ${XML_FILES:.xml=.html} endif -# ------------------------------------------------------------------------------ -if ENABLE_RULE_GENERATOR -dist_udevhome_SCRIPTS += \ - src/rule_generator/write_cd_rules \ - src/rule_generator/write_net_rules +DBUS_PREPROCESS = $(CPP) -P $(DBUS_CFLAGS) -imacros dbus/dbus-protocol.h -dist_udevhome_DATA += \ - src/rule_generator/rule_generator.functions +org.freedesktop.systemd1.%.xml: systemd + $(AM_V_GEN)$(LIBTOOL) --mode=execute $(OBJCOPY) -O binary -j introspect.$* $< $@.tmp && \ + $(STRINGS) $@.tmp | $(AWK) -f $(srcdir)/introspect.awk | \ + $(DBUS_PREPROCESS) -o $@ - && rm $@.tmp -dist_udevrules_DATA += \ - src/rule_generator/75-cd-aliases-generator.rules \ - src/rule_generator/75-persistent-net-generator.rules +CLEANFILES += \ + $(dbusinterface_DATA) + +systemd-install-data-hook: + $(MKDIR_P) -m 0755 \ + $(DESTDIR)$(tmpfilesdir) \ + $(DESTDIR)$(sysconfdir)/tmpfiles.d \ + $(DESTDIR)$(prefix)/lib/modules-load.d \ + $(DESTDIR)$(sysconfdir)/modules-load.d \ + $(DESTDIR)$(prefix)/lib/sysctl.d \ + $(DESTDIR)$(sysconfdir)/sysctl.d \ + $(DESTDIR)$(systemshutdowndir) \ + $(DESTDIR)$(systemgeneratordir) \ + $(DESTDIR)$(usergeneratordir) + $(MKDIR_P) -m 0755 \ + $(DESTDIR)$(systemunitdir) \ + $(DESTDIR)$(userunitdir) \ + $(DESTDIR)$(systemunitdir)/sysinit.target.wants \ + $(DESTDIR)$(systemunitdir)/sockets.target.wants \ + $(DESTDIR)$(systemunitdir)/basic.target.wants \ + $(DESTDIR)$(systemunitdir)/shutdown.target.wants \ + $(DESTDIR)$(systemunitdir)/local-fs.target.wants \ + $(DESTDIR)$(systemunitdir)/runlevel1.target.wants \ + $(DESTDIR)$(systemunitdir)/runlevel2.target.wants \ + $(DESTDIR)$(systemunitdir)/runlevel3.target.wants \ + $(DESTDIR)$(systemunitdir)/runlevel4.target.wants \ + $(DESTDIR)$(systemunitdir)/runlevel5.target.wants \ + $(DESTDIR)$(systemunitdir)/multi-user.target.wants \ + $(DESTDIR)$(systemunitdir)/graphical.target.wants \ + $(DESTDIR)$(pkgsysconfdir)/system \ + $(DESTDIR)$(pkgsysconfdir)/system/sysinit.target.wants \ + $(DESTDIR)$(pkgsysconfdir)/system/local-fs.target.wants \ + $(DESTDIR)$(pkgsysconfdir)/system/multi-user.target.wants \ + $(DESTDIR)$(pkgsysconfdir)/system/getty.target.wants \ + $(DESTDIR)$(pkgsysconfdir)/user \ + $(DESTDIR)$(dbussessionservicedir) \ + $(DESTDIR)$(sysconfdir)/xdg/systemd + ( cd $(DESTDIR)$(sysconfdir)/xdg/systemd/ && \ + rm -f user && \ + $(LN_S) $(pkgsysconfdir)/user user ) + ( cd $(DESTDIR)$(systemunitdir)/sockets.target.wants && \ + rm -f systemd-initctl.socket systemd-shutdownd.socket && \ + $(LN_S) ../systemd-initctl.socket systemd-initctl.socket && \ + $(LN_S) ../systemd-shutdownd.socket systemd-shutdownd.socket ) + ( cd $(DESTDIR)$(systemunitdir)/runlevel1.target.wants && \ + rm -f systemd-update-utmp-runlevel.service && \ + $(LN_S) ../systemd-update-utmp-runlevel.service systemd-update-utmp-runlevel.service ) + ( cd $(DESTDIR)$(systemunitdir)/runlevel2.target.wants && \ + rm -f systemd-update-utmp-runlevel.service && \ + $(LN_S) ../systemd-update-utmp-runlevel.service systemd-update-utmp-runlevel.service ) + ( cd $(DESTDIR)$(systemunitdir)/runlevel3.target.wants && \ + rm -f systemd-update-utmp-runlevel.service && \ + $(LN_S) ../systemd-update-utmp-runlevel.service systemd-update-utmp-runlevel.service ) + ( cd $(DESTDIR)$(systemunitdir)/runlevel4.target.wants && \ + rm -f systemd-update-utmp-runlevel.service && \ + $(LN_S) ../systemd-update-utmp-runlevel.service systemd-update-utmp-runlevel.service ) + ( cd $(DESTDIR)$(systemunitdir)/runlevel5.target.wants && \ + rm -f systemd-update-utmp-runlevel.service && \ + $(LN_S) ../systemd-update-utmp-runlevel.service systemd-update-utmp-runlevel.service ) + ( cd $(DESTDIR)$(systemunitdir)/shutdown.target.wants && \ + rm -f systemd-update-utmp-shutdown.service && \ + $(LN_S) ../systemd-update-utmp-shutdown.service systemd-update-utmp-shutdown.service ) + ( cd $(DESTDIR)$(systemunitdir)/local-fs.target.wants && \ + rm -f systemd-remount-api-vfs.service \ + fsck-root.service \ + remount-rootfs.service \ + tmp.mount && \ + $(LN_S) ../systemd-remount-api-vfs.service systemd-remount-api-vfs.service && \ + $(LN_S) ../fsck-root.service fsck-root.service && \ + $(LN_S) ../remount-rootfs.service remount-rootfs.service && \ + $(LN_S) ../tmp.mount tmp.mount ) + ( cd $(DESTDIR)$(userunitdir) && \ + rm -f shutdown.target sockets.target bluetooth.target printer.target sound.target && \ + $(LN_S) $(systemunitdir)/shutdown.target shutdown.target && \ + $(LN_S) $(systemunitdir)/sockets.target sockets.target && \ + $(LN_S) $(systemunitdir)/bluetooth.target bluetooth.target && \ + $(LN_S) $(systemunitdir)/printer.target printer.target && \ + $(LN_S) $(systemunitdir)/sound.target sound.target ) + ( cd $(DESTDIR)$(systemunitdir) && \ + rm -f runlevel0.target runlevel1.target runlevel2.target runlevel3.target runlevel4.target runlevel5.target runlevel6.target && \ + $(LN_S) poweroff.target runlevel0.target && \ + $(LN_S) rescue.target runlevel1.target && \ + $(LN_S) multi-user.target runlevel2.target && \ + $(LN_S) multi-user.target runlevel3.target && \ + $(LN_S) multi-user.target runlevel4.target && \ + $(LN_S) graphical.target runlevel5.target && \ + $(LN_S) reboot.target runlevel6.target ) + ( cd $(DESTDIR)$(systemunitdir) && \ + rm -f default.target ctrl-alt-del.target autovt@.service && \ + $(LN_S) graphical.target default.target && \ + $(LN_S) reboot.target ctrl-alt-del.target && \ + $(LN_S) getty@.service autovt@.service ) + ( cd $(DESTDIR)$(systemunitdir)/multi-user.target.wants && \ + rm -f getty.target systemd-ask-password-wall.path && \ + $(LN_S) ../getty.target getty.target && \ + $(LN_S) ../systemd-ask-password-wall.path systemd-ask-password-wall.path) + ( cd $(DESTDIR)$(pkgsysconfdir)/system/getty.target.wants && \ + rm -f getty@tty1.service && \ + $(LN_S) $(systemunitdir)/getty@.service getty@tty1.service ) + ( cd $(DESTDIR)$(pkgsysconfdir)/system/multi-user.target.wants && \ + rm -f remote-fs.target && \ + $(LN_S) $(systemunitdir)/remote-fs.target remote-fs.target ) + ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \ + rm -f dev-hugepages.mount \ + dev-mqueue.mount \ + sys-kernel-config.mount \ + sys-kernel-debug.mount \ + sys-fs-fuse-connections.mount \ + systemd-modules-load.service \ + systemd-tmpfiles-setup.service \ + systemd-sysctl.service \ + systemd-ask-password-console.path && \ + $(LN_S) ../dev-hugepages.mount dev-hugepages.mount && \ + $(LN_S) ../dev-mqueue.mount dev-mqueue.mount && \ + $(LN_S) ../sys-kernel-config.mount sys-kernel-config.mount && \ + $(LN_S) ../sys-kernel-debug.mount sys-kernel-debug.mount && \ + $(LN_S) ../sys-fs-fuse-connections.mount sys-fs-fuse-connections.mount && \ + $(LN_S) ../systemd-modules-load.service systemd-modules-load.service && \ + $(LN_S) ../systemd-tmpfiles-setup.service systemd-tmpfiles-setup.service && \ + $(LN_S) ../systemd-sysctl.service systemd-sysctl.service && \ + $(LN_S) ../systemd-ask-password-console.path systemd-ask-password-console.path ) + ( cd $(DESTDIR)$(systemunitdir)/basic.target.wants && \ + rm -f systemd-tmpfiles-clean.timer && \ + $(LN_S) ../systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.timer ) + ( cd $(DESTDIR)$(dbussessionservicedir) && \ + rm -f org.freedesktop.systemd1.service && \ + $(LN_S) ../system-services/org.freedesktop.systemd1.service org.freedesktop.systemd1.service ) +if HAVE_PLYMOUTH + $(MKDIR_P) -m 0755 \ + $(DESTDIR)$(SYSTEM_SYSVINIT_PATH) \ + $(DESTDIR)$(systemunitdir)/reboot.target.wants \ + $(DESTDIR)$(systemunitdir)/kexec.target.wants \ + $(DESTDIR)$(systemunitdir)/poweroff.target.wants \ + $(DESTDIR)$(systemunitdir)/halt.target.wants + ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \ + rm -f plymouth-start.service plymouth-read-write.service && \ + $(LN_S) ../plymouth-start.service plymouth-start.service && \ + $(LN_S) ../plymouth-read-write.service plymouth-read-write.service ) + ( cd $(DESTDIR)$(systemunitdir)/multi-user.target.wants && \ + rm -f plymouth-quit.service plymouth-quit-wait.service && \ + $(LN_S) ../plymouth-quit.service plymouth-quit.service && \ + $(LN_S) ../plymouth-quit-wait.service plymouth-quit-wait.service ) + ( cd $(DESTDIR)$(systemunitdir)/reboot.target.wants && \ + rm -f plymouth-reboot.service && \ + $(LN_S) ../plymouth-reboot.service plymouth-reboot.service ) + ( cd $(DESTDIR)$(systemunitdir)/kexec.target.wants && \ + rm -f plymouth-kexec.service && \ + $(LN_S) ../plymouth-kexec.service plymouth-kexec.service ) + ( cd $(DESTDIR)$(systemunitdir)/poweroff.target.wants && \ + rm -f plymouth-poweroff.service && \ + $(LN_S) ../plymouth-poweroff.service plymouth-poweroff.service ) + ( cd $(DESTDIR)$(systemunitdir)/halt.target.wants && \ + rm -f plymouth-halt.service && \ + $(LN_S) ../plymouth-halt.service plymouth-halt.service ) +endif +if TARGET_MEEGO + $(MKDIR_P) -m 0755 $(DESTDIR)$(systemunitdir)/final.target.wants + ( cd $(DESTDIR)$(systemunitdir)/multi-user.target.wants && \ + rm -f network.target && \ + $(LN_S) $(systemunitdir)/network.target network.target ) + ( cd $(DESTDIR)$(pkgsysconfdir)/system/sysinit.target.wants && \ + rm -f * ) + ( cd $(DESTDIR)$(pkgsysconfdir)/system/local-fs.target.wants && \ + rm -f * ) + ( cd $(DESTDIR)$(pkgsysconfdir)/system/multi-user.target.wants && \ + rm -f * ) + ( cd $(DESTDIR)$(pkgsysconfdir)/system/getty.target.wants && \ + rm -f * ) endif -# ------------------------------------------------------------------------------ -if ENABLE_FLOPPY -create_floppy_devices_SOURCES = src/floppy/create_floppy_devices.c -create_floppy_devices_LDADD = libudev-private.la -pkglibexec_PROGRAMS += create_floppy_devices -dist_udevrules_DATA += src/floppy/60-floppy.rules +if TARGET_FEDORA + $(MKDIR_P) -m 0755 $(DESTDIR)$(systemunitdir)/final.target.wants + ( cd $(DESTDIR)$(systemunitdir)/final.target.wants && \ + rm -f halt-local.service && \ + $(LN_S) $(systemunitdir)/halt-local.service halt-local.service ) + ( cd $(DESTDIR)$(systemunitdir) && \ + rm -f display-manager.service single.service && \ + $(LN_S) prefdm.service display-manager.service && \ + $(LN_S) rescue.service single.service ) + ( cd $(DESTDIR)$(systemunitdir)/graphical.target.wants && \ + rm -f display-manager.service && \ + $(LN_S) $(systemunitdir)/display-manager.service display-manager.service ) endif -# ------------------------------------------------------------------------------ -clean-local: - rm -rf udev-test-install +if TARGET_MANDRIVA + $(MKDIR_P) -m 0755 $(DESTDIR)$(systemunitdir)/final.target.wants + ( cd $(DESTDIR)$(systemunitdir)/final.target.wants && \ + rm -f halt-local.service && \ + $(LN_S) $(systemunitdir)/halt-local.service halt-local.service ) + ( cd $(DESTDIR)$(systemunitdir) && \ + rm -f display-manager.service dm.service single.service && \ + $(LN_S) prefdm.service display-manager.service && \ + $(LN_S) prefdm.service dm.service && \ + $(LN_S) rescue.service single.service ) + ( cd $(DESTDIR)$(systemunitdir)/graphical.target.wants && \ + rm -f display-manager.service && \ + $(LN_S) $(systemunitdir)/display-manager.service display-manager.service ) +endif -distclean-local: - rm -rf autom4te.cache +if TARGET_DEBIAN_OR_UBUNTU + ( cd $(DESTDIR)$(systemunitdir) && \ + rm -f runlevel5.target && \ + $(LN_S) multi-user.target runlevel5.target ) +endif -EXTRA_DIST += \ - $(TESTS) \ - test/rule-syntax-check.py +if TARGET_SUSE + $(MKDIR_P) -m 0755 $(DESTDIR)$(systemunitdir)/final.target.wants + ( cd $(DESTDIR)$(systemunitdir) && \ + rm -f local.service && \ + $(LN_S) rc-local.service local.service ) + ( cd $(DESTDIR)$(systemunitdir)/final.target.wants && \ + rm -f halt-local.service && \ + $(LN_S) $(systemunitdir)/halt-local.service halt-local.service ) +endif -CLEANFILES += \ - $(BUILT_SOURCES) +if TARGET_MAGEIA + $(MKDIR_P) -m 0755 $(DESTDIR)$(systemunitdir)/final.target.wants + ( cd $(DESTDIR)$(systemunitdir)/final.target.wants && \ + rm -f halt-local.service && \ + $(LN_S) $(systemunitdir)/halt-local.service halt-local.service ) + ( cd $(DESTDIR)$(systemunitdir) && \ + rm -f display-manager.service && \ + $(LN_S) prefdm.service display-manager.service && \ + $(LN_S) prefdm.service dm.service ) + ( cd $(DESTDIR)$(systemunitdir)/graphical.target.wants && \ + rm -f display-manager.service && \ + $(LN_S) $(systemunitdir)/display-manager.service display-manager.service ) +endif install-exec-hook: $(INSTALL_EXEC_HOOKS) -install-data-hook: $(INSTALL_DATA_HOOKS) - uninstall-hook: $(UNINSTALL_EXEC_HOOKS) -distcheck-hook: $(DISTCHECK_HOOKS) - -distclean-local: $(DISTCLEAN_LOCAL_HOOKS) +install-data-hook: systemd-install-data-hook $(INSTALL_DATA_HOOKS) -# ------------------------------------------------------------------------------ -PREVIOUS_VERSION = `expr $(VERSION) - 1` -changelog: - @ head -1 ChangeLog | grep -q "to v$(PREVIOUS_VERSION)" - @ mv ChangeLog ChangeLog.tmp - @ echo "Summary of changes from v$(PREVIOUS_VERSION) to v$(VERSION)" >> ChangeLog - @ echo "============================================" >> ChangeLog - @ echo >> ChangeLog - @ git log --pretty=short $(PREVIOUS_VERSION)..HEAD | git shortlog >> ChangeLog - @ echo >> ChangeLog - @ cat ChangeLog - @ cat ChangeLog.tmp >> ChangeLog - @ rm ChangeLog.tmp - -test-install: - rm -rf $(PWD)/udev-test-install/ - make DESTDIR=$(PWD)/udev-test-install install - tree $(PWD)/udev-test-install/ - -git-release: - head -1 ChangeLog | grep -q "to v$(VERSION)" - head -1 NEWS | grep -q "udev $(VERSION)" - git commit -a -m "release $(VERSION)" - git tag -m "udev $(VERSION)" -s $(VERSION) - git gc --prune=0 - -git-sync: - git push - git push --tags - -tar-sync: - rm -f udev-$(VERSION).tar.sign - xz -d -c udev-$(VERSION).tar.xz | gpg --armor --detach-sign --output udev-$(VERSION).tar.sign - kup put udev-$(VERSION).tar.xz udev-$(VERSION).tar.sign /pub/linux/utils/kernel/hotplug/ - -doc-sync: - for i in src/*.html; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done - for i in src/*.html; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/udev/; done - for i in src/docs/html/*.{html,css,png}; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done - for i in src/docs/html/*.{html,css,png}; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/libudev/; done - for i in src/gudev/docs/html/*.{html,css,png}; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done - for i in src/gudev/docs/html/*.{html,css,png}; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/gudev/; done +DISTCHECK_CONFIGURE_FLAGS = \ + --with-dbuspolicydir=$$dc_install_base/$(dbuspolicydir) \ + --with-dbussessionservicedir=$$dc_install_base/$(dbussessionservicedir) \ + --with-dbussystemservicedir=$$dc_install_base/$(dbussystemservicedir) \ + --with-dbusinterfacedir=$$dc_install_base/$(dbusinterfacedir) \ + --with-udevrulesdir=$$dc_install_base/$(udevrulesdir) \ + --with-pamlibdir=$$dc_install_base/$(pamlibdir) \ + --with-rootprefix=$$dc_install_base \ + --disable-split-usr + +upload: all distcheck + cp -v systemd-$(VERSION).tar.xz /home/lennart/git.fedora/systemd/ + scp systemd-$(VERSION).tar.xz fdo:/srv/www.freedesktop.org/www/software/systemd/ + scp man/*.html fdo:/srv/www.freedesktop.org/www/software/systemd/man/ + scp man/*.html tango:public/systemd-man/ + +git-tag: + git tag "v$(VERSION)" -m "systemd $(VERSION)" diff --git a/NEWS b/NEWS index f4f6f4e32..c82e637d0 100644 --- a/NEWS +++ b/NEWS @@ -1,1735 +1,279 @@ -udev 182 -======== -Rules files in /etc/udev/rules.s/ with the same name as rules files in -/run/udev/rules.d/ now always have precedence. The stack of files is now: -/usr/lib (package), /run (runtime, auto-generated), /etc (admin), while -the later ones override the earlier ones. In other words: the admin has -always the last say. +systemd System and Service Manager -USB auto-suspend is now enabled by default for some built-in USB HID -devices. +CHANGES WITH: + * systemd-logingctl and systemd-journalctl have been renamed + to logingctl and journalctl to match systemctl. -/dev/disk/by-path/ links are no longer created for ATA devices behind -an 'ATA transport class', the logic to extract predictable numbers does -not exist in the kernel at this moment. + * The config files: /etc/systemd/systemd-logind.conf and + /etc/systemd/systemd-journald.conf have been renamed to + logind.conf and journald.conf. Package updates should rename + the files to the new names on upgrade. -/dev/disk/by-id/scsi-* compatibility links are no longer created for -ATA devices, they have their own ata-* prefix. +CHANGES WITH 44: + * This is mostly a bugfix release -The s390 rule to set mode == 0666 for /dev/z90crypt is is removed from -the udev tree and will be part of s390utils (or alternatively could be -done by the kernel driver itself). + * Support optional initialization of the machine ID from the + KVM or container configured UUID. -The udev-acl tool is no longer provided, it will be part of a future -ConsoleKit release. On systemd systems, advanced ConsoleKit and udev-acl -functionality are provided by systemd. + * Support immediate reboots with "systemctl reboot -ff" -udev 181 -======== -Require kmod version 5. + * Show /etc/os-release data in systemd-analyze output -Provide /dev/cdrom symlink for /dev/sr0. + * Many bugfixes for the journal, including endianess fixes and + ensuring that disk space enforcement works -udev 180 -======== -Fix for ID_PART_ENTRY_* property names, added by the blkid built-in. The -fix is needed for udisk2 to operate properly. + * sd-login.h is C++ comptaible again -Fix for skipped rule execution when the kernel has removed the device -node in /dev again, before the event was even started. The fix is needed -to run device-mapper/LVM events properly. + * Extend the /etc/os-release format on request of the Debian + folks -Fix for the man page installation, which was skipped when xsltproc was not -installed. + * We now refuse non-UTF8 strings used in various configuration + and unit files. This is done to ensure we don't pass invalid + data over D-Bus or expose it elsewhere. -udev 179 -======== -Bugfix for $name resolution, which broke at least some keymap handling. + * Register Mimo USB Screens as suitable for automatic seat + configuration -udev 178 -======== -Bugfix for the firmware loading behavior with kernel modules which -try to load firmware in the module_init() path. The blocked event -runs into a timout now, which should allow the firmware to be loaded. + * Read SELinux client context from journal clients in a race + free fashion -Bugfix for a wrong DEVNAME= export, which breaks at least the udev-acl -tool. + * Reorder configuration file lookup order. /etc now always + overrides /run in order to allow the administrator to always + and unconditionally override vendor supplied or + automatically generated data. -Bugfix for missing ID_ properties for GPT partitions. + * The various user visible bits of the journal now have man + pages. We still lack man pages for the journal API calls + however. -The RUN+="socket:.." option is deprecated and should not be used. A warning -during rules parsing is printed now. Services which listen to udev events, -need to subscribe to the netlink messages with libudev and not let udev block -in the rules execution until the message is delivered. + * We now ship all man pages in HTML format again in the + tarball. -udev 177 -======== -Bugfix for rule_generator instalation. + Contributions from: Dave Reisner, Dirk Eibach, Frederic + Crozat, Harald Hoyer, Kay Sievers, Lennart Poettering, Marti + Raudsepp, Michal Schmidt, Shawn Landden, Tero Roponen, Thierry + Reding -udev 176 -======== -The 'devtmpfs' filesystem is required now, udev will not create or delete -device nodes anymore, it only adjusts permissions and ownership of device -nodes and maintains additional symlinks. +CHANGES WITH 43: + * This is mostly a bugfix release -A writable /run directory (ususally tmpfs) is required now for a fully -functional udev, there is no longer a fallback to /dev/.udev. + * systems lacking /etc/os-release are no longer supported. -The default 'configure' install locations have changed. Packages for systems -with the historic / vs. /usr split need to be adapted, otherwise udev will -be installed in /usr and not work properly. Example configuration options -to install things the traditional way are in INSTALL. + * Various functionality updates to libsystemd-login.so -The default install location of the 'udevadm' tool moved from 'sbin' -to /usr/bin. Some tools expect udevadm in 'sbin', a symlink to udevadm -needs to be manually created if needed, or --bindir=/sbin be specified. + * Track class of PAM logins to distuingish greeters from + normal user logins. -The expected value of '--libexecdir=' has changed and must no longer contain -the 'udev' directory. + Contributions from: Kay Sievers, Lennart Poettering, Michael + Biebl -Kernel modules are now loaded directly by linking udev to 'libkmod'. The -'modprobe' tool is no longer executed by udev. +CHANGES WITH 42: + * This is an important bugfix release for v41. -The 'blkid' tool is no longer executed from udev rules. Udev links -directly to libblkid now. + * Building man pages is now optional which should be useful + for those building systemd from git but unwilling to install + xsltproc. -Firmware is loaded natively by udev now, the external 'firmware' binary -is no longer used. + * Watchdog support for supervising services is now usable. In + a future release support for hardware watchdogs + (i.e. /dev/watchdog) will be added building on this. -All built-in tools can be listed and tested with 'udevadm test-builtin'. + * Service start rate limiting is now configurable and can be + turned off per service. When a start rate limit is hit a + reboot can automatically be triggered. -The 'udevadm control --reload-rules' option has been renamed to '--reload'. -It now also reloads the kernel module configuration. + * New CanReboot(), CanPowerOff() bus calls in systemd-logind. -The systemd socket files use PassCredentials=yes, which is available in -systemd version 38. + Contributions from: Benjamin Franzke, Bill Nottingham, + Frederic Crozat, Lennart Poettering, Michael Olbrich, Michal + Schmidt, Michał Górny, Piotr Drąg -The udev build system only creates a .xz tarball now. +CHANGES WITH 41: + * The systemd binary is installed /usr/lib/systemd/systemd now; + An existing /sbin/init symlink needs to be adapted with the + package update. -All tabs in the source code used for indentation are replaced by spaces now. :) + * The code that loads kernel modules has been ported to invoke + libkmod directly, instead of modprobe. This means we do not + support systems with module-init-tools anymore. -udev 175 -======== -Bugfixes. + * Watchdog support is now already useful, but still not + complete. -udev 174 -======== -Bugfixes. + * A new kernel command line option systemd.setenv= is + understood to set system wide environment variables + dynamically at boot. -The udev daemon moved to /lib/udev/udevd. Non-systemd init systems -and non-dracut initramfs image generators need to change the init -scripts. Alternatively the udev build needs to move udevd back to -/sbin or create a symlink in /sbin, which is not done by default. + * We now limit the set of capabilities of systemd-journald. -The path_id, usb_id, input_id tools are built-in commands now and -the stand-alone tools do not exist anymore. Static lists of file in -initramfs generators need to be updated. For testing, the commands -can still be executed standalone with 'udevadm test-builtin '. + * We now set SIGPIPE to ignore by default, since it only is + useful in shell pipelines, and has little use in general + code. This can be disabled with IgnoreSIPIPE=no in unit + files. -The fusectl filesystem is no longer mounted directly from udev. -Systemd systems will take care of mounting fusectl and configfs -now. Non-systemd systems need to ship their own rule if they -need these filesystems auto-mounted. - -The long deprecated keys: SYSFS=, ID=, BUS= have been removed. + Contributions from: Benjamin Franzke, Kay Sievers, Lennart + Poettering, Michael Olbrich, Michal Schmidt, Tom Gundersen, + William Douglas -The support for 'udevadm trigger --type=failed, and the -RUN{fail_event_on_error} attribute was removed. +CHANGES WITH 40: + * This is mostly a bugfix release -The udev control socket is now created in /run/udev/control -and no longer as an abstract namespace one. + * We now expose the reason why a service failed in the + "Result" D-Bus property. -The rules to create persistent network interface and cdrom link -rules automatically in /etc/udev/rules.d/ have been disabled by -default. Explicit configuration will be required for these use -cases, udev will no longer try to write any persistent system -configuration from a device hotplug path. + * Rudimentary service watchdog support (will be completed over + the next few releases.) -udev 173 -======== -Bugfixes. - -The udev-acl extra is no longer enabled by default now. To enable it, ---enable-udev_acl needs to be given at ./configure time. On systemd -systems, the udev-acl rules prevent it from running as the functionality -has moved to systemd. + * When systemd forks off in order execute some service we will + now immediately changes its argv[0] to reflect which process + it will execute. This is useful to minimize the time window + with a generic argv[0], which makes bootcharts more useful -udev 172 -======== -Bugfixes. - -Udev now enables kernel media-presence polling if available. Part -of udisks optical drive tray-handling moved to cdrom_id: The tray -is locked as soon as a media is detected to enable the receiving -of media-eject-request events. Media-eject-request events will -eject the media. - -Libudev enumerate is now able to enumerate a subtree of a given -device. - -The mobile-action-modeswitch modeswitch tool was deleted. The -functionality is provided by usb_modeswitch now. + Contributions from: Alvaro Soliverez, Chris Paulson-Ellis, Kay + Sievers, Lennart Poettering, Michael Olbrich, Michal Schmidt, + Mike Kazantsev, Ray Strode -udev 171 -======== -Bugfixes. +CHANGES WITH 39: + * This is mostly a test release, but incorporates many + bugfixes. -The systemd service files require systemd version 28. The systemd -socket activation make it possible now to start 'udevd' and 'udevadm -trigger' in parallel. + * New systemd-cgtop tool to show control groups by their + resource usage. -udev 170 -======== -Fix bug in control message handling, which can lead to a failing -udevadm control --exit. Thanks to Jürg Billeter for help tracking -it down. - -udev 169 -======== -Bugfixes. - -We require at least Linux kernel 2.6.32 now. Some platforms might -require a later kernel that supports accept4() and similar, or -need to backport the trivial syscall wiring to the older kernels. - -The hid2hci tool moved to the bluez package and was removed. - -Many of the extras can be --enable/--disabled at ./configure -time. The --disable-extras option was removed. Some extras have -been disabled by default. The current options and their defaults -can be checked with './configure --help'. - -udev 168 -======== -Bugfixes. - -Udev logs a warning now if /run is not writable at udevd -startup. It will still fall back to /dev/.udev, but this is -now considered a bug. - -The running udev daemon can now cleanly shut down with: - udevadm control --exit - -Udev in initramfs should clean the state of the udev database -with: udevadm info --cleanup-db which will remove all state left -behind from events/rules in initramfs. If initramfs uses ---cleanup-db and device-mapper/LVM, the rules in initramfs need -to add OPTIONS+="db_persist" for all dm devices. This will -prevent removal of the udev database for these devices. - -Spawned programs by PROGRAM/IMPORT/RUN now have a hard timeout of -120 seconds per process. If that timeout is reached the spawned -process will be killed. The event timeout can be overwritten with -udev rules. - -If systemd is used, udev gets now activated by netlink data. -Systemd will bind the netlink socket which will buffer all data. -If needed, such setup allows a seemless update of the udev daemon, -where no event can be lost during a udevd update/restart. -Packages need to make sure to: systemctl stop udev.socket udev.service -or 'mask' udev.service during the upgrade to prevent any unwanted -auto-spawning of udevd. -This version of udev conflicts with systemd version below 25. The -unchanged service files will not wirk correctly. - -udev 167 -======== -Bugfixes. - -The udev runtime data moved from /dev/.udev/ to /run/udev/. The -/run mountpoint is supposed to be a tmpfs mounted during early boot, -available and writable to for all tools at any time during bootup, -it replaces /var/run/, which should become a symlink some day. - -If /run does not exist, or is not writable, udev will fall back using -/dev/.udev/. - -On systemd systems with initramfs and LVM used, packagers must -make sure, that the systemd and initramfs versions match. The initramfs -needs to create the /run mountpoint for udev to store the data, and -mount this tmpfs to /run in the rootfs, so the that the udev database -is preserved for the udev version started in the rootfs. - -The command 'udevadm info --convert-db' is gone. The udev daemon -itself, at startup, converts any old database version if necessary. - -The systemd services files have been reorganized. The udev control -socket is bound by systemd and passed to the started udev daemon. -The udev-settle.service is no longer active by default. Services which -can not handle hotplug setups properly need to actively pull it in, to -act like a barrier. Alternatively the settle service can be unconditionally -'systemctl'enabled, and act like a barrier for basic.target. - -The fstab_import callout is no longer built or installed. Udev -should not be used to mount, does not watch changes to fstab, and -should not mirror fstab values in the udev database. - -udev 166 -======== -Bugfixes. - -New and updated keymaps. - -udev 165 -======== -Bugfixes. - -The udev database has changed, After installation of a new udev -version, 'udevadm info --convert-db' should be called, to let the new -udev/libudev version read the already stored data. - -udevadm now supports quoting of property values, and prefixing of -key names: - $ udevadm info --export --export-prefix=MY_ --query=property -n sda - MY_MAJOR='259' - MY_MINOR='0' - MY_DEVNAME='/dev/sda' - MY_DEVTYPE='disk' - ... - -libudev now supports: - udev_device_get_is_initialized() - udev_enumerate_add_match_is_initialized() -to be able to skip devices the kernel has created , but udev has -not already handled. - -libudev now supports: - udev_device_get_usec_since_initialized() -to retrieve the "age" of a udev device record. - -GUdev supports a more generic GUdevEnumerator class, udev TAG -handling, device initialization and timestamp now. - -The counterpart of /sys/dev/{char,block}/$major:$minor, -/dev/{char,block}/$major:$minor symlinks are now unconditionally -created, even when no rule files exist. - -New and updated keymaps. - -udev 164 -======== -Bugfixes. - -GUdev moved from /usr to /. - -udev 163 -======== -Bugfixes. - -udev 162 -======== -Bugfixes. - -Persistent network naming rules are disabled inside of Qemu/KVM now. - -New and updated keymaps. - -Udev gets unconditionally enabled on systemd installations now. There -is no longer the need to to run 'systemctl enable udev.service'. - -udev 161 -======== -Bugfixes. - -udev 160 -======== -Bugfixes. - -udev 159 -======== -Bugfixes. - -New and fixed keymaps. - -Install systemd service files if applicable. - -udev 158 -======== -Bugfixes. - -All distribution specific rules are removed from the udev source tree, -most of them are no longer needed. The Gentoo rules which allow to support -older kernel versions, which are not covered by the default rules anymore -has moved to rules/misc/30-kernel-compat.rules. - -udev 157 -======== -Bugfixes. - -The option --debug-trace and the environemnt variable UDEVD_MAX_CHILDS= -was removed from udevd. - -Udevd now checks the kernel commandline for the following variables: - udev.log-priority= - udev.children-max= - udev.exec-delay= -to help debuging coldplug setups where the loading of a kernel -module crashes the system. - -The subdirectory in the source tree rules/packages has been renamed to -rules/arch, anc contains only architecture specific rules now. - -udev 156 -======== -Bugfixes. - -udev 155 -======== -Bugfixes. - -Now the udev daemon itself, does on startup: - - copy the content of /lib/udev/devices to /dev - - create the standard symlinks like /dev/std{in,out,err}, - /dev/core, /dev/fd, ... - - use static node information provided by kernel modules - and creates these nodes to allow module on-demand loading - - possibly apply permissions to all ststic nodes from udev - rules which are annotated to match a static node - -The default mode for a device node is 0600 now to match the kernel -created devtmpfs defaults. If GROUP= is specified and no MODE= is -given the default will be 0660. - -udev 154 -======== -Bugfixes. - -Udev now gradually starts to pass control over the primary device nodes -and their names to the kernel, and will in the end only manage the -permissions of the node, and possibly create additional symlinks. -As a first step NAME="" will be ignored, and NAME= setings with names -other than the kernel provided name will result in a logged warning. -Kernels that don't provide device names, or devtmpfs is not used, will -still work as they did before, but it is strongly recommended to use -only the same names for the primary device node as the recent kernel -provides for all devices. - -udev 153 -======== -Fix broken firmware loader search path. - -udev 152 -======== -Bugfixes. - -"udevadm trigger" defaults to "change" events now instead of "add" -events. The "udev boot script" might need to add "--action=add" to -the trigger command if not already there, in case the initial coldplug -events are expected as "add" events. - -The option "all_partitons" was removed from udev. This should not be -needed for usual hardware. Udev can not safely make assumptions -about non-existing partition major/minor numbers, and therefore no -longer provide this unreliable and unsafe option. - -The option "ignore_remove" was removed from udev. With devtmpfs -udev passed control over device nodes to the kernel. This option -should not be needed, or can not work as advertised. Neither -udev nor the kernel will remove device nodes which are copied from -the /lib/udev/devices/ directory. - -All "add|change" matches are replaced by "!remove" in the rules and -in the udev logic. All types of events will update possible symlinks -and permissions, only "remove" is handled special now. - -The modem modeswitch extra was removed and the external usb_modeswitch -program should be used instead. - -New and fixed keymaps. - -udev 151 -======== -Bugfixes. - -udev 150 -======== -Bugfixes. - -Kernels with SYSFS_DEPRECATED=y are not supported since a while. Many users -depend on the current sysfs layout and the information not available in the -deprecated layout. All remaining support for the deprecated sysfs layout is -removed now. - -udev 149 -======== -Fix for a possible endless loop in the new input_id program. - -udev 148 -======== -Bugfixes. - -The option "ignore_device" does no longer exist. There is no way to -ignore an event, as libudev events can not be suppressed by rules. -It only prevented RUN keys from being executed, which results in an -inconsistent behavior in current setups. - -BUS=, SYSFS{}=, ID= are long deprecated and should be SUBSYSTEM(S)=, -ATTR(S){}=, KERNEL(S)=. It will cause a warning once for every rule -file from now on. - -The support for the deprecated IDE devices has been removed from the -default set of rules. Distros who still care about non-libata drivers -need to add the rules to the compat rules file. - -The ID_CLASS property on input devices has been replaced by the more accurate -set of flags ID_INPUT_{KEYBOARD,KEY,MOUSE,TOUCHPAD,TABLET,JOYSTICK}. These are -determined by the new "input_id" prober now. Some devices, such as touchpads, -can have several classes. So if you previously had custom udev rules which e. g. -checked for ENV{ID_CLASS}=="kbd", you need to replace this with -ENV{ID_INPUT_KEYBOARD}=="?*". - -udev 147 -======== -Bugfixes. - -To support DEVPATH strings larger than the maximum file name length, the -private udev database format has changed. If some software still reads the -private files in /dev/.udev/, which it shouldn't, now it's time to fix it. -Please do not port anything to the new format again, everything in /dev/.udev -is and always was private to udev, and may and will change any time without -prior notice. - -Multiple devices claiming the same names in /dev are limited to symlinks -only now. Mixing identical symlink names and node names is not supported. -This reduces the amount of data in the database significantly. - -NAME="%k" causes a warning now. It's is and always was completely superfluous. -It will break kernel supplied DEVNAMEs and therefore it needs to be removed -from all rules. - -Most NAME= instructions got removed. Kernel 2.6.31 supplies the needed names -if they are not the default. To support older kernels, the NAME= rules need to -be added to the compat rules file. - -Symlinks to udevadm with the old command names are no longer resolved to -the udevadm commands. - -The udev-acl tool got adopted to changes in ConsoleKit. Version 0.4.1 is -required now. - -The option "last_rule" does no longer exist. Its use breaks too many -things which expect to be run from independent later rules, and is an idication -that something needs to be fixed properly instead. - -The gudev API is no longer marked as experimental, -G_UDEV_API_IS_SUBJECT_TO_CHANGE is no longer needed. The gudev introspection -is enabled by default now. Various projects already depend on introspection -information to bind dynamic languages to the gudev interfaces. - -udev 146 -======== -Bugfixes. - -The udevadm trigger "--retry-failed" option, which is replaced since quite -a while by "--type=failed" is removed. - -The failed tracking was not working at all for a few releases. The RUN -option "ignore_error" is replaced by a "fail_event_on_error" option, and the -default is not to track any failing RUN executions. - -New keymaps, new modem, hid2hci updated. - -udev 145 -======== -Fix possible crash in udevd when worker processes are busy, rules are -changed at the same time, and workers get killed to reload the rules. - -udev 144 -======== -Bugfixes. - -Properties set with ENV{.FOO}="bar" are marked private by starting the -name with a '.'. They will not be stored in the database, and not be -exported with the event. - -Firmware files are looked up in: - /lib/firmware/updates/$(uname -r) - /lib/firmware/updates - /lib/firmware/$(uname -r) - /lib/firmware" -now. - -ATA devices switched the property from ID_BUS=scsi to ID_BUS=ata. -ata_id, instead of scsi_id, is the default tool now for ATA devices. - -udev 143 -======== -Bugfixes. - -The configure options have changed because another library needs to be -installed in a different location. Instead of exec_prefix and udev_prefix, -libdir, rootlibdir and libexecdir are used. The Details are explained in -the README file. - -Event processes now get re-used after they handled an event. This reduces -the number of forks and the pressure on the CPU significantly, because -cloned event processes no longer cause page faults in the main daemon. -After the events have settled, a few worker processes stay around for -future events, all others get cleaned up. - -To be able to use signalfd(), udev depends on kernel version 2.6.25 now. -Also inotify support is mandatory now to run udev. - -The format of the queue exported by the udev damon has changed. There is -no longer a /dev/.udev/queue/ directory. The current event queue can be -accessed with udevadm settle and libudedv. - -Libudev does not have the unstable API header anymore. From now on, -incompatible changes will be handled by bumping the library major version. - -To build udev from the git tree gtk-doc is needed now. The tarballs will -build without it and contain the pre-built documentation. An online copy -is available here: - http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/ - -The tools from the udev-extras repository have been merged into the main -udev repository. Some of the extras have larger external dependencies, and -they can be disabled with the configure switch --disable-extras. - -udev 142 -======== -Bugfixes. - -The program vol_id and the library libvolume_id are removed from the -repository. Libvolume_id is merged with libblkid from the util-linux-ng -package. Persistent disk links for label and uuid depend on the -util-linux-ng version (2.15) of blkid now. Older versions of blkid -can not be used with udev. - -Libudev allows to subscribe to udev events. To prevent unwanted messages -to be delivered, and waking up the subscribing process, a filter can be -installed, to drop messages inside a kernel socket filter. The filters -match on the : properties of the device. - This is part of the ongoing effort to replace HAL, and switch current -users over to directly use libudev. - Libudev is still marked as experimental, and its interface might -eventually change if needed, but no major changes of the currently exported -interface are expected anymore, and a first stable release should happen -soon. - -A too old kernel (2.6.21) or a kernel with CONFIG_SYSFS_DEPRECATED -is not supported since while and udevd will log an error message at -startup. It should still be able to boot-up, but advanced rules and system -services which depend on the information not available in the old sysfs -format will fail to work correctly. - -DVB device naming is supplied by the kernel now. In case older kernels -need to be supported, the old shell script should be added to a compat -rules file. - -udev 141 -======== -Bugfixes. - -The processed udev events get send back to the netlink socket. Libudev -provides access to these events. This is work-in-progress, to replace -the DeviceKit daemon functionality directly with libudev. There are -upcoming kernel changes to allow non-root users to subcribe to these -events. - -udev 140 -======== -Bugfixes. - -"udevadm settle" now optionally accepts a range of events to wait for, -instead of waiting for "all" events. - -udev 139 -======== -Bugfixes. - -The installed watch for block device metadata changes is now removed -during event hadling, because some (broken) tools may be called from udev -rules and (wrongly) open the device with write access. After the finished -event handling the watch is restored. - -udev 138 -======== -Bugfixes. - -Device nodes can be watched for changes with inotify with OPTIONS="watch". -If closed after being opened for writing, a "change" uevent will occur. -/dev/disk/by-{label,uuid}/* symlinks will be automatically updated. - -udev 137 -======== -Bugfixes. - -The udevadm test command has no longer a --force option, nodes and symlinks -are always updated with a test run now. - -The udevd daemon can be started with --resolve-names=never to avoid all user -and group lookups (e.g. in cut-down systems) or --resolve-names=late to -lookup user and groups every time events are handled. - -udev 136 -======== -Bugfixes. - -We are currently merging the Ubuntu rules in the udev default rules, -and get one step closer to provide a common Linux /dev setup, regarding -device names, symlinks, and default device permissions. On udev startup, -we now expect the following groups to be resolvable to their ids with -glibc's getgrnam(): - disk, cdrom, floppy, tape, audio, video, lp, tty, dialout, kmem. -LDAP setups need to make sure, that these groups are always resolvable at -bootup, with only the rootfs mounted, and without network access available. - -Some systems may need to add some new, currently not used groups, or need -to add some users to new groups, but the cost of this change is minimal, -compared to the pain the current, rather random, differences between the -various distributions cause for upstream projects and third-party vendors. - -In general, "normal" users who log into a machine should never be a member -of any such group, but the device-access should be managed by dynamic ACLs, -which get added and removed for the specific users on login/logout and -session activity/inactivity. These groups are only provided for custom setups, -and mainly system services, to allow proper privilege separation. -A video-streaming daemon uid would be a member of "audio" and "video", to get -access to the sound and video devices, but no "normal" user should ever belong -to the "audio" group, because he could listen to the built-in microphone with -any ssh-session established from the other side of the world. - -/dev/serial/by-{id,path}/ now contains links for ttyUSB devices, -which do not depend on the kernel device name. As usual, unique -devices - only a single one per product connected, or a real -USB serial number in the device - are always found with the same -name in the by-id/ directory. -Completely identical devices may overwrite their names in by-id/ -and can only be found reliably in the by-path/ directory. Devices -specified by by-path/ must not change their connection, like the -USB port number they are plugged in, to keep their name. - -To support some advanced features, Linux 2.6.22 is the oldest supported -version now. The kernel config with enabled SYSFS_DEPRECATED is no longer -supported. Older kernels should still work, and devices nodes should be -reliably created, but some rules and libudev will not work correctly because -the old kernels do not provide the expected information or interfaces. - -udev 135 -======== -Bugfixes. - -Fix for a possible segfault while swapping network interface names in udev -versions 131-134. - -udev 134 -======== -Bugfixes. - -The group "video" is part of the default rules now. - -udev 133 -======== -Bugfix for kernels using SYSFS_DEPRECATED* option and finding parent -block devices in some cases. No common distro uses this option anymore, -and we do not get enough testing for this and recent udev versions. If -this option is not needed to run some old distro with a new kernel, -it should be disabled in the kernel config. - -Bugfix for the $links substitution variable, which may crash if no links -are created. This should not happen in usual setups because we always -create /dev/{block,char}/ links. - -The strings of the parsed rules, which are kept in memory, no longer -contain duplicate entries, or duplicate tails of strings. This, and the -new rules parsing/matching code reduces the total in-memory size of -a huge distro rule sets to 0.08 MB, compared to the 1.2MB of udev -version 130. - -The export of DEVTYPE=disk/partition got removed from the default -rules. This value is available from the kernel. The pnp shell script -modprobe hack is removed from the default rules. ACPI devices have _proper_ -modalias support and take care of the same functionality. -Installations which support old kernels, but install current default -udev rules may want to add that to the compat rules file. - -Libvolume_id now always probes for all known filesystems, and does not -stop at the first match. Some filesystems are marked as "exclusive probe", -and if any other filesytem type matches at the same time, libvolume_id -will, by default, not return any probing result. This is intended to prevent -mis-detection with conflicting left-over signatures found from earlier -file system formats. That way, we no longer depend on the probe-order -in case of multiple competing signatures. In some setups the kernel allows -to mount a volume with just the old filesystem signature still in place. -This may damage the new filesystem and cause data-loss, just by mounting -it. Because volume_id can not decide which one the correct signature is, -the wrong signatures need to be removed manually from the volume, or the -volume needs to be reformatted, to enable filesystem detection and possible -auto-mounting. - -udev 132 -======== -Fix segfault if compiled without optimization and dbg() does not get -compiled out and uses variables which are not available. - -udev 131 -======== -Bugfixes. (And maybe new bugs. :)) - -The rule matching engine got converted from a rule list to a token -array which reduced the in-memory rules representation of a full -featured distros with thousends of udev rules from 1.2MB to 0.12 MB. -Limits like 5 ENV and ATTR matches, and one single instance for most -other keys per rule are gone. - -The NAME assignment is no longer special cased. If later rules assign -a NAME value again, the former value will be overwritten. As usual -for most other keys, the NAME value can be protected by doing a final -assignment with NAME:="". - -All udev code now uses libudev, which is also exported. The library -is still under development, marked as experimental, and its interface -may change as long as the DeviceKit integration is not finished. - -Many thanks to Alan Jenkins for his continuous help, and finding and -optimizing some of the computing expensive parts. - -udev 130 -======== -Bugfixes. - -Kernel devices and device nodes are connected now by reverse indizes in -/sys and /dev. A device number retrieved by a stat() or similar, the -kernel device directory can be found by looking up: - /sys/dev/{block,char}/: -and the device node of the same device by looking up: - /dev/{block,char}/: - -udev 129 -======== -Fix recently introduced bug, which caused a compilation without large -file support, where vol_id does not recognize raid signatures at the end -of a volume. - -Firewire disks now create both, by-id/scsi-* and by-id/ieee-* links. -Seems some kernel versions prevent the creation of the ieee-* links, -so people used the scsi-* link which disappeared now. - -More libudev work. Almost all udevadm functionality comes from libudev -now. - -udevadm trigger has a new option --type, which allows to trigger events -for "devices", for "subsystems", or "failed" devices. The old option ---retry-failed" still works, but is no longer mentioned in the man page. - -udev 128 -======== -Bugfixes. - -The udevadm info --device-id-of-file= output has changed to use -the obvious format. Possible current users should use the --export -option which is not affected. - -The old udev commands symlinks to udevadm are not installed, if -these symlinks are used, a warning is printed. - -udev 127 -======== -Bugfixes. - -Optical drive's media is no longer probed for raid signatures, -reading the end of the device causes some devices to malfunction. -Also the offset of the last session found is used now to probe -for the filesystem. - -The volume_id library got a major version number update to 1, -some deprecated functions are removed. - -A shared library "libudev" gets installed now to provide access -to udev device information. DeviceKit, the successor of HAL, will -need this library to access the udev database and search sysfs for -devices. -The library is currently in an experimental state, also the API is -expected to change, as long as the DeviceKit integration is not -finished. - -udev 126 -======== -We use ./configure now. See INSTALL for details. Current -options are: - --prefix= - "/usr" - prefix for man pages, include files - --exec-prefix= - "" - the root filesystem, prefix for libs and binaries - --sysconfdir= - "/etc" - --with-libdir-name= - "lib" - directory name for libraries, not a path name - multilib 64bit systems may use "lib64" instead of "lib" - --enable-debug - compile-in verbose debug messages - --disable-logging - disable all logging and compile-out all log strings - --with-selinux - link against SELInux libraries, to set the expected context - for created files - -In the default rules, the group "disk" gets permissions 0660 instead -of 0640. One small step closer to unify distro rules. Some day, all -distros hopefully end up with the same set of rules. - -No symlinks to udevadm are installed anymore, if they are still needed, -they should be provided by the package. - -udev 125 -======== -Bugfixes. - -Default udev rules, which are not supposed to be edited by the user, should -be placed in /lib/udev/rules.d/ now, to make it clear that they are private to -the udev package and will be replaced with an update. Udev will pick up rule -files from: - /lib/udev/rules.d/ - default installed rules - /etc/udev/rules.d/ - user rules + on-the-fly generated rules - /dev/.udev/rules.d/ - temporary non-persistent rules created after bootup -It does not matter in which directory a rule file lives, all files are sorted -in lexical order. - -To help creating /dev/root, we have now: - $ udevadm info --export --export-prefix="ROOT_" --device-id-of-file=/ - ROOT_MAJOR=8 - ROOT_MINOR=5 -In case the current --device-id-of-file is already used, please switch to -the --export format version, it saves the output parsing and the old -format will be changed to use ':' as a separator, like the format in the -sysfs 'dev' file. - -udev 124 -======== -Fix cdrom_id to properly recognize blank media. - -udev 123 -======== -Bugfixes. - -Tape drive id-data is queried from /dev/bsg/* instead of the tape -nodes. This avoids rewinding tapes on open(). - -udev 122 -======== -Bugfixes. - -The symlinks udevcontrol and udevtrigger are no longer installed by -the Makefile. - -The scsi_id program does not depend on sysfs anymore. It can speak -SGv4 now, so /dev/bsg/* device nodes can be used, to query SCSI device -data, which should solve some old problems with tape devices, where -we better do not open all tape device nodes to identify the device. - -udev 121 -======== -Many bugfixes. - -The cdrom_id program is replaced by an advanced version, which can -detect most common device types, and also properties of the inserted -media. This is part of moving some basic functionality from HAL into -udev (and the kernel). - -udev 120 -======== -Bugfixes. - -The last WAIT_FOR_SYSFS rule is removed from the default rules. - -The symlinks to udevadm for the debugging tools: udevmonitor and -udevtest are no longer created. - -The symlinks to the udevadm man page for the old tool names are -no longer created. - -Abstract namespace sockets paths in RUN+="socket:@" rules, -should be prefixed with '@' to indicate that the path is not a -real file. - -udev 119 -======== -Bugfixes. - -udev 118 -======== -Bugfixes. - -Udevstart is removed from the tree, it did not get installed for -a long time now, and is long replaced by trigger and settle. - -udev 117 -======== -Bugfixes. - -All udev tools are merged into a single binary called udevadm. -The old names of the tools are built-in commands in udevadm now. -Symlinks to udevadm, with the names of the old tools, provide -the same functionality as the standalone tools. There is also -only a single udevadm.8 man page left for all tools. - -Tools like mkinitramfs should be checked, if they need to include -udevadm in the list of files. - -udev 116 -======== -Bugfixes. - -udev 115 -======== -Bugfixes. - -The etc/udev/rules.d/ directory now contains a default set of basic -udev rules. This initial version is the result of a rules file merge -of Fedora and openSUSE. For these both distros only a few specific -rules are left in their own file, named after the distro. Rules which -are optionally installed, because they are only valid for a specific -architecture, or rules for subsystems which are not always used are -in etc/udev/packages/. - -udev 114 -======== -Bugfixes. - -Dynamic rules can be created in /dev/.udev/rules.d/ to trigger -actions by dynamically created rules. - -SYMLINK=="" matches agains the entries in the list of -currently defined symlinks. The links are not created in the -filesystem at that point in time, but the values can be matched. + * Linking against libacl for ACLs is optional again. If + disabled, support tracking device access for active logins + goes becomes unavailable, and so does access to the user + journals by the respective users. -RUN{ignore_error}+="" will ignore any exit code from the -program and not record as a failed event. + * If a group "adm" exists, journal files are automatically + owned by them, thus allow members of this group full access + to the system journal as well as all user journals. -udev 113 -======== -Bugfixes. + * The journal now stores the SELinux context of the logging + client for all entries. -Final merge of patches/features from the Ubuntu package. + * Add C++ inclusion guards to all public headers -udev 112 -======== -Bugfixes. - -Control characters in filesystem label strings are no longer silenty -removed, but hex-encoded, to be able to uniquely identify the device -by its symlink in /dev/disk/by-label/. -If libvolume_id is used by mount(8), LABEL= will work as expected, -if slashes or other characters are used in the label string. - -To test the existence of a file, TEST=="" and TEST!="" -can be specified now. The TEST key accepts an optional mode mask -TEST{0100}=="". - -Scsi_id now supports a mode without expecting scsi-specific sysfs -entries to allow the extraction of cciss-device persistent properties. - -udev 111 -======== -Bugfixes. - -In the future, we may see uuid's which are just simple character -strings (see the DDF Raid Specification). For that reason vol_id now -exports ID_FS_UUID_SAFE, just like ID_FS_LABEL_SAFE. For things like -the creation of symlinks, the *_SAFE values ensure, that no control -or whitespace characters are used in the filename. - -Possible users of libvolume_id, please use the volume_id_get_* functions. -The public struct will go away in a future release of the library. - -udev 110 -======== -Bugfixes. - -Removal of useless extras/eventrecorder.sh. - -udev 109 -======== -Bugfixes. - -udev 108 -======== -Bugfixes. - -The directory multiplexer for dev.d/ and hotplug.d are finally removed -from the udev package. - -udev 107 -======== -Bugfixes. - -Symlinks can have priorities now, the priority is assigned to the device -and specified with OPTIONS="link_priority=100". Devices with higher -priorities overwrite the symlinks of devices with lower priorities. -If the device that currently owns the link, goes away, the symlink -will be removed, and recreated, pointing to the next device with the -highest actual priority. This should make /dev/disk/by-{label,uuid,id} -more reliable, if multiple devices contain the same metadata and overwrite -these symlinks. - -The dasd_id program is removed from the udev tree, and dasdinfo, with the -needed rules, are part of the s390-tools now. - -Please add KERNEL=="[0-9]*:[0-9]*" to the scsi wait-for-sysfs rule, -we may get the scsi sysfs mess fixed some day, and this will only catch -the devices we are looking for. - -USB serial numbers for storage devices have the target:lun now appended, -to make it possibble to distinguish broken multi-lun devices with all -the same SCSI identifiers. - -Note: The extra "run_directory" which searches and executes stuff in -/etc/hotplug.d/ and /etc/dev.d/ is long deprecated, and will be removed -with the next release. Make sure, that you don't use it anymore, or -provides your own implementation of that inefficient stuff. -We are tired of reports about a "slow udev", because these directories -contain stuff, that runs with _every_ event, instead of using rules, -that run programs only for the matching events. - -udev 106 -======== -Bugfixes. - -udev 105 -======== -Bugfixes. - -DRIVER== will match only for devices that actually have a real -driver. DRIVERS== must be used, if parent devices should be -included in the match. - -Libvolume_id's "linux_raid" detection needed another fix. - -udev 104 -======== -Bugfixes. - -udev 103 -======== -Add additional check to volume_id detection of via_raid, cause -some company decided to put a matching pattern all over the empty -storage area of their music players. - -udev 102 -======== -Fix path_id for SAS devices. - -udev 101 -======== -The udev daemon can be started with --debug-trace now, which will -execute all events serialized to get a chance to catch a possible -action that crashes the box. - -A warning is logged, if PHYSDEV* keys, the "device" link, or a parent -device attribute like $attr{../file} is used, only WAIT_FOR_SYSFS rules -are excluded from the warning. Referencing parent attributes directly -may break when something in the kernel driver model changes. Udev will -just find the attribute by walking up the parent chain. - -Udevtrigger now sorts the list of devices depending on the device -dependency, so a "usb" device is triggered after the parent "pci" -device. - -udev 100 -======== -Revert persistent-storage ata-serial '_' '-' replacement. - -udev 099 -======== -Bugfixes. - -Udevtrigger can now filter the list of devices to be triggered. Matches -for subsystems or sysfs attributes can be specified. - -The entries in /dev/.udev/queue and /dev/.udev/failed have changed to -zero-sized files to avoid pointing to /sys and confuse broken tools which -scan the /dev directory. To retry failed events, udevtrigger --retry-failed -should be used now. - -The rules and scripts to create udev rules for persistent network -devices and optical drives are in the extras/rules_generator directory -now. If you use something similar, please consider replacing your own -version with this, to share the support effort. The rule_generator -installs its own rules into /etc/udev/rules.d. - -The cdrom_id tool installs its own rule now in /etc/udev/rules.d, cause -the rule_generator depends on cdrom_id to be called in an earlier rule. - -udev 098 -======== -Bugfixes. - -Renaming of some key names (the old names still work): -BUS -> SUBSYSTEMS, ID -> KERNELS, SYSFS -> ATTRS, DRIVER -> DRIVERS. -(The behavior of the key DRIVER will change soon in one of the next -releases, to match only the event device, please switch to DRIVERS -instead. If DRIVER is used, it will behave like DRIVERS, but an error -is logged. -With the new key names, we have a more consistent and simpler scheme. -We can match the properties of the event device only, with: KERNEL, -SUBSYSTEM, ATTR, DRIVER. Or include all the parent devices in the match, -with: KERNELS, SUBSYSTEMS, ATTRS, DRIVERS. ID, BUS, SYSFS, DRIVER are no -longer mentioned in the man page and should be switched in the rule -files. - -ATTR{file}="value" can be used now, to write to a sysfs file of the -event device. Instead of: - ..., SYSFS{type}=="0|7|14", RUN+="/bin/sh -c 'echo 60 > /sys$$DEVPATH/timeout'" -we now can do: - ..., ATTR{type}=="0|7|14", ATTR{timeout}="60" - -All the PHYSDEV* keys are deprecated and will be removed from a -future kernel: - PHYDEVPATH - is the path of a parent device and should not be - needed at all. - PHYSDEVBUS - is just a SUBSYSTEM value of a parent, and can be - matched with SUBSYSTEMS== - PHYSDEVDRIVER - for bus devices it is available as ENV{DRIVER}. - Newer kernels will have DRIVER in the environment, - for older kernels udev puts in. Class device will - no longer carry this property of a parent and - DRIVERS== can be used to match such a parent value. -Note that ENV{DRIVER} is only available for a few bus devices, where -the driver is already bound at device event time. On coldplug, the -events for a lot devices are already bound to a driver, and they will have -that value set. But on hotplug, at the time the kernel creates the device, -it can't know what driver may claim the device after that, therefore -in most cases it will be empty. - -Failed events should now be re-triggered with: - udevtrigger --retry-failed. -Please switch to this command, so we keep the details of the /dev/.udev/failed/ -files private to the udev tools. We may need to switch the current symlink -target, cause some obviously broken tools try to scan all files in /dev -including /dev/.udev/, find the links to /sys and end up stat()'ing sysfs files -million times. This takes ages on slow boxes. - -The udevinfo attribute walk (-a) now works with giving a device node -name (-n) instead of a devpath (-p). The query now always works, also when -no database file was created by udev. - -The built-in /etc/passwd /etc/group parser is removed, we always depend on -getpwnam() and getgrnam() now. One of the next releases will depend on -fnmatch() and may use getopt_long(). - -udev 097 -======== -Bugfixes and small improvements. - -udev 096 -======== -Fix path_id for recent kernels. - -udev 095 -======== -%e is finally gone. - -Added support for swapping network interface names, by temporarily -renaming the device and wait for the target name to become free. - -udev 094 -======== -The built-in MODALIAS key and substitution is removed. - -udev 093 -======== -The binary firmware helper is replaced by the usual simple -shell script. Udevsend is removed from the tree. - -udev 092 -======== -Bugfix release. - -udev 091 -======== -Some more keys require the correct use of '==' and '=' depending -on the kind of operation beeing an assignment or a match. Rules -with invalid operations are skipped and logged to syslog. Please -test with udevtest if the parsing of your rules throws errors and -fix possibly broken rules. - -udev 090 -======== -Provide "udevsettle" to wait for all current udev events to finish. -It also watches the current kernel netlink queue by comparing the -even sequence number to make sure that there are no current pending -events that have not already arrived in the daemon. - -udev 089 -======== -Fix rule to skip persistent rules for removable IDE devices, which -also skipped optical IDE drives. - -All *_id program are installed in /lib/udev/ by default now. - -No binary is stripped anymore as this should be done in the -packaging process and not at build time. - -libvolume_id is provided as a shared library now and vol_id is -linked against it. Also one of the next HAL versions will require -this library, and the HAL build process will also require the -header file to be installed. The copy of the same code in HAL will -be removed to have only a single copy left on the system. - -udev 088 -======== -Add persistent links for SCSI tapes. The rules file is renamed -to 60-persistent-storage.rules. - -Create persistent path for usb devices. Can be used for all sorts -of devices that can't be distinguished by other properties like -multiple identical keyboards and mice connected to the same box. - -Provide "udevtrigger" program to request events on coldplug. The -shell script is much too slow with thousends of devices. - -udev 087 -======== -Fix persistent disk rules to exclude removable IDE drives. - -Warn if %e, $modalias or MODALIAS is used. - -udev 086 -======== -Fix queue export, which wasn't correct for subsequent add/remove -events for the same device. - -udev 085 -======== -Fix cramfs detection on big endian. - -Make WAIT_FOR_SYSFS usable in "normal" rules and silent if the whole -device goes away. - -udev 084 -======== -If BUS== and SYSFS{}== have been used in the same rule, the sysfs -attributes were only checked at the parent device that matched the -by BUS requested subsystem. Fix it to also look at the device we -received the event for. - -Build variable CROSS has changed to CROSS_COMPILE to match the kernel -build name. - -udev 083 -======== -Fix a bug where NAME="" would prevent RUN from beeing executed. - -RUN="/bin/program" does not longer automatically add the subsystem -as the first parameter. This is from the days of /sbin/hotplug -which is dead now and it's just confusing to need to add a space at -the end of the program name to prevent this. -If you use rules that need the subsystem as the first parameter, -like the old "udev_run_hotlugd" and "udev_run_devd", add the subsystem -to the key like RUN+="/bin/program $env{SUBSYSTEM}". - -udev 082 -======== -The udev man page has moved to udev(7) as it does not describe a command -anymore. The programs udev, udevstart and udevsend are no longer installed -by default and must be copied manually, if they should be installed or -included in a package. - -Fix a bug where "ignore_device" could run earlier collected RUN keys before -the ignore rule was applied. - -More preparation for future sysfs changes. usb_id and scsi_id no longer -depend on a magic order of devices in the /devices chain. Specific devices -should be requested by their subsytem. - -This will always find the scsi parent device without depending on a specific -path position: - dev = sysfs_device_get(devpath); - dev_usb = sysfs_device_get_parent_with_subsystem(dev, "scsi"); - -The "device" link in the current sysfs layout will be automatically -_resolved_ as a parent and in the new sysfs layout it will just _be_ the -parent in the devpath. If a device is requested by it's symlink, like all -class devices in the new sysfs layout will look like, it gets automatically -resolved and substituted with the real devpath and not the symlink path. - -Note: -A similar logic must be applied to _all_ sysfs users, including -scripts, that search along parent devices in sysfs. The explicit use of -the "device" link must be avoided. With the future sysfs layout all -DEVPATH's will start with /devices/ and have a "subsystem" symlink poiting -back to the "class" or the "bus". The layout of the parent devices in -/devices is not necessarily expected to be stable across kernel releases and -searching for parents by their subsystem should make sysfs users tolerant -for changed parent chains. - -udev 081 -======== -Prepare udev to work with the experimental kernel patch, that moves -/sys/class devices to /sys/devices and /sys/block to /sys/class/block. - -Clarify BUS, ID, $id usage and fix $id behavior. This prepares for -moving the class devices to /sys/devices. - -Thanks again to Marco for help finding a hopefully nice compromise -to make %b simpler and working again. - -udev 080 -======== -Complete removal of libsysfs, replaced by simple helper functions -which are much simpler and a bit faster. The udev daemon operatesentirely -on event parameters and does not use sysfs for simple rules anymore. -Please report any new bugs/problems, that may be caused by this big -change. They will be fixed immediately. - -The enumeration format character '%e' is deprecated and will be -removed sometimes from a future udev version. It never worked correctly -outside of udevstart, so we can't use it with the new parallel -coldplug. A simple enumeration is as useless as the devfs naming -scheme, just get rid of both if you still use it. - -MODALIAS and $modalias is not needed and will be removed from one of -the next udev versions, replace it in all rules with ENV{MODALIAS} or -the sysfs "modalias" value. - -Thanks a lot to Marco for all his help on finding and fixing bugs. - -udev 079 -======== -Let scsi_id request libata drive serial numbers from page 0x80. - -Renamed etc/udev/persistent.rules to persistent-disk.rules and -added /dev/disk/by-name/* for device mapper device names. - -Removed %e from the man page. It never worked reliably outside -of udevstart and udevstart is no longer recommended to use. - -udev 078 -======== -Symlinks are now exported to the event environment. Hopefully it's no -longer needed to run udevinfo from an event process, like it was -mentioned on the hotplug list: - UDEV [1134776873.702967] add@/block/sdb - ... - DEVNAME=/dev/sdb - DEVLINKS=/dev/disk/by-id/usb-IBM_Memory_Key_0218B301030027E8 /dev/disk/by-path/usb-0218B301030027E8:0:0:0 - -udev 077 -======== -Fix a problem if udevsend is used as the hotplug handler and tries to use -syslog, which causes a "vc" event loop. 2.6.15 will make udevsend obsolete -and this kind of problems will hopefully go away soon. - -udev 076 -======== -All built-in logic to work around bad sysfs timing is removed with this -version. The need to wait for sysfs files is almost fixed with a kernel -version that doesn't work with this udev version anyway. Until we fix -the timing of the "bus" link creation, the former integrated logic should -be emulated by a rule placed before all other rules: - ACTION=="add", DEVPATH=="/devices/*", ENV{PHYSDEVBUS}=="?*", WAIT_FOR_SYSFS="bus" - -The option "udev_db" does no longer exist. All udev state will be in -/$udev_root/.udev/ now, there is no longer an option to set this -to anything else. -If the init script or something else used this value, just depend on -this hardcoded path. But remember _all_content_ of this directory is -still private to udev and can change at any time. - -Default location for rule sripts and helper programs is now: /lib/udev/. -Everything that is not useful on the commandline should go into this -directory. Some of the helpers in the extras folder are installed there -now. The rules need to be changed, to find the helpers there. - -Also /lib/udev/devices is recommended as a directory where packages or -the user can place real device nodes, which get copied over to /dev at -every boot. This should replace the various solutions with custom config -files. - -Udevsend does no longer start the udev daemon. This must be done with -the init script that prepares /dev on tmpfs and creates the initial nodes, -before starting the daemon. - -udev 075 -======== -Silent a too verbose error logging for the old hotplug.d/ dev.d/ -emulation. - -The copy of klibc is removed. A systemwide installed version of klibc -should be used to build a klibc udev now. - -udev 074 -======== -NAME="" will not create any nodes, but execute RUN keys. To completely -ignore an event the OPTION "ignore_device" should be used. - -After removal of the reorder queue, events with a TIMEOUT can be executed -without any queuing now. - -udev 073 -======== -Fixed bug in udevd, if inotify is not available. We depend on netlink -uevents now, kernels without that event source will not work with that -version of udev anymore. - -udev 072 -======== -The rule parsing happens now in the daemon once at startup, all udev -event processes inherit the already parsed rules from the daemon. -It is shipped with SUSE10.0 and reduces heavily the system load at -startup. The option to save precompiled rules and let the udev process -pick the them up is removed, as it's no longer needed. - -Kernel 2.6.15 will have symlinks at /class/input pointing to the real -device. Libsysfs is changed to "translate" the requested link into the -real device path, as it would happen with the hotplug event. Otherwise -device removal and the udev database will not work. - -Using 'make STRIPCMD=' will leave the binaries unstripped for debugging -and packaging. - -A few improvements for vol_id, the filesytem probing code. - -udev 071 -======== -Fix a stupid typo in extras/run_directory for "make install". - -scsi_id creates the temporary devnode now in /dev for usage with a -non-writable /tmp directory. - -The uevent kernel socket buffer can carry app. 50.000 events now, -let's see who can break this again. :) - -The upcoming kernel will have a new input driver core integration. -Some class devices are now symlinks to the real device. libsysfs -needs a fix for this to work correctly. Udevstart of older udev -versions will _not_ create these devices! - -udev 070 -======== -Fix a 'install' target in the Makefile, that prevents EXTRAS from -beeing installed. - -udev 069 -======== -A bunch of mostly trivial bugfixes. From now on no node name or -symlink name can contain any character than plain whitelisted ascii -characters or validated utf8 byte-streams. This is needed for the -/dev/disk/by-label/* links, because we import untrusted data and -export it to the filesystem. - -udev 068 -======== -More bugfixes. If udevd was started from the kernel, we don't -have stdin/stdout/stderr, which broke the forked tools in some -situations. - -udev 067 -======== -Bugfix. udevstart event ordering was broken for a long time. -The new run_program() uncovered it, because /dev/null was not -available while we try to run external programs. -Now udevstart should create it before we run anything. - -udev 066 -======== -Minor bugfixes and some distro rules updates. If you don't have the -persistent disk rules in /dev/disk/by-*/* on your distro, just -grab it from here. :) - -udev 065 -======== -We can use socket communication now to pass events from udev to -other programs: - RUN+="socket:/org/freedesktop/hal/udev_event" -will pass the whole udev event to the HAL daemon without the need -for a forked helper. (See ChangeLog for udevmonitor, as an example) - -udev 064 -======== -Mostly bugfixes and see ChangeLog. - -The test for the existence of an environment value should be -switched from: - ENV{KEY}=="*" to ENV{KEY}=="?*" -because "*" will not fail anymore, if the key does not exist or -is empty. - -udev 063 -======== -Bugfixes and a few tweaks described in the ChangeLog. - -udev 062 -======== -Mostly a Bugfix release. - -Added WAIT_FOR_SYSFS="" to be able to fight against the sysfs -timing with custom rules. - -udev 061 -======== -We changed the internal rule storage format. Our large rule files took -2 MB of RAM, with the change we are down to 99kB. - -If the device-node has been created with default name and no symlink or -options are to remenber, it is not longer stored in the udevdb. HAL will -need to be updated to work correctly with that change. - -To overrride optimization flags, OPTFLAGS may be used now. - -udev 060 -======== -Bugfix release. - -udev 059 -======== -Major changes happened with this release. The goal is to take over the -complete kernel-event handling and provide a more efficient way to dispatch -kernel events. Replacing most of the current shell script logic and the -kernel forked helper with a netlink-daemon and a rule-based event handling. - -o udevd listens to netlink events now. The first valid netlink event - will make udevd ignore any message from udevsend that contains a - SEQNUM, to avoid duplicate events. The forked events can be disabled - with: - echo "" > /proc/sys/kernel/hotplug - For full support, the broken input-subsytem needs to be fixed, not to - bypass the driver core. - -o /etc/dev.d/ + /etc/hotplug.d/ directory multiplexing is completely - removed from udev itself and must be emulated by calling small - helper binaries provided in the extras folder: - make EXTRAS=extras/run_directory/ - will build udev_run_devd and udev_run_hotplugd, which can be called - from a rule if needed: - RUN+="/sbin/udev_run_hotplugd" - The recommended way to handle this is to convert all the calls from - the directories to explicit udev rules and get completely rid of the - multiplexing. (To catch a ttyUSB event, you now no longer need to - fork and exit 300 tty script instances you are not interested in, it - is just one rule that matches exactly the device.) - -o udev handles now _all_ events not just events for class and block - devices, this way it is possible to control the complete event - behavior with udev rules. Especially useful for rules like: - ACTION="add", DEVPATH="/devices/*", MODALIAS=="?*", RUN+="/sbin/modprobe $modalias" - -o As used in the modalias rule, udev supports now textual - substitution placeholder along with the usual format chars. This - needs to be documented, for now it's only visible in udev_rules_parse.c. - -o The rule keys support now more operations. This is documented in the - man page. It is possible to add values to list-keys like the SYMLINK - and RUN list with KEY+="value" and to clear the list by assigning KEY="". - Also "final"-assignments are supported by using KEY:="value", which will - prevent changing the key by any later rule. - -o kernel 2.6.12 has the "detached_state" attribute removed from - sysfs, which was used to recognize sysfs population. We switched that - to wait for the "bus" link, which is only available in kernels after 2.6.11. - Running this udev version on older kernels may cause a short delay for - some events. - -o To provide infrastructure for persistent device naming, the id programs: - scsi_id, vol_id (former udev_volume_id), and ata_id (new) are able now - to export the probed data in environment key format: - pim:~ # /sbin/ata_id --export /dev/hda - ID_MODEL=HTS726060M9AT00 - ID_SERIAL=MRH401M4G6UM9B - ID_REVISION=MH4OA6BA - - The following rules: - KERNEL="hd*[!0-9]", IMPORT="/sbin/ata_id --export $tempnode" - KERNEL="hd*[!0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_MODEL}_$env{ID_SERIAL}" - - Will create: - kay@pim:~> tree /dev/disk - /dev/disk - |-- by-id - | |-- HTS726060M9AT00_MRH401M4G6UM9B -> ../../hda - | `-- IBM-Memory_Key -> ../../sda - |-- by-label - | |-- swap -> ../../hda1 - | |-- date -> ../../sda1 - | `-- home -> ../../hda3 - `-- by-uuid - |-- 2E08712B0870F2E7 -> ../../hda3 - |-- 9352cfef-7687-47bc-a2a3-34cf136f72e1 -> ../../hda1 - |-- E845-7A89 -> ../../sda1 - `-- b2a61681-3812-4f13-a4ff-920d70604299 -> ../../hda2 - - The IMPORT= operation will import these keys in the environment and make - it available for later PROGRAM= and RUN= executed programs. The keys are - also stored in the udevdb and can be queried from there with one of the - next udev versions. - -o A few binaries are silently added to the repository, which can be used - to replay kernel events from initramfs instead of using coldplug. udevd - can be instructed now to queue-up events while the stored events from - initramfs are filled into the udevd-queue. This code is still under - development and there is no documentation now besides the code itself. - The additional binaries get compiled, but are not installed by default. - -o There is also a temporary fix for a performance problem where too many - events happen in parallel and every event needs to parse the rules. - udev can now read precompiled rules stored on disk. This is likely to be - replaced by a more elegant solution in a future udev version. - -udev 058 -======== -With kernel version 2.6.12, the sysfs file "detached_state" was removed. -Fix for libsysfs not to expect this file was added. - -udev 057 -======== -All rules are applied now, but only the first matching rule with a NAME-key -will be applied. All later rules with NAME-key are completely ignored. This -way system supplied symlinks or permissions gets applied to user-defined -naming rules. - -Note: -Please check your rules setup, if you may need to add OPTIONS="last_rule" -to some rules, to keep the old behavior. - -The rules are read on "remove"-events too. That makes is possible to match -with keys that are available on remove (KERNEL, SUBSYSTEM, ID, ENV, ...) to -instruct udev to ignore an event (OPTIONS="ignore_device"). -The new ACTION-key may be used to let a rule act only at a "remove"-event. - -The new RUN-key supports rule-based execution of programs after device-node -handling. This is meant as a general replacement for the dev.d/-directories -to give fine grained control over the execution of programs. - -The %s{}-sysfs format char replacement values are searched at any of the -devices in the device chain now, not only at the class-device. - -We support log priority levels now. The value udev_log in udev.conf is used -to determine what is printed to syslog. This makes it possible to -run a version with compiled-in debug messages in a production environment -which is sometimes needed to find a bug. -It is still possible to supress the inclusion of _any_ syslog usage with -USE_LOG=false to create the smallest possible binaries if needed. -The configured udev_log value can be overridden with the environment variable -UDEV_LOG. - -udev 056 -======== -Possible use of a system-wide klibc: - make USE_KLIBC=true KLCC=/usr/bin/klcc all -will link against an external klibc and our own version will be ignored. - -udev 055 -======== -We support an unlimited count of symlinks now. - -If USE_STATIC=true is passed to a glibc build, we link statically and use -a built-in userdb parser to resolve user and group names. - -The PLACE= key is gone. It can be replaced by an ID= for a long time, because -we walk up the chain of physical devices to find a match. - -The KEY="" format supports '=', '==', '!=,' , '+=' now. This makes it -easy to skip certain attribute matches without composing rules with weird -character class negations like: - KERNEL="[!s][!c][!d]*" -this can now be replaced with: - KERNEL!="scd*" -The current simple '=' is still supported, and should work as it does today, -but existing rules should be converted if possible, to be better readable. - -We have new ENV{}== key now, to match against a maximum of 5 environment -variables. - -udevstart is its own binary again, because we don't need co carry this araound -with every forked event. + * New output mode "cat" in the journal to print only text + messages, without any meta data like date or time. + + * Include tiny X server wrapper as a temporary stop-gap to + teach XOrg udev display enumeration. This is used by display + managers such as gdm, and will go away as soon as XOrg + learned native udev hotplugging for display devices. + + * Add new systemd-cat tool for executing arbitrary programs + with STDERR/STDOUT connected to the journal. Can also act as + BSD logger replacement, and does so by default. + + * Optionally store all locally generated coredumps in the + journal along with meta data. + + * systemd-tmpfiles learnt four new commands: n, L, c, b, for + writing short strings to files (for usage for /sys), and for + creating symlinks, character and block device nodes. + + * New unit file option ControlGroupPersistent= to make cgroups + persistent, following the mechanisms outlined in + http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups + + * Support multiple local RTCs in a sane way + + * No longer monopolize IO when replaying readahead data on + rotating disks, since we might starve non-file-system IO to + death, since fanotify() will not see accesses done by blkid, + or fsck. + + * Don't show kernel threads in systemd-cgls anymore, unless + requested with new -k switch. + + Contributions from: Dan Horák, Kay Sievers, Lennart + Poettering, Michal Schmidt + +CHANGES WITH 38: + * This is mostly a test release, but incorporates many + bugfixes. + + * The git repository moved to: + git://anongit.freedesktop.org/systemd/systemd + ssh://git.freedesktop.org/git/systemd/systemd + + * First release with the journal + http://0pointer.de/blog/projects/the-journal.html + + * The journal replaces both systemd-kmsg-syslogd and + systemd-stdout-bridge. + + * New sd_pid_get_unit() API call in libsystemd-logind + + * Many systemadm clean-ups + + * Introduce remote-fs-pre.target which is ordered before all + remote mounts and may be used to start services before all + remote mounts. + + * Added Mageia support + + * Add bash completion for systemd-loginctl + + * Actively monitor PID file creation for daemons which exit in + the parent process before having finished writing the PID + file in the daemon process. Daemons which do this need to be + fixed (i.e. PID file creation must have finished before the + parent exits), but we now react a bit more gracefully to them. + + * Add colourful boot output, mimicking the well-known output + of existing distributions. + + * New option PassCredentials= for socket units, for + compatibility with a recent kernel ABI breakage. + + * /etc/rc.local is now hooked in via a generator binary, and + thus will no longer act as synchronization point during + boot. + + * systemctl list-unit-files now supports --root=. + + * systemd-tmpfiles now understands two new commands: z, Z for + relabelling files according to the SELinux database. This is + useful to apply SELinux labels to specific files in /sys, + among other things. + + * Output of SysV services is now forwarded to both the console + and the journal by default, not only just the console. + + * New man pages for all APIs from libsystemd-login. + + * The build tree got reorganized and a the build system is a + lot more modular allowing embedded setups to specifically + select the components of systemd they are interested in. + + * Support for Linux systems lacking the kernel VT subsystem is + restored. + + * configure's --with-rootdir= got renamed to + --with-rootprefix= to follow the naming used by udev and + kmod + + * Unless specified otherwise we'll now install to /usr instead + of /usr/local by default. + + * Processes with '@' in argv[0][0] are now excluded from the + final shut-down killing spree, following the logic explained + in: + http://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons + + * All processes remaining in a service cgroup when we enter + the START or START_PRE states are now killed with + SIGKILL. That means it is no longer possible to spawn + background processes from ExecStart= lines (which was never + supported anyway, and bad style). + + * New PropagateReloadTo=/PropagateReloadFrom= options to bind + reloading of units together. + + Contributions from: Bill Nottingham, Daniel Walsh, Dave + Reisner, Dexter Morgan, Gregs Gregs, Jonathan Nieder, Kay + Sievers, Lennart Poettering, Michael Biebl, Michal Schmidt, + Michał Górny, Ran Benita, Thomas Jarosch, Tim Waugh, Tollef + Fog Heen, Tom Gundersen, Zbigniew Jędrzejewski-Szmek diff --git a/README b/README index 38459c6b2..6b0eb51ec 100644 --- a/README +++ b/README @@ -1,101 +1,104 @@ -udev - Linux userspace device management - -Integrating udev in the system has complex dependencies and may differ from -distribution to distribution. A system may not be able to boot up or work -reliably without a properly installed udev version. The upstream udev project -does not recommend replacing a distro's udev installation with the upstream -version. - -The upstream udev project's set of default rules may require a most recent -kernel release to work properly. - -Tools and rules shipped by udev are not public API and may change at any time. -Never call any private tool in /usr/lib/udev from any external application; it -might just go away in the next release. Access to udev information is only offered -by udevadm and libudev. Tools and rules in /usr/lib/udev and the entire contents -of the /run/udev directory are private to udev and do change whenever needed. - -Requirements: - - Version 2.6.34 of the Linux kernel with sysfs, procfs, signalfd, inotify, - unix domain sockets, networking and hotplug enabled - - - Some architectures might need a later kernel, that supports accept4(), - or need to backport the accept4() syscall wiring in the kernel. - - - These options are required: - CONFIG_DEVTMPFS=y - CONFIG_HOTPLUG=y - CONFIG_INOTIFY_USER=y - CONFIG_NET=y - CONFIG_PROC_FS=y - CONFIG_SIGNALFD=y - CONFIG_SYSFS=y - CONFIG_SYSFS_DEPRECATED*=n - CONFIG_UEVENT_HELPER_PATH="" - - - These options might be needed: - CONFIG_BLK_DEV_BSG=y (SCSI devices) - CONFIG_TMPFS_POSIX_ACL=y (user ACLs for device nodes) - - - The /dev directory needs the 'devtmpfs' filesystem mounted. - Udev only manages the permissions and ownership of the - kernel-provided device nodes, and possibly creates additional symlinks. - - - Udev requires /run to be writable, which is usually done by mounting a - 'tmpfs' filesystem. - - - This version of udev does not work properly with the CONFIG_SYSFS_DEPRECATED* - option enabled. - - - The deprecated hotplug helper /sbin/hotplug should be disabled in the - kernel configuration, it is not needed today, and may render the system - unusable because the kernel may create too many processes in parallel - so that the system runs out-of-memory. - - - The proc filesystem must be mounted on /proc, and the sysfs filesystem must - be mounted at /sys. No other locations are supported by a standard - udev installation. - - - The default rule sset requires the following group names resolvable at udev startup: - disk, cdrom, floppy, tape, audio, video, lp, tty, dialout, and kmem. - Especially in LDAP setups, it is required that getgrnam() be able to resolve - these group names with only the rootfs mounted and while no network is - available. - - - Some udev extras have external dependencies like: - libglib2, usbutils, pciutils, and gperf. - All these extras can be disabled with configure options. - -Setup: - - The udev daemon should be started to handle device events sent by the kernel. - During bootup, the events for already existing devices can be replayed, so - that they are configured by udev. The systemd service files contain the - needed commands to start the udev daemon and the coldplug sequence. - - - Restarting the daemon never applies any rules to existing devices. - - - New/changed rule files are picked up automatically; there is usually no - daemon restart or signal needed. - -Operation: - - Based on events the kernel sends out on device creation/removal, udev - creates/removes device nodes and symlinks in the /dev directory. - - - All kernel events are matched against a set of specified rules, which - possibly hook into the event processing and load required kernel - modules to set up devices. For all devices, the kernel exports a major/minor - number; if needed, udev creates a device node with the default kernel - device name. If specified, udev applies permissions/ownership to the device - node, creates additional symlinks pointing to the node, and executes - programs to handle the device. - - - The events udev handles, and the information udev merges into its device - database, can be accessed with libudev: - http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/ - http://www.kernel.org/pub/linux/utils/kernel/hotplug/gudev/ - -For more details about udev and udev rules, see the udev man pages: - http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev/ - -Please direct any comment/question to the linux-hotplug mailing list at: - linux-hotplug@vger.kernel.org +systemd System and Service Manager + +DETAILS: + http://0pointer.de/blog/projects/systemd.html + +WEB SITE: + http://www.freedesktop.org/wiki/Software/systemd + +GIT: + git://anongit.freedesktop.org/systemd/systemd + ssh://git.freedesktop.org/git/systemd/systemd + +GITWEB: + http://cgit.freedesktop.org/systemd/systemd + +MAILING LIST: + http://lists.freedesktop.org/mailman/listinfo/systemd-devel + http://lists.freedesktop.org/mailman/listinfo/systemd-commits + +IRC: + #systemd on irc.freenode.org + +BUG REPORTS: + https://bugs.freedesktop.org/enter_bug.cgi?product=systemd + +AUTHOR: + Lennart Poettering with major support from Kay Sievers + +LICENSE: + GPLv2+ for all code, except sd-daemon.[ch] and + sd-readahead.[ch] which are MIT + +REQUIREMENTS: + Linux kernel >= 2.6.39 + with devtmpfs + with cgroups (but it's OK to disable all controllers) + optional but strongly recommended: autofs4, ipv6 + libudev >= 172 + dbus >= 1.4.0 + libcap + PAM >= 1.1.2 (optional) + libcryptsetup (optional) + libaudit (optional) + libselinux (optional) + tcpwrappers (optional) + + When you build from git you need the following additional dependencies: + + docbook-xsl + xsltproc + automake + autoconf + libtool + gperf + make, gcc, and similar tools + + During runtime you need the following dependencies: + + util-linux > v2.18 (requires fsck -l, agetty -s) + sulogin (from sysvinit-tools, optional but recommended) + plymouth (optional) + dracut (optional) + + When systemd-hostnamed is used it is strongly recommended to + install nss-myhostname to ensure that in a world of + dynamically changing hostnames the hostname stays resolveable + under all circumstances. In fact, systemd-hostnamed will warn + if nss-myhostname is not installed. Packagers are encouraged to + add a dependency on nss-myhostname to the package that + includes systemd-hostnamed. + + Note that D-Bus can link against libsystemd-login.so, which + results in a cyclic build dependency. To accomodate for this + please build D-Bus without systemd first, then build systemd, + then rebuild D-Bus with systemd support. + +WARNINGS: + systemd will warn you during boot if /etc/mtab is not a + symlink to /proc/mounts. Please ensure that /etc/mtab is a + proper symlink. + + systemd will warn you during boot if /usr is on a different + file system than /. While in systemd itself very little will + break if /usr is on a separate partition many of its + dependencies very likely will break sooner or later in one + form or another. For example udev rules tend to refer to + binaries in /usr, binaries that link to libraries in /usr or + binaries that refer to data files in /usr. Since these + breakages are not always directly visible systemd will warn + about this, since this kind of file system setup is not really + supported anymore by the basic set of Linux OS components. + + For more information on this issue consult + http://freedesktop.org/wiki/Software/systemd/separate-usr-is-broken + +ENGINEERING AND CONSULTING SERVICES: + ProFUSION offers professional + engineering and consulting services for systemd for embedded + and other use. Please contact Gustavo Barbieri + for more information. + + Disclaimer: This notice is not a recommendation or official + endorsement. However, ProFUSION's upstream work has been very + beneficial for the systemd project. diff --git a/TODO b/TODO index 8b8b9c8f8..4f3b15704 100644 --- a/TODO +++ b/TODO @@ -1,22 +1,344 @@ - - find a way to tell udev to not cancel firmware - requests in initramfs +Bugfixes: - - scsi_id -> sg3_utils? +* swap units that are activated by one name but shown in the kernel under another are semi-broken - - make gtk-doc optional like kmod +* make anaconda write timeout=0 for encrypted devices - - move /usr/lib/udev/devices/ to tmpfiles +* make sure timeouts are applied to Type=oneshot services. - - trigger --subsystem-match=usb/usb_device +* Dangling symlinks of .automount unit files in .wants/ directories, set up + automount points even when the original .automount file did not exist + anymore. Only the .mount unit was still around. - - kill rules_generator +* make polkit checks async - - have a $attrs{} ? +* properly handle .mount unit state tracking when two mount points are stacked one on top of another on the exact same mount point. - - remove RUN+="socket:" +Features: - - libudev.so.1 - - symbol versioning - - return object with *_unref() - - udev_monitor_from_socket() - - udev_queue_get_failed_list_entry() +* allow configuration of console width/height in vconsole.conf + +* PrivateTmp should apply to both /tmp and /var/tmp + +* fstab should take priority over units in /usr + +* cleanup syslog 'priority' vs. 'level' wording + +* journal: if mmap() fails for mapping window try to unmap a a few older maps + +* add flag file for shutdownd so that clients can check whether a shutdown is queued + +* dbus upstream still refers to dbus.target and shouldn't + +* when a service has the same env var set twice we actually store it twice and return that in systemctl show -p... We should only show the last setting + +* add man page documenting all kernel cmdline options, including stuff like fsck.mode= + +* show getty in container mode, not sulogin + +* support container_ttys= + +* journald: make configurable "store-on-var", "store-on-run", "dont-store", "auto" + (store-persistent, store-volatile?) + +* Add ConditionReadWriteFileSystem= so that systemd-sysctl doesn't get executed when /proc/sys is read-only + +* unset container= and container_uuid= for child processes + +* when bind mounting /etc/machine-id, do so from /run/machine-id + +* introduce mix of BindTo and Requisite + +* journalctl: show multiline log messages sanely, expand tabs, and show all valid utf8 messages + +* introduce NeedsMounts= or so to create .mount dependencies automatically for a specific path + +* add DeleteSocketsOnStop=yes|no option to socket units + +* add shutdown inhibit API for usage by libvirt and friends + +* journal: store euid in journal if it differs from uid + +* support chrony in addition to ntpd in timedated + +* document crypttab(5) + +* There's currently no way to cancel fsck (used to be possible via C-c or c on the console) + +* hook up /dev/watchdog with main event loop for embedded, server uses + +* when dumping cgroup contents, include main/control PID of a service, explicitly + +* keep an eye on https://bugzilla.gnome.org/show_bug.cgi?id=670100 + +* D-Bus: always pass cred data along each message + +* journal: allow turning off logging entirely + +* journal: sanely deal with entries which are larger than the individual file size, but where the componets would fit + +* add command to systemctl to plot dependency graph as tree (see rhbz 795365) + +* make logind reserve tty10 or so for text logins, so that gdm never picks it up + +* add option to sockets to avoid activation. Instead just drop packets/connections, see http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/ + +* isolate for getty is still broken, due to logind + +* default unix qlen is too small (10). bump sysctl? add sockopt? + +* support units generated by a generator and placed in /run/systemd/system/; the directory is + currently ignored because it is empty before the generatores are executed + +* Possibly, detect whether SysV init scripts can do reloading by looking for "echo Usage:" lines + +* figure out whether we should leave dbus around during shutdown + +* add interface to allow immediate rotation of the journal, and even flushing. + +* dbus: in fedora, make the machine a symlink to /etc/machine-id + +* journald: reuse XZ context + +* logind: add equivalent to sd_pid_get_owner_uid() to the D-Bus API + +* write RPM spec macros for presets + +* journal: write man pages for API + +* journal: OR matches are borked + +* journal: extend hash tables as we go + +* journal: API for looking for retrieving "all values of this field" + +* journal: deal nicely with byte-by-byte copied files, especially regards header + +* journal: local deserializer of export mode, http server + +* journal: message catalog + +* journal: forward-secure signatures + +* document the exit codes when services fail before they are exec()ed + +* rework namespace support, don't use pivot_root, and mount things after creating the namespace, not before + +* systemctl journal command + +* journalctl: --cursor support, priority filtering + +* systemctl status: show coredumps + +* systemctl status: show whether journal was rotated since service started + +* save coredump in Windows/Mozilla minidump format + +* support crash reporting operation modes (https://live.gnome.org/GnomeOS/Design/Whiteboards/ProblemReporting) + +* journal: allow per-entry control on /var vs. /run (think incognito browser mode) + +* clean up session cgroups that remain after logout (think sshd), but eventually run empty + +* support "systemctl stop foobar@.service" to stop all units matching a certain template + +* move to LGPL2+ + +* logind: allow showing logout dialog from system + +* document that %% can be used to write % in a string that is specifier extended + +* when an instanced service exits, remove its parent cgroup too if possible. + +* Make libselinux, libattr, libcap, libdl dependencies only of the tools which actually need them. + +* as Tom Gundersen pointed out there's a always a dep loop if people use crypto file systems with random keys + +* unset container=, container_uuid= in PID1? + +* automatically escape unit names passed on the service (i.e. think "systemctl start serial-getty.service@serial/by-path/jshdfjsdfhkjh" being automatically escaped as necessary. + +* if we can not get user quota for tmpfs, mount a separate tmpfs instance + for every user in /run/user/$USER with a configured maximum size + +* default to actual 32bit PIDs, via /proc/sys/kernel/pid_max + +* add an option to make mounts private/shareable and so on, enable this for root by default + +* be able to specify a forced restart of service A where service B depends on, in case B + needs to be auto-respawned? + +* Something is wrong with symlink handling of "autovt@.service" in "systemctl list-unit-files" + +* when a bus name of a service disappears from the bus make sure to queue further activation requests + +* something like ConditionExec= or ExecStartPre= without failure state + +* tmpfiles: apply "x" on "D" too (see patch from William Douglas) + +* don't set $HOME in services unless requested + +* hide PAM/TCPWrap options in fragment parser when compile time disabled + +* when we automatically restart a service, ensure we restart its rdeps, too. + +* allow Type=simple with PIDFile= + https://bugzilla.redhat.com/show_bug.cgi?id=723942 + +* move PAM code into its own binary + +* warn if the user stops a service but not its associated socket + +* logind: spawn user@..service on login + +* logind: non-local X11 server handling + +* implement Register= switch in .socket units to enable registration + in Avahi, RPC and other socket registration services. + +* make sure systemd-ask-password-wall does not shutdown systemd-ask-password-console too early + +* readahead: use BTRFS_IOC_DEFRAG_RANGE instead of BTRFS_IOC_DEFRAG ioctl, with START_IO + +* readahead: check whether a btrfs volume includes ssd by checking mount flag "ssd" + +* support sd_notify() style notification when reload begins (RELOADING=1), reload is finished (READY=1), and add ReloadSignal= then to use in combination + +* support sd_notify() style notification when shutting down, to make auto-exit bus services work (STOPPING=1) + +* verify that the AF_UNIX sockets of a service in the fs still exist + when we start a service in order to avoid confusion when a user + assumes starting a service is enough to make it accessible + +* Make it possible to set the keymap independently from the font on + the kernel cmdline. Right now setting one resets also the other. + +* move nss-myhostname into systemd + +* and a dbus call to generate target from current state + +* drop /.readahead on bigger upgrades with yum + +* add inode nr check to readahead to suppress preloading changed files + +* add support for /bin/mount -s + +* GC unreferenced jobs (such as .device jobs) + +* when failing to start a service due to ratelimiting, try again later, if restart=always is set + +* write blog stories about: + - enabling dbus services + - status update + - how to make changes to sysctl and sysfs attributes + - remote access + - how to pass throw-away units to systemd, or dynamically change properties of existing units + - how to integrate cgconfig and suchlike with systemd + - resource control in systemd + +* allow port=0 in .socket units + +* move readahead files into /var, look for them with .path units + +* teach dbus to activate all services it finds in /etc/systemd/services/org-*.service + +* support systemd.mask= on the kernel command line. + +* when key file cannot be found, read it from kbd in cryptsetup + +* reuse mkdtemp namespace dirs in /tmp? + +* recreate systemd's D-Bus private socket file on SIGUSR2 + +* Support --test based on current system state + +* investigate whether the gnome pty helper should be moved into systemd, to provide cgroup support. + +* maybe introduce ExecRestartPre= + +* configurable jitter for timer events + +* timer events with system resume + +* timer events on calendar time + +* dot output for --test showing the 'initial transaction' + +* calendar time support in timer, iCalendar semantics for the timer stuff (RFC2445) + http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=99ee5315dac6211e972fa3f23bcc9a0343ff58c4 + +* implicitly import "defaults" settings file into all types +* exec settings override +* writable cgroups dbus properties for live changes + +* read config fragments for all units from /lib/systemd/system/foobar.service.d/ to override/extend specific settings + +* port over to LISTEN_FDS/LISTEN_PID: + - rpcbind (/var/run/rpcbind.sock!) HAVEPATCH + - cups HAVEPATCH + - postfix, saslauthd + - apache/samba + - libvirtd (/var/run/libvirt/libvirt-sock-ro) + - bluetoothd (/var/run/sdp! @/org/bluez/audio!) + - distccd + +* auditd service files + +* fingerprint.target, wireless.target, gps.target, netdevice.target + +* io priority during initialization + +* systemctl list-jobs - show dependencies + +* add systemctl switch to dump transaction without executing it + +* suspend, resume support? + +* drop cap bounding set in readahead and other services + +External: + +* dbus: + - get process transport into dbus for systemctl -P/-H (PENDING) + - dbus --user + - natively watch for dbus-*.service symlinks (PENDING) + - allow specification of socket mode/umask when allocating DBusServer + - allow disabling of fd passing when connecting a AF_UNIX connection + - allow disabling of UID passing for AUTH EXTERNAL + +* systemd --user + PR_SET_CHILD_REAPER patch: https://lkml.org/lkml/2011/7/28/426 + (patch in linux-next, on the way to the next kernel) + +* fix alsa mixer restore to not print error when no config is stored + +* gnome-shell python script/glxinfo/is-accelerated must die + +* make cryptsetup lower --iter-time + +* patch kernel for xattr support in /dev, /proc/, /sys and /sys/fs/cgroup? + +* NTP: the kernel's 11-minutes-mode syncs the system time to the RTC, but only + in an ~30 minutes window. It does not adjust larger differences. Find a way + to tell the kernel, to always do a full time sync when the RTC is in UTC and + we are in 11-minutes-mode. When we trust the system time to NTP we also want + the RTC to sync up. + +* patch kernel for cpu feature modalias for autoloading aes/kvm/... + (patches in linux-next, on the way to the next kernel) + +* kernel: add device_type = "fb", "fbcon" to class "graphics" + +Regularly: + +* look for close() vs. close_nointr() vs. close_nointr_nofail() + +* check for strerror(r) instead of strerror(-r) + +* Use PR_SET_PROCTITLE_AREA if it becomes available in the kernel + +* %m in printf() instead of strerror(); + +* pahole + +* set_put(), hashmap_put() return values check. i.e. == 0 doesn't free()! diff --git a/autogen.sh b/autogen.sh index 55ee03afd..9ca53772a 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,44 +1,56 @@ -#!/bin/sh -e +#!/bin/bash + +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with systemd; If not, see . if [ -f .git/hooks/pre-commit.sample -a ! -f .git/hooks/pre-commit ] ; then - cp -p .git/hooks/pre-commit.sample .git/hooks/pre-commit && \ - chmod +x .git/hooks/pre-commit && \ - echo "Activated pre-commit hook." + cp -p .git/hooks/pre-commit.sample .git/hooks/pre-commit && \ + chmod +x .git/hooks/pre-commit && \ + echo "Activated pre-commit hook." fi -gtkdocize -autoreconf --install --symlink +intltoolize --force --automake +autoreconf --force --install --symlink libdir() { - echo $(cd $1/$(gcc -print-multi-os-directory); pwd) + echo $(cd $1/$(gcc -print-multi-os-directory); pwd) } -args="$args \ ---prefix=/usr \ +args="\ --sysconfdir=/etc \ +--localstatedir=/var \ --libdir=$(libdir /usr/lib) \ ---with-selinux \ ---enable-gtk-doc" +--libexecdir=/usr/lib" -if [ -L /bin ]; then -args="$args \ ---libexecdir=/usr/lib \ ---with-systemdsystemunitdir=/usr/lib/systemd/system \ -" -else +if [ ! -L /bin ]; then args="$args \ --with-rootprefix= \ ----with-rootlibdir=$(libdir /lib) \ ---bindir=/sbin \ ---libexecdir=/lib \ ---with-systemdsystemunitdir=/lib/systemd/system \ +--with-rootlibdir=$(libdir /lib) \ " fi -echo -echo "----------------------------------------------------------------" -echo "Initialized build system. For a common configuration please run:" -echo "----------------------------------------------------------------" -echo -echo "./configure CFLAGS='-g -O1' $args" -echo +if [ "x$1" != "xc" ]; then + echo + echo "----------------------------------------------------------------" + echo "Initialized build system. For a common configuration please run:" + echo "----------------------------------------------------------------" + echo + echo "./configure CFLAGS='-g -O0' $args" + echo +else + echo ./configure CFLAGS='-g -O0' $args + ./configure CFLAGS='-g -O0' $args + make clean +fi diff --git a/configure.ac b/configure.ac index b31b62f28..9a9a78923 100644 --- a/configure.ac +++ b/configure.ac @@ -1,242 +1,657 @@ -AC_PREREQ(2.60) -AC_INIT([udev], - [182], - [linux-hotplug@vger.kernel.org], - [udev], - [http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html]) -AC_CONFIG_SRCDIR([src/udevd.c]) -AC_CONFIG_AUX_DIR([build-aux]) -AM_INIT_AUTOMAKE([check-news foreign 1.11 -Wall -Wno-portability silent-rules tar-pax no-dist-gzip dist-xz subdir-objects]) +# This file is part of systemd. +# +# Copyright 2010 Lennart Poettering +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with systemd; If not, see . + +AC_PREREQ(2.63) + +AC_INIT([systemd],[44],[systemd-devel@lists.freedesktop.org]) +AC_CONFIG_SRCDIR([src/main.c]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADERS([config.h]) AC_USE_SYSTEM_EXTENSIONS AC_SYS_LARGEFILE -AC_CONFIG_MACRO_DIR([m4]) +AC_PREFIX_DEFAULT([/usr]) +AM_INIT_AUTOMAKE([foreign 1.11 -Wall -Wno-portability silent-rules tar-pax no-dist-gzip dist-xz subdir-objects check-news]) + +AC_SUBST(PACKAGE_URL, [http://www.freedesktop.org/wiki/Software/systemd]) + +AC_CANONICAL_HOST +AC_DEFINE_UNQUOTED([CANONICAL_HOST], "$host", [Canonical host string.]) +AS_IF([test "x$host_cpu" = "xmips" || test "x$host_cpu" = "xmipsel" || + test "x$host_cpu" = "xmips64" || test "x$host_cpu" = "xmips64el"], + [AC_DEFINE(ARCH_MIPS, [], [Whether on mips arch])]) + AM_SILENT_RULES([yes]) -LT_INIT([disable-static]) -AC_PROG_AWK -AC_PROG_SED + +# i18n stuff for the PolicyKit policy files +IT_PROG_INTLTOOL([0.40.0]) + +GETTEXT_PACKAGE=systemd +AC_SUBST(GETTEXT_PACKAGE) + AC_PROG_MKDIR_P -GTK_DOC_CHECK(1.10) -AC_PREFIX_DEFAULT([/usr]) +AC_PROG_LN_S +AC_PROG_SED +AC_PROG_GREP +AC_PROG_AWK + +AC_PROG_CC +AC_PROG_CC_C99 +AM_PROG_CC_C_O +AC_PROG_GCC_TRADITIONAL + +AC_CHECK_TOOL(OBJCOPY, objcopy) +AC_CHECK_TOOL(STRINGS, strings) +AC_CHECK_TOOL(GPERF, gperf) +if test -z "$GPERF" ; then + AC_MSG_ERROR([*** gperf not found]) +fi + +CC_CHECK_FLAGS_APPEND([with_cflags], [CFLAGS], [\ + -pipe \ + -Wall \ + -W \ + -Wextra \ + -Wno-inline \ + -Wvla \ + -Wundef \ + -Wformat=2 \ + -Wlogical-op \ + -Wsign-compare \ + -Wformat-security \ + -Wmissing-include-dirs \ + -Wformat-nonliteral \ + -Wold-style-definition \ + -Wpointer-arith \ + -Winit-self \ + -Wdeclaration-after-statement \ + -Wfloat-equal \ + -Wmissing-prototypes \ + -Wstrict-prototypes \ + -Wredundant-decls \ + -Wmissing-declarations \ + -Wmissing-noreturn \ + -Wshadow \ + -Wendif-labels \ + -Wcast-align \ + -Wstrict-aliasing=2 \ + -Wwrite-strings \ + -Wno-long-long \ + -Wno-overlength-strings \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -Wno-unused-result \ + -Werror=overflow \ + -Wp,-D_FORTIFY_SOURCE=2 \ + -ffast-math \ + -fno-common \ + -fdiagnostics-show-option \ + -fno-strict-aliasing \ + -fvisibility=hidden \ + -ffunction-sections \ + -fdata-sections]) +AC_SUBST([WARNINGFLAGS], $with_cflags) + +CC_CHECK_FLAGS_APPEND([with_ldflags], [LDFLAGS], [\ + -Wl,--as-needed \ + -Wl,--gc-sections]) +AC_SUBST([GCLDFLAGS], $with_ldflags) + +LT_PREREQ(2.2) +LT_INIT + +AC_SEARCH_LIBS([clock_gettime], [rt], [], [AC_MSG_ERROR([*** POSIX RT library not found])]) +AC_SEARCH_LIBS([dlsym], [dl], [], [AC_MSG_ERROR([*** Dynamic linking loader library not found])]) + +save_LIBS="$LIBS" +LIBS= +AC_SEARCH_LIBS([cap_init], [cap], [], [AC_MSG_ERROR([*** POSIX caps library not found])]) +AC_CHECK_HEADERS([sys/capability.h], [], [AC_MSG_ERROR([*** POSIX caps headers not found])]) +CAP_LIBS="$LIBS" +LIBS="$save_LIBS" +AC_SUBST(CAP_LIBS) + +# This makes sure pkg.m4 is available. +m4_pattern_forbid([^_?PKG_[A-Z_]+$],[*** pkg.m4 missing, please install pkg-config]) + +PKG_CHECK_MODULES(UDEV, [ libudev >= 172 ]) +PKG_CHECK_MODULES(DBUS, [ dbus-1 >= 1.3.2 ]) +PKG_CHECK_MODULES(KMOD, [ libkmod >= 5 ]) + +have_ima=yes +AC_ARG_ENABLE([ima], AS_HELP_STRING([--disable-ima],[Disable optional IMA support]), + [case "${enableval}" in + yes) have_ima=yes ;; + no) have_ima=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-ima) ;; + esac], + [have_ima=yes]) + +if test "x${have_ima}" != xno ; then + AC_DEFINE(HAVE_IMA, 1, [Define if IMA is available]) +fi + +have_selinux=no +AC_ARG_ENABLE(selinux, AS_HELP_STRING([--disable-selinux], [Disable optional SELINUX support])) +if test "x$enable_selinux" != "xno"; then + PKG_CHECK_MODULES(SELINUX, [ libselinux ], + [AC_DEFINE(HAVE_SELINUX, 1, [Define if SELinux is available]) have_selinux=yes], have_selinux=no) + if test "x$have_selinux" = xno -a "x$enable_selinux" = xyes; then + AC_MSG_ERROR([*** SELinux support requested but libraries not found]) + fi +fi +AM_CONDITIONAL(HAVE_SELINUX, [test "$have_selinux" = "yes"]) + +have_xz=no +AC_ARG_ENABLE(xz, AS_HELP_STRING([--disable-xz], [Disable optional XZ support])) +if test "x$enable_xz" != "xno"; then + PKG_CHECK_MODULES(XZ, [ liblzma ], + [AC_DEFINE(HAVE_XZ, 1, [Define if XZ is available]) have_xz=yes], have_xz=no) + if test "x$have_xz" = xno -a "x$enable_xz" = xyes; then + AC_MSG_ERROR([*** Xz support requested but libraries not found]) + fi +fi +AM_CONDITIONAL(HAVE_XZ, [test "$have_xz" = "yes"]) + +AC_ARG_ENABLE([tcpwrap], + AS_HELP_STRING([--disable-tcpwrap],[Disable optional TCP wrappers support]), + [case "${enableval}" in + yes) have_tcpwrap=yes ;; + no) have_tcpwrap=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-tcpwrap) ;; + esac], + [have_tcpwrap=auto]) + +if test "x${have_tcpwrap}" != xno ; then + ACX_LIBWRAP + if test "x${LIBWRAP_LIBS}" = x ; then + if test "x$have_tcpwrap" = xyes ; then + AC_MSG_ERROR([*** TCP wrappers support not found.]) + fi + have_tcpwrap=no + else + have_tcpwrap=yes + fi +else + LIBWRAP_LIBS= +fi +AC_SUBST(LIBWRAP_LIBS) + +AC_ARG_ENABLE([pam], + AS_HELP_STRING([--disable-pam],[Disable optional PAM support]), + [case "${enableval}" in + yes) have_pam=yes ;; + no) have_pam=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-pam) ;; + esac], + [have_pam=auto]) + +if test "x${have_pam}" != xno ; then + AC_CHECK_HEADERS( + [security/pam_modules.h security/pam_modutil.h security/pam_ext.h], + [have_pam=yes], + [if test "x$have_pam" = xyes ; then + AC_MSG_ERROR([*** PAM headers not found.]) + fi]) + + AC_CHECK_LIB( + [pam], + [pam_syslog], + [have_pam=yes], + [if test "x$have_pam" = xyes ; then + AC_MSG_ERROR([*** libpam not found.]) + fi]) + + if test "x$have_pam" = xyes ; then + PAM_LIBS="-lpam -lpam_misc" + AC_DEFINE(HAVE_PAM, 1, [PAM available]) + else + have_pam=no + fi +else + PAM_LIBS= +fi +AC_SUBST(PAM_LIBS) +AM_CONDITIONAL([HAVE_PAM], [test "x$have_pam" != xno]) + +AC_ARG_ENABLE([acl], + AS_HELP_STRING([--disable-acl],[Disable optional ACL support]), + [case "${enableval}" in + yes) have_acl=yes ;; + no) have_acl=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-acl) ;; + esac], + [have_acl=auto]) + +if test "x${have_acl}" != xno ; then + AC_CHECK_HEADERS( + [sys/acl.h acl/libacl.h], + [have_acl=yes], + [if test "x$have_acl" = xyes ; then + AC_MSG_ERROR([*** ACL headers not found.]) + fi]) + + AC_CHECK_LIB( + [acl], + [acl_get_file], + [have_acl=yes], + [if test "x$have_acl" = xyes ; then + AC_MSG_ERROR([*** libacl not found.]) + fi]) + + if test "x$have_acl" = xyes ; then + ACL_LIBS="-lacl" + AC_DEFINE(HAVE_ACL, 1, [ACL available]) + else + have_acl=no + fi +else + ACL_LIBS= +fi +AC_SUBST(ACL_LIBS) +AM_CONDITIONAL([HAVE_ACL], [test "x$have_acl" != xno]) + +AC_ARG_ENABLE([audit], + AS_HELP_STRING([--disable-audit],[Disable optional AUDIT support]), + [case "${enableval}" in + yes) have_audit=yes ;; + no) have_audit=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-audit) ;; + esac], + [have_audit=auto]) + +if test "x${have_audit}" != xno ; then + AC_CHECK_HEADERS( + [libaudit.h], + [have_audit=yes], + [if test "x$have_audit" = xyes ; then + AC_MSG_ERROR([*** AUDIT headers not found.]) + fi]) + + AC_CHECK_LIB( + [audit], + [audit_open], + [have_audit=yes], + [if test "x$have_audit" = xyes ; then + AC_MSG_ERROR([*** libaudit not found.]) + fi]) + + if test "x$have_audit" = xyes ; then + AUDIT_LIBS="-laudit" + AC_DEFINE(HAVE_AUDIT, 1, [AUDIT available]) + else + have_audit=no + fi +else + AUDIT_LIBS= +fi +AC_SUBST(AUDIT_LIBS) + +have_libcryptsetup=no +AC_ARG_ENABLE(libcryptsetup, AS_HELP_STRING([--disable-libcryptsetup], [disable libcryptsetup tools])) +if test "x$enable_libcryptsetup" != "xno"; then + PKG_CHECK_MODULES(LIBCRYPTSETUP, [ libcryptsetup ], + [AC_DEFINE(HAVE_LIBCRYPTSETUP, 1, [Define if libcryptsetup is available]) have_libcryptsetup=yes], have_libcryptsetup=no) + if test "x$have_libcryptsetup" = xno -a "x$enable_libcryptsetup" = xyes; then + AC_MSG_ERROR([*** libcryptsetup support requested but libraries not found]) + fi +fi +AM_CONDITIONAL(HAVE_LIBCRYPTSETUP, [test "$have_libcryptsetup" = "yes"]) + +have_binfmt=no +AC_ARG_ENABLE(binfmt, AS_HELP_STRING([--disable-binfmt], [disable binfmt tool])) +if test "x$enable_binfmt" != "xno"; then + have_binfmt=yes +fi +AM_CONDITIONAL(ENABLE_BINFMT, [test "$have_binfmt" = "yes"]) + +have_vconsole=no +AC_ARG_ENABLE(vconsole, AS_HELP_STRING([--disable-vconsole], [disable vconsole tool])) +if test "x$enable_vconsole" != "xno"; then + have_vconsole=yes +fi +AM_CONDITIONAL(ENABLE_VCONSOLE, [test "$have_vconsole" = "yes"]) + +have_readahead=no +AC_ARG_ENABLE(readahead, AS_HELP_STRING([--disable-readahead], [disable readahead tools])) +if test "x$enable_readahead" != "xno"; then + have_readahead=yes +fi +AM_CONDITIONAL(ENABLE_READAHEAD, [test "$have_readahead" = "yes"]) + +have_quotacheck=no +AC_ARG_ENABLE(quotacheck, AS_HELP_STRING([--disable-quotacheck], [disable quotacheck tools])) +if test "x$enable_quotacheck" != "xno"; then + have_quotacheck=yes +fi +AM_CONDITIONAL(ENABLE_QUOTACHECK, [test "$have_quotacheck" = "yes"]) + +have_randomseed=no +AC_ARG_ENABLE(randomseed, AS_HELP_STRING([--disable-randomseed], [disable randomseed tools])) +if test "x$enable_randomseed" != "xno"; then + have_randomseed=yes +fi +AM_CONDITIONAL(ENABLE_RANDOMSEED, [test "$have_randomseed" = "yes"]) + +have_logind=no +AC_ARG_ENABLE(logind, AS_HELP_STRING([--disable-logind], [disable login daemon])) +if test "x$enable_logind" != "xno"; then + have_logind=yes +fi +AM_CONDITIONAL(ENABLE_LOGIND, [test "$have_logind" = "yes"]) +AS_IF([test "$have_logind" = "yes"], [ AC_DEFINE(HAVE_LOGIND, [1], [Logind support available]) ]) + +have_hostnamed=no +AC_ARG_ENABLE(hostnamed, AS_HELP_STRING([--disable-hostnamed], [disable hostname daemon])) +if test "x$enable_hostnamed" != "xno"; then + have_hostnamed=yes +fi +AM_CONDITIONAL(ENABLE_HOSTNAMED, [test "$have_hostnamed" = "yes"]) + +have_timedated=no +AC_ARG_ENABLE(timedated, AS_HELP_STRING([--disable-timedated], [disable timedate daemon])) +if test "x$enable_timedated" != "xno"; then + have_timedated=yes +fi +AM_CONDITIONAL(ENABLE_TIMEDATED, [test "$have_timedated" = "yes"]) + +have_localed=no +AC_ARG_ENABLE(localed, AS_HELP_STRING([--disable-localed], [disable locale daemon])) +if test "x$enable_localed" != "xno"; then + have_localed=yes +fi +AM_CONDITIONAL(ENABLE_LOCALED, [test "$have_localed" = "yes"]) + +have_coredump=no +AC_ARG_ENABLE(coredump, AS_HELP_STRING([--disable-coredump], [disable coredump hook])) +if test "x$enable_coredump" != "xno"; then + have_coredump=yes +fi +AM_CONDITIONAL(ENABLE_COREDUMP, [test "$have_coredump" = "yes"]) + +have_manpages=no +AC_ARG_ENABLE(manpages, AS_HELP_STRING([--disable-manpages], [disable manpages])) +if test "x$enable_manpages" != "xno"; then + have_manpages=yes +fi +AM_CONDITIONAL(ENABLE_MANPAGES, [test "$have_manpages" = "yes"]) AC_PATH_PROG([XSLTPROC], [xsltproc]) AM_CONDITIONAL(HAVE_XSLTPROC, test x"$XSLTPROC" != x) -AC_SEARCH_LIBS([clock_gettime], [rt], [], [AC_MSG_ERROR([POSIX RT library not found])]) +AC_PATH_PROG([M4], [m4]) + +AC_ARG_WITH(distro, AS_HELP_STRING([--with-distro=DISTRO],[Specify the distribution to target: One of fedora, suse, debian, ubuntu, arch, gentoo, slackware, altlinux, mandriva, meego, mageia, angstrom or other])) +if test "z$with_distro" = "z"; then + if test "$cross_compiling" = yes; then + AC_MSG_WARN([Target distribution cannot be reliably detected when cross-compiling. You should specify it with --with-distro (see $0 --help for recognized distros)]) + else + with_distro=$($GREP '^ID=' /etc/os-release | $SED 's/ID=//'); + fi + if test "z$with_distro" = "z"; then + with_distro=other + fi +fi +with_distro=`echo ${with_distro} | tr '[[:upper:]]' '[[:lower:]]' ` +AC_DEFINE_UNQUOTED(DISTRIBUTION, ["${with_distro}"], [Target Distribution]) -PKG_CHECK_MODULES(BLKID, blkid >= 2.20) +# Location of the init scripts as mandated by LSB +SYSTEM_SYSVINIT_PATH=/etc/init.d +SYSTEM_SYSVRCND_PATH=/etc/rc.d -PKG_CHECK_MODULES(KMOD, libkmod >= 5) +M4_DEFINES= +have_plymouth=no + +case $with_distro in + fedora) + SYSTEM_SYSVINIT_PATH=/etc/rc.d/init.d + AC_DEFINE(TARGET_FEDORA, [], [Target is Fedora/RHEL]) + M4_DEFINES=-DTARGET_FEDORA=1 + have_plymouth=yes + ;; + opensuse|suse) + SYSTEM_SYSVRCND_PATH=/etc/init.d + AC_DEFINE(TARGET_SUSE, [], [Target is openSUSE/SLE]) + M4_DEFINES=-DTARGET_SUSE=1 + have_plymouth=yes + ;; + debian) + SYSTEM_SYSVRCND_PATH=/etc + AC_DEFINE(TARGET_DEBIAN, [], [Target is Debian]) + M4_DEFINES=-DTARGET_DEBIAN=1 + ;; + ubuntu) + SYSTEM_SYSVRCND_PATH=/etc + AC_DEFINE(TARGET_UBUNTU, [], [Target is Ubuntu]) + M4_DEFINES=-DTARGET_UBUNTU=1 + ;; + arch) + SYSTEM_SYSVINIT_PATH=/etc/rc.d + SYSTEM_SYSVRCND_PATH=/etc + AC_DEFINE(TARGET_ARCH, [], [Target is ArchLinux]) + M4_DEFINES=-DTARGET_ARCH=1 + ;; + gentoo) + SYSTEM_SYSVINIT_PATH= + SYSTEM_SYSVRCND_PATH= + AC_DEFINE(TARGET_GENTOO, [], [Target is Gentoo]) + M4_DEFINES=-DTARGET_GENTOO=1 + ;; + slackware) + SYSTEM_SYSVINIT_PATH=/etc/rc.d/init.d + AC_DEFINE(TARGET_SLACKWARE, [], [Target is Slackware]) + M4_DEFINES=-DTARGET_SLACKWARE=1 + ;; + frugalware) + SYSTEM_SYSVINIT_PATH=/etc/rc.d + AC_DEFINE(TARGET_FRUGALWARE, [], [Target is Frugalware]) + M4_DEFINES=-DTARGET_FRUGALWARE=1 + have_plymouth=yes + ;; + altlinux) + SYSTEM_SYSVINIT_PATH=/etc/rc.d/init.d + AC_DEFINE(TARGET_ALTLINUX, [], [Target is ALTLinux]) + M4_DEFINES=-DTARGET_ALTLINUX=1 + have_plymouth=yes + ;; + mandriva) + SYSTEM_SYSVINIT_PATH=/etc/rc.d/init.d + AC_DEFINE(TARGET_MANDRIVA, [], [Target is Mandriva]) + M4_DEFINES=-DTARGET_MANDRIVA=1 + have_plymouth=yes + ;; + meego) + SYSTEM_SYSVINIT_PATH= + SYSTEM_SYSVRCND_PATH= + AC_DEFINE(TARGET_MEEGO, [], [Target is MeeGo]) + M4_DEFINES=-DTARGET_MEEGO=1 + ;; + angstrom) + SYSTEM_SYSVRCND_PATH=/etc + AC_DEFINE(TARGET_ANGSTROM, [], [Target is Ångström]) + M4_DEFINES=-DTARGET_ANGSTROM=1 + ;; + mageia) + SYSTEM_SYSVINIT_PATH=/etc/rc.d/init.d + AC_DEFINE(TARGET_MAGEIA, [], [Target is Mageia]) + M4_DISTRO_FLAG=-DTARGET_MAGEIA=1 + have_plymouth=yes + ;; + other) + ;; + *) + AC_MSG_ERROR([Your distribution (${with_distro}) is not yet supported, SysV init scripts could not be found! (patches welcome); you can specify --with-distro=other to skip this check]) + ;; +esac + +AC_ARG_WITH([sysvinit-path], + [AS_HELP_STRING([--with-sysvinit-path=PATH], + [Specify the path to where the SysV init scripts are located @<:@default=based on distro@:>@])], + [SYSTEM_SYSVINIT_PATH="$withval"], + []) + +AC_ARG_WITH([sysvrcd-path], + [AS_HELP_STRING([--with-sysvrcd-path=PATH], + [Specify the path to the base directory for the SysV rcN.d directories @<:@default=based on distro@:>@])], + [SYSTEM_SYSVRCND_PATH="$withval"], + []) + +AC_SUBST(SYSTEM_SYSVINIT_PATH) +AC_SUBST(SYSTEM_SYSVRCND_PATH) +AC_SUBST(M4_DEFINES) + +if test "x${SYSTEM_SYSVINIT_PATH}" != "x" -a "x${SYSTEM_SYSVRCND_PATH}" != "x"; then + AC_DEFINE(HAVE_SYSV_COMPAT, [], [SysV init scripts and rcN.d links are supported.]) + SYSTEM_SYSV_COMPAT="yes" + M4_DEFINES="$M4_DEFINES -DHAVE_SYSV_COMPAT" +elif test "x${SYSTEM_SYSVINIT_PATH}" != "x" -o "x${SYSTEM_SYSVRCND_PATH}" != "x"; then + AC_MSG_ERROR([*** You need both --with-sysvinit-path=PATH and --with-sysvrcd-path=PATH to enable SysV compatibility support, or both empty to disable it.]) +else + SYSTEM_SYSV_COMPAT="no" +fi + +AC_ARG_WITH([tty-gid], + [AS_HELP_STRING([--with-tty-gid=GID], + [Specify the numeric GID of the 'tty' group])], + [AC_DEFINE_UNQUOTED(TTY_GID, [$withval], [GID of the 'tty' group])], + []) + +AC_ARG_ENABLE(plymouth, AS_HELP_STRING([--enable-plymouth], [enable plymouth support])) +if test -n "$enable_plymouth"; then + have_plymouth="$enable_plymouth" +fi + +AM_CONDITIONAL(TARGET_FEDORA, test x"$with_distro" = xfedora) +AM_CONDITIONAL(TARGET_SUSE, test x"$with_distro" = xsuse) +AM_CONDITIONAL(TARGET_DEBIAN, test x"$with_distro" = xdebian) +AM_CONDITIONAL(TARGET_UBUNTU, test x"$with_distro" = xubuntu) +AM_CONDITIONAL(TARGET_DEBIAN_OR_UBUNTU, test x"$with_distro" = xdebian -o x"$with_distro" = xubuntu) +AM_CONDITIONAL(TARGET_ARCH, test x"$with_distro" = xarch) +AM_CONDITIONAL(TARGET_GENTOO, test x"$with_distro" = xgentoo) +AM_CONDITIONAL(TARGET_SLACKWARE, test x"$with_distro" = xslackware) +AM_CONDITIONAL(TARGET_FRUGALWARE, test x"$with_distro" = xfrugalware) +AM_CONDITIONAL(TARGET_ALTLINUX, test x"$with_distro" = xaltlinux) +AM_CONDITIONAL(TARGET_MANDRIVA, test x"$with_distro" = xmandriva) +AM_CONDITIONAL(TARGET_MEEGO, test x"$with_distro" = xmeego) +AM_CONDITIONAL(TARGET_ANGSTROM, test x"$with_distro" = xangstrom) +AM_CONDITIONAL(TARGET_MAGEIA, test x"$with_distro" = xmageia) + +AM_CONDITIONAL(HAVE_PLYMOUTH, test "$have_plymouth" = "yes") +AM_CONDITIONAL(HAVE_SYSV_COMPAT, test "$SYSTEM_SYSV_COMPAT" = "yes") + +AC_ARG_WITH([dbuspolicydir], + AS_HELP_STRING([--with-dbuspolicydir=DIR], [D-Bus policy directory]), + [], + [with_dbuspolicydir=`pkg-config --variable=sysconfdir dbus-1`/dbus-1/system.d]) + +AC_ARG_WITH([dbussessionservicedir], + AS_HELP_STRING([--with-dbussessionservicedir=DIR], [D-Bus session service directory]), + [], + [with_dbussessionservicedir=`pkg-config --variable=session_bus_services_dir dbus-1`]) + +AC_ARG_WITH([dbussystemservicedir], + AS_HELP_STRING([--with-dbussystemservicedir=DIR], [D-Bus system service directory]), + [], + [with_dbussystemservicedir=`pkg-config --variable=session_bus_services_dir dbus-1`/../system-services]) + +AC_ARG_WITH([dbusinterfacedir], + AS_HELP_STRING([--with-dbusinterfacedir=DIR], [D-Bus interface directory]), + [], + [with_dbusinterfacedir=`pkg-config --variable=session_bus_services_dir dbus-1`/../interfaces]) + +AC_ARG_WITH([udevrulesdir], + AS_HELP_STRING([--with-udevrulesdir=DIR], [Directory for udev rules]), + [], + [with_udevrulesdir=`pkg-config --variable=udevdir udev`/rules.d]) AC_ARG_WITH([rootprefix], - AS_HELP_STRING([--with-rootprefix=DIR], [rootfs directory prefix for config files and kernel modules]), - [], [with_rootprefix=${ac_default_prefix}]) -AC_SUBST([rootprefix], [$with_rootprefix]) + AS_HELP_STRING([--with-rootprefix=DIR], [rootfs directory prefix for config files and kernel modules]), + [], [with_rootprefix=${ac_default_prefix}]) AC_ARG_WITH([rootlibdir], - AS_HELP_STRING([--with-rootlibdir=DIR], [rootfs directory to install shared libraries]), - [], [with_rootlibdir=$libdir]) -AC_SUBST([rootlib_execdir], [$with_rootlibdir]) - -AC_ARG_WITH([selinux], - AS_HELP_STRING([--with-selinux], [enable SELinux support]), - [], [with_selinux=no]) -AS_IF([test "x$with_selinux" = "xyes"], [ - LIBS_save=$LIBS - AC_CHECK_LIB(selinux, getprevcon, - [], - AC_MSG_ERROR([SELinux selected but libselinux not found])) - LIBS=$LIBS_save - SELINUX_LIBS="-lselinux -lsepol" - AC_DEFINE(WITH_SELINUX, [1] ,[SELinux support.]) -]) -AC_SUBST([SELINUX_LIBS]) -AM_CONDITIONAL(WITH_SELINUX, [test "x$with_selinux" = "xyes"]) - -AC_ARG_ENABLE([debug], - AS_HELP_STRING([--enable-debug], [enable debug messages @<:@default=disabled@:>@]), - [], [enable_debug=no]) -AS_IF([test "x$enable_debug" = "xyes"], [ AC_DEFINE(ENABLE_DEBUG, [1], [Debug messages.]) ]) - -AC_ARG_ENABLE([logging], - AS_HELP_STRING([--disable-logging], [disable system logging @<:@default=enabled@:>@]), - [], enable_logging=yes) -AS_IF([test "x$enable_logging" = "xyes"], [ AC_DEFINE(ENABLE_LOGGING, [1], [System logging.]) ]) - -AC_ARG_ENABLE([manpages], - AS_HELP_STRING([--disable-manpages], [disable man pages @<:@default=enabled@:>@]), - [], enable_manpages=yes) -AM_CONDITIONAL([ENABLE_MANPAGES], [test "x$enable_manpages" = "xyes"]) - -if test "x$cross_compiling" = "xno" ; then - AC_CHECK_FILES([/usr/share/pci.ids], [pciids=/usr/share/pci.ids]) - AC_CHECK_FILES([/usr/share/hwdata/pci.ids], [pciids=/usr/share/hwdata/pci.ids]) - AC_CHECK_FILES([/usr/share/misc/pci.ids], [pciids=/usr/share/misc/pci.ids]) -fi - -AC_ARG_WITH(usb-ids-path, - [AS_HELP_STRING([--with-usb-ids-path=DIR], [Path to usb.ids file])], - [USB_DATABASE=${withval}], - [if test -n "$usbids" ; then - USB_DATABASE="$usbids" - else - PKG_CHECK_MODULES(USBUTILS, usbutils >= 0.82) - AC_SUBST([USB_DATABASE], [$($PKG_CONFIG --variable=usbids usbutils)]) - fi]) -AC_MSG_CHECKING([for USB database location]) -AC_MSG_RESULT([$USB_DATABASE]) -AC_SUBST(USB_DATABASE) - -AC_ARG_WITH(pci-ids-path, - [AS_HELP_STRING([--with-pci-ids-path=DIR], [Path to pci.ids file])], - [PCI_DATABASE=${withval}], - [if test -n "$pciids" ; then - PCI_DATABASE="$pciids" - else - AC_MSG_ERROR([pci.ids not found, try --with-pci-ids-path=]) - fi]) -AC_MSG_CHECKING([for PCI database location]) -AC_MSG_RESULT([$PCI_DATABASE]) -AC_SUBST(PCI_DATABASE) - -AC_ARG_WITH(firmware-path, - AS_HELP_STRING([--with-firmware-path=DIR[[[:DIR[...]]]]], - [Firmware search path (default=ROOTPREFIX/lib/firmware/updates:ROOTPREFIX/lib/firmware)]), - [], [with_firmware_path="$rootprefix/lib/firmware/updates:$rootprefix/lib/firmware"]) -OLD_IFS=$IFS -IFS=: -for i in $with_firmware_path; do - if test "x${FIRMWARE_PATH}" = "x"; then - FIRMWARE_PATH="\\\"${i}/\\\"" - else - FIRMWARE_PATH="${FIRMWARE_PATH}, \\\"${i}/\\\"" - fi -done -IFS=$OLD_IFS -AC_SUBST([FIRMWARE_PATH], [$FIRMWARE_PATH]) - -AC_ARG_WITH([systemdsystemunitdir], - AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]), - [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)]) -AS_IF([test "x$with_systemdsystemunitdir" != "xno"], [ AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) ]) -AM_CONDITIONAL(WITH_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != "xno" ]) - -# ------------------------------------------------------------------------------ -# GUdev - libudev gobject interface -# ------------------------------------------------------------------------------ -AC_ARG_ENABLE([gudev], - AS_HELP_STRING([--disable-gudev], [disable Gobject libudev support @<:@default=enabled@:>@]), - [], [enable_gudev=yes]) -AS_IF([test "x$enable_gudev" = "xyes"], [ PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.22.0 gobject-2.0 >= 2.22.0]) ]) - -AC_ARG_ENABLE([introspection], - AS_HELP_STRING([--disable-introspection], [disable GObject introspection @<:@default=enabled@:>@]), - [], [enable_introspection=yes]) -AS_IF([test "x$enable_introspection" = "xyes"], [ - PKG_CHECK_MODULES([INTROSPECTION], [gobject-introspection-1.0 >= 0.6.2]) - AC_DEFINE([ENABLE_INTROSPECTION], [1], [enable GObject introspection support]) - AC_SUBST([G_IR_SCANNER], [$($PKG_CONFIG --variable=g_ir_scanner gobject-introspection-1.0)]) - AC_SUBST([G_IR_COMPILER], [$($PKG_CONFIG --variable=g_ir_compiler gobject-introspection-1.0)]) - AC_SUBST([G_IR_GENERATE], [$($PKG_CONFIG --variable=g_ir_generate gobject-introspection-1.0)]) - AC_SUBST([GIRDIR], [$($PKG_CONFIG --define-variable=datadir=${datadir} --variable=girdir gobject-introspection-1.0)]) - AC_SUBST([GIRTYPELIBDIR], [$($PKG_CONFIG --define-variable=libdir=${libdir} --variable=typelibdir gobject-introspection-1.0)]) -]) -AM_CONDITIONAL([ENABLE_INTROSPECTION], [test "x$enable_introspection" = "xyes"]) -AM_CONDITIONAL([ENABLE_GUDEV], [test "x$enable_gudev" = "xyes"]) - -# ------------------------------------------------------------------------------ -# keymap - map custom hardware's multimedia keys -# ------------------------------------------------------------------------------ -AC_ARG_ENABLE([keymap], - AS_HELP_STRING([--disable-keymap], [disable keymap fixup support @<:@default=enabled@:>@]), - [], [enable_keymap=yes]) -AS_IF([test "x$enable_keymap" = "xyes"], [ - AC_PATH_PROG([GPERF], [gperf]) - if test -z "$GPERF"; then - AC_MSG_ERROR([gperf is needed]) - fi - - AC_CHECK_HEADER([linux/input.h], [:], AC_MSG_ERROR([kernel headers not found])) - AC_SUBST([INCLUDE_PREFIX], [$(echo '#include ' | eval $ac_cpp -E - | sed -n '/linux\/input.h/ {s:.*"\(.*\)/linux/input.h".*:\1:; p; q}')]) -]) -AM_CONDITIONAL([ENABLE_KEYMAP], [test "x$enable_keymap" = "xyes"]) - -# ------------------------------------------------------------------------------ -# mtd_probe - autoloads FTL module for mtd devices -# ------------------------------------------------------------------------------ -AC_ARG_ENABLE([mtd_probe], - AS_HELP_STRING([--disable-mtd_probe], [disable MTD support @<:@default=enabled@:>@]), - [], [enable_mtd_probe=yes]) -AM_CONDITIONAL([ENABLE_MTD_PROBE], [test "x$enable_mtd_probe" = "xyes"]) - -# ------------------------------------------------------------------------------ -# rule_generator - persistent network and optical device rule generator -# ------------------------------------------------------------------------------ -AC_ARG_ENABLE([rule_generator], - AS_HELP_STRING([--enable-rule_generator], [enable persistent network + cdrom links support @<:@default=disabled@:>@]), - [], [enable_rule_generator=no]) -AM_CONDITIONAL([ENABLE_RULE_GENERATOR], [test "x$enable_rule_generator" = "xyes"]) - -# ------------------------------------------------------------------------------ -# create_floppy_devices - historical floppy kernel device nodes (/dev/fd0h1440, ...) -# ------------------------------------------------------------------------------ -AC_ARG_ENABLE([floppy], - AS_HELP_STRING([--enable-floppy], [enable legacy floppy support @<:@default=disabled@:>@]), - [], [enable_floppy=no]) -AM_CONDITIONAL([ENABLE_FLOPPY], [test "x$enable_floppy" = "xyes"]) - -my_CFLAGS="-Wall \ --Wmissing-declarations -Wmissing-prototypes \ --Wnested-externs -Wpointer-arith \ --Wpointer-arith -Wsign-compare -Wchar-subscripts \ --Wstrict-prototypes -Wshadow \ --Wformat-security -Wtype-limits" -AC_SUBST([my_CFLAGS]) - -AC_CONFIG_HEADERS(config.h) -AC_CONFIG_FILES([ - Makefile - src/docs/Makefile - src/docs/version.xml - src/gudev/docs/Makefile - src/gudev/docs/version.xml + AS_HELP_STRING([--with-rootlibdir=DIR], [Root directory for libraries necessary for boot]), + [], + [with_rootlibdir=${libdir}]) + +AC_ARG_WITH([pamlibdir], + AS_HELP_STRING([--with-pamlibdir=DIR], [Directory for PAM modules]), + [], + [with_pamlibdir=${with_rootlibdir}/security]) + +AC_ARG_ENABLE([split-usr], + AS_HELP_STRING([--enable-split-usr], [Assume that /bin, /sbin aren\'t symlinks into /usr]), + [], + [AS_IF([test "x${ac_default_prefix}" != "x${with_rootprefix}"], [ + enable_split_usr=yes + ], [ + enable_split_usr=no + ])]) + +AS_IF([test "x${enable_split_usr}" = "xyes"], [ + AC_DEFINE(HAVE_SPLIT_USR, 1, [Define if /bin, /sbin aren't symlinks into /usr]) ]) +AC_SUBST([dbuspolicydir], [$with_dbuspolicydir]) +AC_SUBST([dbussessionservicedir], [$with_dbussessionservicedir]) +AC_SUBST([dbussystemservicedir], [$with_dbussystemservicedir]) +AC_SUBST([dbusinterfacedir], [$with_dbusinterfacedir]) +AC_SUBST([udevrulesdir], [$with_udevrulesdir]) +AC_SUBST([pamlibdir], [$with_pamlibdir]) +AC_SUBST([rootprefix], [$with_rootprefix]) +AC_SUBST([rootlibdir], [$with_rootlibdir]) + +AC_CONFIG_FILES([Makefile po/Makefile.in]) AC_OUTPUT AC_MSG_RESULT([ - $PACKAGE $VERSION - ======== + $PACKAGE_NAME $VERSION + Distribution: ${with_distro} + SysV compatibility: ${SYSTEM_SYSV_COMPAT} + SysV init scripts: ${SYSTEM_SYSVINIT_PATH} + SysV rc?.d directories: ${SYSTEM_SYSVRCND_PATH} + libcryptsetup: ${have_libcryptsetup} + tcpwrap: ${have_tcpwrap} + PAM: ${have_pam} + AUDIT: ${have_audit} + IMA: ${have_ima} + SELinux: ${have_selinux} + XZ: ${have_xz} + ACL: ${have_acl} + binfmt: ${have_binfmt} + vconsole: ${have_vconsole} + readahead: ${have_readahead} + quotacheck: ${have_quotacheck} + randomseed: ${have_randomseed} + logind: ${have_logind} + hostnamed: ${have_hostnamed} + timedated: ${have_timedated} + localed: ${have_localed} + coredump: ${have_coredump} + plymouth: ${have_plymouth} prefix: ${prefix} - rootprefix: ${rootprefix} - sysconfdir: ${sysconfdir} - bindir: ${bindir} - libdir: ${libdir} - rootlibdir: ${rootlib_execdir} - libexecdir: ${libexecdir} - datarootdir: ${datarootdir} - mandir: ${mandir} - includedir: ${includedir} - include_prefix: ${INCLUDE_PREFIX} - systemdsystemunitdir: ${systemdsystemunitdir} - firmware path: ${FIRMWARE_PATH} - usb.ids: ${USB_DATABASE} - pci.ids: ${PCI_DATABASE} - - compiler: ${CC} - cflags: ${CFLAGS} - ldflags: ${LDFLAGS} - xsltproc: ${XSLTPROC} - gperf: ${GPERF} - - logging: ${enable_logging} - debug: ${enable_debug} - selinux: ${with_selinux} - - man pages ${enable_manpages} - gudev: ${enable_gudev} - gintrospection: ${enable_introspection} - keymap: ${enable_keymap} - mtd_probe: ${enable_mtd_probe} - rule_generator: ${enable_rule_generator} - floppy: ${enable_floppy} + rootprefix: ${with_rootprefix} + libexec dir: ${libexecdir} + lib dir: ${libdir} + rootlib dir: ${with_rootlibdir} + PAM modules dir: ${with_pamlibdir} + udev rules dir: ${with_udevrulesdir} + D-Bus policy dir: ${with_dbuspolicydir} + D-Bus session dir: ${with_dbussessionservicedir} + D-Bus system dir: ${with_dbussystemservicedir} + D-Bus interfaces dir: ${with_dbusinterfacedir} + Split /usr: ${enable_split_usr} + man pages: ${have_manpages} ]) diff --git a/introspect.awk b/introspect.awk new file mode 100644 index 000000000..593191384 --- /dev/null +++ b/introspect.awk @@ -0,0 +1,13 @@ +BEGIN { + print "" + print "" +} + +// { + print +} + +END { + print "" +} diff --git a/m4/.gitignore b/m4/.gitignore index 0ca2c0372..55eaa803a 100644 --- a/m4/.gitignore +++ b/m4/.gitignore @@ -1,4 +1,6 @@ +intltool.m4 libtool.m4 -lt*m4 -gtk-doc.m4 - +ltoptions.m4 +ltsugar.m4 +ltversion.m4 +lt~obsolete.m4 diff --git a/m4/acx_libwrap.m4 b/m4/acx_libwrap.m4 new file mode 100644 index 000000000..ccf8afc0a --- /dev/null +++ b/m4/acx_libwrap.m4 @@ -0,0 +1,19 @@ +AC_DEFUN([ACX_LIBWRAP], [ +LIBWRAP_LIBS= +saved_LIBS="$LIBS" +LIBS="$LIBS -lwrap" +AC_MSG_CHECKING([for tcpwrap library and headers]) +AC_LINK_IFELSE( +[AC_LANG_PROGRAM( +[#include +#include +int allow_severity = LOG_INFO; +int deny_severity = LOG_WARNING;], +[struct request_info *req; +return hosts_access (req);])], +[AC_DEFINE(HAVE_LIBWRAP, [], [Have tcpwrap?]) +LIBWRAP_LIBS="-lwrap" +AC_MSG_RESULT(yes)], +[AC_MSG_RESULT(no)]) +LIBS="$saved_LIBS" +]) diff --git a/m4/attributes.m4 b/m4/attributes.m4 new file mode 100644 index 000000000..e354375e3 --- /dev/null +++ b/m4/attributes.m4 @@ -0,0 +1,288 @@ +dnl Macros to check the presence of generic (non-typed) symbols. +dnl Copyright (c) 2006-2008 Diego Pettenò +dnl Copyright (c) 2006-2008 xine project +dnl Copyright (c) 2012 Lucas De Marchi +dnl +dnl This program is free software; you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation; either version 2, or (at your option) +dnl any later version. +dnl +dnl This program is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +dnl GNU General Public License for more details. +dnl +dnl You should have received a copy of the GNU General Public License +dnl along with this program; if not, write to the Free Software +dnl Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +dnl 02110-1301, USA. +dnl +dnl As a special exception, the copyright owners of the +dnl macro gives unlimited permission to copy, distribute and modify the +dnl configure scripts that are the output of Autoconf when processing the +dnl Macro. You need not follow the terms of the GNU General Public +dnl License when using or distributing such scripts, even though portions +dnl of the text of the Macro appear in them. The GNU General Public +dnl License (GPL) does govern all other use of the material that +dnl constitutes the Autoconf Macro. +dnl +dnl This special exception to the GPL applies to versions of the +dnl Autoconf Macro released by this project. When you make and +dnl distribute a modified version of the Autoconf Macro, you may extend +dnl this special exception to the GPL to apply to your modified version as +dnl well. + +dnl Check if FLAG in ENV-VAR is supported by compiler and append it +dnl to WHERE-TO-APPEND variable +dnl CC_CHECK_FLAG_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG]) + +AC_DEFUN([CC_CHECK_FLAG_APPEND], [ + AC_CACHE_CHECK([if $CC supports flag $3 in envvar $2], + AS_TR_SH([cc_cv_$2_$3]), + [eval "AS_TR_SH([cc_save_$2])='${$2}'" + eval "AS_TR_SH([$2])='$3'" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([int a = 0; int main(void) { return a; } ])], + [eval "AS_TR_SH([cc_cv_$2_$3])='yes'"], + [eval "AS_TR_SH([cc_cv_$2_$3])='no'"]) + eval "AS_TR_SH([$2])='$cc_save_$2'"]) + + AS_IF([eval test x$]AS_TR_SH([cc_cv_$2_$3])[ = xyes], + [eval "$1='${$1} $3'"]) +]) + +dnl CC_CHECK_FLAGS_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG1 FLAG2]) +AC_DEFUN([CC_CHECK_FLAGS_APPEND], [ + for flag in $3; do + CC_CHECK_FLAG_APPEND($1, $2, $flag) + done +]) + +dnl Check if the flag is supported by linker (cacheable) +dnl CC_CHECK_LDFLAGS([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND]) + +AC_DEFUN([CC_CHECK_LDFLAGS], [ + AC_CACHE_CHECK([if $CC supports $1 flag], + AS_TR_SH([cc_cv_ldflags_$1]), + [ac_save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $1" + AC_LINK_IFELSE([int main() { return 1; }], + [eval "AS_TR_SH([cc_cv_ldflags_$1])='yes'"], + [eval "AS_TR_SH([cc_cv_ldflags_$1])="]) + LDFLAGS="$ac_save_LDFLAGS" + ]) + + AS_IF([eval test x$]AS_TR_SH([cc_cv_ldflags_$1])[ = xyes], + [$2], [$3]) +]) + +dnl define the LDFLAGS_NOUNDEFINED variable with the correct value for +dnl the current linker to avoid undefined references in a shared object. +AC_DEFUN([CC_NOUNDEFINED], [ + dnl We check $host for which systems to enable this for. + AC_REQUIRE([AC_CANONICAL_HOST]) + + case $host in + dnl FreeBSD (et al.) does not complete linking for shared objects when pthreads + dnl are requested, as different implementations are present; to avoid problems + dnl use -Wl,-z,defs only for those platform not behaving this way. + *-freebsd* | *-openbsd*) ;; + *) + dnl First of all check for the --no-undefined variant of GNU ld. This allows + dnl for a much more readable commandline, so that people can understand what + dnl it does without going to look for what the heck -z defs does. + for possible_flags in "-Wl,--no-undefined" "-Wl,-z,defs"; do + CC_CHECK_LDFLAGS([$possible_flags], [LDFLAGS_NOUNDEFINED="$possible_flags"]) + break + done + ;; + esac + + AC_SUBST([LDFLAGS_NOUNDEFINED]) +]) + +dnl Check for a -Werror flag or equivalent. -Werror is the GCC +dnl and ICC flag that tells the compiler to treat all the warnings +dnl as fatal. We usually need this option to make sure that some +dnl constructs (like attributes) are not simply ignored. +dnl +dnl Other compilers don't support -Werror per se, but they support +dnl an equivalent flag: +dnl - Sun Studio compiler supports -errwarn=%all +AC_DEFUN([CC_CHECK_WERROR], [ + AC_CACHE_CHECK( + [for $CC way to treat warnings as errors], + [cc_cv_werror], + [CC_CHECK_CFLAGS_SILENT([-Werror], [cc_cv_werror=-Werror], + [CC_CHECK_CFLAGS_SILENT([-errwarn=%all], [cc_cv_werror=-errwarn=%all])]) + ]) +]) + +AC_DEFUN([CC_CHECK_ATTRIBUTE], [ + AC_REQUIRE([CC_CHECK_WERROR]) + AC_CACHE_CHECK([if $CC supports __attribute__(( ifelse([$2], , [$1], [$2]) ))], + AS_TR_SH([cc_cv_attribute_$1]), + [ac_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $cc_cv_werror" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([$3])], + [eval "AS_TR_SH([cc_cv_attribute_$1])='yes'"], + [eval "AS_TR_SH([cc_cv_attribute_$1])='no'"]) + CFLAGS="$ac_save_CFLAGS" + ]) + + AS_IF([eval test x$]AS_TR_SH([cc_cv_attribute_$1])[ = xyes], + [AC_DEFINE( + AS_TR_CPP([SUPPORT_ATTRIBUTE_$1]), 1, + [Define this if the compiler supports __attribute__(( ifelse([$2], , [$1], [$2]) ))] + ) + $4], + [$5]) +]) + +AC_DEFUN([CC_ATTRIBUTE_CONSTRUCTOR], [ + CC_CHECK_ATTRIBUTE( + [constructor],, + [void __attribute__((constructor)) ctor() { int a; }], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_FORMAT], [ + CC_CHECK_ATTRIBUTE( + [format], [format(printf, n, n)], + [void __attribute__((format(printf, 1, 2))) printflike(const char *fmt, ...) { fmt = (void *)0; }], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_FORMAT_ARG], [ + CC_CHECK_ATTRIBUTE( + [format_arg], [format_arg(printf)], + [char *__attribute__((format_arg(1))) gettextlike(const char *fmt) { fmt = (void *)0; }], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_VISIBILITY], [ + CC_CHECK_ATTRIBUTE( + [visibility_$1], [visibility("$1")], + [void __attribute__((visibility("$1"))) $1_function() { }], + [$2], [$3]) +]) + +AC_DEFUN([CC_ATTRIBUTE_NONNULL], [ + CC_CHECK_ATTRIBUTE( + [nonnull], [nonnull()], + [void __attribute__((nonnull())) some_function(void *foo, void *bar) { foo = (void*)0; bar = (void*)0; }], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_UNUSED], [ + CC_CHECK_ATTRIBUTE( + [unused], , + [void some_function(void *foo, __attribute__((unused)) void *bar);], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_SENTINEL], [ + CC_CHECK_ATTRIBUTE( + [sentinel], , + [void some_function(void *foo, ...) __attribute__((sentinel));], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_DEPRECATED], [ + CC_CHECK_ATTRIBUTE( + [deprecated], , + [void some_function(void *foo, ...) __attribute__((deprecated));], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_ALIAS], [ + CC_CHECK_ATTRIBUTE( + [alias], [weak, alias], + [void other_function(void *foo) { } + void some_function(void *foo) __attribute__((weak, alias("other_function")));], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_MALLOC], [ + CC_CHECK_ATTRIBUTE( + [malloc], , + [void * __attribute__((malloc)) my_alloc(int n);], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_PACKED], [ + CC_CHECK_ATTRIBUTE( + [packed], , + [struct astructure { char a; int b; long c; void *d; } __attribute__((packed));], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_CONST], [ + CC_CHECK_ATTRIBUTE( + [const], , + [int __attribute__((const)) twopow(int n) { return 1 << n; } ], + [$1], [$2]) +]) + +AC_DEFUN([CC_FLAG_VISIBILITY], [ + AC_REQUIRE([CC_CHECK_WERROR]) + AC_CACHE_CHECK([if $CC supports -fvisibility=hidden], + [cc_cv_flag_visibility], + [cc_flag_visibility_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $cc_cv_werror" + CC_CHECK_CFLAGS_SILENT([-fvisibility=hidden], + cc_cv_flag_visibility='yes', + cc_cv_flag_visibility='no') + CFLAGS="$cc_flag_visibility_save_CFLAGS"]) + + AS_IF([test "x$cc_cv_flag_visibility" = "xyes"], + [AC_DEFINE([SUPPORT_FLAG_VISIBILITY], 1, + [Define this if the compiler supports the -fvisibility flag]) + $1], + [$2]) +]) + +AC_DEFUN([CC_FUNC_EXPECT], [ + AC_REQUIRE([CC_CHECK_WERROR]) + AC_CACHE_CHECK([if compiler has __builtin_expect function], + [cc_cv_func_expect], + [ac_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $cc_cv_werror" + AC_COMPILE_IFELSE([AC_LANG_SOURCE( + [int some_function() { + int a = 3; + return (int)__builtin_expect(a, 3); + }])], + [cc_cv_func_expect=yes], + [cc_cv_func_expect=no]) + CFLAGS="$ac_save_CFLAGS" + ]) + + AS_IF([test "x$cc_cv_func_expect" = "xyes"], + [AC_DEFINE([SUPPORT__BUILTIN_EXPECT], 1, + [Define this if the compiler supports __builtin_expect() function]) + $1], + [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_ALIGNED], [ + AC_REQUIRE([CC_CHECK_WERROR]) + AC_CACHE_CHECK([highest __attribute__ ((aligned ())) supported], + [cc_cv_attribute_aligned], + [ac_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $cc_cv_werror" + for cc_attribute_align_try in 64 32 16 8 4 2; do + AC_COMPILE_IFELSE([AC_LANG_SOURCE([ + int main() { + static char c __attribute__ ((aligned($cc_attribute_align_try))) = 0; + return c; + }])], [cc_cv_attribute_aligned=$cc_attribute_align_try; break]) + done + CFLAGS="$ac_save_CFLAGS" + ]) + + if test "x$cc_cv_attribute_aligned" != "x"; then + AC_DEFINE_UNQUOTED([ATTRIBUTE_ALIGNED_MAX], [$cc_cv_attribute_aligned], + [Define the highest alignment supported]) + fi +]) diff --git a/man/Makefile b/man/Makefile new file mode 120000 index 000000000..bd1047548 --- /dev/null +++ b/man/Makefile @@ -0,0 +1 @@ +../src/Makefile \ No newline at end of file diff --git a/man/binfmt.d.xml b/man/binfmt.d.xml new file mode 100644 index 000000000..f5ec805e2 --- /dev/null +++ b/man/binfmt.d.xml @@ -0,0 +1,111 @@ + + + + + + + + binfmt.d + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + binfmt.d + 5 + + + + binfmt.d + Configure additional binary formats at boot + + + + /etc/binfmt.d/*.conf + /run/binfmt.d/*.conf + /usr/lib/binfmt.d/*.conf + + + + Description + + systemd uses + files from the above directories to configure + additional binary formats to register during boot in + the kernel. + + + + Configuration Format + + Each file contains a list of binfmt_misc kernel + binary format rules. Consult binfmt_misc.txt + for more information on registration of additional + binary formats and how to write rules. + + Empty lines and lines beginning with ; and # are + ignored. Note that this means you may not use ; and # + as delimiter in binary format rules. + + Each configuration file is named in the style of + <program>.conf. + Files in /etc/ overwrite + files with the same name in /usr/lib/. + Files in /run overwrite files with + the same name in /etc/ and + /usr/lib/. Packages should install their + configuration files in /usr/lib/, files + in /etc/ are reserved for the local + administration, which possibly decides to overwrite the + configurations installed from packages. All files are sorted + by filename in alphabetical order, regardless in which of the + directories they reside, to ensure that a specific + configuration file takes precedence over another file with + an alphabetically later name. + + + + Example + + /etc/binfmt.d/wine.conf example: + + # Start WINE on Windows executables +:DOSWin:M::MZ::/usr/bin/wine: + + + + + See Also + + systemd1, + wine8 + + + + diff --git a/man/custom-html.xsl b/man/custom-html.xsl new file mode 100644 index 000000000..2d2f45879 --- /dev/null +++ b/man/custom-html.xsl @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/man/daemon.xml b/man/daemon.xml new file mode 100644 index 000000000..997ee5b25 --- /dev/null +++ b/man/daemon.xml @@ -0,0 +1,948 @@ + + + + + + + + + daemon + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + daemon + 7 + + + + daemon + Writing and Packaging System Daemons + + + + Description + + A daemon is a service process that runs in the + background and supervises the system or provides + functionality to other processes. Traditionally, + daemons are implemented following a scheme originating + in SysV Unix. Modern daemons should follow a simpler + yet more powerful scheme (here called "new-style" + daemons), as implemented by + systemd1. This + manual page covers both schemes, and in + particular includes recommendations for daemons that + shall be included in the systemd init system. + + + SysV Daemons + + When a traditional SysV daemon + starts, it should execute the following steps + as part of the initialization. Note that these + steps are unnecessary for new-style daemons (see below), + and should only be implemented if compatibility + with SysV is essential. + + + Close all open file + descriptors except STDIN, STDOUT, + STDERR (i.e. the first three file + descriptors 0, 1, 2). This ensures + that no accidentally passed file + descriptor stays around in the daemon + process. On Linux this is best + implemented by iterating through + /proc/self/fd, + with a fallback of iterating from file + descriptor 3 to the value returned by + getrlimit() for + RLIMIT_NOFILE. + + Reset all signal + handlers to their default. This is + best done by iterating through the + available signals up to the limit of + _NSIG and resetting them to + SIG_DFL. + + Reset the signal mask + using + sigprocmask(). + + Sanitize the + environment block, removing or + resetting environment variables that + might negatively impact daemon + runtime. + + Call fork(), + to create a background + process. + + In the child, call + setsid() to + detach from any terminal and create an + independent session. + + In the child, call + fork() again, to + ensure the daemon can never re-acquire + a terminal again. + + Call exit() in the + first child, so that only the second + child (the actual daemon process) + stays around. This ensures that the + daemon process is reparented to + init/PID 1, as all daemons should + be. + + In the daemon process, + connect /dev/null + to STDIN, STDOUT, + STDERR. + + In the daemon process, + reset the umask to 0, so that the file + modes passed to open(), mkdir() and + suchlike directly control the access + mode of the created files and + directories. + + In the daemon process, + change the current directory to the + root directory (/), in order to avoid + that the daemon involuntarily + blocks mount points from being + unmounted. + + In the daemon process, + write the daemon PID (as returned by + getpid()) to a + PID file, for example + /var/run/foobar.pid + (for a hypothetical daemon "foobar"), + to ensure that the daemon cannot be + started more than once. This must be + implemented in race-free fashion so + that the PID file is only updated when + at the same time it is verified that + the PID previously stored in the PID + file no longer exists or belongs to a + foreign process. Commonly some kind of + file locking is employed to implement + this logic. + + In the daemon process, + drop privileges, if possible and + applicable. + + From the daemon + process notify the original process + started that initialization is + complete. This can be implemented via + an unnamed pipe or similar + communication channel that is created + before the first + fork() and hence + available in both the original and the + daemon process. + + Call + exit() in the + original process. The process that + invoked the daemon must be able to + rely that this + exit() happens + after initialization is complete and + all external communication channels + established and + accessible. + + + The BSD daemon() function should not be + used, as it implements only a subset of these steps. + + A daemon that needs to provide + compatibility with SysV systems should + implement the scheme pointed out + above. However, it is recommended to make this + behaviour optional and configurable via a + command line argument, to ease debugging as + well as to simplify integration into systems + using systemd. + + + + New-Style Daemons + + Modern services for Linux should be + implemented as new-style daemons. This makes it + easier to supervise and control them at + runtime and simplifies their + implementation. + + For developing a new-style daemon none + of the initialization steps recommended for + SysV daemons need to be implemented. New-style + init systems such as systemd make all of them + redundant. Moreover, since some of these steps + interfere with process monitoring, file + descriptor passing and other functionality of + the init system it is recommended not to + execute them when run as new-style + service. + + Note that new-style init systems + guarantee execution of daemon processes in + clean process contexts: it is guaranteed that + the environment block is sanitized, that the + signal handlers and mask is reset and that no + left-over file descriptors are passed. Daemons + will be executed in their own session, and + STDIN/STDOUT/STDERR connected to + /dev/null unless + otherwise configured. The umask is reset. + + It is recommended for new-style daemons + to implement the following: + + + If SIGTERM is + received, shut down the daemon and + exit cleanly. + + If SIGHUP is received, + reload the configuration files, if + this applies. + + Provide a correct exit + code from the main daemon process, as + this is used by the init system to + detect service errors and problems. It + is recommended to follow the exit code + scheme as defined in the LSB + recommendations for SysV init + scripts. + + If possible and + applicable expose the daemon's control + interface via the D-Bus IPC system and + grab a bus name as last step of + initialization. + + For integration in + systemd, provide a + .service unit + file that carries information about + starting, stopping and otherwise + maintaining the daemon. See + systemd.service5 + for details. + + As much as possible, + rely on the init systemd's + functionality to limit the access of + the daemon to files, services and + other resources. i.e. in the case of + systemd, rely on systemd's resource + limit control instead of implementing + your own, rely on systemd's privilege + dropping code instead of implementing + it in the daemon, and similar. See + systemd.exec5 + for the available + controls. + + If D-Bus is used, make + your daemon bus-activatable, via + supplying a D-Bus service activation + configuration file. This has multiple + advantages: your daemon may be started + lazily on-demand; it may be started in + parallel to other daemons requiring it + -- which maximizes parallelization and + boot-up speed; your daemon can be + restarted on failure, without losing + any bus requests, as the bus queues + requests for activatable services. See + below for details. + + If your daemon + provides services to other local + processes or remote clients via a + socket, it should be made + socket-activatable following the + scheme pointed out below. Like D-Bus + activation this enables on-demand + starting of services as well as it + allows improved parallelization of + service start-up. Also, for state-less + protocols (such as syslog, DNS) a + daemon implementing socket-based + activation can be restarted without + losing a single request. See below for + details. + + If applicable a daemon + should notify the init system about + startup completion or status updates + via the + sd_notify3 + interface. + + Instead of using the + syslog() call to log directly to the + system syslog service, a new-style daemon may + choose to simply log to STDERR via + fprintf(), which is then forwarded to + syslog by the init system. If log + priorities are necessary these can be + encoded by prefixing individual log + lines with strings like "<4>" + (for log priority 4 "WARNING" in the + syslog priority scheme), following a + similar style as the Linux kernel's + printk() priority system. In fact, + using this style of logging also + enables the init system to optionally + direct all application logging to the + kernel log buffer (kmsg), as + accessible via + dmesg1. This + kind of logging may be enabled by + setting + StandardError=syslog + in the service unit file. For details + see + sd-daemon7 + and + systemd.exec5. + + + + These recommendations are similar but + not identical to the Apple + MacOS X Daemon Requirements. + + + + + Activation + + New-style init systems provide multiple + additional mechanisms to activate services, as + detailed below. It is common that services are + configured to be activated via more than one mechanism + at the same time. An example for systemd: + bluetoothd.service might get + activated either when Bluetooth hardware is plugged + in, or when an application accesses its programming + interfaces via D-Bus. Or, a print server daemon might + get activated when traffic arrives at an IPP port, or + when a printer is plugged in, or when a file is queued + in the printer spool directory. Even for services that + are intended to be started on system bootup + unconditionally it is a good idea to implement some of + the various activation schemes outlined below, in + order to maximize parallelization: if a daemon + implements a D-Bus service or listening socket, + implementing the full bus and socket activation scheme + allows starting of the daemon with its clients in + parallel (which speeds up boot-up), since all its + communication channels are established already, and no + request is lost because client requests will be queued + by the bus system (in case of D-Bus) or the kernel (in + case of sockets), until the activation is + completed. + + + Activation on Boot + + Old-style daemons are usually activated + exclusively on boot (and manually by the + administrator) via SysV init scripts, as + detailed in the LSB + Linux Standard Base Core + Specification. This method of + activation is supported ubiquitously on Linux + init systems, both old-style and new-style + systems. Among other issues SysV init scripts + have the disadvantage of involving shell + scripts in the boot process. New-style init + systems generally employ updated versions of + activation, both during boot-up and during + runtime and using more minimal service + description files. + + In systemd, if the developer or + administrator wants to make sure a service or + other unit is activated automatically on boot + it is recommended to place a symlink to the + unit file in the .wants/ + directory of either + multi-user.target or + graphical.target, which + are normally used as boot targets at system + startup. See + systemd.unit5 + for details about the + .wants/ directories, and + systemd.special7 + for details about the two boot targets. + + + + + Socket-Based Activation + + In order to maximize the possible + parallelization and robustness and simplify + configuration and development, it is + recommended for all new-style daemons that + communicate via listening sockets to employ + socket-based activation. In a socket-based + activation scheme the creation and binding of + the listening socket as primary communication + channel of daemons to local (and sometimes + remote) clients is moved out of the daemon + code and into the init system. Based on + per-daemon configuration the init system + installs the sockets and then hands them off + to the spawned process as soon as the + respective daemon is to be started. + Optionally activation of the service can be + delayed until the first inbound traffic + arrives at the socket, to implement on-demand + activation of daemons. However, the primary + advantage of this scheme is that all providers + and all consumers of the sockets can be + started in parallel as soon as all sockets + are established. In addition to that daemons + can be restarted with losing only a minimal + number of client transactions or even any + client request at all (the latter is + particularly true for state-less protocols, + such as DNS or syslog), because the socket + stays bound and accessible during the restart, + and all requests are queued while the daemon + cannot process them. + + New-style daemons which support socket + activation must be able to receive their + sockets from the init system, instead of of + creating and binding them themselves. For + details about the programming interfaces for + this scheme provided by systemd see + sd_listen_fds3 + and + sd-daemon7. For + details about porting existing daemons to + socket-based activation see below. With + minimal effort it is possible to implement + socket-based activation in addition to + traditional internal socket creation in the + same codebase in order to support both + new-style and old-style init systems from the + same daemon binary. + + systemd implements socket-based + activation via .socket + units, which are described in + systemd.socket5. When + configuring socket units for socket-based + activation it is essential that all listening + sockets are pulled in by the special target + unit sockets.target. It + is recommended to place a + WantedBy=sockets.target + directive in the [Install] + section, to automatically add such a + dependency on installation of a socket + unit. Unless + DefaultDependencies=no is + set the necessary ordering dependencies are + implicitly created for all socket units. For + more information about + sockets.target see + systemd.special7. It + is not necessary or recommended to place any + additional dependencies on socket units (for + example from + multi-user.target or + suchlike) when one is installed in + sockets.target. + + + + Bus-Based Activation + + When the D-Bus IPC system is used for + communication with clients, new-style daemons + should employ bus activation so that they are + automatically activated when a client + application accesses their IPC + interfaces. This is configured in D-Bus + service files (not to be confused with systemd + service unit files!). To ensure that D-Bus + uses systemd to start-up and maintain the + daemon use the + SystemdService= directive + in these service files, to configure the + matching systemd service for a D-Bus + service. e.g.: for a D-Bus service whose D-Bus + activation file is named + org.freedesktop.RealtimeKit.service, + make sure to set + SystemdService=rtkit-daemon.service + in that file, to bind it to the systemd + service + rtkit-daemon.service. This + is needed to make sure that the daemon is + started in a race-free fashion when activated + via multiple mechanisms simultaneously. + + + + Device-Based Activation + + Often, daemons that manage a particular + type of hardware should be activated only when + the hardware of the respective kind is plugged + in or otherwise becomes available. In a + new-style init system it is possible to bind + activation to hardware plug/unplug events. In + systemd, kernel devices appearing in the + sysfs/udev device tree can be exposed as units + if they are tagged with the string + "systemd". Like any other + kind of unit they may then pull in other units + when activated (i.e. Plugged in) and thus + implement device-based activation. Systemd + dependencies may be encoded in the udev + database via the + SYSTEMD_WANTS= + property. See + systemd.device5 + for details. Often it is nicer to pull in + services from devices only indirectly via + dedicated targets. Example: instead of pulling + in bluetoothd.service + from all the various bluetooth dongles and + other hardware available, pull in + bluetooth.target from them and + bluetoothd.service from + that target. This provides for nicer + abstraction and gives administrators the + option to enable + bluetoothd.service via + controlling a + bluetooth.target.wants/ + symlink uniformly with a command like + enable of + systemctl1 + instead of manipulating the udev + ruleset. + + + + Path-Based Activation + + Often, runtime of daemons processing + spool files or directories (such as a printing + system) can be delayed until these file system + objects change state, or become + non-empty. New-style init systems provide a + way to bind service activation to file system + changes. systemd implements this scheme via + path-based activation configured in + .path units, as outlined + in + systemd.path5. + + + + Timer-Based Activation + + Some daemons that implement clean-up + jobs that are intended to be executed in + regular intervals benefit from timer-based + activation. In systemd, this is implemented + via .timer units, as + described in + systemd.timer5. + + + + Other Forms of Activation + + Other forms of activation have been + suggested and implemented in some + systems. However, often there are simpler or + better alternatives, or they can be put + together of combinations of the schemes + above. Example: sometimes it appears useful to + start daemons or .socket + units when a specific IP address is configured + on a network interface, because network + sockets shall be bound to the + address. However, an alternative to implement + this is by utilizing the Linux IP_FREEBIND + socket option, as accessible via + FreeBind=yes in systemd + socket files (see + systemd.socket5 + for details). This option, when enabled, + allows sockets to be bound to a non-local, not + configured IP address, and hence allows + bindings to a particular IP address before it + actually becomes available, making such an + explicit dependency to the configured address + redundant. Another often suggested trigger for + service activation is low system + load. However, here too, a more convincing + approach might be to make proper use of + features of the operating system: in + particular, the CPU or IO scheduler of + Linux. Instead of scheduling jobs from + userspace based on monitoring the OS + scheduler, it is advisable to leave the + scheduling of processes to the OS scheduler + itself. systemd provides fine-grained access + to the CPU and IO schedulers. If a process + executed by the init system shall not + negatively impact the amount of CPU or IO + bandwidth available to other processes, it + should be configured with + CPUSchedulingPolicy=idle + and/or + IOSchedulingClass=idle. Optionally, + this may be combined with timer-based + activation to schedule background jobs during + runtime and with minimal impact on the system, + and remove it from the boot phase + itself. + + + + + Integration with Systemd + + + Writing Systemd Unit Files + + When writing systemd unit files, it is + recommended to consider the following + suggestions: + + + If possible do not use + the Type=forking + setting in service files. But if you + do, make sure to set the PID file path + using PIDFile=. See + systemd.service5 + for details. + + If your daemon + registers a D-Bus name on the bus, + make sure to use + Type=dbus in the + service file if + possible. + + Make sure to set a + good human-readable description string + with + Description=. + + Do not disable + DefaultDependencies=, + unless you really know what you do and + your unit is involved in early boot or + late system shutdown. + + Normally, little if + any dependencies should need to + be defined explicitly. However, if you + do configure explicit dependencies, only refer to + unit names listed on + systemd.special7 + or names introduced by your own + package to keep the unit file + operating + system-independent. + + Make sure to include + an [Install] + section including installation + information for the unit file. See + systemd.unit5 + for details. To activate your service + on boot make sure to add a + WantedBy=multi-user.target + or + WantedBy=graphical.target + directive. To activate your socket on + boot, make sure to add + WantedBy=sockets.target. Usually + you also want to make sure that when + your service is installed your socket + is installed too, hence add + Also=foo.socket in + your service file + foo.service, for + a hypothetical program + foo. + + + + + + Installing Systemd Service Files + + At the build installation time + (e.g. make install during + package build) packages are recommended to + install their systemd unit files in the + directory returned by pkg-config + systemd + --variable=systemdsystemunitdir (for + system services), resp. pkg-config + systemd + --variable=systemduserunitdir + (for user services). This will make the + services available in the system on explicit + request but not activate them automatically + during boot. Optionally, during package + installation (e.g. rpm -i + by the administrator) symlinks should be + created in the systemd configuration + directories via the enable + command of the + systemctl1 + tool, to activate them automatically on + boot. + + Packages using + autoconf1 + are recommended to use a configure script + excerpt like the following to determine the + unit installation path during source + configuration: + + PKG_PROG_PKG_CONFIG +AC_ARG_WITH([systemdsystemunitdir], + AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]), + [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)]) +if test "x$with_systemdsystemunitdir" != xno; then + AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) +fi +AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != xno ]) + + This snippet allows automatic + installation of the unit files on systemd + machines, and optionally allows their + installation even on machines lacking + systemd. (Modification of this snippet for the + user unit directory is left as an exercise for the + reader.) + + Additionally, to ensure that + make distcheck continues to + work, it is recommended to add the following + to the top-level Makefile.am + file in + automake1-based + projects: + + DISTCHECK_CONFIGURE_FLAGS = \ + --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) + + Finally, unit files should be installed in the system with an automake excerpt like the following: + + if HAVE_SYSTEMD +systemdsystemunit_DATA = \ + foobar.socket \ + foobar.service +endif + + In the + rpm8 + .spec file use a snippet like + the following to enable/disable the service + during installation/deinstallation. Consult + the packaging guidelines of your distribution + for details and the equivalent for other + package managers: + + %post +if [ $1 -eq 1 ]; then + # On install (not upgrade), enable (but don't start) the + # units by default + /bin/systemctl enable foobar.service foobar.socket >/dev/null 2>&1 || : + + # Alternatively, just call + # /bin/systemctl daemon-reload >/dev/null 2>&1 || : + # here, if the daemon should not be enabled by default on + # installation +fi + +%preun +if [ $1 -eq 0 ]; then + # On uninstall (not upgrade), disable and stop the units + /bin/systemctl --no-reload disable foobar.service foobar.socket >/dev/null 2>&1 || : + /bin/systemctl stop foobar.service foobar.socket >/dev/null 2>&1 || : +fi + +%postun +# Reload init system configuration, to make systemd honour changed +# or deleted unit files +/bin/systemctl daemon-reload >/dev/null 2>&1 || : +if [ $1 -ge 1 ] ; then + # On upgrade (not uninstall), optionally, restart the daemon + /bin/systemctl try-restart foobar.service >/dev/null 2>&1 || : +fi + + Depending on whether your service should + or should not be started/stopped/restarted + during package installation, deinstallation or + upgrade, a different set of commands may be + specified. See + systemctl1 + for details. + + To facilitate upgrades from a package + version that shipped only SysV init scripts to + a package version that ships both a SysV init + script and a native systemd service file, use + a fragment like the following: + + %triggerun -- foobar < 0.47.11-1 +if /sbin/chkconfig --level 5 foobar ; then + /bin/systemctl --no-reload enable foobar.service foobar.socket >/dev/null 2>&1 || : +fi + + Where 0.47.11-1 is the first package + version that includes the native unit + file. This fragment will ensure that the first + time the unit file is installed it will be + enabled if and only if the SysV init script is + enabled, thus making sure that the enable + status is not changed. Note that + chkconfig is a command + specific to Fedora which can be used to check + whether a SysV init script is enabled. Other + operating systems will have to use different + commands here. + + + + + Porting Existing Daemons + + Since new-style init systems such as systemd are + compatible with traditional SysV init systems it is + not strictly necessary to port existing daemons to the + new style. However doing so offers additional + functionality to the daemons as well as simplifying + integration into new-style init systems. + + To port an existing SysV compatible daemon the + following steps are recommended: + + + If not already implemented, + add an optional command line switch to the + daemon to disable daemonization. This is + useful not only for using the daemon in + new-style init systems, but also to ease + debugging. + + If the daemon offers + interfaces to other software running on the + local system via local AF_UNIX sockets, + consider implementing socket-based activation + (see above). Usually a minimal patch is + sufficient to implement this: Extend the + socket creation in the daemon code so that + sd_listen_fds3 + is checked for already passed sockets + first. If sockets are passed (i.e. when + sd_listen_fds() returns a + positive value), skip the socket creation step + and use the passed sockets. Secondly, ensure + that the file-system socket nodes for local + AF_UNIX sockets used in the socket-based + activation are not removed when the daemon + shuts down, if sockets have been + passed. Third, if the daemon normally closes + all remaining open file descriptors as part of + its initialization, the sockets passed from + the init system must be spared. Since + new-style init systems guarantee that no + left-over file descriptors are passed to + executed processes, it might be a good choice + to simply skip the closing of all remaining + open file descriptors if sockets are + passed. + + Write and install a systemd + unit file for the service (and the sockets if + socket-based activation is used, as well as a + path unit file, if the daemon processes a + spool directory), see above for + details. + + If the daemon exposes + interfaces via D-Bus, write and install a + D-Bus activation file for the service, see + above for details. + + + + + See Also + + systemd1, + sd-daemon7, + sd_listen_fds3, + sd_notify3, + daemon3, + systemd.service5 + + + + diff --git a/man/halt.xml b/man/halt.xml new file mode 100644 index 000000000..97a53ba35 --- /dev/null +++ b/man/halt.xml @@ -0,0 +1,181 @@ + + + + + + + + + halt + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + halt + 8 + + + + halt + poweroff + reboot + Halt, power-off or reboot the machine + + + + + halt OPTIONS + + + poweroff OPTIONS + + + reboot OPTIONS + + + + + Description + + halt, + poweroff, reboot + may be used to halt, power-off or reboot the + machine. + + + + + Options + + The following options are understood: + + + + + + Prints a short help + text and exits. + + + + + + Halt the machine, + regardless which one of the three + commands is invoked. + + + + + + + Power-off the machine, + regardless which one of the three + commands is invoked. + + + + + + Reboot the machine, + regardless which one of the three + commands is invoked. + + + + + + + Force immediate halt, + power-off, reboot. Don't contact the + init system. + + + + + + + Only write wtmp + shutdown entry, don't actually halt, + power-off, reboot. + + + + + + + Don't write wtmp + shutdown entry. + + + + + + + Don't sync hard disks/storage media before + halt, power-off, + reboot. + + + + + + Don't send wall + message before + halt, power-off, reboot. + + + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + Notes + + These are legacy commands available for + compatibility only. + + + + See Also + + systemd1, + systemctl1, + shutdown8, + wall1 + + + + diff --git a/man/hostname.xml b/man/hostname.xml new file mode 100644 index 000000000..1acda1af5 --- /dev/null +++ b/man/hostname.xml @@ -0,0 +1,95 @@ + + + + + + + + + /etc/hostname + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + hostname + 5 + + + + hostname + Local host name configuration file + + + + /etc/hostname + + + + Description + + The /etc/hostname file + configures the name of the local system that is set + during boot, with the + sethostname2 + system call. It should contain a single + newline-terminated host name string. The + host name may be a free-form string up to 64 characters + in length, however it is recommended that it consists + only of 7bit ASCII lower-case characters and no spaces or dots, + and limits itself to the format allowed for DNS domain + name labels, even though this is not a + strict requirement. + + Depending on the operating system other + configuration files might be checked for configuration + of the host name as well, however only as fallback. + + + + History + + The simple configuration file format of + /etc/hostname originates from + Debian GNU/Linux. + + + + See Also + + systemd1, + sethostname2, + hostname1, + hostname7, + machine-id5, + machine-info5 + + + + diff --git a/man/journalctl.xml b/man/journalctl.xml new file mode 100644 index 000000000..f6e46cfbc --- /dev/null +++ b/man/journalctl.xml @@ -0,0 +1,260 @@ + + + + + + + + + journalctl + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + journalctl + 1 + + + + journalctl + Query the systemd journal + + + + + journalctl OPTIONS MATCH + + + + + Description + + journalctl may be + used to query the contents of the + systemd1 + journal. + + If called without parameter will show the full + contents of the journal, starting with the oldest + entry collected. + + If a match argument is passed the output is + filtered accordingly. A match is in the format + FIELD=VALUE, + e.g. _SYSTEMD_UNIT=httpd.service. + + Output is interleaved from all accessible + journal files, whether they are rotated or currently + being written, and regardless whether they belong to the + system itself or are accessible user journals. + + All users are granted access to their private + per-user journals. However, by default only root and + users who are members of the adm + group get access to the system journal and the + journals of other users. + + + + Options + + The following options are understood: + + + + + + + Prints a short help + text and exits. + + + + + + Prints a short version + string and exits. + + + + + + Do not pipe output into a + pager. + + + + + + + Show all fields in + full, even if they include unprintable + characters or are very + long. + + + + + + + Show only most recent + journal entries, and continously print + new entries as they are appended to + the journal. + + + + + + + Controls the number of + journal lines to show, counting from + the most recent ones. Takes a positive + integer argument. In follow mode + defaults to 10, otherwise is unset + thus not limiting how many lines are + shown. + + + + + + Show all stored output + lines, even in follow mode. Undoes the + effect of + . + + + + + + + Controls the + formatting of the journal entries that are + shown. Takes one of + short, + short-monotonic, + verbose, + export, + json, + cat. short + is the default and generates an output + that is mostly identical to the + formatting of classic syslog log + files, showing one line per journal + entry. short-monotonic + is very similar but shows monotonic + timestamps instead of wallclock + timestamps. verbose + shows the full structered entry items + with all + fiels. export + serializes the journal into a binary + (but mostly text-based) stream + suitable for backups and network + transfer. json + formats entries as JSON data + structures. cat + generates a very terse output only + showing the actual message of each + journal entry with no meta data, not + even a timestamp. + + + + + + + Suppresses any warning + message regarding inaccessable system + journals when run as normal + user. + + + + + + + Show only locally + generated messages. + + + + + + Instead of showing + journal contents generate a new 128 + bit ID suitable for identifying + messages. This is intended for usage + by developers who need a new + identifier for a new message they + introduce and want to make + recognizable. Will print the new ID in + three different formats which can be + copied into source code or + similar. + + + + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + Environment + + + + $SYSTEMD_PAGER + Pager to use when + is not given; + overrides $PAGER. Setting + this to an empty string or the value + cat is equivalent to passing + . + + + + + + See Also + + systemd1, + systemctl1, + journald.conf5 + + + + diff --git a/man/journald.conf.xml b/man/journald.conf.xml new file mode 100644 index 000000000..a9b0f66de --- /dev/null +++ b/man/journald.conf.xml @@ -0,0 +1,254 @@ + + + + + + + + + journald.conf + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + journald.conf + 5 + + + + journald.conf + Journal service configuration file + + + + journald.conf + + + + Description + + This files configures various parameters of the systemd journal service. + + + + + Options + + All options are configured in the + [Journal] section: + + + + + Compress= + + Takes a boolean + value. If enabled (the default) data + objects that shall be stored in the + journal and are larger than a certain + threshold are compressed with the XZ + compression algorithm before they are + written to the file + system. + + + + RateLimitInterval= + RateLimitBurst= + + Configures the rate + limiting that is applied to all + messages generated on the system. If + in the time interval defined by + RateLimitInterval= + more messages than specified in + RateLimitBurst= are + logged by a service all further + messages within the interval are + dropped, until the interval is over. A + message about the number of dropped + messages is generated. This rate + limiting is applied per-service, so + that two services which log do not + interfere with each other's + limit. Defaults to 100 messages in + 10s. The time specification for + RateLimitInterval= + may be specified in the following + units: s, + min, + h, + ms, + us. To turn off any + kind of rate limiting, set either + value to 0. + + + + SystemMaxUse= + SystemKeepFree= + SystemMaxFileSize= + SystemMinFileSize= + RuntimeMaxUse= + RuntimeKeepFree= + RuntimeMaxFileSize= + RuntimeMinFileSize= + + Enforce size limits on + the journal files stored. The options + prefixed with + System apply to the + journal files when stored on a + persistant file system, more + specifically + /var/log/journal. The + options prefixed with + Runtime apply to + the journal files when stored on a + volatile in-memory file system, more + specifically + /run/log/journal. The + former is used only when + /var is mounted, + writable and the directory + /var/log/journal + exists. Otherwise only the latter + applies. Note that this means that + during early boot and if the + administrator disabled persistant + logging only the latter options apply, + while the former apply if persistant + logging is enabled and the system is + fully booted + up. SystemMaxUse= + and RuntimeMaxUse= + control how much disk space the + journal may use up at + maximum. Defaults to 10% of the size + of the respective file + system. SystemKeepFree= + and + RuntimeKeepFree= + control how much disk space the + journal shall always leave free for + other uses if less than the disk space + configured in + SystemMaxUse= and + RuntimeMaxUse= is + available. Defaults to 5% of the size + of the respective file + system. SystemMaxFileSize= + and + RuntimeMaxFileSize= + control how large individual journal + files may grow at maximum. This + influences the granularity in which + disk space is made available through + rotation, i.e. deletion of historic + data. Defaults to one eigth of the + values configured with + SystemMaxUse= and + RuntimeMaxUse=, so + that usually seven rotated journal + files are kept as + history. SystemMinFileSize= + and + RuntimeMinFileSize= + control how large individual journal + files grow at minimum. Defaults to + 64K. Specify values in bytes or use + K, M, G, T, P, E as units for the + specified sizes. Note that size limits + are enforced synchronously to journal + files as they are extended, and need + no explicit rotation step triggered by + time. + + + + ForwardToSyslog= + ForwardToKMsg= + ForwardToConsole= + + Control whether log + messages received by the journal + daemon shall be forwarded to a + traditional syslog daemon, to the + kernel log buffer (kmsg), or to the + system console. These options take + boolean arguments. If forwarding to + syslog is enabled but no syslog daemon + is running the respective option has + no effect. By default only forwarding + to syslog is enabled. These settings + may be overriden at boot time with the + kernel command line options + systemd_journald.forward_to_syslog=, + systemd_journald.forward_to_kmsg= + and + systemd_journald.forward_to_console=. If + forwarding to the kernel log buffer and + ImportKernel= is + enabled at the same time care is taken + to avoid logging loops. It is safe to + use these options in combination. + + + + + ImportKernel= + + Controls whether + kernel log messages shall be stored in + the journal. Takes a boolean argument + and defaults to enabled. Note that + currently only one userspace service + can read kernel messages at a time, + which means that kernel log message + reading might get corrupted if it + is enabled in more than one service, + for example in both the journal and a + traditional syslog service. + + + + + + + + See Also + + systemd1, + journalctl1, + systemd.conf5 + + + + diff --git a/man/locale.conf.xml b/man/locale.conf.xml new file mode 100644 index 000000000..37239974b --- /dev/null +++ b/man/locale.conf.xml @@ -0,0 +1,146 @@ + + + + + + + + + locale.conf + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + locale.conf + 5 + + + + locale.conf + configuration file for locale settings + + + + /etc/locale.conf + + + + Description + + The /etc/locale.conf file + configures system-wide locale settings. + + The basic file format of + locale.conf is a + newline-separated list of environment-like + shell-compatible variable assignments. It is possible + to source the configuration from shell scripts, + however, beyond mere variable assignments no shell + features are supported, allowing applications to read + the file without implementing a shell compatible + execution engine. + + Note that the kernel command line options + locale.LANG=, + locale.LANGUAGE=, + locale.LC_CTYPE=, + locale.LC_NUMERIC=, + locale.LC_TIME=, + locale.LC_COLLATE=, + locale.LC_MONETARY=, + locale.LC_MESSAGES=, + locale.LC_PAPER=, + locale.LC_NAME=, + locale.LC_ADDRESS=, + locale.LC_TELEPHONE=, + locale.LC_MEASUREMENT=, + locale.LC_IDENTIFICATION= may be + used to override the locale settings at boot. + + The locale settings configured in + /etc/locale.conf are system-wide + and are inherited by every service or user, unless + overridden or unset by individual programs or + individual users. + + Depending on the operating system other + configuration files might be checked for locale + configuration as well, however only as + fallback. + + + + Options + + The following locale settings may be set using + /etc/locale.conf: + LANG=, + LANGUAGE=, + LC_CTYPE=, + LC_NUMERIC=, + LC_TIME=, + LC_COLLATE=, + LC_MONETARY=, + LC_MESSAGES=, + LC_PAPER=, + LC_NAME=, + LC_ADDRESS=, + LC_TELEPHONE=, + LC_MEASUREMENT=, + LC_IDENTIFICATION=. Note that + LC_ALL may not be be configured in + this file. For details about the meaning and semantics + of these settings, refer to + locale7. + + + + Example + + + German locale with English messages + + /etc/locale.conf: + + LANG=de_DE.UTF-8 +LC_MESSAGES=C + + + + + + See Also + + systemd1, + locale7 + + + + diff --git a/man/loginctl.xml b/man/loginctl.xml new file mode 100644 index 000000000..af1d631cf --- /dev/null +++ b/man/loginctl.xml @@ -0,0 +1,457 @@ + + + + + + + + + loginctl + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + loginctl + 1 + + + + loginctl + Control the systemd login manager + + + + + loginctl OPTIONS COMMAND NAME + + + + + Description + + loginctl may be used to + introspect and control the state of the + systemd1 + login manager. + + + + Options + + The following options are understood: + + + + + + + Prints a short help + text and exits. + + + + + + Prints a short version + string and exits. + + + + + + + When showing + session/user/ properties, limit + display to certain properties as + specified as argument. If not + specified all set properties are + shown. The argument should be a + property name, such as + Sessions. If + specified more than once all + properties with the specified names + are shown. + + + + + + + When showing + unit/job/manager properties, show all + properties regardless whether they are + set or not. + + + + + + + Do not pipe output into a + pager. + + + + + + When used with + kill-session, + choose which processes to kill. Must + be one of , or + to select whether + to kill only the leader process of the + session or all processes of the + session. If omitted defaults to + . + + + + + + + When used with + kill-session or + kill-user, choose + which signal to send to selected + processes. Must be one of the well + known signal specifiers such as + SIGTERM, SIGINT or SIGSTOP. If omitted + defaults to + . + + + + + + + Execute operation + remotely. Specify a hostname, or + username and hostname separated by @, + to connect to. This will use SSH to + talk to the remote login manager + instance. + + + + + + + Acquire privileges via + PolicyKit before executing the + operation. + + + + The following commands are understood: + + + + list-sessions + + List current sessions. + + + + session-status [ID...] + + Show terse runtime + status information about one or more + sessions. This function is intended to + generate human-readable output. If you + are looking for computer-parsable + output, use + show-session + instead. + + + + show-session [ID...] + + Show properties of one + or more sessions or the manager + itself. If no argument is specified + properties of the manager will be + shown. If a session ID is specified + properties of the session is shown. By + default, empty properties are + suppressed. Use + to show those too. To select specific + properties to show use + . This + command is intended to be used + whenever computer-parsable output is + required. Use + session-status if + you are looking for formatted + human-readable + output. + + + + activate [ID...] + + Activate one or more + sessions. This brings one or more + sessions into the foreground, if + another session is currently in the + foreground on the respective + seat. + + + + lock-session [ID...] + unlock-session [ID...] + + Activates/deactivates + the screen lock on one or more + sessions, if the session supports it. + + + + terminate-session [ID...] + + Terminates a + session. This kills all processes of + the session and deallocates all + resources attached to the + session. + + + + kill-session [ID...] + + Send a signal to one + or more processes of the session. Use + to select + which process to kill. Use + to select + the signal to send. + + + + list-users + + List currently logged + in users. + + + + user-status [USER...] + + Show terse runtime + status information about one or more + logged in users. This function is + intended to generate human-readable + output. If you are looking for + computer-parsable output, use + show-user + instead. Users may be specified by + their usernames or numeric user + IDs. + + + + show-user [USER...] + + Show properties of one + or more users or the manager + itself. If no argument is specified + properties of the manager will be + shown. If a user is specified + properties of the user is shown. By + default, empty properties are + suppressed. Use + to show those too. To select specific + properties to show use + . This + command is intended to be used + whenever computer-parsable output is + required. Use + user-status if + you are looking for formatted + human-readable + output. + + + + enable-linger [USER...] + disable-linger [USER...] + + Enable/disable user + lingering for one or more users. If + enabled for a specific user a user + manager is spawned for him/her at + boot, and kept around after + logouts. This allows users who aren't + logged in to run long-running + services. + + + + terminate-user [USER...] + + Terminates all + sessions of a user. This kills all + processes of all sessions of the user + and deallocates all runtime resources + attached to the + user. + + + + kill-user [USER...] + + Send a signal to all + processes of a user. Use + to select + the signal to send. + + + + list-seats + + List currently + available seats on the local + system. + + + + seat-status [NAME...] + + Show terse runtime + status information about one or more + seats. This function is + intended to generate human-readable + output. If you are looking for + computer-parsable output, use + show-seat + instead. + + + + show-seat [NAME...] + + Show properties of one + or more seats or the manager + itself. If no argument is specified + properties of the manager will be + shown. If a seat is specified + properties of the seat are shown. By + default, empty properties are + suppressed. Use + to show those too. To select specific + properties to show use + . This + command is intended to be used + whenever computer-parsable output is + required. Use + seat-status if you + are looking for formatted + human-readable + output. + + + + attach [NAME] [DEVICE...] + + Attach one or more + devices to a seat. The devices should + be specified via device paths in the + /sys file + system. To create a new seat attach at + least one graphics card to a + previously unused seat names. seat + names may consist only of a-z, A-Z, + 0-9, "-" and "_" and must be prefixed + with "seat". To drop assignment of a + device to a specific seat just + reassign it to a different seat, or + use + flush-devices. + + + + flush-devices + + Removes all device + assignments previously created with + attach. After this + call only automatically generated + seats will remain and all seat + hardware is assigned to + them. + + + + terminate-seat [NAME...] + + Terminates all + sessions on a seat. This kills all + processes of all sessions on a seat and + deallocates all runtime resources + attached to them. + + + + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + Environment + + + + $SYSTEMD_PAGER + Pager to use when + is not given; + overrides $PAGER. Setting + this to an empty string or the value + cat is equivalent to passing + . + + + + + + See Also + + systemd1, + systemctl1, + logind.conf5 + + + + diff --git a/man/logind.conf.xml b/man/logind.conf.xml new file mode 100644 index 000000000..950f81fa9 --- /dev/null +++ b/man/logind.conf.xml @@ -0,0 +1,175 @@ + + + + + + + + + logind.conf + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + logind.conf + 5 + + + + logind.conf + Login manager configuration file + + + + logind.conf + + + + Description + + This files configures various parameters of the systemd login manager. + + + + + Options + + All options are configured in the + [Login] section: + + + + + NAutoVTs= + + Takes a positive + integer. How many virtual terminals to + allocate by default and when switched + to autospawn autovt + services on (if they are otherwise + unused). These services are + instantiated from a template of + autovt@.service + with the virtual terminal TTY name, + e.g. autovt@tty4.service. By + default + autovt@.service + is linked to + getty@.service, + i.e. login prompts are started + dynamically as the user switches to + unused virtual terminals, and this + parameter hence controls how many + gettys are available on the virtual + terminals. Defaults to 6. When set to + 0, automatic spawning of + autovt services is + disabled. + + + + KillUserProcesses= + + Takes a boolean + argument. Configures whether the + processes of a user should be killed + when she or he completely logs out (i.e. after + her/his last session ended). Defaults to + no. + + + + KillOnlyUsers= + KillExcludeUsers= + + These settings take + space separated lists of user names + that influence the effect of + KillUserProcesses=. If + not empty only processes of users + listed in + KillOnlyUsers will + be killed when they log out + entirely. Processes of users listed in + KillExcludeUsers= + are excluded from being + killed. KillExcludeUsers= + defaults to root + and takes precedence over + KillOnlyUsers= + which defaults to the empty list. + + + + Controllers= + ResetControllers= + + These settings control + the default control group hierarchies + users logging are added to. When + logging in users will get private + control groups in all hierarchies + listed in + Controllers= and be + reset to the root control group in all + hierarchies listed in + ResetControllers=. Controllers= + defaults to the empty list, + ResetControllers= + defaults to + cpu. + + + + Note that setting + KillUserProcesses=1 will break tools + like + screen1. + + Note that KillUserProcesses=1 + is a weaker version of + kill-session-processes=1 which may + be configured per-service for + pam_systemd8. The + latter kills processes of a session as soon as it + ends, the former kills processes as soon as the last + session of the user ends. + + + + See Also + + systemd1, + loginctl1, + systemd.conf5 + + + + diff --git a/man/machine-id.xml b/man/machine-id.xml new file mode 100644 index 000000000..97c622c6f --- /dev/null +++ b/man/machine-id.xml @@ -0,0 +1,143 @@ + + + + + + + + + /etc/machine-id + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + machine-id + 5 + + + + machine-id + local machine ID configuration file + + + + /etc/machine-id + + + + Description + + The /etc/machine-id file + contains the unique machine id of the local system + that is set during installation. The machine ID is a + single newline-terminated, hexadecimal, lowercase 32 + character machine ID string. (When decoded from + hexadecimal this corresponds with a 16 byte/128 bit + string.) + + The machine ID is usually generated from a + random source during system installation and stays + constant for all subsequent boots. Optionally, for + stateless systems it is generated during runtime at + boot if it is found to be empty. + + The machine ID does not change based on user + configuration, or when hardware is replaced. + + This machine ID adheres to the same format and + logic as the D-Bus machine ID. + + Programs may use this ID to identify the host + with a globally unique ID in the network, that does + not change even if the local network configuration + changes. Due to this and its greater length it is + a more useful replacement for the + gethostid3 + call POSIX specifies. + + The + systemd-machine-id-setup1 + tool may be used by installer tools to initialize the + machine ID at install time. + + + + Relation to OSF UUIDs + + Note that the machine ID historically is not an + OSF UUID as defined by RFC + 4122, nor a Microsoft GUID. Starting with + systemd v30 newly generated machine IDs however do + qualify as v4 UUIDs. + + In order to maintain compatibility with existing + installations, an application requiring a UUID should + decode the machine ID, and then apply the following + operations to turn it into a valid OSF v4 UUID. With + id being an unsigned character + array: + + /* Set UUID version to 4 --- truly random generation */ +id[6] = (id[6] & 0x0F) | 0x40; +/* Set the UUID variant to DCE */ +id[8] = (id[8] & 0x3F) | 0x80; + + (This code is inspired by + generate_random_uuid() of + drivers/char/random.c from the + kernel sources.) + + + + + History + + The simple configuration file format of + /etc/machine-id originates in the + /var/lib/dbus/machine-id file + introduced by D-Bus. In fact this latter file might be a + symlink to + /etc/machine-id. + + + + See Also + + systemd1, + systemd-machine-id-setup1, + gethostid3, + hostname5, + machine-info5, + os-release5 + + + + diff --git a/man/machine-info.xml b/man/machine-info.xml new file mode 100644 index 000000000..240da25a6 --- /dev/null +++ b/man/machine-info.xml @@ -0,0 +1,147 @@ + + + + + + + + + machine-info + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + machine-info + 5 + + + + machine-info + Local machine information file + + + + /etc/machine-info + + + + Description + + The /etc/machine-info file + contains machine meta data. + + The basic file format of + machine-info is a + newline-separated list of environment-like + shell-compatible variable assignments. It is possible + to source the configuration from shell scripts, + however, beyond mere variable assignments no shell + features are supported, allowing applications to read + the file without implementing a shell compatible + execution engine. + + /etc/machine-info contains + meta data about the machine that is set by the user or + administrator. + + Depending on the operating system other + configuration files might be checked for machine + information as well, however only as fallback. + + + + Options + + The following machine meta data parameters may + be set using + /etc/machine-info: + + + + + PRETTY_HOSTNAME= + + A pretty + human-readable UTF8 machine identifier + string. This should contain a name + like Lennart's + Laptop which is useful to + present to the user and does not + suffer by the syntax limitations of + internet domain names. If possible the + internet host name as configured in + /etc/hostname + should be kept similar to this + one. Example: if this value is + Lennart's Computer + an Internet host name of + lennarts-computer + might be a good choice. If this + parameter is not set an application + should fall back to the Internet host + name for presentation + purposes. + + + + ICON_NAME= + + An icon identifying + this machine according to the XDG + Icon Naming Specification. If + this parameter is not set an + application should fall back to + computer or a + similar icon name. + + + + + + + + Example + + PRETTY_HOSTNAME="Lennart's Computer" +ICON_NAME=computer-laptop + + + + See Also + + systemd1, + os-release5, + hostname5, + machine-id5 + + + + diff --git a/man/modules-load.d.xml b/man/modules-load.d.xml new file mode 100644 index 000000000..e2f7d5c68 --- /dev/null +++ b/man/modules-load.d.xml @@ -0,0 +1,112 @@ + + + + + + + + modules-load.d + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + modules-load.d + 5 + + + + modules-load.d + Configure kernel modules to load at boot + + + + /etc/modules-load.d/*.conf + /run/modules-load.d/*.conf + /usr/lib/modules-load.d/*.conf + + + + Description + + systemd uses + files from the above directories to configure + kernel modules to load during boot in a static list. + Each configuration file is named in the style of + /etc/modules-load.d/<program>.conf. Note + that it is usually a better idea to use the automatic + module loading by PCI ID, by DMI ID or similar + triggers configured in the kernel modules themselves + instead of relying on static configuration like + this. + + + + Configuration Format + + The configuration files should simply contain a + list of kernel module names to load, separated by + newlines. Empty lines and lines whose first + non-whitespace character is # or ; are ignored. + + Each configuration file is named in the style of + <program>.conf. + Files in /etc/ overwrite + files with the same name in /usr/lib/. + Files in /run overwrite files with + the same name in /etc/ and + /usr/lib/. Packages should install their + configuration files in /usr/lib/, files + in /etc/ are reserved for the local + administration, which possibly decides to overwrite the + configurations installed from packages. All files are sorted + by filename in alphabetical order, regardless in which of the + directories they reside, to ensure that a specific + configuration file takes precedence over another file with + an alphabetically later name. + + + + Example + + /etc/modules-load.d/virtio-net.conf example: + + # Load virtio-net.ko at boot +virtio-net + + + + + See Also + + systemd1, + modprobe8 + + + + diff --git a/man/os-release.xml b/man/os-release.xml new file mode 100644 index 000000000..ff8fdf16b --- /dev/null +++ b/man/os-release.xml @@ -0,0 +1,350 @@ + + + + + + + + + os-release + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + os-release + 5 + + + + os-release + Operating system identification + + + + /etc/os-release + + + + Description + + The /etc/os-release file + contains operating system identification data. + + The basic file format of + os-release is a newline-separated + list of environment-like shell-compatible variable + assignments. It is possible to source the + configuration from shell scripts, however, beyond mere + variable assignments no shell features are supported + (this means variable expansion is explicitly not + supported), allowing applications to read the file + without implementing a shell compatible execution + engine. Variable assignment values should be enclosed + in double or single quotes if they include spaces, + semicolons or other special characters outside of A-Z, + a-z, 0-9. All strings should be in UTF-8 format, and + non-printable characters should not be used. If double + or single quotes or backslashes are to be used within + variable assignments they should be escaped with + backslashes, following shell style. It is not + supported to concatenate multiple individually quoted + strings. Lines beginning with "#" shall be ignored as + comments. + + /etc/os-release contains + data that is defined by the operating system vendor + and should not be changed by the administrator. + + As this file only encodes names and identifiers + it should not be localized. + + The file /etc/os-release might + be a symlink to another file, but it is important that + the file is available from earliest boot on, and hence + must be located on the root file system. + + For a longer rationale for + /etc/os-release please refer to + the Announcement of /etc/os-release. + + + + Options + + The following OS identifications parameters may be set using + /etc/os-release: + + + + + NAME= + + A string identifying + the operating system, without a + version component, and suitable for + presentation to the user. If not set + defaults to + NAME=Linux. Example: + NAME=Fedora or + NAME="Debian + GNU/Linux". + + + + VERSION= + + A string identifying + the operating system version, + excluding any OS name information, + possibly including a release code + name, and suitable for presentation to + the user. This field is + optional. Example: + VERSION=17 or + VERSION="17 (Beefy + Miracle)". + + + + ID= + + A lower-case string + (no spaces or other characters outside + of 0-9, a-z, ".", "_" and "-") + identifying the operating system, + excluding any version information and + suitable for processing by scripts or + usage in generated file names. If not + set defaults to + ID=linux. Example: + ID=fedora or + ID=debian. + + + + ID_LIKE= + + A space-separated list + of operating system identifiers in the + same syntax as the + ID= setting. Should + list identifiers of operating systems + that are closely related to the local + operating system in regards to + packaging and programming interfaces, + for example listing one or more + OS identifiers the local + OS is a derivative from. An + OS should generally only list other OS + identifiers it itself is a derivative + from, and not any OSes that + are derived from it, but symmetric + relationships are possible. Build + scripts and similar should check this + variable if they need to identify the + local operating system and the value + of ID= is not + recognized. Operating systems should + be listed in order of how closely the + local operating system relates to the + listed ones, starting with the + closest. This field is + optional. Example: for an operating + system with + ID=centos an + assignment of ID_LIKE="rhel + fedora" would be + appropriate. For an operating system + with ID=ubuntu an + assignment of + ID_LIKE=debian is + appropriate. + + + + VERSION_ID= + + A lower-case string + (mostly numeric, no spaces or other + characters outside of 0-9, a-z, ".", + "_" and "-") identifying the operating + system version, excluding any OS name + information or release code name, and + suitable for processing by scripts or + usage in generated file names. This + field is optional. Example: + VERSION_ID=17 or + VERSION_ID=11.04. + + + + PRETTY_NAME= + + A pretty operating + system name in a format suitable for + presentation to the user. May or may + not contain a release code name or OS + version of some kind, as suitable. If + not set defaults to + PRETTY_NAME="Linux". Example: + PRETTY_NAME="Fedora 17 (Beefy + Miracle)". + + + + ANSI_COLOR= + + A suggested + presentation color when showing the + OS name on the console. This + should be specified as string suitable + for inclusion in the ESC [ m + ANSI/ECMA-48 escape code for setting + graphical rendition. This field is + optional. Example: + ANSI_COLOR="0;31" + for red, or + ANSI_COLOR="1;34" + for light blue. + + + + CPE_NAME= + + A CPE name for the + operating system, following the Common + Platform Enumeration + Specification as proposed by + the MITRE Corporation. This field + is optional. Example: + CPE_NAME="cpe:/o:fedoraproject:fedora:17" + + + + + HOME_URL= + SUPPORT_URL= + BUG_REPORT_URL= + + Links to resources on + the Internet related the operating + system. HOME_URL= + should refer to the homepage of the of + operating system, or alternatively + some homepage of the specific version + of the operating + system. SUPPORT_URL= + should refer to the main support page + for the operating system, if there is + any. This is primarily intended for + operating systems which vendors + provide support + for. BUG_REPORT_URL= + should refer to the main bug reporting + page for the operating system, if + there is any. This is primarily + intended for operating systems that + rely on community QA. These settings + are optional, and providing only some + of these settings is common. These + URLs are intended to be exposed in + "About this system" UIs behind links + with captions such as "About this + Operating System", "Obtain Support" + resp. "Report a Bug". The values should + be in RFC3986 + format, and should be + http: or + https: URLs, and + possibly mailto: or + tel:. Only one URL + shall be listed in each setting. If + multiple resources need to be + referenced it is recommended to + provide an online landing page linking + all available resources. Examples: + HOME_URL="https://fedoraproject.org/" + and + BUG_REPORT_URL="https://bugzilla.redhat.com/" + + + + + + If you are reading this file from C code or a + shell script to determine the OS or a specific version + of it, use the ID and VERSION_ID fields, possibly with + ID_LIKE as fallback for ID. When looking for an OS + identification string for presentation to the user use + the PRETTY_NAME field. + + Note that operating system vendors may choose + not to provide version information, for example to + accommodate for rolling releases. In this case VERSION + and VERSION_ID may be unset. Applications should not + rely on these fields to be set. + + Operating system vendors may extend the file + format and introduce new fields. It is highly + recommended to prefix new fields with an OS specific + name in order to avoid name clashes. Applications + reading this file must ignore unknown fields. Example: + DEBIAN_BTS="debbugs://bugs.debian.org/" + + + + Example + + NAME=Fedora +VERSION="17 (Beefy Miracle)" +ID=fedora +VERSION_ID=17 +PRETTY_NAME="Fedora 17 (Beefy Miracle)" +ANSI_COLOR="0;34" +CPE_NAME="cpe:/o:fedoraproject:fedora:17" +HOME_URL="https://fedoraproject.org/" +BUG_REPORT_URL="https://bugzilla.redhat.com/" + + + + See Also + + systemd1, + lsb_release1, + hostname5, + machine-id5, + machine-info5 + + + + diff --git a/man/pam_systemd.xml b/man/pam_systemd.xml new file mode 100644 index 000000000..c07b46bab --- /dev/null +++ b/man/pam_systemd.xml @@ -0,0 +1,316 @@ + + + + + + + + + pam_systemd + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + pam_systemd + 8 + + + + pam_systemd + Register user sessions in the systemd control group hierarchy + + + + + pam_systemd.so + + + + + Description + + pam_systemd registers user + sessions in the systemd control group + hierarchy. + + On login, this module ensures the following: + + + If it does not exist yet, the + user runtime directory + /run/user/$USER is + created and its ownership changed to the user + that is logging in. + + The + $XDG_SESSION_ID environment + variable is initialized. If auditing is + available and + pam_loginuid.so run before + this module (which is highly recommended), the + variable is initialized from the auditing + session id + (/proc/self/sessionid). Otherwise + an independent session counter is + used. + + A new control group + /user/$USER/$XDG_SESSION_ID + is created and the login process moved into + it. + + + On logout, this module ensures the following: + + + If + $XDG_SESSION_ID is set and + specified, all + remaining processes in the + /user/$USER/$XDG_SESSION_ID + control group are killed and the control group + is removed. + + If last subgroup of the + /user/$USER control group + was removed the + $XDG_RUNTIME_DIR directory + and all its contents are + removed, too. + + + If the system was not booted up with systemd as + init system, this module does nothing and immediately + returns PAM_SUCCESS. + + + + + Options + + The following options are understood: + + + + + + Takes a boolean + argument. If true, all processes + created by the user during his session + and from his session will be + terminated when he logs out from his + session. + + + + + + Takes a comma + separated list of user names or + numeric user ids as argument. If this + option is used the effect of the + options + will apply only to the listed + users. If this option is not used the + option applies to all local + users. Note that + + takes precedence over this list and is + hence subtracted from the list + specified here. + + + + + + Takes a comma + separated list of user names or + numeric user ids as argument. Users + listed in this argument will not be + subject to the effect of + . Note + that that this option takes precedence + over + , and + hence whatever is listed for + + is guaranteed to never be killed by + this PAM module, independent of any + other configuration + setting. + + + + + + Takes a comma + separated list of control group + controllers in which hierarchies a + user/session control group will be + created by default for each user + logging in, in addition to the control + group in the named 'name=systemd' + hierarchy. If omitted, defaults to an + empty list. + + + + + + Takes a comma + separated list of control group + controllers in which hierarchies the + logged in processes will be reset to + the root control + group. + + + + + + Takes a boolean + argument. If yes, the module will log + debugging information as it + operates. + + + + Note that setting + kill-session-processes=1 will break tools + like + screen1. + + Note that + kill-session-processes=1 is a + stricter version of + KillUserProcesses=1 which may be + configured system-wide in + logind.conf5. The + former kills processes of a session as soon as it + ends, the latter kills processes as soon as the last + session of the user ends. + + If the options are omitted they default to + , + , + , + , + , + . + + + + Module Types Provided + + Only is provided. + + + + Environment + + The following environment variables are set for the processes of the user's session: + + + + $XDG_SESSION_ID + + A session identifier, + suitable to be used in file names. The + string itself should be considered + opaque, although often it is just the + audit session ID as reported by + /proc/self/sessionid. Each + ID will be assigned only once during + machine uptime. It may hence be used + to uniquely label files or other + resources of this + session. + + + + $XDG_RUNTIME_DIR + + Path to a user-private + user-writable directory that is bound + to the user login time on the + machine. It is automatically created + the first time a user logs in and + removed on his final logout. If a user + logs in twice at the same time, both + sessions will see the same + $XDG_RUNTIME_DIR + and the same contents. If a user logs + in once, then logs out again, and logs + in again, the directory contents will + have been lost in between, but + applications should not rely on this + behaviour and must be able to deal with + stale files. To store session-private + data in this directory the user should + include the value of $XDG_SESSION_ID + in the filename. This directory shall + be used for runtime file system + objects such as AF_UNIX sockets, + FIFOs, PID files and similar. It is + guaranteed that this directory is + local and offers the greatest possible + file system feature set the + operating system + provides. + + + + + + Example + + #%PAM-1.0 +auth required pam_unix.so +auth required pam_nologin.so +account required pam_unix.so +password required pam_unix.so +session required pam_unix.so +session required pam_loginuid.so +session required pam_systemd.so kill-session-processes=1 + + + + See Also + + pam.conf5, + pam.d5, + pam8, + pam_loginuid8, + logind.conf5, + systemd1 + + + + diff --git a/man/runlevel.xml b/man/runlevel.xml new file mode 100644 index 000000000..160d1b14e --- /dev/null +++ b/man/runlevel.xml @@ -0,0 +1,154 @@ + + + + + + + + + runlevel + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + runlevel + 8 + + + + runlevel + Print previous and current SysV runlevel + + + + + runlevel options + + + + + Description + + runlevel prints the previous + and current SysV runlevel if they are known. + + The two runlevel characters are separated by a + single space character. If a runlevel cannot be + determined, N is printed instead. If neither can be + determined, the word "unknown" is printed. + + Unless overridden in the environment, this will + check the utmp database for recent runlevel + changes. + + + + Options + + The following option is understood: + + + + + + Prints a short help + text and exits. + + + + + + + Exit status + + If one or both runlevels could be determined, 0 + is returned, a non-zero failure code otherwise. + + + + + Environment + + + + $RUNLEVEL + + If + $RUNLEVEL is set, + runlevel will print + this value as current runlevel and + ignore utmp. + + + + $PREVLEVEL + + If + $PREVLEVEL is set + runlevel will print + this value as previous runlevel and + ignore utmp. + + + + + + Files + + + + /var/run/utmp + + The utmp database + runlevel reads the + previous and current runlevel + from. + + + + + + + Notes + + This is a legacy command available for compatibility + only. It should not be used anymore, as the concept of + runlevels is obsolete. + + + + See Also + + systemd1, + systemctl1 + + + + diff --git a/man/sd-daemon.xml b/man/sd-daemon.xml new file mode 100644 index 000000000..4ea88e43d --- /dev/null +++ b/man/sd-daemon.xml @@ -0,0 +1,172 @@ + + + + + + + + + sd-daemon + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd-daemon + 7 + + + + sd-daemon + Reference implementation of APIs for + new-style daemons + + + + + #include <systemd/sd-daemon.h> + + + + pkg-config --cflags --libs libsystemd-daemon + + + + + + Description + + sd-daemon.c and + sd-daemon.h provide a reference + implementation of various APIs for new-style daemons, + as implemented by the + systemd1 + init system. + + See + sd_listen_fds3, + sd_notify3, + sd_booted3, + sd_is_fifo3 + for more information about the functions + implemented. In addition to these functions a couple + of logging prefixes are defined as macros: + + #define SD_EMERG "<0>" /* system is unusable */ +#define SD_ALERT "<1>" /* action must be taken immediately */ +#define SD_CRIT "<2>" /* critical conditions */ +#define SD_ERR "<3>" /* error conditions */ +#define SD_WARNING "<4>" /* warning conditions */ +#define SD_NOTICE "<5>" /* normal but significant condition */ +#define SD_INFO "<6>" /* informational */ +#define SD_DEBUG "<7>" /* debug-level messages */ + + These prefixes are intended to be used in + conjunction with STDERR-based logging as implemented + by systemd. If a systemd service definition file is + configured with StandardError=syslog + or StandardError=kmsg these + prefixes can be used to encode a log level in lines + printed. This is similar to the kernel + printk()-style logging. See + klogctl2 + for more information. + + The log levels are identical to + syslog3's + log level system. To use these prefixes simply prefix + every line with one of these strings. A line that is + not prefixed will be logged at the default log level + SD_INFO. + + + Hello World + + A daemon may log with the log level + NOTICE by issuing this call: + + fprintf(stderr, SD_NOTICE "Hello World!\n"); + + + + + Notes + + These interfaces are provided by the reference + implementation of APIs for new-style daemons and + distributed with the systemd package. The algorithms + they implement are simple, and can easily be + reimplemented in daemons if it is important to support + this interface without using the reference + implementation. See the respective function man pages + for details. + + In addition, for details about the algorithms + check the liberally licensed reference implementation + sources: + + resp. + + These APIs are implemented in the reference + implementation's sd-daemon.c and + sd-daemon.h files. These + interfaces are available as shared library, which can + be compiled and linked to with the + libsystemd-daemon + pkg-config1 + file. Alternatively, applications consuming these APIs + may copy the implementation into their source tree, + either verbatim or in excerpts. + + The functions directly related to new-style + daemons become NOPs when -DDISABLE_SYSTEMD is set + during compilation and the reference implementation is + used as drop-in files. In addition, if + sd-daemon.c is compiled on + non-Linux systems they become NOPs. + + + + See Also + + systemd1, + sd_listen_fds3, + sd_notify3, + sd_booted3, + sd_is_fifo3, + daemon7, + systemd.service5, + systemd.socket5, + fprintf3, + sd-readahead7, + pkg-config1 + + + + diff --git a/man/sd-login.xml b/man/sd-login.xml new file mode 100644 index 000000000..3fc0e16f6 --- /dev/null +++ b/man/sd-login.xml @@ -0,0 +1,146 @@ + + + + + + + + + sd-login + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd-login + 7 + + + + sd-login + APIs for + tracking logins + + + + + #include <systemd/sd-login.h> + + + + pkg-config --cflags --libs libsystemd-login + + + + + Description + + sd-login.h provides APIs to + introspect and monitor seat, login session and user + status information on the local system. + + See Multi-Seat + on Linux for an introduction into multi-seat + support on Linux, the background for this set of APIs. + + Note that these APIs only allow purely passive access + and monitoring of seats, sessions and users. To + actively make changes to the seat configuration, + terminate login sessions, or switch session on a seat + you need to utilize the D-Bus API of + systemd-logind, instead. + + These functions synchronously access data in + /proc, + /sys/fs/cgroup and + /run. All of these are virtual + file systems, hence the runtime cost of the accesses + is relatively cheap. + + It is possible (and often a very good choice) to + mix calls to the synchronous interface of + sd-login.h with the asynchronous + D-Bus interface of systemd-logind. However, if this is + done you need to think a bit about possible races + since the stream of events from D-Bus and from + sd-login.h interfaces such as the + login monitor are asynchronous and not ordered against + each other. + + If the functions return string arrays, these are + generally NULL terminated and need to be freed by the + caller with the libc + free3 + call after use, including the strings referenced + therein. Similar, individual strings returned need to + be freed, as well. + + As a special exception, instead of an empty + string array NULL may be returned, which should be + treated equivalent to an empty string array. + + See + sd_pid_get_session3, + sd_uid_get_state3, + sd_session_is_active3, + sd_seat_get_active3, + sd_get_seats3, + sd_login_monitor_new3 + for more information about the functions + implemented. + + + + Notes + + These APIs are implemented as shared library, + which can be compiled and linked to with the + libsystemd-login + pkg-config1 + file. + + + + See Also + + systemd1, + sd_pid_get_session3, + sd_uid_get_state3, + sd_session_is_active3, + sd_seat_get_active3, + sd_get_seats3, + sd_login_monitor_new3, + sd-daemon7, + sd-readahead7, + pkg-config1 + + + + diff --git a/man/sd-readahead.xml b/man/sd-readahead.xml new file mode 100644 index 000000000..7fb863412 --- /dev/null +++ b/man/sd-readahead.xml @@ -0,0 +1,117 @@ + + + + + + + + + sd-readahead + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd-readahead + 7 + + + + sd-readahead + Reference implementation of APIs for + controlling boot-time read-ahead + + + + + #include "sd-readahead.h" + + + + + Description + + sd-readahead.c and + sd-readahead.h provide a + reference implementation for APIs for controlling boot-time + read-ahead, as implemented by the read-ahead subsystem + of the + systemd1 + init system. + + See + sd_readahead3 + for more information about the function + implemented. + + + + Notes + + This interface is provided by the reference + implementation of APIs for controlling boot-time + read-ahead and distributed with the systemd + package. The algorithms it implements are simple, and + can easily be reimplemented in daemons if it is + important to support this interface without using the + reference implementation. See the respective function + man pages for details. + + In addition, for details about the algorithms + check the liberally licensed reference implementation + sources: + + resp. + + These APIs are implemented in the reference + implementation's drop-in + sd-readahead.c and + sd-readahead.h files. It is + recommended that applications consuming these APIs copy + the implementation into their source tree, either + verbatim or in excerpts. These interfaces are + currently not available in a dynamic library. + + The functions provided by this interface become + NOPs when -DDISABLE_SYSTEMD is set during + compilation. In addition, if + sd-readhead.c is compiled on + non-Linux systems it becomes NOPs. + + + + See Also + + systemd1, + sd_readahead3, + sd-daemon7 + + + + diff --git a/man/sd_booted.xml b/man/sd_booted.xml new file mode 100644 index 000000000..141625d9a --- /dev/null +++ b/man/sd_booted.xml @@ -0,0 +1,128 @@ + + + + + + + + + sd_booted + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd_booted + 3 + + + + sd_booted + Test whether the system is running the systemd init system. + + + + + #include <systemd/sd-daemon.h> + + + int sd_booted + void + + + + + + Description + sd_booted() checks whether + the system was booted up using the systemd init system. + + + + Return Value + + On failure, this call returns a negative + errno-style error code. If the system was booted up + with systemd as init system, this call returns a + positive return value, zero otherwise. + + + + Notes + + This function is provided by the reference + implementation of APIs for new-style daemons and + distributed with the systemd package. The algorithm it + implements is simple, and can easily be reimplemented + in daemons if it is important to support this + interface without using the reference + implementation. + + Internally, this function checks whether the + /sys/fs/cgroup/systemd virtual file + system is mounted, by comparing the st_dev value of + the stat() data of + /sys/fs/cgroup and + /sys/fs/cgroup/systemd. + + For details about the algorithm check the + liberally licensed reference implementation sources: + + resp. + + sd_booted() is implemented + in the reference implementation's + sd-daemon.c and + sd-daemon.h files. These + interfaces are available as shared library, which can + be compiled and linked to with the + libsystemd-daemon + pkg-config1 + file. Alternatively, applications consuming these APIs + may copy the implementation into their source + tree. For more details about the reference + implementation see + sd_daemon7. + + If the reference implementation is used as + drop-in files and -DDISABLE_SYSTEMD is set during + compilation this function will always return 0 and + otherwise become a NOP. + + + + See Also + + systemd1, + sd_daemon7 + + + + diff --git a/man/sd_get_seats.xml b/man/sd_get_seats.xml new file mode 100644 index 000000000..2ac76500e --- /dev/null +++ b/man/sd_get_seats.xml @@ -0,0 +1,127 @@ + + + + + + + + + sd_get_seats + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd_get_seats + 3 + + + + sd_get_seats + sd_get_sessions + sd_get_uids + Determine available seats, sessions and logged in users + + + + + #include <systemd/sd-login.h> + + + int sd_get_seats + char*** seats + + + + int sd_get_sessions + char*** sessions + + + + int sd_get_uids + char*** sessions + + + + + + + Description + + sd_get_seats() may be used + to determine all currently available local + seats. Returns a NULL terminated array of seat + identifiers. The returned array and all strings it + references need to be freed with the libc + free3 + call after use. Note that instead of an empty array + NULL may be returned and should be considered + equivalent to an empty array. + + Similar, sd_get_sessions() may + be used to determine all current login sessions. + + Similar, sd_get_uids() may + be used to determine all Unix users who currently have login sessions. + + + + Return Value + + On success sd_get_seats(), + sd_get_sessions() and + sd_get_uids() return the number + of entries in the arrays. On failure, these calls + return a negative errno-style error code. + + + + Notes + + The sd_get_seats(), + sd_get_sessions() and + sd_get_uids() interfaces + are available as shared library, which can be compiled + and linked to with the + libsystemd-login + pkg-config1 + file. + + + + See Also + + + systemd1, + sd-login7, + sd_session_get_seat3 + + + + diff --git a/man/sd_is_fifo.xml b/man/sd_is_fifo.xml new file mode 100644 index 000000000..4db512012 --- /dev/null +++ b/man/sd_is_fifo.xml @@ -0,0 +1,217 @@ + + + + + + + + + sd_is_fifo + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd_is_fifo + 3 + + + + sd_is_fifo + sd_is_socket + sd_is_socket_inet + sd_is_socket_unix + sd_is_mq + Check the type of a file descriptor + + + + + #include <systemd/sd-daemon.h> + + + int sd_is_fifo + int fd + const char *path + + + + int sd_is_socket + int fd + int family + int type + int listening + + + + int sd_is_socket_inet + int fd + int family + int type + int listening + uint16_t port + + + + int sd_is_socket_unix + int fd + int type + int listening + const char* path + size_t length + + + + int sd_is_mq + int fd + const char *path + + + + + + + Description + + sd_is_fifo() may be called + to check whether the specified file descriptor refers + to a FIFO or pipe. If the path + parameter is not NULL, it is checked whether the FIFO + is bound to the specified file system path. + + sd_is_socket() may be + called to check whether the specified file descriptor + refers to a socket. It the + family parameter is not + AF_UNSPEC it is checked whether the socket is of the + specified family (AF_UNIX, AF_INET, ...). If the + type parameter is not 0 it is + checked whether the socket is of the specified type + (SOCK_STREAM, SOCK_DGRAM, ...). If the + listening parameter is positive + it is checked whether the socket is in accepting mode, + i.e. listen() has been called for + it. If listening is 0, it is + checked whether the socket is not in this mode. If the + parameter is negative, no such check is made. The + listening parameter should only + be used for stream sockets and should be set to a + negative value otherwise. + + sd_is_socket_inet() is + similar to sd_is_socket(), but + optionally checks the IPv4 or IPv6 port number the + socket is bound to, unless port + is zero. For this call family + must be passed as either AF_UNSPEC, AF_INET or + AF_INET6. + + sd_is_socket_unix() is + similar to sd_is_socket(), but + optionally checks the AF_UNIX path the socket is bound + to, unless the path parameter + is NULL. For normal file system AF_UNIX sockets set + the length parameter to 0. For + Linux abstract namespace sockets set the + length to the size of the + address, including the initial 0 byte and set + path to the initial 0 byte of + the socket address. + + sd_is_mq() may be called to + check whether the specified file descriptor refers to + a POSIX message queue. If the + path parameter is not NULL, it + is checked whether the message queue is bound to the + specified name. + + + + Return Value + + On failure, these calls return a negative + errno-style error code. If the file descriptor is of + the specified type and bound to the specified address + a positive return value is returned, otherwise + zero. + + + + Notes + + These functions are provided by the reference + implementation of APIs for new-style daemons and + distributed with the systemd package. The algorithms + they implement are simple, and can easily be + reimplemented in daemons if it is important to support + this interface without using the reference + implementation. + + Internally, these function use a combination of + fstat() and + getsockname() to check the file + descriptor type and where it is bound to. + + For details about the algorithms check the + liberally licensed reference implementation sources: + + resp. + + sd_is_fifo() and the + related functions are implemented in the reference + implementation's sd-daemon.c and + sd-daemon.h files. These + interfaces are available as shared library, which can + be compiled and linked to with the + libsystemd-daemon + pkg-config1 + file. Alternatively, applications consuming these APIs + may copy the implementation into their source + tree. For more details about the reference + implementation see + sd_daemon7. + + These functions continue to work as described, + even if -DDISABLE_SYSTEMD is set during + compilation. + + + + See Also + + systemd1, + sd-daemon7, + sd_listen_fds3, + systemd.service5, + systemd.socket5 + + + + diff --git a/man/sd_listen_fds.xml b/man/sd_listen_fds.xml new file mode 100644 index 000000000..c3c70a0df --- /dev/null +++ b/man/sd_listen_fds.xml @@ -0,0 +1,203 @@ + + + + + + + + + sd_listen_fds + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd_listen_fds + 3 + + + + sd_listen_fds + Check for file descriptors passed by the init system. + + + + + #include <systemd/sd-daemon.h> + + #define SD_LISTEN_FDS_START 3 + + + int sd_listen_fds + int unset_environment + + + + + + Description + + sd_listen_fds() shall be + called by a daemon to check for file descriptors + passed by the init system as part of the socket-based + activation logic. + + If the unset_environment + parameter is non-zero + sd_listen_fds() will unset the + $LISTEN_FDS/$LISTEN_PID + environment variables before returning (regardless + whether the function call itself succeeded or + not). Further calls to + sd_listen_fds() will then fail, + but the variables are no longer inherited by child + processes. + + If a daemon receives more than one file + descriptor, they will be passed in the same order as + configured in the systemd socket definition + file. Nonetheless it is recommended to verify the + correct socket types before using them. To simplify + this checking the functions + sd_is_fifo3, + sd_is_socket3, + sd_is_socket_inet3, + sd_is_socket_unix3 + are provided. In order to maximize flexibility it is + recommended to make these checks as loose as possible + without allowing incorrect setups. i.e. often the + actual port number a socket is bound to matters little + for the service to work, hence it should not be + verified. On the other hand, whether a socket is a + datagram or stream socket matters a lot for the most + common program logics and should be checked. + + This function call will set the FD_CLOEXEC flag + for all passed file descriptors to avoid further + inheritance to children of the calling process. + + + + Return Value + + On failure, this call returns a negative + errno-style error code. If + $LISTEN_FDS/$LISTEN_PID + was not set or was not correctly set for this daemon and + hence no file descriptors were received, 0 is + returned. Otherwise the number of file descriptors + passed is returned. The application may find them + starting with file descriptor SD_LISTEN_FDS_START, + i.e. file descriptor 3. + + + + Notes + + This function is provided by the reference + implementation of APIs for new-style daemons and + distributed with the systemd package. The algorithm it + implements is simple, and can easily be reimplemented + in daemons if it is important to support this + interface without using the reference + implementation. + + Internally, this function checks whether the + $LISTEN_PID environment variable + equals the daemon PID. If not, it returns + immediately. Otherwise it parses the number passed in + the $LISTEN_FDS environment + variable, then sets the FD_CLOEXEC flag for the parsed + number of file descriptors starting from + SD_LISTEN_FDS_START. Finally it returns the parsed + number. + + For details about the algorithm check the + liberally licensed reference implementation sources: + + resp. + + sd_listen_fds() is + implemented in the reference implementation's + sd-daemon.c and + sd-daemon.h files. These + interfaces are available as shared library, which can + be compiled and linked to with the + libsystemd-daemon + pkg-config1 + file. Alternatively, applications consuming these APIs + may copy the implementation into their source + tree. For more details about the reference + implementation see + sd-daemon7. + + If the reference implementation is used as + drop-in files and -DDISABLE_SYSTEMD is set during + compilation this function will always return 0 and + otherwise become a NOP. + + + + Environment + + + + $LISTEN_PID + $LISTEN_FDS + + Set by the init system + for supervised processes that use + socket-based activation. This + environment variable specifies the + data + sd_listen_fds() + parses. See above for + details. + + + + + + See Also + + + systemd1, + sd-daemon7, + sd_is_fifo3, + sd_is_socket3, + sd_is_socket_inet3, + sd_is_socket_unix3, + daemon7, + systemd.service5, + systemd.socket5 + + + + diff --git a/man/sd_login_monitor_new.xml b/man/sd_login_monitor_new.xml new file mode 100644 index 000000000..de484329a --- /dev/null +++ b/man/sd_login_monitor_new.xml @@ -0,0 +1,172 @@ + + + + + + + + + sd_login_monitor_new + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd_login_monitor_new + 3 + + + + sd_login_monitor_new + sd_login_monitor_unref + sd_login_monitor_flush + sd_login_monitor_get_fd + Monitor login sessions, seats and users + + + + + #include <systemd/sd-login.h> + + + int sd_login_monitor_new + const char* category + sd_login_monitor** ret + + + + sd_login_monitor* sd_login_monitor_unref + sd_login_monitor* m + + + + int sd_login_monitor_flush + sd_login_monitor* m + + + + int sd_login_monitor_get_fd + sd_login_monitor* m + + + + + + + Description + + sd_login_monitor_new() may + be used to monitor login session, users and seats. Via + a monitor object a file descriptor can be integrated + into an application defined event loop which is woken + up each time a user logs in, logs out or a seat is + added or removed, or a session, user, or seat changes + state otherwise. The first parameter takes a string + which can be either seat (to get + only notifications about seats being added, removed or + changed), session (to get only + notifications about sessions being created or removed + or changed) or uid (to get only + notifications when a user changes state in respect to + logins). If notifications shall be generated in all + these conditions, NULL may be passed. Note that in + future additional categories may be defined. The + second parameter returns a monitor object and needs to + be freed with the + sd_login_monitor_unref() call + after use. + + sd_login_monitor_unref() + may be used to destroy a monitor object. Note that + this will invalidate any file descriptor returned by + sd_login_monitor_get_fd(). + + sd_login_monitor_flush() + may be used to reset the wakeup state of the monitor + object. Whenever an event causes the monitor to wake + up the event loop via the file descriptor this + function needs to be called to reset the wake-up + state. If this call is not invoked the file descriptor + will immediately wake up the event loop again. + + sd_login_monitor_get_fd() + may be used to retrieve the file descriptor of the + monitor object that may be integrated in an + application defined event loop, based around + poll2 + or a similar interface. The application should include + the returned file descriptor as wake up source for + POLLIN events. Whenever a wake-up is triggered the + file descriptor needs to be reset via + sd_login_monitor_flush(). An + application needs to reread the login state with a + function like + sd_get_seats3 + or similar to determine what changed. + + + + Return Value + + On success + sd_login_monitor_new() and + sd_login_monitor_flush() return 0 + or a positive integer. On success + sd_login_monitor_get_fd() returns + a Unix file descriptor. On failure, these calls return + a negative errno-style error code. + + sd_login_monitor_unref() + always returns NULL. + + + + Notes + + The sd_login_monitor_new(), + sd_login_monitor_unref(), sd_login_monitor_flush() and + sd_login_monitor_get_fd() interfaces + are available as shared library, which can be compiled + and linked to with the + libsystemd-login + pkg-config1 + file. + + + + See Also + + + systemd1, + sd-login7, + sd_get_seats3 + + + + diff --git a/man/sd_notify.xml b/man/sd_notify.xml new file mode 100644 index 000000000..9d9ea4132 --- /dev/null +++ b/man/sd_notify.xml @@ -0,0 +1,312 @@ + + + + + + + + + sd_notify + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd_notify + 3 + + + + sd_notify + sd_notifyf + Notify init system about start-up completion and other daemon status changes + + + + + #include <systemd/sd-daemon.h> + + + int sd_notify + int unset_environment + const char *state + + + + int sd_notifyf + int unset_environment + const char *format + ... + + + + + + Description + sd_notify() shall be called + by a daemon to notify the init system about status + changes. It can be used to send arbitrary information, + encoded in an environment-block-like string. Most + importantly it can be used for start-up completion + notification. + + If the unset_environment + parameter is non-zero sd_notify() + will unset the $NOTIFY_SOCKET + environment variable before returning (regardless + whether the function call itself succeeded or + not). Further calls to + sd_notify() will then fail, but + the variable is no longer inherited by child + processes. + + The state parameter + should contain an newline-separated list of variable + assignments, similar in style to an environment + block. A trailing newline is implied if none is + specified. The string may contain any kind of variable + assignments, but the following shall be considered + well-known: + + + + READY=1 + + Tells the init system + that daemon startup is finished. This + is only used by systemd if the service + definition file has Type=notify + set. The passed argument is a boolean + "1" or "0". Since there is little + value in signalling non-readiness, the + only value daemons should send is + "READY=1". + + + + STATUS=... + + Passes a single-line + status string back to the init system + that describes the daemon state. This + is free-form and can be used for + various purposes: general state + feedback, fsck-like programs could + pass completion percentages and + failing programs could pass a human + readable error message. Example: + "STATUS=Completed 66% of file system + check..." + + + + ERRNO=... + + If a daemon fails, the + errno-style error code, formatted as + string. Example: "ERRNO=2" for + ENOENT. + + + + BUSERROR=... + + If a daemon fails, the + D-Bus error-style error code. Example: + "BUSERROR=org.freedesktop.DBus.Error.TimedOut" + + + + MAINPID=... + + The main pid of the + daemon, in case the init system did + not fork off the process + itself. Example: + "MAINPID=4711" + + + + WATCHDOG=1 + + Tells systemd to + update the watchdog timestamp. + Services using this feature should do + this in regular intervals. A watchdog + framework can use the timestamps to + detect failed + services. + + + + It is recommended to prefix variable names that + are not shown in the list above with + X_ to avoid namespace + clashes. + + Note that systemd will accept status data sent + from a daemon only if the + NotifyAccess= option is correctly + set in the service definition file. See + systemd.service5 + for details. + + sd_notifyf() is similar to + sd_notify() but takes a + printf()-like format string plus + arguments. + + + + Return Value + + On failure, these calls return a negative + errno-style error code. If + $NOTIFY_SOCKET was not set and + hence no status data could be sent, 0 is returned. If + the status was sent these functions return with a + positive return value. In order to support both, init + systems that implement this scheme and those which + don't, it is generally recommended to ignore the return + value of this call. + + + + Notes + + These functions are provided by the reference + implementation of APIs for new-style daemons and + distributed with the systemd package. The algorithms + they implement are simple, and can easily be + reimplemented in daemons if it is important to support + this interface without using the reference + implementation. + + Internally, these functions send a single + datagram with the state string as payload to the + AF_UNIX socket referenced in the + $NOTIFY_SOCKET environment + variable. If the first character of + $NOTIFY_SOCKET is @ the string is + understood as Linux abstract namespace socket. The + datagram is accompanied by the process credentials of + the sending daemon, using SCM_CREDENTIALS. + + For details about the algorithms check the + liberally licensed reference implementation sources: + + resp. + + sd_notify() and + sd_notifyf() are implemented in + the reference implementation's + sd-daemon.c and + sd-daemon.h files. These + interfaces are available as shared library, which can + be compiled and linked to with the + libsystemd-daemon + pkg-config1 + file. Alternatively, applications consuming these APIs + may copy the implementation into their source tree. For + more details about the reference implementation see + sd_daemon7. + + If the reference implementation is used as + drop-in files and -DDISABLE_SYSTEMD is set during + compilation these functions will always return 0 and + otherwise become a NOP. + + + + Environment + + + + $NOTIFY_SOCKET + + Set by the init system + for supervised processes for status + and start-up completion + notification. This environment variable + specifies the socket + sd_notify() talks + to. See above for details. + + + + + + Examples + + + Start-up Notification + + When a daemon finished starting up, it + might issue the following call to notify + the init system: + + sd_notify(0, "READY=1"); + + + + Extended Start-up Notification + + A daemon could send the following after + completing initialization: + + sd_notifyf(0, "READY=1\n" + "STATUS=Processing requests...\n" + "MAINPID=%lu", + (unsigned long) getpid()); + + + + Error Cause Notification + + A daemon could send the following shortly before exiting, on failure + + sd_notifyf(0, "STATUS=Failed to start up: %s\n" + "ERRNO=%i", + strerror(errno), + errno); + + + + + See Also + + systemd1, + sd_daemon7, + daemon7, + systemd.service5 + + + + diff --git a/man/sd_pid_get_session.xml b/man/sd_pid_get_session.xml new file mode 100644 index 000000000..94f533022 --- /dev/null +++ b/man/sd_pid_get_session.xml @@ -0,0 +1,160 @@ + + + + + + + + + sd_pid_get_session + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd_pid_get_session + 3 + + + + sd_pid_get_session + sd_pid_get_unit + sd_pid_get_owner_uid + Determine session, service or owner of a session of a specific PID + + + + + #include <systemd/sd-login.h> + + + int sd_pid_get_session + pid_t pid + char** session + + + + int sd_pid_get_unit + pid_t pid + char** unit + + + + int sd_pid_get_owner_uid + pid_t pid + uid_t* uid + + + + + + Description + + sd_pid_get_session() may be + used to determine the login session identifier of a + process identified by the specified process + identifier. The session identifier is a short string, + suitable for usage in file system paths. Note that not + all processes are part of a login session (e.g. system + service processes, user processes that are shared + between multiple sessions of the same user, or kernel + threads). For processes not being part of a login + session this function will fail. The returned string + needs to be freed with the libc + free3 + call after use. + + sd_pid_get_unit() may be + used to determine the systemd unit (i.e. system + service) identifier of a process identified by the + specified process identifier. The unit name is a short + string, suitable for usage in file system paths. Note + that not all processes are part of a unit/service + (e.g. user processes, or kernel threads). For + processes not being part of a systemd unit/system + service this function will fail. The returned string + needs to be freed with the libc + free3 + call after use. + + sd_pid_get_owner_uid() may + be used to determine the Unix user identifier of the + owner of the session of a process identified the + specified PID. Note that this function will succeed + for user processes which are shared between multiple + login sessions of the same user, where + sd_pid_get_session() will + fail. For processes not being part of a login session + and not being a shared process of a user this function + will fail. + + If the pid paramater of any + of these functions is passed as 0 the operation is + executed for the calling process. + + + + Return Value + + On success these calls return 0 or a positive + integer. On failure, these calls return a negative + errno-style error code. + + + + Notes + + The sd_pid_get_session(), + sd_pid_get_pid(), and + sd_pid_get_owner_uid() interfaces + are available as shared library, which can be compiled + and linked to with the + libsystemd-login + pkg-config1 + file. + + Note that the login session identifier as + returned by sd_pid_get_session() + is completely unrelated to the process session + identifier as returned by + getsid2. + + + + See Also + + + systemd1, + sd-login7, + sd_session_is_active3, + getsid2 + + + + diff --git a/man/sd_readahead.xml b/man/sd_readahead.xml new file mode 100644 index 000000000..2e7e09c5e --- /dev/null +++ b/man/sd_readahead.xml @@ -0,0 +1,178 @@ + + + + + + + + + sd_readahead + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd_readahead + 3 + + + + sd_readahead + Control ongoing disk boot-time read-ahead operations + + + + + #include "sd-readahead.h" + + + int sd_readahead + const char *action + + + + + + Description + sd_readahead() may be + called by programs involved with early boot-up to + control ongoing boot-time disk read-ahead operations. It may be + used to terminate read-ahead operations in case an + uncommon disk access pattern is to be expected and + hence read-ahead replay or collection is unlikely to + have the desired speed-up effect on the current or + future boot-ups. + + The action should be one + of the following strings: + + + + cancel + + Terminates read-ahead + data collection, and drops all + read-ahead data collected during this + boot-up. + + + + done + + Terminates read-ahead + data collection, but keeps all + read-ahead data collected during this + boot-up around for use during + subsequent boot-ups. + + + + noreplay + + Terminates read-ahead + replay. + + + + + + + + Return Value + + On failure, these calls return a negative + errno-style error code. It is generally recommended to + ignore the return value of this call. + + + + Notes + + This function is provided by the reference + implementation of APIs for controlling boot-time + read-ahead and distributed with the systemd + package. The algorithm it implements is simple, and + can easily be reimplemented in daemons if it is + important to support this interface without using the + reference implementation. + + Internally, this function creates a file in + /run/systemd/readahead/ which is + then used as flag file to notify the read-ahead + subsystem. + + For details about the algorithm check the + liberally licensed reference implementation sources: + + resp. + + sd_readahead() is + implemented in the reference implementation's drop-in + sd-readahead.c and + sd-readahead.h files. It is + recommended that applications consuming this API copy + the implementation into their source tree. For more + details about the reference implementation see + sd-readahead7 + + If -DDISABLE_SYSTEMD is set during compilation + this function will always return 0 and otherwise + become a NOP. + + + + Examples + + + Cancelling all read-ahead operations + + During boots where SELinux has to + relabel the file system hierarchy, it will + create a large amount of disk accesses that + are not necessary during normal boots. Hence + it is a good idea to disable both read-ahead replay and read-ahead collection. + + + sd_readahead("cancel"); +sd_readahead("noreplay"); + + + + + + See Also + + systemd1, + sd-readahead7, + daemon7 + + + + diff --git a/man/sd_seat_get_active.xml b/man/sd_seat_get_active.xml new file mode 100644 index 000000000..acc6ee4ea --- /dev/null +++ b/man/sd_seat_get_active.xml @@ -0,0 +1,157 @@ + + + + + + + + + sd_seat_get_active + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd_seat_get_active + 3 + + + + sd_seat_get_active + sd_seat_get_sessions + sd_seat_can_multi_session + Determine state of a specific seat + + + + + #include <systemd/sd-login.h> + + + int sd_seat_get_active + const char* seat + char** session + uid_t* uid + + + + int sd_seat_get_sessions + const char* seat + char*** sessions + uid_t** uid + unsigned* n_uids + + + + int sd_seat_can_multi_session + const char* seat + + + + + + Description + + sd_seat_get_active() may be + used to determine which session is currently active on + a seat, if there is any. Returns the session + identifier and the user identifier of the Unix user + the session is belonging to. Either the session or the + user identifier parameter can be be passed NULL, in + case only one of the parameters shall be queried. The + returned string needs to be freed with the libc + free3 + call after use. + + sd_seat_get_sessions() may + be used to determine all sessions on the specified + seat. Returns two arrays, one (NULL terminated) with + the session identifiers of the sessions and one with + the user identifiers of the Unix users the sessions + belong to. An additional parameter may be used to + return the number of entries in the latter array. The + two arrays and the latter parameter may be passed as + NULL in case these values need not to be + determined. The arrays and the strings referenced by + them need to be freed with the libc + free3 + call after use. Note that instead of an empty array + NULL may be returned and should be considered + equivalent to an empty array. + + sd_seat_can_multi_session() + may be used to determine whether a specific seat is + capable of multi-session, i.e. allows multiple login + sessions in parallel (whith only one being active at a + time). + + If the seat parameter of any + of these functions is passed as NULL the operation is + executed for the seat of the session of the calling + process, if there is any. + + + + Return Value + + On success + sd_seat_get_active() return + return 0 or a positive integer. On success + sd_seat_get_sessions() returns + the number of entries in the session identifier + array. If the test succeeds + sd_seat_can_multi_session returns + a positive integer, if it fails 0. On failure, these + calls return a negative errno-style error code. + + + + Notes + + The sd_seat_get_active(), + sd_seat_get_sessions(), and + sd_seat_can_multi_session() interfaces + are available as shared library, which can be compiled + and linked to with the + libsystemd-login + pkg-config1 + file. + + + + See Also + + + systemd1, + sd-login7, + sd_session_get_seat3 + + + + diff --git a/man/sd_session_is_active.xml b/man/sd_session_is_active.xml new file mode 100644 index 000000000..afdeed55d --- /dev/null +++ b/man/sd_session_is_active.xml @@ -0,0 +1,206 @@ + + + + + + + + + sd_session_is_active + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd_session_is_active + 3 + + + + sd_session_is_active + sd_session_get_uid + sd_session_get_seat + sd_session_get_service + sd_session_get_type + sd_session_get_class + sd_session_get_display + Determine state of a specific session + + + + + #include <systemd/sd-login.h> + + + int sd_session_is_active + const char* session + + + + int sd_session_get_uid + const char* session + uid_t* uid + + + + int sd_session_get_seat + const char* session + char** seat + + + + int sd_session_get_service + const char* session + char** service + + + + int sd_session_get_type + const char* session + char** type + + + + int sd_session_get_class + const char* session + char** class + + + + int sd_session_get_display + const char* session + char** display + + + + + + Description + + sd_session_is_active() may + be used to determine whether the session identified by + the specified session identifier is currently active + (i.e. currently in the foreground and available for + user input) or not. + + sd_session_get_uid() may be + used to determine the user identifier of the Unix user the session + identified by the specified session identifier belongs + to. + + sd_session_get_seat() may + be used to determine the seat identifier of the seat + the session identified by the specified session + identifier belongs to. Note that not all sessions are + attached to a seat, this call will fail for them. The + returned string needs to be freed with the libc + free3 + call after use. + + sd_session_get_service() + may be used to determine the name of the service (as + passed during PAM session setup) that registered the + session identified by the specified session + identifier. The returned string needs to be freed with + the libc + free3 + call after use. + + sd_session_get_type() may + be used to determine the type of the session + identified by the specified session identifier. The + returned string is one of x11, + tty or + unspecified and needs to be freed + with the libc + free3 + call after use. + + sd_session_get_class() may + be used to determine the class of the session + identified by the specified session identifier. The + returned string is one of user, + greeter or + lock-screen and needs to be freed + with the libc + free3 + call after use. + + sd_session_get_display() + may be used to determine the X11 display of the + session identified by the specified session + identifier. The returned string is one of needs to be + freed with the libc + free3 + call after use. + + If the session parameter of + any of these functions is passed as NULL the operation + is executed for the session the calling process is a + member of, if there is any. + + + + Return Value + + If the test succeeds + sd_session_is_active() returns a + positive integer, if it fails 0. On success + sd_session_get_uid(), + sd_session_get_service() and + sd_session_get_seat() return 0 or + a positive integer. On failure, these calls return a + negative errno-style error code. + + + + Notes + + The sd_session_is_active(), + sd_session_get_uid(), + sd_session_get_service() and + sd_session_get_seat() interfaces + are available as shared library, which can be compiled + and linked to with the + libsystemd-login + pkg-config1 + file. + + + + See Also + + + systemd1, + sd-login7, + sd_pid_get_session3 + + + + diff --git a/man/sd_uid_get_state.xml b/man/sd_uid_get_state.xml new file mode 100644 index 000000000..9249021aa --- /dev/null +++ b/man/sd_uid_get_state.xml @@ -0,0 +1,185 @@ + + + + + + + + + sd_uid_get_state + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sd_uid_get_state + 3 + + + + sd_uid_get_state + sd_uid_is_on_seat + sd_uid_get_sessions + sd_uid_get_seats + Determine login state of a specific Unix user ID + + + + + #include <systemd/sd-login.h> + + + int sd_uid_get_state + uid_t uid + char** state + + + + int sd_uid_is_on_seat + uid_t uid + int require_active + const char* seat + + + + int sd_uid_get_sessions + uid_t uid + int require_active + char*** sessions + + + + int sd_uid_get_seats + uid_t uid + int require_active + char*** seats + + + + + + Description + + sd_uid_get_state() may be + used to determine the login state of a specific Unix + user identifier. The following states are currently + known: offline (user not logged in + at all), lingering (user not logged + in, but some user services running), + online (user logged in, but not + active), active (user logged in on + an active seat). In the future additional states might + be defined, client code should be written to be robust + in regards to additional state strings being + returned. The returned string needs to be freed with + the libc + free3 + call after use. + + sd_uid_is_on_seat() may be + used to determine whether a specific user is logged in + or active on a specific seat. Accepts a Unix user + identifier and a seat identifier string as + parameters. The require_active + parameter is a boolean. If non-zero (true) this + function will test if the user is active (i.e. has a + session that is in the foreground and accepting user + input) on the specified seat, otherwise (false) only + if the user is logged in (and possibly inactive) on + the specified seat. + + sd_uid_get_sessions() may + be used to determine the current sessions of the + specified user. Acceptes a Unix user identifier as + parameter. The require_active + boolean parameter controls whether the returned list + shall consist of only those sessions where the user is + currently active (true) or where the user is currently + logged in at all, possibly inactive (false). The call + returns a NULL terminated string array of session + identifiers in sessions which + needs to be freed by the caller with the libc + free3 + call after use, including all the strings + referenced. If the string array parameter is passed as + NULL the array will not be filled in, but the return + code still indicates the number of current + sessions. Note that instead of an empty array NULL may + be returned and should be considered equivalent to an + empty array. + + Similar, sd_uid_get_seats() + may be used to determine the list of seats on which + the user currently has sessions. Similar semantics + apply, however note that the user may have + multiple sessions on the same seat as well as sessions + with no attached seat and hence the number of entries + in the returned array may differ from the one returned + by sd_uid_get_sessions(). + + + + Return Value + + On success + sd_uid_get_state() returns 0 or a + positive integer. If the test succeeds + sd_uid_is_on_seat() returns a + positive integer, if it fails + 0. sd_uid_get_sessions() and + sd_uid_get_seats() return the + number of entries in the returned arrays. On failure, + these calls return a negative errno-style error + code. + + + + Notes + + The sd_uid_get_state(), + sd_uid_is_on_seat(), + sd_uid_get_sessions(), and + sd_uid_get_seats() interfaces are + available as shared library, which can be compiled and + linked to with the libsystemd-login + pkg-config1 + file. + + + + See Also + + + systemd1, + sd-login7, + sd_pid_get_owner_uid3 + + + + diff --git a/man/shutdown.xml b/man/shutdown.xml new file mode 100644 index 000000000..c8c4b5462 --- /dev/null +++ b/man/shutdown.xml @@ -0,0 +1,188 @@ + + + + + + + + + shutdown + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + shutdown + 8 + + + + shutdown + Halt, power-off or reboot the machine + + + + + shutdown OPTIONS TIME WALL + + + + + Description + + shutdown may be used to halt, + power-off or reboot the machine. + + The first argument may be a time string (which + is usually now). Optionally, this + may be followed by a wall message to be sent to all + logged-in users before going down. + + The time string may either be in the format + hh:mm for hour/minutes specifying + the time to execute the shutdown at, specified in 24h + clock format. Alternatively it may be in the syntax + +m referring to the specified + number of minutes m from now. now + is an alias for +0, i.e. for + triggering an immediate shutdown. If no time argument + is specified, +1 is + implied. + + Note that to specify a wall message you must + specify a time argument, too. + + If the time argument is used, 5 minutes + before the system goes down the + /etc/nologin file is created to + ensure that further logins shall not be + allowed. + + + + Options + + The following options are understood: + + + + + + Prints a short help + text and exits. + + + + + + + Halt the machine. + + + + + + + Power-off the + machine (the default). + + + + + + + Reboot the + machine. + + + + + + Equivalent to + , unless + is + specified. + + + + + + Don't halt, power-off, + reboot, just write wall + message. + + + + + + Don't send wall + message before + halt, power-off, reboot. + + + + + + Cancel a pending + shutdown. This may be used cancel the + effect of an invocation of + shutdown with a + time argument that is not + +0 or + now. + + + + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + Notes + + This is a legacy command available for + compatibility only. + + + + See Also + + systemd1, + systemctl1, + halt8, + wall1 + + + + diff --git a/man/sysctl.d.xml b/man/sysctl.d.xml new file mode 100644 index 000000000..20f2e2482 --- /dev/null +++ b/man/sysctl.d.xml @@ -0,0 +1,123 @@ + + + + + + + + sysctl.d + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + sysctl.d + 5 + + + + sysctl.d + Configure kernel parameters at boot + + + + /etc/sysctl.d/*.conf + /run/sysctl.d/*.conf + /usr/lib/sysctl.d/*.conf + + + + Description + + systemd uses configuration + files from the above directories to configure + sysctl8 + kernel parameters during boot. + + + + Configuration Format + + The configuration files contain a list of + variable assignments, separated by newlines. Empty + lines and lines whose first non-whitespace character + is # or ; are ignored. + + Note that both / and . are accepted as label + separators within sysctl variable + names. kernel.domainname=foo and + kernel/domainname=foo hence are + entirely equivalent. + + Each configuration file shall be named in the + style of <program>.conf. + Files in /run/ override files + with the same name in /usr/lib/. + Files in /etc override files with + the same name in /run/ and + /usr/lib/. Packages should + install their configuration files in + /usr/lib/. Files in + /etc/ are reserved for the local + administrator, who may use this logic to override the + configuration installed by vendor packages. All + configuration files are sorted by their name in + alphabetical order, regardless in which of the + directories they reside, to guarantee that a specific + configuration file takes precedence over another file + with an alphabetically earlier name, if both files + contain the same variable setting. + + If the administrator wants to disable a + configuration file supplied by the vendor the + recommended way is to place a symlink to + /dev/null in + /etc/sysctl.d carrying with the + same name. + + + + Example + + /etc/sysctl.d/domain-name.conf example: + + # Set kernel YP domain name +kernel.domainname=example.com + + + + + See Also + + systemd1, + sysctl8, + sysctl.conf5 + + + + diff --git a/man/systemctl.xml b/man/systemctl.xml new file mode 100644 index 000000000..167a72b58 --- /dev/null +++ b/man/systemctl.xml @@ -0,0 +1,1190 @@ + + + + + + + + + systemctl + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemctl + 1 + + + + systemctl + Control the systemd system and service manager + + + + + systemctl OPTIONS COMMAND NAME + + + + + Description + + systemctl may be used to + introspect and control the state of the + systemd1 + system and service manager. + + + + Options + + The following options are understood: + + + + + + + Prints a short help + text and exits. + + + + + + Prints a short version + string and exits. + + + + + + + When listing units, + limit display to certain unit + types. If not specified units of all + types will be shown. The argument + should be a unit type name such as + , + and + similar. + + + + + + + When showing + unit/job/manager properties, limit + display to certain properties as + specified as argument. If not + specified all set properties are + shown. The argument should be a + property name, such as + MainPID. If + specified more than once all + properties with the specified names + are shown. + + + + + + + When listing units, + show all units, regardless of their + state, including inactive units. When + showing unit/job/manager properties, + show all properties regardless whether + they are set or not. + + + + + + When listing units, + show only failed units. Do not confuse + with + . + + + + + + Do not ellipsize unit + names and truncate unit descriptions + in the output of + list-units and + list-jobs. + + + + + + If the requested + operation conflicts with a pending + unfinished job, fail the command. If + this is not specified the requested + operation will replace the pending job, + if necessary. Do not confuse + with + . + + + + + + When enqueuing a new + job ignore all its dependencies and + execute it immediately. If passed no + required units of the unit passed will + be pulled in, and no ordering + dependencies will be honoured. This is + mostly a debugging and rescue tool for + the administrator and should not be + used by + applications. + + + + + + + Suppress output to + STDOUT in + snapshot, + is-active, + enable and + disable. + + + + + + Do not synchronously wait for + the requested operation to finish. If this is + not specified the job will be verified, + enqueued and systemctl will + wait until it is completed. By passing this + argument it is only verified and + enqueued. + + + + + + Do not print a legend, i.e. + the column headers and the footer with hints. + + + + + + + Do not pipe output into a + pager. + + + + + + Talk to the systemd + system manager. (Default) + + + + + + Talk to the systemd + manager of the calling user. + + + + + + + When used in + conjunction with the + dot command (see + below), selects which dependencies are + shown in the dependency graph. If + is passed + only dependencies of type + After= or + Before= are + shown. If + is passed only dependencies of type + Requires=, + RequiresOverridable=, + Requisite=, + RequisiteOverridable=, + Wants= and + Conflicts= are + shown. If neither is passed, shows + dependencies of all these + types. + + + + + + Don't send wall + message before + halt, power-off, reboot. + + + + + + When used with + enable and + disable, operate on the + global user configuration + directory, thus enabling or disabling + a unit file globally for all future + logins of all users. + + + + + + When used with + enable and + disable, do not + implicitly reload daemon configuration + after executing the + changes. + + + + + + When used with + start and related + commands, disables asking for + passwords. Background services may + require input of a password or + passphrase string, for example to + unlock system hard disks or + cryptographic certificates. Unless + this option is specified and the + command is invoked from a terminal + systemctl will + query the user on the terminal for the + necessary secrets. Use this option to + switch this behavior off. In this + case the password must be supplied by + some other means (for example + graphical password agents) or the + service might fail. + + + + + + When used with + kill, choose which + processes to kill. Must be one of + , + or + to select whether + to kill only the main process of the + unit, the control process or all + processes of the unit. If omitted + defaults to + . + + + + + + + When used with + kill, choose which + signal to send to selected + processes. Must be one of the well + known signal specifiers such as + SIGTERM, SIGINT or SIGSTOP. If + omitted defaults to + . + + + + + + + When used with + enable, override any + existing conflicting + symlinks. + + When used with + halt, + poweroff, + reboot or + kexec execute the + selected operation without shutting + down all units. However, all processes + will be killed forcibly and all file + systems are unmounted or remounted + read-only. This is hence a drastic but + relatively safe option to request an + immediate reboot. If + is specified + twice for these operations, they will + be executed immediately without + terminating any processes or umounting + any file systems. Warning: specifying + twice with + any of these operations might result + in data loss. + + + + + + When used with + enable/disable/is-enabled (and + related commands), use alternative + root path when looking for unit + files. + + + + + + When used with + enable/disable/is-enabled (and related commands), make + changes only temporarily, so that they + are dropped on the next reboot. This + will have the effect that changes are + not made in subdirectories of + /etc but in + /run, with + identical immediate effects, however, + since the latter is lost on reboot, + the changes are lost + too. + + + + + + + Execute operation + remotely. Specify a hostname, or + username and hostname separated by @, + to connect to. This will use SSH to + talk to the remote systemd + instance. + + + + + + + Acquire privileges via + PolicyKit before executing the + operation. + + + + + + + When used with + status controls the + number of journal lines to show, + counting from the most recent + ones. Takes a positive integer + argument. Defaults to + 10. + + + + + + + When used with + status continously + prints new journal entries as they are + appended to the + journal. + + + + + + + When used with + status controls the + formatting of the journal entries that + are shown. For the available choices + see + journalctl1. Defaults + to + short. + + + + + The following commands are understood: + + + + list-units + + List known units. + + + start [NAME...] + + Start (activate) one + or more units specified on the command + line. + + + stop [NAME...] + + Stop (deactivate) one + or more units specified on the command + line. + + + reload [NAME...] + + Asks all units listed + on the command line to reload their + configuration. Note that this will + reload the service-specific + configuration, not the unit + configuration file of systemd. If you + want systemd to reload the + configuration file of a unit use the + daemon-reload + command. In other words: for the + example case of Apache, this will + reload Apache's + httpd.conf in the + web server, not the + apache.service + systemd unit file. + + This command should not be + confused with the + daemon-reload or + load + commands. + + + + restart [NAME...] + + Restart one or more + units specified on the command + line. If the units are not running yet + they will be + started. + + + try-restart [NAME...] + + Restart one or more + units specified on the command + line if the units are running. Do + nothing if units are not running. + Note that for compatibility + with Red Hat init scripts + condrestart is + equivalent to this command. + + + reload-or-restart [NAME...] + + Reload one or more + units if they support it. If not, + restart them instead. If the units + are not running yet they will be + started. + + + reload-or-try-restart [NAME...] + + Reload one or more + units if they support it. If not, + restart them instead. Do nothing if + the units are not running. Note that + for compatibility with SysV init + scripts + force-reload is + equivalent to this + command. + + + isolate [NAME] + + Start the unit + specified on the command line and its + dependencies and stop all others. + + This is similar to changing the + runlevel in a traditional init system. The + isolate command will + immediately stop processes that are not + enabled in the new unit, possibly including + the graphical environment or terminal you + are currently using. + + Note that this works only on units + where is + enabled. See + systemd.unit5 + for details. + + + kill [NAME...] + + Send a signal to one + or more processes of the unit. Use + to select + which process to kill. Use + to + select the kill mode and + to select + the signal to send. + + + is-active [NAME...] + + Check whether any of + the specified units are active + (i.e. running). Returns an exit code + 0 if at least one is active, non-zero + otherwise. Unless + is specified + this will also print the current unit + state to STDOUT. + + + status [NAME...|PID...] + + Show terse runtime + status information about one or more + units, followed by its most recent log + data from the journal. This function + is intended to generate human-readable + output. If you are looking for + computer-parsable output, use + show instead. If a + PID is passed information about the + unit the process of the PID belongs to + is shown. + + + show [NAME...|JOB...] + + Show properties of one + or more units, jobs or the manager + itself. If no argument is specified + properties of the manager will be + shown. If a unit name is specified + properties of the unit is shown, and + if a job id is specified properties of + the job is shown. By default, empty + properties are suppressed. Use + to show those + too. To select specific properties to + show use + . This + command is intended to be used + whenever computer-parsable output is + required. Use + status if you are + looking for formatted human-readable + output. + + + + reset-failed [NAME...] + + Reset the + 'failed' state of the + specified units, or if no unit name is + passed of all units. When a unit fails + in some way (i.e. process exiting with + non-zero error code, terminating + abnormally or timing out) it will + automatically enter the + 'failed' state and + its exit code and status is recorded + for introspection by the administrator + until the service is restarted or + reset with this + command. + + + + list-unit-files + + List installed unit files. + + + + + enable [NAME...] + + Enable one or more + unit files, as specified on the + command line. This will create a + number of symlinks as encoded in the + [Install] sections + of the unit files. After the symlinks + have been created the systemd + configuration is reloaded (in a way + that is equivalent to + daemon-reload) to + ensure the changes are taken into + account immediately. Note that this + does not have the effect that any of + the units enabled are also started at + the same time. If this is desired a + separate start + command must be invoked for the + unit. + + This command will + print the actions executed. This + output may be suppressed by passing + . + + Note that this operation creates + only the suggested symlinks for the + units. While this command is the + recommended way to manipulate the unit + configuration directory, the + administrator is free to make + additional changes manually, by + placing or removing symlinks in the + directory. This is particularly useful + to create configurations that deviate + from the suggested default + installation. In this case the + administrator must make sure to invoke + daemon-reload + manually as necessary, to ensure his + changes are taken into account. + + Enabling units should not be + confused with starting (activating) + units, as done by the + start + command. Enabling and starting units + is orthogonal: units may be enabled + without being started and started + without being enabled. Enabling simply + hooks the unit into various suggested + places (for example, so that the unit + is automatically started on boot or + when a particular kind of hardware is + plugged in). Starting actually spawns + the daemon process (in case of service + units), or binds the socket (in case + of socket units), and so + on. + + Depending on whether + , + or + is specified + this enables the unit for the system, + for the calling user only + or for all future logins of all + users. Note that in the latter case no + systemd daemon configuration is + reloaded. + + + + + disable [NAME...] + + Disables one or more + units. This removes all symlinks to + the specified unit files from the unit + configuration directory, and hence + undoes the changes made by + enable. Note + however that this removes + all symlinks to the unit files + (i.e. including manual additions), not + just those actually created by + enable. This call + implicitly reloads the systemd daemon + configuration after completing the + disabling of the units. Note that this + command does not implicitly stop the + units that is being disabled. If this + is desired an additional + stopcommand should + be executed afterwards. + + This command will print the + actions executed. This output may be + suppressed by passing + . + + + This command honors + , + , + in a similar + way as + enable. + + + + is-enabled [NAME...] + + Checks whether any of + the specified unit files is enabled + (as with + enable). Returns an + exit code of 0 if at least one is + enabled, non-zero otherwise. Prints + the current enable status. To suppress + this output use + . + + + + reenable [NAME...] + + Reenable one or more + unit files, as specified on the + command line. This is a combination of + disable and + enable and is + useful to reset the symlinks a unit is + enabled with to the defaults + configured in the + [Install] section + of the unit file. + + + + + preset [NAME...] + + Reset one or more unit + files, as specified on the command + line, to the defaults configured in a + preset file. This has the same effect + as disable or + enable, depending + how the unit is listed in the preset + files. + + + + + mask [NAME...] + + Mask one or more unit + files, as specified on the command + line. This will link these units to + /dev/null, making + it impossible to start them. This is a stronger version + of disable, since + it prohibits all kinds of activation + of the unit, including manual + activation. Use this option with + care. + + + + + unmask [NAME...] + + Unmask one or more + unit files, as specified on the + command line. This will undo the + effect of + mask. + + + + + link [NAME...] + + Link a unit file that + is not in the unit file search paths + into the unit file search path. This + requires an absolute path to a unit + file. The effect of this can be undone + with disable. The + effect of this command is that a unit + file is available for + start and other + commands although it isn't installed + directly in the unit search + path. + + + + + load [NAME...] + + Load one or more units + specified on the command line. This + will simply load their configuration + from disk, but not start them. To + start them you need to use the + start command which + will implicitly load a unit that has + not been loaded yet. Note that systemd + garbage collects loaded units that are + not active or referenced by an active + unit. This means that units loaded + this way will usually not stay loaded + for long. Also note that this command + cannot be used to reload unit + configuration. Use the + daemon-reload + command for that. All in all, this + command is of little use except for + debugging. + This command should not be + confused with the + daemon-reload or + reload + commands. + + + list-jobs + + List jobs that are in progress. + + + cancel [JOB...] + + Cancel one or more + jobs specified on the command line by + their numeric job + IDs. If no job id is specified, cancel all pending jobs. + + + dump + + Dump server + status. This will output a (usually + very long) human readable manager + status dump. Its format is subject to + change without notice and should not + be parsed by + applications. + + + dot + + Generate textual + dependency graph description in dot + format for further processing with the + GraphViz + dot1 + tool. Use a command line like + systemctl dot | dot -Tsvg > + systemd.svg to generate a + graphical dependency tree. Unless + or + is passed + the generated graph will show both + ordering and requirement + dependencies. + + + snapshot [NAME] + + Create a snapshot. If + a snapshot name is specified, the new + snapshot will be named after it. If + none is specified an automatic + snapshot name is generated. In either + case, the snapshot name used is + printed to STDOUT, unless + is + specified. + + A snapshot refers to a saved + state of the systemd manager. It is + implemented itself as a unit that is + generated dynamically with this + command and has dependencies on all + units active at the time. At a later + time the user may return to this state + by using the + isolate command on + the snapshot unit. + + Snapshots are only useful for + saving and restoring which units are + running or are stopped, they do not + save/restore any other + state. Snapshots are dynamic and lost + on reboot. + + + delete [NAME...] + + Remove a snapshot + previously created with + snapshot. + + + daemon-reload + + Reload systemd manager + configuration. This will reload all + unit files and recreate the entire + dependency tree. While the daemon is + reloaded, all sockets systemd listens + on on behalf of user configuration will + stay accessible. This + command should not be confused with + the load or + reload + commands. + + + daemon-reexec + + Reexecute the systemd + manager. This will serialize the + manager state, reexecute the process + and deserialize the state again. This + command is of little use except for + debugging and package + upgrades. Sometimes it might be + helpful as a heavy-weight + daemon-reload. While + the daemon is reexecuted all sockets + systemd listens on on behalf of user + configuration will stay + accessible. + + + show-environment + + Dump the systemd + manager environment block. The + environment block will be dumped in + straight-forward form suitable for + sourcing into a shell script. This + environment block will be passed to + all processes the manager + spawns. + + + set-environment [NAME=VALUE...] + + Set one or more + systemd manager environment variables, + as specified on the command + line. + + + unset-environment [NAME...] + + Unset one or more + systemd manager environment + variables. If only a variable name is + specified it will be removed + regardless of its value. If a variable + and a value are specified the variable + is only removed if it has the + specified value. + + + default + + Enter default + mode. This is mostly equivalent to + start + default.target. + + + rescue + + Enter rescue + mode. This is mostly equivalent to + isolate + rescue.target but also + prints a wall message to all + users. + + + emergency + + Enter emergency + mode. This is mostly equivalent to + isolate + emergency.target but also + prints a wall message to all + users. + + + halt + + Shut down and halt the + system. This is mostly equivalent to + start halt.target + but also prints a wall message to all + users. If combined with + shutdown of + all running services is skipped, + however all processes are killed and + all file systems are unmounted or + mounted read-only, immediately + followed by the system halt. If + is specified + twice the the operation is immediately + executed without terminating any + processes or unmounting any file + systems. This may result in data + loss. + + + poweroff + + Shut down and + power-off the system. This is mostly + equivalent to start + poweroff.target but also + prints a wall message to all users. If + combined with + shutdown of all running services is + skipped, however all processes are + killed and all file systems are + unmounted or mounted read-only, + immediately followed by the powering + off. If is + specified twice the the operation is + immediately executed without + terminating any processes or + unmounting any file systems. This may + result in data loss. + + + reboot + + Shut down and reboot + the system. This is mostly equivalent + to start + reboot.target but also + prints a wall message to all users. If + combined with + shutdown of all running services is + skipped, however all processes are + killed and all file systems are + unmounted or mounted read-only, + immediately followed by the reboot. If + is specified + twice the the operation is immediately + executed without terminating any + processes or unmounting any file + systems. This may result in data + loss. + + + kexec + + Shut down and reboot + the system via kexec. This is mostly + equivalent to start + kexec.target but also prints + a wall message to all users. If + combined with + shutdown of all running services is + skipped, however all processes are killed + and all file systems are unmounted or + mounted read-only, immediately + followed by the + reboot. + + + exit + + Ask the systemd + manager to quit. This is only + supported for user service managers + (i.e. in conjunction with the + option) and + will fail otherwise. + + + + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + Environment + + + + $SYSTEMD_PAGER + Pager to use when + is not given; + overrides $PAGER. Setting + this to an empty string or the value + cat is equivalent to passing + . + + + + + + See Also + + systemd1, + systemadm1, + journalctl1, + loginctl1, + systemd.unit5, + systemd.special7, + wall1 + + + + diff --git a/man/systemd-ask-password.xml b/man/systemd-ask-password.xml new file mode 100644 index 000000000..c607b1a36 --- /dev/null +++ b/man/systemd-ask-password.xml @@ -0,0 +1,183 @@ + + + + + + + + + systemd-ask-password + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-ask-password + 1 + + + + systemd-ask-password + Query the user for a system password + + + + + systemd-ask-password OPTIONS MESSAGE + + + + + Description + + systemd-ask-password may be + used to query a system password or passphrase from the + user, using a question message specified on the + command line. When run from a TTY it will query a + password on the TTY and print it to STDOUT. When run + with no TTY or with it will + query the password system-wide and allow active users + to respond via several agents. The latter is + only available to privileged processes. + + The purpose of this tool is to query system-wide + passwords -- that is passwords not attached to a + specific user account. Examples include: unlocking + encrypted hard disks when they are plugged in or at + boot, entering an SSL certificate passphrase for web + and VPN servers. + + Existing agents are: a boot-time password agent + asking the user for passwords using Plymouth; a + boot-time password agent querying the user directly on + the console; an agent requesting password input via a + wall1 + message; an agent suitable for running in a GNOME + session; a command line agent which can be started + temporarily to process queued password requests; a TTY + agent that is temporarily spawned during + systemctl1 + invocations. + + Additional password agents may be implemented + according to the systemd + Password Agent Specification. + + If a password is queried on a tty the user may + press TAB to hide the asterisks normally shown for + each character typed. Pressing Backspace as first key + achieves the same effect. + + + + + Options + + The following options are understood: + + + + + + + Prints a short help + text and exits. + + + + + + Specify an icon name + alongside the password query, which may + be used in all agents supporting + graphical display. The icon name + should follow the XDG + Icon Naming + Specification. + + + + + + Specify the query + timeout in seconds. Defaults to + 90s. + + + + + + Never ask for password + on current TTY even if one is + available. Always use agent + system. + + + + + + If passed accept + cached passwords, i.e. passwords + previously typed in. + + + + + + When used in + conjunction with + + accept multiple passwords. This will + output one password per + line. + + + + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + See Also + + systemd1, + systemctl1, + plymouth8, + wall1 + + + + diff --git a/man/systemd-cat.xml b/man/systemd-cat.xml new file mode 100644 index 000000000..350a345d4 --- /dev/null +++ b/man/systemd-cat.xml @@ -0,0 +1,205 @@ + + + + + + + + + systemd-cat + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-cat + 1 + + + + systemd-cat + Connect a pipeline or program's output with the journal + + + + + systemd-cat OPTIONS COMMAND ARGUMENTS + + + systemd-cat OPTIONS + + + + + Description + + systemd-cat may be used to + connect STDOUT and STDERR of a process with the + journal, or as a filter tool in a shell pipeline to + pass the output the previous pipeline element + generates to the journal. + + If no parameter is passed + systemd-cat will write + everything it reads from standard input (STDIN) to the journal. + + If parameters are passed they are executed as + command line with standard output (STDOUT) and standard + error output (STDERR) connected to the journal, so + that all it writes is stored in the journal. + + + + Options + + The following options are understood: + + + + + + + Prints a short help + text and exits. + + + + + + Prints a short version + string and exits. + + + + + + + Specify a short string + that is used to identify the logging + tool. If not specified no identifying + string is written to the journal. + + + + + + + Specify the default + priority level for the logged + messages. Pass one of + emerg, + alert, + crit, + err, + warning, + notice, + info, + debug, resp. a + value between 0 and 7 (corresponding + to the same named levels). These + priority values are the same as + defined by + syslog3. Defaults + to info. Note that + this simply controls the default, + individual lines may be logged with + different levels if they are prefixed + accordingly. For details see + + below. + + + + + + Controls whether lines + read are parsed for syslog priority + level prefixes. If enabled (the + default) a line prefixed with a + priority prefix such as + <5> is logged + at priority 5 + (notice), and + similar for the other priority + levels. Takes a boolean + argument. + + + + + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + Examples + + + Invoke a program + + This calls /bin/ls + with STDOUT/STDERR connected to the + journal: + + # systemd-cat ls + + + + Usage in a shell pipeline + + This builds a shell pipeline also + invoking /bin/ls and + writes the output it generates to the + journal: + + # ls | systemd-cat + + + Even though the two examples have very similar + effects the first is preferable since only one process + is running at a time, and both STDOUT and STDERR are + captured while in the second example only STDOUT is + captured. + + + + See Also + + systemd1, + systemctl1, + logger1 + + + + diff --git a/man/systemd-cgls.xml b/man/systemd-cgls.xml new file mode 100644 index 000000000..1e53147e1 --- /dev/null +++ b/man/systemd-cgls.xml @@ -0,0 +1,123 @@ + + + + + + + + + systemd-cgls + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-cgls + 1 + + + + systemd-cgls + Recursively show control group contents + + + + + systemd-cgls OPTIONS CGROUP + + + + + Description + + systemd-cgls recursively + shows the contents of the selected Linux control group + hierarchy in a tree. If arguments are specified shows + all member processes of the specified control groups + plus all their subgroups and their members. The + control groups may either be specified by their full + file paths or are assumed in the systemd control group + hierarchy. If no argument is specified and the current + working directory is beneath the control group mount + point /sys/fs/cgroup shows the contents + of the control group the working directory refers + to. Otherwise the full systemd control group hierarchy + is shown. + + + + Options + + The following options are understood: + + + + + + + Prints a short help + text and exits. + + + + + + Do not pipe output into a + pager. + + + + + + Include kernel + threads in output. + + + + + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + See Also + + systemd1, + systemctl1, + systemd-cgtop1, + ps1 + + + + diff --git a/man/systemd-cgtop.xml b/man/systemd-cgtop.xml new file mode 100644 index 000000000..2d67ae5ef --- /dev/null +++ b/man/systemd-cgtop.xml @@ -0,0 +1,246 @@ + + + + + + + + + systemd-cgtop + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-cgtop + 1 + + + + systemd-cgtop + Show top control groups by their resource usage + + + + + systemd-cgtop OPTIONS + + + + + Description + + systemd-cgtop shows the top + control groups of the local Linux control group + hierarchy, ordered by their CPU, memory and disk I/O load. The + display is refreshed in regular intervals (by default + every 1s), similar in style to + top1. + + Resource usage is only accounted for control + groups in the relevant hierarchy, i.e. CPU usage is + only accounted for control groups in the + cpuacct hierarchy, memory usage + only for those in memory and disk + I/O usage for those in + blkio. systemd1 + by default places all services in their own control + group in the cpuacct hierarchy, but + not in memory nor + blkio. If resource monitoring for + these resources is required it is recommended to add + blkio and memory + to the DefaultControllers= setting + in /etc/systemd/system.conf (see + systemd.conf5 + for details). Alternatively, it is possible to enable + resource accounting individually for services, by + making use of the ControlGroup= + option in the unit files (See + systemd.exec5 + for details). + + To emphasize this: unless + blkio and memory + are enabled for the services in question with either + of the options suggested above no resource accounting + will be available for system services and the data shown + by systemd-cgtop will be + incomplete. + + + + Options + + The following options are understood: + + + + + + + Prints a short help + text and exits. + + + + + + Order by control group + path name. + + + + + + Order by number of + tasks in control + group (i.e. threads and processes). + + + + + + Order by CPU load. + + + + + + Order by memory usage. + + + + + + Order by disk I/O load. + + + + + + + Specify refresh delay + in seconds (or if one of + ms, + us, + min is specified as + unit in this time + unit). + + + + + + Maximum control group + tree traversal depth. Specifies how + deep systemd-cgtop + shall traverse the control group + hierarchies. If 0 is specified only + the root group is monitored, for 1 + only the first level of control groups + is monitored, and so on. Defaults to + 2. + + + + + + + + + Keys + + systemd-cgtop is an + interactive tool and may be controlled via user input + using the following keys: + + + + h + + Shows a short help text. + + + + SPACE + + Immediately refresh output. + + + + q + + Terminate the program. + + + + + p + t + c + m + i + + Change ordering of control groups + by path, number of tasks, CPU load, + memory usage resp. IO + load. + + + + + + - + + Increase, + resp. decrease refresh + delay. + + + + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + See Also + + systemd1, + systemctl1, + systemd-cgls1, + top1 + + + + diff --git a/man/systemd-machine-id-setup.xml b/man/systemd-machine-id-setup.xml new file mode 100644 index 000000000..49b92f689 --- /dev/null +++ b/man/systemd-machine-id-setup.xml @@ -0,0 +1,114 @@ + + + + + + + + + systemd-machine-id-setup + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-machine-id-setup + 1 + + + + systemd-machine-id-setup + Initialize the machine ID in /etc/machine-id + + + + + systemd-machine-id-setup + + + + + Description + + systemd-machine-id-setup may + be used by system installer tools to initialize the + machine ID stored in + /etc/machine-id at install time + with a randomly generated ID. See + machine-id5 + for more information about this file. + + This tool will execute no operation if + /etc/machine-id is already + initialized. + + If a valid D-Bus machine ID is already + configured for the system the D-Bus machine ID is + copied and used to initialize the machine ID in + /etc/machine-id. + + If run inside a KVM virtual machine and a UUID + is passed via the option this + UUID is used to initialize the machine ID instead of a + randomly generated one. The caller must ensure that the + UUID passed is sufficiently unique and is different + for every booted instanced of the VM. + + Similar, if run inside a Linux container + environment and a UUID is set for the container this + is used to initialize the machine ID. For details see + the documentation of the Container + Interface. + + + + + Options + + This tool does not take any options or arguments. + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + See Also + + systemd1, + machine-id5, + dbus-uuidgen1 + + + + diff --git a/man/systemd-notify.xml b/man/systemd-notify.xml new file mode 100644 index 000000000..c5ffafe89 --- /dev/null +++ b/man/systemd-notify.xml @@ -0,0 +1,218 @@ + + + + + + + + + systemd-notify + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-notify + 1 + + + + systemd-notify + Notify init system about start-up completion and other daemon status changes + + + + + systemd-notify OPTIONS VARIABLE=VALUE + + + + + Description + + systemd-notify may be + called by daemon scripts to notify the init system + about status changes. It can be used to send arbitrary + information, encoded in an environment-block-like list + of strings. Most importantly it can be used for + start-up completion notification. + + This is mostly just a wrapper around + sd_notify() and makes this + functionality available to shell scripts. For details + see + sd_notify3. + + The command line may carry a list of + environment variables to send as part of the status + update. + + Note that systemd will refuse reception of + status updates from this command unless + NotifyAccess=all is set for the + service unit this command is called from. + + + + + Options + + The following options are understood: + + + + + + + Prints a short help + text and exits. + + + + + + Prints a short version + string and exits. + + + + + + Inform the init system + about service start-up + completion. This is equivalent to + systemd-notify + READY=1. For details about + the semantics of this option see + sd_notify3. + + + + + + Inform the init system + about the main PID of the + daemon. Takes a PID as argument. If + the argument is omitted the PID of the + process that invoked + systemd-notify is + used. This is equivalent to + systemd-notify + MAINPID=$PID. For details + about the semantics of this option see + sd_notify3. + + + + + + Send a free-form + status string for the daemon to the + init systemd. This option takes the + status string as argument. This is + equivalent to systemd-notify + STATUS=.... For details + about the semantics of this option see + sd_notify3. + + + + + + Returns 0 if the + system was booted up with systemd, + non-zero otherwise. If this option is + passed no message is sent. This option + is hence unrelated to the other + options. For details about the + semantics of this option see + sd_booted3. + + + + + + Controls disk + read-ahead operations. The argument + must be a string, and either "cancel", + "done" or "noreplay". For details + about the semantics of this option see + sd_readahead3. + + + + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + Example + + + Start-up Notification and Status Updates + + A simple shell daemon that sends + start-up notifications after having set up its + communication channel. During runtime it sends + further status updates to the init + system: + + #!/bin/bash + +mkfifo /tmp/waldo +systemd-notify --ready --status="Waiting for data..." + +while : ; do + read a < /tmp/waldo + systemd-notify --status="Processing $a" + + # Do something with $a ... + + systemd-notify --status="Waiting for data..." +done + + + + + See Also + + systemd1, + systemctl1, + systemd.unit5, + sd_notify3, + sd_booted3 + + + + diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml new file mode 100644 index 000000000..dbd2ff5a8 --- /dev/null +++ b/man/systemd-nspawn.xml @@ -0,0 +1,215 @@ + + + + + + + + + systemd-nspawn + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-nspawn + 1 + + + + systemd-nspawn + Spawn a namespace container for debugging, testing and building + + + + + systemd-nspawn OPTIONS COMMAND ARGS + + + + + Description + + systemd-nspawn may be used to + run a command or OS in a light-weight namespace + container. In many ways it is similar to + chroot1, + but more powerful since it fully virtualizes the file + system hierarchy, as well as the process tree, the + various IPC subsystems and the host and domain + name. + + systemd-nspawn limits access + to various kernel interfaces in the container to + read-only, such as /sys, + /proc/sys or + /sys/fs/selinux. Network + interfaces and the system clock may not be changed + from within the container. Device nodes may not be + created. The host system cannot be rebooted and kernel + modules may not be loaded from within the + container. + + Note that even though these security precautions + are taken systemd-nspawn is not + suitable for secure container setups. Many of the + security features may be circumvented and are hence + primarily useful to avoid accidental changes to the + host system from the container. The intended use of + this program is debugging and testing as well as + building of packages, distributions and software + involved with boot and systems management. + + In contrast to + chroot1 + systemd-nspawn may be used to boot + full Linux-based operating systems in a + container. + + Use a tool like + debootstrap8 or mock1 + to set up an OS directory tree suitable as file system + hierarchy for systemd-nspawn containers. + + Note that systemd-nspawn will + mount file systems private to the container to + /dev, + /run and similar. These will + not be visible outside of the container, and their + contents will be lost when the container exits. + + Note that running two + systemd-nspawn containers from the + same directory tree will not make processes in them + see each other. The PID namespace separation of the + two containers is complete and the containers will + share very few runtime objects except for the + underlying file system. + + + + Options + + If no arguments are passed the container is set + up and a shell started in it, otherwise the passed + command and arguments are executed in it. The + following options are understood: + + + + + + + Prints a short help + text and exits. + + + + + + + Directory to use as + file system root for the namespace + container. If omitted the current + directory will be + used. + + + + + + + Run the command + under specified user, create home + directory and cd into it. As rest + of systemd-nspawn, this is not + the security feature and limits + against accidental changes only. + + + + + + + Turn off networking in + the container. This makes all network + interfaces unavailable in the + container, with the exception of the + loopback device. + + + + + + + + Example 1 + + # debootstrap --arch=amd64 unstable debian-tree/ +# systemd-nspawn -D debian-tree/ + + This installs a minimal Debian unstable + distribution into the directory + debian-tree/ and then spawns a + shell in a namespace container in it. + + + + + Example 2 + + # mock --init +# systemd-nspawn -D /var/lib/mock/fedora-rawhide-x86_64/root/ /sbin/init systemd.log_level=debug + + This installs a minimal Fedora distribution into + a subdirectory of /var/lib/mock/ + and then boots an OS in a namespace container in it, + with systemd as init system, configured for debug + logging. + + + + + Exit status + + The exit code of the program executed in the + container is returned. + + + + See Also + + systemd1, + chroot1, + debootstrap8, + mock1 + + + + diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml new file mode 100644 index 000000000..bbb80b2f9 --- /dev/null +++ b/man/systemd-tmpfiles.xml @@ -0,0 +1,152 @@ + + + + + + + + + systemd-tmpfiles + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-tmpfiles + 8 + + + + systemd-tmpfiles + Creates, deletes and cleans up volatile + and temporary files and directories. + + + + + systemd-tmpfiles OPTIONS CONFIGURATION FILE + + + + + Description + + systemd-tmpfiles creates, + deletes and cleans up volatile and temporary files and + directories, based on the configuration from + /etc/tmpfiles.d/. See + tmpfiles.d5 + for more details on these files. + + If invoked with no arguments applies all + directives from all configuration files in + /etc/tmpfiles.d/*.conf. If one or + more absolute file names are passed on the command + line only the directives in these files are + applied. + + + + Options + + The following options are understood: + + + + + + If this option is passed all + files and directories marked with f, + F, d, D in the configuration files are + created. Files and directories marked with z, + Z have their ownership, access mode and security + labels set. + + + + + If this option is + passed all files and directories with + an age parameter configured will be + cleaned up. + + + + + If this option is + passed all files and directories marked + with r, R in the configuration files + are removed. + + + + Only apply rules that + apply to paths with the specified + prefix. + + + + + + + Prints a short help + text and exits. + + + + + It is possible to combine + , , + and in one invocation. For + example, during boot the following command line is + executed to ensure that all temporary and volatile + directories are removed and created according to the + configuration file: + + systemd-tmpfiles --remove --create + + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + See Also + + systemd1, + tmpfiles.d5, + tmpwatch8 + + + + diff --git a/man/systemd.automount.xml b/man/systemd.automount.xml new file mode 100644 index 000000000..754d1e37f --- /dev/null +++ b/man/systemd.automount.xml @@ -0,0 +1,167 @@ + + + + + + + + + systemd.automount + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.automount + 5 + + + + systemd.automount + systemd automount configuration files + + + + systemd.automount + + + + Description + + A unit configuration file whose name ends in + .automount encodes information + about a file system automount point controlled and + supervised by systemd. + + This man page lists the configuration options + specific to this unit type. See + systemd.unit5 + for the common options of all unit configuration + files. The common configuration items are configured + in the generic [Unit] and [Install] sections. The + automount specific configuration options are configured + in the [Automount] section. + + Automount units must be named after the + automount directories they control. Example: the + automount point /home/lennart + must be configured in a unit file + home-lennart.automount. For + details about the escaping logic used to convert a + file system path to a unit name see + systemd.unit5. + + For each automount unit file a matching mount + unit file (see + systemd.mount5 + for details) must exist which is activated when the + automount path is accessed. Example: if an automount + unit home-lennart.automount is + active and the user accesses + /home/lennart the mount unit + home-lennart.mount will be + activated. + + Automount units may be used to implement + on-demand mounting as well as parallelized mounting of + file systems. + + If an automount point is beneath another mount + point in the file system hierarchy a dependency + between both units is created automatically. + + + + <filename>fstab</filename> + + Automount units may either be configured via unit + files, or via /etc/fstab (see + fstab5 + for details). + + For details how systemd parses + /etc/fstab see + systemd.mount5. + + If an automount point is configured in both + /etc/fstab and a unit file the + configuration in the latter takes precedence. + + + + Options + + Automount files must include an [Automount] + section, which carries information about the file + system automount points it supervises. The options + specific to the [Automount] section of automount units + are the following: + + + + + Where= + Takes an absolute path + of a directory of the automount + point. If the automount point is not + existing at time of the automount + point is installed it is created. This + string must be reflected in the unit + file name. (See above.) This option is + mandatory. + + + + DirectoryMode= + Directories of + automount points (and any parent + directories) are automatically created + if needed. This option specifies the + file system access mode used when + creating these directories. Takes an + access mode in octal + notation. Defaults to + 0755. + + + + + + See Also + + systemd1, + systemctl8, + systemd.unit5, + systemd.mount5, + mount8, + automount8 + + + + diff --git a/man/systemd.conf.xml b/man/systemd.conf.xml new file mode 100644 index 000000000..ba144da8c --- /dev/null +++ b/man/systemd.conf.xml @@ -0,0 +1,162 @@ + + + + + + + + + systemd.conf + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.conf + 5 + + + + systemd.conf + systemd manager configuration file + + + + system.conf + user.conf + + + + Description + + When run as system instance systemd reads the + configuration file system.conf, + otherwise user.conf. These + configuration files contain a few settings controlling + basic manager operations. + + + + + Options + + All options are configured in the + [Manager] section: + + + + + LogLevel= + LogTarget= + LogColor= + LogLocation= + DumpCore=yes + CrashShell=no + ShowStatus=yes + SysVConsole=yes + CrashChVT=1 + DefaultStandardOutput=journal + DefaultStandardError=inherit + + Configures various + parameters of basic manager + operation. These options may be + overridden by the respective command + line arguments. See + systemd1 + for details about these command line + arguments. + + + + CPUAffinity= + + Configures the initial + CPU affinity for the init + process. Takes a space-separated list + of CPU indexes. + + + + MountAuto=yes + SwapAuto=yes + + Configures whether + systemd should automatically activate + all swap or mounts listed in + /etc/fstab, or + whether this job is left to some other + system script. + + + + DefaultControllers=cpu + + Configures in which + cgroup controller hierarchies to + create per-service cgroups + automatically, in addition to the + name=systemd named hierarchy. Defaults + to 'cpu'. Takes a space separated list + of controller names. Pass an empty + string to ensure that systemd does not + touch any hierarchies but its + own. + + + + JoinControllers=cpu,cpuacct + + Configures controllers + that shall be mounted in a single + hierarchy. By default systemd will + mount all controllers which are + enabled in the kernel in individual + hierachies, with the exception of + those listed in this setting. Takes a + space separated list of comma + separated controller names, in order + to allow multiple joined + hierarchies. Defaults to + 'cpu,cpuacct'. Pass an empty string to + ensure that systemd mounts all + controllers in separate + hierarchies. + + + + + + See Also + + systemd1 + + + + diff --git a/man/systemd.device.xml b/man/systemd.device.xml new file mode 100644 index 000000000..63863bebd --- /dev/null +++ b/man/systemd.device.xml @@ -0,0 +1,168 @@ + + + + + + + + + systemd.device + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.device + 5 + + + + systemd.device + systemd device configuration files + + + + systemd.device + + + + Description + + A unit configuration file whose name ends in + .device encodes information about + a device unit as exposed in the + sysfs/udev7 + device tree. + + This unit type has no specific options. See + systemd.unit5 + for the common options of all unit configuration + files. The common configuration items are configured + in the generic [Unit] and + [Install] sections. A separate + [Device] section does not exist, + since no device-specific options may be + configured. + + systemd will automatically create dynamic device + units for all kernel devices that are marked with the + "systemd" udev tag (by default all block and network + devices, and a few others). This may be used to define + dependencies between devices and other + units. + + Device units are named after the + /sys and + /dev paths they control. Example: + the device /dev/sda5 is exposed + in systemd as dev-sda5.device. For + details about the escaping logic used to convert a + file system path to a unit name see + systemd.unit5. + + + + + The udev Database + + The settings of device units may either be + configured via unit files, or directly from the udev + database (which is recommended). The following udev + properties are understood by systemd: + + + + SYSTEMD_WANTS= + Adds dependencies of + type Wants from + this unit to all listed units. This + may be used to activate arbitrary + units, when a specific device becomes + available. Note that this and the + other tags are not taken into account + unless the device is tagged with the + "systemd" string in + the udev database, because otherwise + the device is not exposed as systemd + unit. + + + + SYSTEMD_ALIAS= + Adds an additional + alias name to the device unit. This + must be an absolute path that is + automatically transformed into a unit + name. (See above.) + + + + SYSTEMD_READY= + If set to 0 systemd + will consider this device unplugged + even if it shows up in the udev + tree. If this property is unset or set + to 1 the device will be considered + plugged the moment it shows up in the + udev tree. This property has no + influence on the behaviour when a + device disappears from the udev + tree. This option is useful to support + devices that initially show up in an + uninitialized state in the tree, and for + which a changed event is generated the + moment they are fully set + up. + + + + ID_MODEL_FROM_DATABASE= + ID_MODEL= + + If set, this property is + used as description string for the + device unit. + + + + + + + + + See Also + + systemd1, + systemctl8, + systemd.unit5, + udev7 + + + + diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml new file mode 100644 index 000000000..e6f49c9fd --- /dev/null +++ b/man/systemd.exec.xml @@ -0,0 +1,1106 @@ + + + + + + + + + systemd.exec + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.exec + 5 + + + + systemd.exec + systemd execution environment configuration + + + + systemd.service, + systemd.socket, + systemd.mount, + systemd.swap + + + + Description + + Unit configuration files for services, sockets, + mount points and swap devices share a subset of + configuration options which define the execution + environment of spawned processes. + + This man page lists the configuration options + shared by these four unit types. See + systemd.unit5 + for the common options of all unit configuration + files, and + systemd.service5, + systemd.socket5, + systemd.swap5 + and + systemd.mount5 + for more information on the specific unit + configuration files. The execution specific + configuration options are configured in the [Service], + [Socket], [Mount] resp. [Swap] section, depending on the unit + type. + + + + Options + + + + + WorkingDirectory= + + Takes an absolute + directory path. Sets the working + directory for executed + processes. + + + + RootDirectory= + + Takes an absolute + directory path. Sets the root + directory for executed processes, with + the + chroot2 + system call. If this is used it must + be ensured that the process and all + its auxiliary files are available in + the chroot() + jail. + + + + User= + Group= + + Sets the Unix user + resp. group the processes are executed + as. Takes a single user resp. group + name or ID as argument. If no group is + set the default group of the user is + chosen. + + + + SupplementaryGroups= + + Sets the supplementary + Unix groups the processes are executed + as. This takes a space separated list + of group names or IDs. This option may + be specified more than once in which + case all listed groups are set as + supplementary groups. This option does + not override but extends the list of + supplementary groups configured in the + system group database for the + user. + + + + Nice= + + Sets the default nice + level (scheduling priority) for + executed processes. Takes an integer + between -20 (highest priority) and 19 + (lowest priority). See + setpriority2 + for details. + + + + OOMScoreAdjust= + + Sets the adjustment + level for the Out-Of-Memory killer for + executed processes. Takes an integer + between -1000 (to disable OOM killing + for this process) and 1000 (to make + killing of this process under memory + pressure very likely). See proc.txt + for details. + + + + IOSchedulingClass= + + Sets the IO scheduling + class for executed processes. Takes an + integer between 0 and 3 or one of the + strings , + , + or + . See + ioprio_set2 + for details. + + + + IOSchedulingPriority= + + Sets the IO scheduling + priority for executed processes. Takes + an integer between 0 (highest + priority) and 7 (lowest priority). The + available priorities depend on the + selected IO scheduling class (see + above). See + ioprio_set2 + for details. + + + + CPUSchedulingPolicy= + + Sets the CPU + scheduling policy for executed + processes. Takes one of + , + , + , + or + . See + sched_setscheduler2 + for details. + + + + CPUSchedulingPriority= + + Sets the CPU + scheduling priority for executed + processes. Takes an integer between 1 + (lowest priority) and 99 (highest + priority). The available priority + range depends on the selected CPU + scheduling policy (see above). See + sched_setscheduler2 + for details. + + + + CPUSchedulingResetOnFork= + + Takes a boolean + argument. If true elevated CPU + scheduling priorities and policies + will be reset when the executed + processes fork, and can hence not leak + into child processes. See + sched_setscheduler2 + for details. Defaults to false. + + + + CPUAffinity= + + Controls the CPU + affinity of the executed + processes. Takes a space-separated + list of CPU indexes. See + sched_setaffinity2 + for details. + + + + UMask= + + Controls the file mode + creation mask. Takes an access mode in + octal notation. See + umask2 + for details. Defaults to + 0022. + + + + Environment= + + Sets environment + variables for executed + processes. Takes a space-separated + list of variable assignments. This + option may be specified more than once + in which case all listed variables + will be set. If the same variable is + set twice the later setting will + override the earlier setting. See + environ7 + for details. + + + EnvironmentFile= + Similar to + Environment= but + reads the environment variables from a + text file. The text file should + contain new-line separated variable + assignments. Empty lines and lines + starting with ; or # will be ignored, + which may be used for commenting. The + parser strips leading and + trailing whitespace from the values + of assignments, unless you use + double quotes ("). + The + argument passed should be an absolute + file name, optionally prefixed with + "-", which indicates that if the file + does not exist it won't be read and no + error or warning message is + logged. The files listed with this + directive will be read shortly before + the process is executed. Settings from + these files override settings made + with + Environment=. If + the same variable is set twice from + these files the files will be read in + the order they are specified and the + later setting will override the + earlier setting. + + + + StandardInput= + Controls where file + descriptor 0 (STDIN) of the executed + processes is connected to. Takes one + of , + , + , + or + . If + is selected + standard input will be connected to + /dev/null, + i.e. all read attempts by the process + will result in immediate EOF. If + is selected + standard input is connected to a TTY + (as configured by + TTYPath=, see + below) and the executed process + becomes the controlling process of the + terminal. If the terminal is already + being controlled by another process the + executed process waits until the current + controlling process releases the + terminal. + + is similar to , + but the executed process is forcefully + and immediately made the controlling + process of the terminal, potentially + removing previous controlling + processes from the + terminal. is + similar to but if + the terminal already has a controlling + process start-up of the executed + process fails. The + option is only + valid in socket-activated services, + and only when the socket configuration + file (see + systemd.socket5 + for details) specifies a single socket + only. If this option is set standard + input will be connected to the socket + the service was activated from, which + is primarily useful for compatibility + with daemons designed for use with the + traditional + inetd8 + daemon. This setting defaults to + . + + + StandardOutput= + Controls where file + descriptor 1 (STDOUT) of the executed + processes is connected to. Takes one + of , + , + , + , + , + , + , + , + or + . If set to + the file + descriptor of standard input is + duplicated for standard output. If set + to standard + output will be connected to + /dev/null, + i.e. everything written to it will be + lost. If set to + standard output will be connected to a + tty (as configured via + TTYPath=, see + below). If the TTY is used for output + only the executed process will not + become the controlling process of the + terminal, and will not fail or wait + for other processes to release the + terminal. + connects standard output to the + syslog3 + system syslog + service. + connects it with the kernel log buffer + which is accessible via + dmesg1. + connects it with the journal which is + accessible via + journalctl1 + (Note that everything that is written + to syslog or kmsg is implicitly stored + in the journal as well, those options + are hence supersets of this + one). , + and + work + similarly but copy the output to the + system console as + well. connects + standard output to a socket from + socket activation, semantics are + similar to the respective option of + StandardInput=. + This setting defaults to the value set + with + + in + systemd.conf5, + which defaults to + . + + + StandardError= + Controls where file + descriptor 2 (STDERR) of the executed + processes is connected to. The + available options are identical to + those of + StandardOutput=, + with one exception: if set to + the file + descriptor used for standard output is + duplicated for standard error. This + setting defaults to the value set with + + in + systemd.conf5, + which defaults to + . + + + TTYPath= + Sets the terminal + device node to use if standard input, + output or stderr are connected to a + TTY (see above). Defaults to + /dev/console. + + + TTYReset= + Reset the terminal + device specified with + TTYPath= before and + after execution. Defaults to + no. + + + TTYVHangup= + Disconnect all clients + which have opened the terminal device + specified with + TTYPath= + before and after execution. Defaults + to + no. + + + TTYVTDisallocate= + If the the terminal + device specified with + TTYPath= is a + virtual console terminal try to + deallocate the TTY before and after + execution. This ensures that the + screen and scrollback buffer is + cleared. Defaults to + no. + + + SyslogIdentifier= + Sets the process name + to prefix log lines sent to syslog or + the kernel log buffer with. If not set + defaults to the process name of the + executed process. This option is only + useful when + StandardOutput= or + StandardError= are + set to or + . + + + SyslogFacility= + Sets the syslog + facility to use when logging to + syslog. One of , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + or + . See + syslog3 + for details. This option is only + useful when + StandardOutput= or + StandardError= are + set to . + Defaults to + . + + + SyslogLevel= + Default syslog level + to use when logging to syslog or the + kernel log buffer. One of + , + , + , + , + , + , + , + . See + syslog3 + for details. This option is only + useful when + StandardOutput= or + StandardError= are + set to or + . Note that + individual lines output by the daemon + might be prefixed with a different log + level which can be used to override + the default log level specified + here. The interpretation of these + prefixes may be disabled with + SyslogLevelPrefix=, + see below. For details see + sd-daemon7. + + Defaults to + . + + + + SyslogLevelPrefix= + Takes a boolean + argument. If true and + StandardOutput= or + StandardError= are + set to or + log lines + written by the executed process that + are prefixed with a log level will be + passed on to syslog with this log + level set but the prefix removed. If + set to false, the interpretation of + these prefixes is disabled and the + logged lines are passed on as-is. For + details about this prefixing see + sd-daemon7. + Defaults to true. + + + + TimerSlackNSec= + Sets the timer slack + in nanoseconds for the executed + processes. The timer slack controls the + accuracy of wake-ups triggered by + timers. See + prctl2 + for more information. Note that in + contrast to most other time span + definitions this parameter takes an + integer value in nano-seconds and does + not understand any other + units. + + + + LimitCPU= + LimitFSIZE= + LimitDATA= + LimitSTACK= + LimitCORE= + LimitRSS= + LimitNOFILE= + LimitAS= + LimitNPROC= + LimitMEMLOCK= + LimitLOCKS= + LimitSIGPENDING= + LimitMSGQUEUE= + LimitNICE= + LimitRTPRIO= + LimitRTTIME= + These settings control + various resource limits for executed + processes. See + setrlimit2 + for details. Use the string + infinity to + configure no limit on a specific + resource. + + + + PAMName= + Sets the PAM service + name to set up a session as. If set + the executed process will be + registered as a PAM session under the + specified service name. This is only + useful in conjunction with the + User= setting. If + not set no PAM session will be opened + for the executed processes. See + pam8 + for details. + + + + TCPWrapName= + If this is a + socket-activated service this sets the + tcpwrap service name to check the + permission for the current connection + with. This is only useful in + conjunction with socket-activated + services, and stream sockets (TCP) in + particular. It has no effect on other + socket types (e.g. datagram/UDP) and + on processes unrelated to socket-based + activation. If the tcpwrap + verification fails daemon start-up + will fail and the connection is + terminated. See + tcpd8 + for details. Note that this option may + be used to do access control checks + only. Shell commands and commands + described in + hosts_options5 + are not supported. + + + + CapabilityBoundingSet= + + Controls which + capabilities to include in the + capability bounding set for the + executed process. See + capabilities7 + for details. Takes a whitespace + separated list of capability names as + read by + cap_from_name3. + Capabilities listed will be included + in the bounding set, all others are + removed. If the list of capabilities + is prefixed with ~ all but the listed + capabilities will be included, the + effect of the assignment + inverted. Note that this option does + not actually set or unset any + capabilities in the effective, + permitted or inherited capability + sets. That's what + Capabilities= is + for. If this option is not used the + capability bounding set is not + modified on process execution, hence + no limits on the capabilities of the + process are enforced. + + + + SecureBits= + Controls the secure + bits set for the executed process. See + capabilities7 + for details. Takes a list of strings: + , + , + , + , + and/or + . + + + + + Capabilities= + Controls the + capabilities7 + set for the executed process. Take a + capability string describing the + effective, permitted and inherited + capability sets as documented in + cap_from_text3. + Note that these capability sets are + usually influenced by the capabilities + attached to the executed file. Due to + that + CapabilityBoundingSet= + is probably the much more useful + setting. + + + + ControlGroup= + + Controls the control + groups the executed processes shall be + made members of. Takes a + space-separated list of cgroup + identifiers. A cgroup identifier has a + format like + cpu:/foo/bar, + where "cpu" identifies the kernel + control group controller used, and + /foo/bar is the + control group path. The controller + name and ":" may be omitted in which + case the named systemd control group + hierarchy is implied. Alternatively, + the path and ":" may be omitted, in + which case the default control group + path for this unit is implied. This + option may be used to place executed + processes in arbitrary groups in + arbitrary hierarchies -- which can be + configured externally with additional + execution limits. By default systemd + will place all executed processes in + separate per-unit control groups + (named after the unit) in the systemd + named hierarchy. Since every process + can be in one group per hierarchy only + overriding the control group path in + the named systemd hierarchy will + disable automatic placement in the + default group. This option is + primarily intended to place executed + processes in specific paths in + specific kernel controller + hierarchies. It is however not + recommended to manipulate the service + control group path in the systemd + named hierarchy. For details about + control groups see cgroups.txt. + + + + ControlGroupModify= + Takes a boolean + argument. If true, the control groups + created for this unit will be owned by + the user specified with + User= (and the + appropriate group), and he/she can create + subgroups as well as add processes to + the group. + + + + ControlGroupPersistent= + Takes a boolean + argument. If true, the control groups + created for this unit will be marked + to be persistent, i.e. systemd will + not remove them when stopping the + unit. The default is false, meaning + that the control groups will be + removed when the unit is stopped. For + details about the semantics of this + logic see PaxControlGroups. + + + + ControlGroupAttribute= + + Set a specific control + group attribute for executed + processes, and (if needed) add the the + executed processes to a cgroup in the + hierarchy of the controller the + attribute belongs to. Takes two + space-separated arguments: the + attribute name (syntax is + cpu.shares where + cpu refers to a + specific controller and + shares to the + attribute name), and the attribute + value. Example: + ControlGroupAttribute=cpu.shares + 512. If this option is used + for an attribute that belongs to a + kernel controller hierarchy the unit + is not already configured to be added + to (for example via the + ControlGroup= + option) then the unit will be added to + the controller and the default unit + cgroup path is implied. Thus, using + ControlGroupAttribute= + is in most case sufficient to make use + of control group enforcements, + explicit + ControlGroup= are + only necessary in case the implied + default control group path for a + service is not desirable. For details + about control group attributes see + cgroups.txt. This + option may appear more than once, in + order to set multiple control group + attributes. + + + + CPUShares= + + Assign the specified + overall CPU time shares to the + processes executed. Takes an integer + value. This controls the + cpu.shares control + group attribute, which defaults to + 1024. For details about this control + group attribute see sched-design-CFS.txt. + + + + MemoryLimit= + MemorySoftLimit= + + Limit the overall memory usage + of the executed processes to a certain + size. Takes a memory size in bytes. If + the value is suffixed with K, M, G or + T the specified memory size is parsed + as Kilobytes, Megabytes, Gigabytes, + resp. Terabytes (to the base + 1024). This controls the + memory.limit_in_bytes + and + memory.soft_limit_in_bytes + control group attributes. For details + about these control group attributes + see memory.txt. + + + + DeviceAllow= + DeviceDeny= + + Control access to + specific device nodes by the executed processes. Takes two + space separated strings: a device node + path (such as + /dev/null) + followed by a combination of r, w, m + to control reading, writing resp. + creating of the specific device node + by the unit. This controls the + devices.allow + and + devices.deny + control group attributes. For details + about these control group attributes + see devices.txt. + + + + BlockIOWeight= + + Set the default or + per-device overall block IO weight + value for the executed + processes. Takes either a single + weight value (between 10 and 1000) to + set the default block IO weight, or a + space separated pair of a file path + and a weight value to specify the + device specific weight value (Example: + "/dev/sda 500"). The file path may be + specified as path to a block device + node or as any other file in which + case the backing block device of the + file system of the file is + determined. This controls the + blkio.weight and + blkio.weight_device + control group attributes, which + default to 1000. Use this option + multiple times to set weights for + multiple devices. For details about + these control group attributes see + blkio-controller.txt. + + + + BlockIOReadBandwidth= + BlockIOWriteBandwidth= + + Set the per-device + overall block IO bandwith limit for + the executed processes. Takes a space + separated pair of a file path and a + bandwith value (in bytes per second) + to specify the device specific + bandwidth. The file path may be + specified as path to a block device + node or as any other file in which + case the backing block device of the + file system of the file is determined. + If the bandwith is suffixed with K, M, + G, or T the specified bandwith is + parsed as Kilobytes, Megabytes, + Gigabytes, resp. Terabytes (Example: + "/dev/disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0 + 5M"). This controls the + blkio.read_bps_device + and + blkio.write_bps_device + control group attributes. Use this + option multiple times to set bandwith + limits for multiple devices. For + details about these control group + attributes see blkio-controller.txt. + + + + ReadWriteDirectories= + ReadOnlyDirectories= + InaccessibleDirectories= + + Sets up a new + file-system name space for executed + processes. These options may be used + to limit access a process might have + to the main file-system + hierarchy. Each setting takes a + space-separated list of absolute + directory paths. Directories listed in + ReadWriteDirectories= + are accessible from within the + namespace with the same access rights + as from outside. Directories listed in + ReadOnlyDirectories= + are accessible for reading only, + writing will be refused even if the + usual file access controls would + permit this. Directories listed in + InaccessibleDirectories= + will be made inaccessible for processes + inside the namespace. Note that + restricting access with these options + does not extend to submounts of a + directory. You must list submounts + separately in these settings to + ensure the same limited access. These + options may be specified more than + once in which case all directories + listed will have limited access from + within the + namespace. + + + + PrivateTmp= + + Takes a boolean + argument. If true sets up a new file + system namespace for the executed + processes and mounts a private + /tmp directory + inside it, that is not shared by + processes outside of the + namespace. This is useful to secure + access to temporary files of the + process, but makes sharing between + processes via + /tmp + impossible. Defaults to + false. + + + + PrivateNetwork= + + Takes a boolean + argument. If true sets up a new + network namespace for the executed + processes and configures only the + loopback network device + lo inside it. No + other network devices will be + available to the executed process. + This is useful to securely turn off + network access by the executed + process. Defaults to + false. + + + + MountFlags= + + Takes a mount + propagation flag: + , + or + , which + control whether namespaces set up with + ReadWriteDirectories=, + ReadOnlyDirectories= + and + InaccessibleDirectories= + receive or propagate new mounts + from/to the main namespace. See + mount1 + for details. Defaults to + , i.e. the new + namespace will both receive new mount + points from the main namespace as well + as propagate new mounts to + it. + + + + UtmpIdentifier= + + Takes a a four + character identifier string for an + utmp/wtmp entry for this service. This + should only be set for services such + as getty + implementations where utmp/wtmp + entries must be created and cleared + before and after execution. If the + configured string is longer than four + characters it is truncated and the + terminal four characters are + used. This setting interprets %I style + string replacements. This setting is + unset by default, i.e. no utmp/wtmp + entries are created or cleaned up for + this service. + + + + IgnoreSIGPIPE= + + Takes a boolean + argument. If true causes SIGPIPE to be + ignored in the executed + process. Defaults to true, since + SIGPIPE generally is useful only in + shell pipelines. + + + + + + + See Also + + systemd1, + systemctl8, + journalctl8, + systemd.unit5, + systemd.service5, + systemd.socket5, + systemd.swap5, + systemd.mount5 + + + + diff --git a/man/systemd.mount.xml b/man/systemd.mount.xml new file mode 100644 index 000000000..8f1cc514c --- /dev/null +++ b/man/systemd.mount.xml @@ -0,0 +1,278 @@ + + + + + + + + + systemd.mount + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.mount + 5 + + + + systemd.mount + systemd mount configuration files + + + + systemd.mount + + + + Description + + A unit configuration file whose name ends in + .mount encodes information about + a file system mount point controlled and supervised by + systemd. + + This man page lists the configuration options + specific to this unit type. See + systemd.unit5 + for the common options of all unit configuration + files. The common configuration items are configured + in the generic [Unit] and [Install] sections. The + mount specific configuration options are configured + in the [Mount] section. + + Additional options are listed in + systemd.exec5, + which define the execution environment the + mount8 + binary is executed in. + + Mount units must be named after the mount point + directories they control. Example: the mount point + /home/lennart must be configured + in a unit file + home-lennart.mount. For details + about the escaping logic used to convert a file system + path to a unit name see + systemd.unit5. + + Optionally, a mount unit may be accompanied by + an automount unit, to allow on-demand or parallelized + mounting. See + systemd.automount5. + + If an mount point is beneath another mount point + in the file system hierarchy, a dependency between both + units is created automatically. + + Mount points created at runtime independent on + unit files or /etc/fstab will be + monitored by systemd and appear like any other mount + unit in systemd. + + + + <filename>/etc/fstab</filename> + + Mount units may either be configured via unit + files, or via /etc/fstab (see + fstab5 + for details). + + When reading /etc/fstab a + few special mount options are understood by systemd + which influence how dependencies are created for mount + points from /etc/fstab. If + is set in + system.conf (which is the + default), or if is + specified as mount option, then systemd will create a + dependency of type from either + local-fs.target or + remote-fs.target, depending + whether the file system is local or remote. If + is set, an + automount unit will be created for the file + system. See + systemd.automount5 + for details. If + is + specified it may be used to configure how long systemd + should wait for a device to show up before giving up + on an entry from + /etc/fstab. Specify a time in + seconds or explicitly specifiy a unit as + s, min, + h, ms. + + If a mount point is configured in both + /etc/fstab and a unit file, the + configuration in the latter takes precedence. + + + + Options + + Mount files must include a [Mount] section, + which carries information about the file system mount points it + supervises. A number of options that may be used in + this section are shared with other unit types. These + options are documented in + systemd.exec5. The + options specific to the [Mount] section of mount + units are the following: + + + + + What= + Takes an absolute path + of a device node, file or other + resource to mount. See + mount8 + for details. If this refers to a + device node, a dependency on the + respective device unit is + automatically created. (See + systemd.device5 for more information.) + This option is + mandatory. + + + + Where= + Takes an absolute path + of a directory of the mount point. If + the mount point is not existing at + time of mounting, it is created. This + string must be reflected in the unit + file name. (See above.) This option is + mandatory. + + + + Type= + Takes a string for the + filesystem type. See + mount8 + for details. This setting is + optional. + + + + Options= + + Mount options to use + when mounting. This takes a comma + separated list of options. This + setting is optional. + + + + DirectoryMode= + Directories of mount + points (and any parent directories) + are automatically created if + needed. This option specifies the file + system access mode used when creating + these directories. Takes an access + mode in octal notation. Defaults to + 0755. + + + + TimeoutSec= + Configures the time to + wait for the mount command to + finish. If a command does not exit + within the configured time the mount + will be considered failed and be shut + down again. All commands still running + will be terminated forcibly via + SIGTERM, and after another delay of + this time with SIGKILL. (See + below.) + Takes a unit-less value in seconds, or + a time span value such as "5min + 20s". Pass 0 to disable the timeout + logic. Defaults to + 90s. + + + + KillMode= + Specifies how + processes of this mount shall be + killed. One of + , + , + . + + This option is mostly equivalent + to the + option of service files. See + systemd.service5 + for details. + + + + KillSignal= + Specifies which signal + to use when killing a process of this + mount. Defaults to SIGTERM. + + + + + SendSIGKILL= + Specifies whether to + send SIGKILL to remaining processes + after a timeout, if the normal + shutdown procedure left processes of + the mount around. Takes a boolean + value. Defaults to "yes". + + + + + + + See Also + + systemd1, + systemctl8, + systemd.unit5, + systemd.exec5, + systemd.device5, + mount8 + + + + diff --git a/man/systemd.path.xml b/man/systemd.path.xml new file mode 100644 index 000000000..5b1ff75f7 --- /dev/null +++ b/man/systemd.path.xml @@ -0,0 +1,220 @@ + + + + + + + + + systemd.path + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.path + 5 + + + + systemd.path + systemd path configuration files + + + + systemd.path + + + + Description + + A unit configuration file whose name ends in + .path encodes information about + a path monitored by systemd, for + path-based activation. + + This man page lists the configuration options + specific to this unit type. See + systemd.unit5 + for the common options of all unit configuration + files. The common configuration items are configured + in the generic [Unit] and [Install] sections. The + path specific configuration options are configured in + the [Path] section. + + For each path file, a matching unit file must + exist, describing the unit to activate when the path + changes. By default, a service by the same name as the + path (except for the suffix) is activated. Example: a + path file foo.path activates a + matching service foo.service. The + unit to activate may be controlled by + Unit= (see below). + + Internally, path units use the + inotify7 + API to monitor file systems. Due to that, it suffers by the + same limitations as inotify, and for example cannot be + used to monitor files or directories changed by other + machines on remote NFS file systems. + + If an path unit is beneath another mount + point in the file system hierarchy, a dependency + between both units is created automatically. + + Unless DefaultDependencies= + is set to , path units will + implicitly have dependencies of type + Conflicts= and + Before= on + shutdown.target. These ensure + that path units are terminated cleanly prior to system + shutdown. Only path units involved with early boot or + late system shutdown should disable this + option. + + + + Options + + Path files must include a [Path] section, + which carries information about the path(s) it + monitors. The options specific to the [Path] section + of path units are the following: + + + + PathExists= + PathExistsGlob= + PathChanged= + PathModified= + DirectoryNotEmpty= + + Defines paths to + monitor for certain changes: + PathExists= may be + used to watch the mere existence of a + file or directory. If the file + specified exists the configured unit + is + activated. PathExistsGlob= + works similar, but checks for the + existance of at least one file + matching the globbing pattern + specified. PathChanged= + may be used to watch a file or + directory and activate the configured + unit whenever it changes. It is not activated + on every write to the watched file but it is + activated if the file which was open for writing + gets closed. PathModified= + is similar, but additionally it is activated + also on simple writes to the watched file. + + DirectoryNotEmpty= + may be used to watch a directory and + activate the configured unit whenever + it contains at least one file. + + The arguments of these + directives must be absolute file + system paths. + + Multiple directives may be + combined, of the same and of different + types, to watch multiple paths. + + If a path is already existing + (in case of + PathExists= and + PathExistsGlob=) or + a directory already is not empty (in + case of + DirectoryNotEmpty=) + at the time the path unit is + activated, then the configured unit is + immediately activated as + well. Something similar does not apply + to PathChanged=. + + + + Unit= + + The unit to activate + when any of the configured paths + changes. The argument is a unit name, + whose suffix is not + .path. If not + specified, this value defaults to a + service that has the same name as the + path unit, except for the suffix. (See + above.) It is recommended that the + unit name that is activated and the + unit name of the path unit are named + identical, except for the + suffix. + + + MakeDirectory= + + Takes a boolean + argument. If true the directories to + watch are created before + watching. This option is ignored for + PathExists= + settings. Defaults to + . + + + DirectoryMode= + + If + MakeDirectory= is + enabled use the mode specified here to + create the directories in + question. Takes an access mode in + octal notation. Defaults to + . + + + + + + See Also + + systemd1, + systemctl8, + systemd.unit5, + systemd.service5, + inotify7 + + + + diff --git a/man/systemd.service.xml b/man/systemd.service.xml new file mode 100644 index 000000000..837a992ba --- /dev/null +++ b/man/systemd.service.xml @@ -0,0 +1,837 @@ + + + + + + + + + systemd.service + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.service + 5 + + + + systemd.service + systemd service configuration files + + + + systemd.service + + + + Description + + A unit configuration file whose name ends in + .service encodes information + about a process controlled and supervised by + systemd. + + This man page lists the configuration options + specific to this unit type. See + systemd.unit5 + for the common options of all unit configuration + files. The common configuration items are configured + in the generic [Unit] and + [Install] sections. The service + specific configuration options are configured in the + [Service] section. + + Additional options are listed in + systemd.exec5, + which define the execution environment the commands + are executed in. + + Unless DefaultDependencies= + is set to , service units will + implicitly have dependencies of type + Requires= and + After= on + basic.target as well as + dependencies of type Conflicts= and + Before= on + shutdown.target. These ensure + that normal service units pull in basic system + initialization, and are terminated cleanly prior to + system shutdown. Only services involved with early + boot or late system shutdown should disable this + option. + + If a service is requested under a certain name + but no unit configuration file is found, systemd looks + for a SysV init script by the same name (with the + .service suffix removed) and + dynamically creates a service unit from that + script. This is useful for compatibility with + SysV. + + + + Options + + Service files must include a + [Service] section, which carries + information about the service and the process it + supervises. A number of options that may be used in + this section are shared with other unit types. These + options are documented in + systemd.exec5. The + options specific to the [Service] + section of service units are the following: + + + + Type= + + Configures the process + start-up type for this service + unit. One of , + , + , + , + . + + If set to + (the default + value) it is expected that the process + configured with + ExecStart= is the + main process of the service. In this + mode, if the process offers + functionality to other processes on + the system its communication channels + should be installed before the daemon + is started up (e.g. sockets set up by + systemd, via socket activation), as + systemd will immediately proceed + starting follow-up units. + + If set to + it is + expected that the process configured + with ExecStart= + will call fork() + as part of its start-up. The parent process is + expected to exit when start-up is + complete and all communication + channels set up. The child continues + to run as the main daemon + process. This is the behaviour of + traditional UNIX daemons. If this + setting is used, it is recommended to + also use the + PIDFile= option, so + that systemd can identify the main + process of the daemon. systemd will + proceed starting follow-up units as + soon as the parent process + exits. + + Behaviour of + is similar + to , however + it is expected that the process has to + exit before systemd starts follow-up + units. RemainAfterExit= + is particularly useful for this type + of service. + + Behaviour of + is similar to + , however it is + expected that the daemon acquires a + name on the D-Bus bus, as configured + by + BusName=. systemd + will proceed starting follow-up units + after the D-Bus bus name has been + acquired. Service units with this + option configured implicitly gain + dependencies on the + dbus.socket + unit. + + Behaviour of + is similar to + , however it is + expected that the daemon sends a + notification message via + sd_notify3 + or an equivalent call when it finished + starting up. systemd will proceed + starting follow-up units after this + notification message has been sent. If + this option is used + NotifyAccess= (see + below) should be set to open access to + the notification socket provided by + systemd. If + NotifyAccess= is + not set, it will be implicitly set to + . + + + + + RemainAfterExit= + + Takes a boolean value + that specifies whether the service + shall be considered active even when + all its processes exited. Defaults to + . + + + + + GuessMainPID= + + Takes a boolean value + that specifies whether systemd should + try to guess the main PID of a service + should if it cannot be determined + reliably. This option is ignored + unless + is set and + is unset because for the other types + or with an explicitly configured PID + file the main PID is always known. The + guessing algorithm might come to + incorrect conclusions if a daemon + consists of more than one process. If + the main PID cannot be determined + failure detection and automatic + restarting of a service will not work + reliably. Defaults to + . + + + + + PIDFile= + + Takes an absolute file + name pointing to the PID file of this + daemon. Use of this option is + recommended for services where + Type= is set to + . systemd will + read the PID of the main process of + the daemon after start-up of the + service. systemd will not write to the + file configured here. + + + + + BusName= + + Takes a D-Bus bus + name, where this service is reachable + as. This option is mandatory for + services where + Type= is set to + , but its use + is otherwise recommended as well if + the process takes a name on the D-Bus + bus. + + + + + ExecStart= + Takes a command line + that is executed when this service + shall be started up. The first token + of the command line must be an + absolute file name, then followed by + arguments for the process. It is + mandatory to set this option for all + services. This option may not be + specified more than once, except when + Type=oneshot is + used in which case more than one + ExecStart= line is + accepted which are then invoked one by + one, sequentially in the order they + appear in the unit file. + + Optionally, if the absolute file + name is prefixed with + @, the second token + will be passed as + argv[0] to the + executed process, followed by the + further arguments specified. If the + first token is prefixed with + - 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 both + - and + @ are used for the + same command the former must precede + the latter. Unless + Type=forking is + set, the process started via this + command line will be considered the + main process of the daemon. The + command line accepts % specifiers as + described in + systemd.unit5. + + On top of that basic environment + variable substitution is + supported. Use + ${FOO} as part of a + word, or as word of its own on the + command line, in which case it will be + replaced by the value of the + environment variable including all + whitespace it contains, resulting in a + single argument. Use + $FOO as a separate + word on the command line, in which + case it will be replaced by the value + of the environment variable split up + at whitespace, resulting in no or more + arguments. Note that the first + argument (i.e. the program to execute) + may not be a variable, and must be a + literal and absolute path + name. + + + + ExecStartPre= + ExecStartPost= + Additional commands + that are executed before (resp. after) + the command in + ExecStart=. Multiple + command lines may be concatenated in a + single directive, by separating them + by semicolons (these semicolons must + be passed as separate words). In that + case, the commands are executed one + after the other, + serially. Alternatively, these + directives may be specified more than + once with the same effect. However, + the latter syntax is not recommended + for compatibility with parsers + suitable for XDG + .desktop files. + Use of these settings is + optional. Specifier and environment + variable substitution is + supported. + + + + ExecReload= + Commands to execute to + trigger a configuration reload in the + service. This argument takes multiple + command lines, following the same + scheme as pointed out for + ExecStartPre= + above. Use of this setting is + optional. Specifier and environment + variable substitution is supported + here following the same scheme as for + ExecStart=. One + special environment variable is set: + if known $MAINPID is + set to the main process of the + daemon, and may be used for command + lines like the following: + /bin/kill -HUP + $MAINPID. + + + + ExecStop= + Commands to execute to + stop the service started via + ExecStart=. This + argument takes multiple command lines, + following the same scheme as pointed + out for + ExecStartPre= + above. Use of this setting is + optional. All processes remaining for + a service after the commands + configured in this option are run are + terminated according to the + KillMode= setting + (see below). If this option is not + specified the process is terminated + right-away when service stop is + requested. Specifier and environment + variable substitution is supported + (including + $MAINPID, see + above). + + + + ExecStopPost= + Additional commands + that are executed after the service + was stopped using the commands + configured in + ExecStop=. This + argument takes multiple command lines, + following the same scheme as pointed + out for + ExecStartPre. Use + of these settings is + optional. Specifier and environment + variable substitution is + supported. + + + + RestartSec= + Configures the time to + sleep before restarting a service (as + configured with + Restart=). Takes a + unit-less value in seconds, or a time + span value such as "5min + 20s". Defaults to + 100ms. + + + + TimeoutSec= + Configures the time to + wait for start-up and stop. If a + daemon service does not signal + start-up completion within the + configured time the service will be + considered failed and be shut down + again. If a service is asked to stop + but does not terminate in the + specified time it will be terminated + forcibly via SIGTERM, and after + another delay of this time with + SIGKILL. (See + KillMode= + below.) Takes a unit-less value in seconds, or a + time span value such as "5min + 20s". Pass 0 to disable the timeout + logic. Defaults to + 90s. + + + + WatchdogSec= + Configures the + watchdog timeout for a service. This + is activated when the start-up is + completed. The service must call + sd_notify3 + regularly with "WATCHDOG=1". If the + time between two such calls is larger + than the configured time then the + service is placed in a failure + state. By setting + Restart= + to or + the service + will be automatically restarted. The + time configured here will be passed to + the executed service process in the + WATCHDOG_USEC= + environment variable. If + this option is used + NotifyAccess= (see + below) should be set to open access to + the notification socket provided by + systemd. If + NotifyAccess= is not + set, it will be implicitly set to + . Defaults to 0, + which disables this + feature. + + + + Restart= + Configures whether the + main service process shall be + restarted when it exits. Takes one of + , + , + , + or + . If set to + (the default) the + service will not be restarted when it + exits. If set to + it will be + restarted only when it exited cleanly, + i.e. terminated with an exit code of + 0. If set to + it will be + restarted only when it exited with an + exit code not equalling 0, when + terminated by a signal, when an + operation times out or when the + configured watchdog timeout is + triggered. If set to + it will be + restarted only if it exits due to + reception of an uncaught signal. If + set to the + service will be restarted regardless + whether it exited cleanly or not, + got terminated abnormally by a + signal or hit a timeout. + + + + PermissionsStartOnly= + Takes a boolean + argument. If true, the permission + related execution options as + configured with + User= and similar + options (see + systemd.exec5 + for more information) are only applied + to the process started with + ExecStart=, and not + to the various other + ExecStartPre=, + ExecStartPost=, + ExecReload=, + ExecStop=, + ExecStopPost= + commands. If false, the setting is + applied to all configured commands the + same way. Defaults to + false. + + + + RootDirectoryStartOnly= + Takes a boolean + argument. If true, the root directory + as configured with the + RootDirectory= + option (see + systemd.exec5 + for more information) is only applied + to the process started with + ExecStart=, and not + to the various other + ExecStartPre=, + ExecStartPost=, + ExecReload=, + ExecStop=, + ExecStopPost= + commands. If false, the setting is + applied to all configured commands the + same way. Defaults to + false. + + + + SysVStartPriority= + Set the SysV start + priority to use to order this service + in relation to SysV services lacking + LSB headers. This option is only + necessary to fix ordering in relation + to legacy SysV services, that have no + ordering information encoded in the + script headers. As such it should only + be used as temporary compatibility + option, and not be used in new unit + files. Almost always it is a better + choice to add explicit ordering + directives via + After= or + Before=, + instead. For more details see + systemd.unit5. If + used, pass an integer value in the + range 0-99. + + + + KillMode= + Specifies how + processes of this service shall be + killed. One of + , + , + . + + If set to + all + remaining processes in the control + group of this service will be + terminated on service stop, after the + stop command (as configured with + ExecStop=) is + executed. If set to + only the main + process itself is killed. If set to + no process is + killed. In this case only the stop + command will be executed on service + stop, but no process be killed + otherwise. Processes remaining alive + after stop are left in their control + group and the control group continues + to exist after stop unless it is + empty. Defaults to + . + + Processes will first be + terminated via SIGTERM (unless the + signal to send is changed via + KillSignal=). If + then after a delay (configured via the + TimeoutSec= option) + processes still remain, the + termination request is repeated with + the SIGKILL signal (unless this is + disabled via the + SendSIGKILL= + option). See + kill2 + for more + information. + + + + KillSignal= + Specifies which signal + to use when killing a + service. Defaults to SIGTERM. + + + + + SendSIGKILL= + Specifies whether to + send SIGKILL to remaining processes + after a timeout, if the normal + shutdown procedure left processes of + the service around. Takes a boolean + value. Defaults to "yes". + + + + + NonBlocking= + Set O_NONBLOCK flag + for all file descriptors passed via + socket-based activation. If true, all + file descriptors >= 3 (i.e. all except + STDIN/STDOUT/STDERR) will have + the O_NONBLOCK flag set and hence are in + non-blocking mode. This option is only + useful in conjunction with a socket + unit, as described in + systemd.socket5. Defaults + to false. + + + + NotifyAccess= + Controls access to the + service status notification socket, as + accessible via the + sd_notify3 + call. Takes one of + (the default), + or + . If + no daemon status + updates are accepted from the service + processes, all status update messages + are ignored. If + only service updates sent from the + main process of the service are + accepted. If all + services updates from all members of + the service's control group are + accepted. This option should be set to + open access to the notification socket + when using + Type=notify or + WatchdogUsec= (see + above). If those options are used but + NotifyAccess= not + configured it will be implicitly set + to + . + + + + Sockets= + Specifies the name of + the socket units this service shall + inherit the sockets from when the + service is started. Normally it + should not be necessary to use this + setting as all sockets whose unit + shares the same name as the service + (ignoring the different suffix of course) + are passed to the spawned + process. + + Note that the same socket may be + passed to multiple processes at the + same time. Also note that a different + service may be activated on incoming + traffic than inherits the sockets. Or + in other words: The + Service= setting of + .socket units + doesn't have to match the inverse of the + Sockets= setting of + the .service it + refers to. + + + + FsckPassNo= + Set the fsck passno + priority to use to order this service + in relation to other file system + checking services. This option is only + necessary to fix ordering in relation + to fsck jobs automatically created for + all /etc/fstab + entries with a value in the fs_passno + column > 0. As such it should only be + used as option for fsck + services. Almost always it is a better + choice to add explicit ordering + directives via + After= or + Before=, + instead. For more details see + systemd.unit5. If + used, pass an integer value in the + same range as + /etc/fstab's + fs_passno column. See + fstab5 + for details. + + + + StartLimitInterval= + StartLimitBurst= + + Configure service + start rate limiting. By default + services which are started more often + than 5 times within 10s are not + permitted to start any more times + until the 10s interval ends. With + these two options this rate limiting + may be modified. Use + StartLimitInterval= + to configure the checking interval + (defaults to 10s, set to 0 to disable + any kind of rate limiting). Use + StartLimitBurst= to + configure how many starts per interval + are allowed (defaults to 5). These + configuration options are particularly + useful in conjunction with + Restart=. + + + + StartLimitAction= + + Configure the action + to take if the rate limit configured + with + StartLimitInterval= + and + StartLimitBurst= is + hit. Takes one of + , + , + or + . If + is set, + hitting the rate limit will trigger no + action besides that the start will not + be + permitted. + causes a reboot following the normal + shutdown procedure (i.e. equivalent to + systemctl reboot), + causes + an forced reboot which will terminate + all processes forcibly but should + cause no dirty file systems on reboot + (i.e. equivalent to systemctl + reboot -f) and + + causes immediate execution of the + reboot2 + system call, which might result in + data loss. Defaults to + . + + + + + + + See Also + + systemd1, + systemctl8, + systemd.unit5, + systemd.exec5 + + + + diff --git a/man/systemd.snapshot.xml b/man/systemd.snapshot.xml new file mode 100644 index 000000000..a3e23225c --- /dev/null +++ b/man/systemd.snapshot.xml @@ -0,0 +1,87 @@ + + + + + + + + + systemd.snapshot + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.snapshot + 5 + + + + systemd.snapshot + systemd snapshot units + + + + systemd.snapshot + + + + Description + + Snapshot units are not configured via unit + configuration files. Nonetheless they are named + similar to filenames. A unit name whose name ends in + .snapshot refers to a dynamic + snapshot of the systemd runtime state. + + Snapshots are not configured on disk but created + dynamically via systemctl snapshot + (see + systemctl8 + for details) or an equivalent command. When created, + they will automatically get dependencies on the + currently activated units. They act as saved + runtime state of the systemd manager. Later on, the + user may choose to return to the saved state via + systemctl isolate. They are + useful to roll back to a defined state after + temporarily starting/stopping services or + similar. + + + + See Also + + systemd1, + systemctl8, + systemd.unit5 + + + + diff --git a/man/systemd.socket.xml b/man/systemd.socket.xml new file mode 100644 index 000000000..d9921e496 --- /dev/null +++ b/man/systemd.socket.xml @@ -0,0 +1,674 @@ + + + + + + + + + systemd.socket + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.socket + 5 + + + + systemd.socket + systemd socket configuration files + + + + systemd.socket + + + + Description + + A unit configuration file whose name ends in + .socket encodes information about + an IPC or network socket or a file system FIFO + controlled and supervised by systemd, for socket-based + activation. + + This man page lists the configuration options + specific to this unit type. See + systemd.unit5 + for the common options of all unit configuration + files. The common configuration items are configured + in the generic [Unit] and [Install] sections. The + socket specific configuration options are configured + in the [Socket] section. + + Additional options are listed in + systemd.exec5, + which define the execution environment the + , + , + and + commands are executed + in. + + For each socket file a matching service file + (see + systemd.service5 + for details) must exist, describing the service to + start on incoming traffic on the socket. Depending on + the setting of (see below), + this must either be named like the socket unit, but + with the suffix replaced; or it must be a template + file named the same way. Example: a socket file + foo.socket needs a matching + service foo.service if + is set. If + is set a service template + file foo@.service must exist from + which services are instantiated for each incoming + connection. + + Unless DefaultDependencies= + is set to , socket units will + implicitly have dependencies of type + Requires= and + After= on + sysinit.target as well as + dependencies of type Conflicts= and + Before= on + shutdown.target. These ensure + that socket units pull in basic system + initialization, and are terminated cleanly prior to + system shutdown. Only sockets involved with early + boot or late system shutdown should disable this + option. + + Socket units may be used to implement on-demand + starting of services, as well as parallelized starting + of services. + + Note that the daemon software configured for + socket activation with socket units needs to be able + to accept sockets from systemd, either via systemd's + native socket passing interface (see + sd_listen_fds3 + for details) or via the traditional + inetd8-style + socket passing (i.e. sockets passed in via STDIN and + STDOUT, using StandardInput=socket + in the service file). + + + + Options + + Socket files must include a [Socket] section, + which carries information about the socket or FIFO it + supervises. A number of options that may be used in + this section are shared with other unit types. These + options are documented in + systemd.exec5. The + options specific to the [Socket] section of socket + units are the following: + + + + ListenStream= + ListenDatagram= + ListenSequentialPacket= + Specifies an address + to listen on for a stream + (SOCK_STREAM), datagram (SOCK_DGRAM) + resp. sequential packet + (SOCK_SEQPACKET) socket. The address + can be written in various formats: + + If the address starts with a + slash (/), it is read as file system + socket in the AF_UNIX socket + family. + + If the address starts with an + at symbol (@) it is read as abstract + namespace socket in the AF_UNIX + family. The @ is replaced with a NUL + character before binding. For details + see + unix7. + + If the address string is a + single number it is read as port + number to listen on for both IPv4 and + IPv6. + + If the address string is a + string in the format v.w.x.y:z it is + read as IPv4 specifier for listening + on an address v.w.x.y on a port + z. + + If the address string is a + string in the format [x]:y it is read + as IPv6 address x on a port y. + + Note that SOCK_SEQPACKET + (i.e. ListenSequentialPacket=) + is only available for AF_UNIX + sockets. SOCK_STREAM + (i.e. ListenStream=) + when used for IP sockets refers to TCP + sockets, SOCK_DGRAM + (i.e. ListenDatagram=) + to UDP. + + These options may be specified + more than once in which case incoming + traffic on any of the sockets will trigger + service activation, and all listed + sockets will be passed to the service, + regardless whether there is incoming + traffic on them or not. + + If an IP address is used here, it + is often desirable to listen on it + before the interface it is configured + on is up and running, and even + regardless whether it will be up and + running ever at all. To deal with this it is + recommended to set the + FreeBind= option + described below. + + + + ListenFIFO= + Specifies a file + system FIFO to listen on. This expects + an absolute file system path as + argument. Behaviour otherwise is very + similar to the + ListenDatagram= + directive above. + + + + ListenSpecial= + Specifies a special + file in the file system to listen + on. This expects an absolute file + system path as argument. Behaviour + otherwise is very similar to the + ListenFIFO= + directive above. Use this to open + character device nodes as well as + special files in + /proc and + /sys. + + + + ListenNetlink= + Specifies a Netlink + family to create a socket for to + listen on. This expects a short string + referring to the AF_NETLINK family + name (such as audit + or kobject-uevent) + as argument, optionally suffixed by a + whitespace followed by a multicast + group integer. Behaviour otherwise is + very similar to the + ListenDatagram= + directive above. + + + + ListenMessageQueue= + Specifies a POSIX + message queue name to listen on. This + expects a valid message queue name + (i.e. beginning with /). Behaviour + otherwise is very similar to the + ListenFIFO= + directive above. On Linux message + queue descriptors are actually file + descriptors and can be inherited + between processes. + + + + BindIPv6Only= + Takes a one of + , + or + . Controls + the IPV6_V6ONLY socket option (see + ipv67 + for details). If + , IPv6 sockets + bound will be accessible via both IPv4 + and IPv6. If + , they will + be accessible via IPv6 only. If + (which is the + default, surprise!) the system wide + default setting is used, as controlled + by + /proc/sys/net/ipv6/bindv6only. + + + + + Backlog= + Takes an unsigned + integer argument. Specifies the number + of connections to queue that have not + been accepted yet. This setting + matters only for stream and sequential + packet sockets. See + listen2 + for details. Defaults to SOMAXCONN + (128). + + + + BindToDevice= + Specifies a network + interface name to bind this socket + to. If set traffic will only be + accepted from the specified network + interfaces. This controls the + SO_BINDTODEVICE socket option (see + socket7 + for details). If this option is used, + an automatic dependency from this + socket unit on the network interface + device unit + (systemd.device5 + is created. + + + + DirectoryMode= + If listening on a file + system socket of FIFO, the parent + directories are automatically created + if needed. This option specifies the + file system access mode used when + creating these directories. Takes an + access mode in octal + notation. Defaults to + 0755. + + + + SocketMode= + If listening on a file + system socket of FIFO, this option + specifies the file system access mode + used when creating the file + node. Takes an access mode in octal + notation. Defaults to + 0666. + + + + Accept= + Takes a boolean + argument. If true, a service instance + is spawned for each incoming + connection and only the connection + socket is passed to it. If false, all + listening sockets themselves are + passed to the started service unit, + and only one service unit is spawned + for all connections (also see + above). This value is ignored for + datagram sockets and FIFOs where + a single service unit unconditionally + handles all incoming traffic. Defaults + to . For + performance reasons, it is recommended + to write new daemons only in a way + that is suitable for + . This + option is mostly useful to allow + daemons designed for usage with + inetd8, + to work unmodified with systemd socket + activation. + + + + MaxConnections= + The maximum number of + connections to simultaneously run + services instances for, when + is + set. If more concurrent connections + are coming in, they will be refused + until at least one existing connection + is terminated. This setting has no + effect for sockets configured with + or datagram + sockets. Defaults to + 64. + + + + KeepAlive= + Takes a boolean + argument. If true, the TCP/IP stack + will send a keep alive message after + 2h (depending on the configuration of + /proc/sys/net/ipv4/tcp_keepalive_time) + for all TCP streams accepted on this + socket. This controls the SO_KEEPALIVE + socket option (see + socket7 + and the TCP + Keepalive HOWTO for details.) + Defaults to + . + + + + Priority= + Takes an integer + argument controlling the priority for + all traffic sent from this + socket. This controls the SO_PRIORITY + socket option (see + socket7 + for details.). + + + + ReceiveBuffer= + SendBuffer= + Takes an integer + argument controlling the receive + resp. send buffer sizes of this + socket. This controls the SO_RCVBUF + resp. SO_SNDBUF socket options (see + socket7 + for details.). + + + + IPTOS= + Takes an integer + argument controlling the IP + Type-Of-Service field for packets + generated from this socket. This + controls the IP_TOS socket option (see + ip7 + for details.). Either a numeric string + or one of , + , + or + may be + specified. + + + + IPTTL= + Takes an integer + argument controlling the IPv4 + Time-To-Live/IPv6 Hop-Count field for + packets generated from this + socket. This sets the + IP_TTL/IPV6_UNICAST_HOPS socket + options (see + ip7 + and + ipv67 + for details.) + + + + Mark= + Takes an integer + value. Controls the firewall mark of + packets generated by this socket. This + can be used in the firewall logic to + filter packets from this socket. This + sets the SO_MARK socket option. See + iptables8 + for details. + + + + PipeSize= + Takes an integer + value. Controls the pipe buffer size + of FIFOs configured in this socket + unit. See + fcntl2 + for details. + + + + MessageQueueMaxMessages=, + MessageQueueMessageSize= + These two settings + take integer values and control the + mq_maxmsg resp. mq_msgsize field when + creating the message queue. Note that + either none or both of these variables + need to be set. See + mq_setattr3 + for details. + + + + FreeBind= + Takes a boolean + value. Controls whether the socket can + be bound to non-local IP + addresses. This is useful to configure + sockets listening on specific IP + addresses before those IP addresses + are successfully configured on a + network interface. This sets the + IP_FREEBIND socket option. For + robustness reasons it is recommended + to use this option whenever you bind a + socket to a specific IP + address. Defaults to . + + + + Transparent= + Takes a boolean + value. Controls the IP_TRANSPARENT + socket option. Defaults to + . + + + + Broadcast= + Takes a boolean + value. This controls the SO_BROADCAST + socket option, which allows broadcast + datagrams to be sent from this + socket. Defaults to + . + + + + PassCredentials= + Takes a boolean + value. This controls the SO_PASSCRED + socket option, which allows AF_UNIX sockets to + receive the credentials of the sending + process in an ancillary message. + Defaults to + . + + + + PassSecurity= + Takes a boolean + value. This controls the SO_PASSSEC + socket option, which allows AF_UNIX + sockets to receive the security + context of the sending process in an + ancillary message. Defaults to + . + + + + TCPCongestion= + Takes a string + value. Controls the TCP congestion + algorithm used by this socket. Should + be one of "westwood", "veno", "cubic", + "lp" or any other available algorithm + supported by the IP stack. This + setting applies only to stream + sockets. + + + + ExecStartPre= + ExecStartPost= + Takes one or more + command lines, which are executed + before (resp. after) the listening + sockets/FIFOs are created and + bound. The first token of the command + line must be an absolute file name, + then followed by arguments for the + process. Multiple command lines may be + specified following the same scheme as + used for + ExecStartPre= of + service unit files. + + + + ExecStopPre= + ExecStopPost= + Additional commands + that are executed before (resp. after) + the listening sockets/FIFOs are closed + and removed. Multiple command lines + may be specified following the same + scheme as used for + ExecStartPre= of + service unit files. + + + + TimeoutSec= + Configures the time to + wait for the commands specified in + ExecStartPre=, + ExecStartPost=, + ExecStopPre= and + ExecStopPost= to + finish. If a command does not exit + within the configured time, the socket + will be considered failed and be shut + down again. All commands still running, + will be terminated forcibly via + SIGTERM, and after another delay of + this time with SIGKILL. (See + below.) + Takes a unit-less value in seconds, or + a time span value such as "5min + 20s". Pass 0 to disable the timeout + logic. Defaults to + 90s. + + + + KillMode= + Specifies how + processes of this socket unit shall be + killed. One of + , + , + . + + This option is mostly equivalent + to the + option of service files. See + systemd.service5 + for details. + + + + KillSignal= + Specifies which signal + to use when killing a process of this + socket. Defaults to SIGTERM. + + + + + SendSIGKILL= + Specifies whether to + send SIGKILL to remaining processes + after a timeout, if the normal + shutdown procedure left processes of + the socket around. Takes a boolean + value. Defaults to "yes". + + + + + Service= + Specifies the service + unit name to activate on incoming + traffic. This defaults to the service + that bears the same name as the socket + (ignoring the different suffixes). In + most cases it should not be necessary + to use this option. + + + + + + + See Also + + systemd1, + systemctl8, + systemd.unit5, + systemd.exec5, + systemd.service5 + + + + diff --git a/man/systemd.special.xml b/man/systemd.special.xml new file mode 100644 index 000000000..116a43ccf --- /dev/null +++ b/man/systemd.special.xml @@ -0,0 +1,725 @@ + + + + + + + + + systemd.special + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.special + 7 + + + + systemd.special + special systemd units + + + + basic.target, + ctrl-alt-del.target, + dbus.service, + default.target, + display-manager.service, + emergency.target, + exit.service, + graphical.target, + halt.target, + kbrequest.target, + local-fs.target, + local-fs-pre.target, + mail-transfer-agent.target, + multi-user.target, + network.target, + nss-lookup.target, + poweroff.target, + reboot.target, + remote-fs.target, + remote-fs-pre.target, + rescue.target, + rpcbind.target, + runlevel2.target, + runlevel3.target, + runlevel4.target, + runlevel5.target, + shutdown.target, + sigpwr.target, + sockets.target, + swap.target, + sysinit.target, + syslog.target, + systemd-initctl.service, + systemd-initctl.socket, + systemd-stdout-syslog-bridge.service, + systemd-stdout-syslog-bridge.socket, + time-sync.target, + umount.target + + + + Description + + A few units are treated specially by + systemd. They have special internal semantics and + cannot be renamed. + + + + Special System Units + + + + basic.target + + A special target unit + covering early boot-up. + systemd automatically + adds dependencies of the types + Requires and After for this + target unit to all SysV + service units configured for + runlevel 1 to 5. + Usually this should pull-in + all sockets, mount points, + swap devices and other basic + initialization necessary for + the general purpose + daemons. Most normal daemons + should have dependencies of + type After and Requires on + this unit. + + + + ctrl-alt-del.target + + systemd starts this + target whenever + Control+Alt+Del is pressed on + the console. Usually this + should be aliased (symlinked) + to + reboot.target. + + + + dbus.service + + A special unit for the + D-Bus system bus. As soon as + this service is fully started + up systemd will connect to it + and register its + service. + + + + default.target + + The default unit systemd + starts at bootup. Usually this + should be aliased (symlinked) + to + multi-user.target + or + graphical.target. + The default unit systemd + starts at bootup can be + overridden with the + systemd.unit= + kernel command line option. + + + + display-manager.service + + The display manager + service. Usually this should + be aliased (symlinked) to + xdm.service + or a similar display manager + service. + systemd automatically + adds dependencies of type + After for this target unit to + all SysV init script service + units with a LSB header + referring to the + $x-display-manager + facility, for compatibility + with Debian. + + + + emergency.target + + A special target unit + that starts an emergency + shell on the main + console. This unit is supposed + to be used with the kernel + command line option + systemd.unit= + and has otherwise little use. + + + + + graphical.target + + A special target unit + for setting up a graphical + login screen. This pulls in + multi-user.target. + + Units that are needed + for graphical login shall add + Wants dependencies for their + unit to this unit (or + multi-user.target) + during installation. + + + + halt.target + + A special target unit + for shutting down and halting the system. + + Applications wanting to + halt the system should start + this unit. + + + + kbrequest.target + + systemd starts this + target whenever Alt+ArrowUp is + pressed on the console. This + is a good candidate to be + aliased (symlinked) to + rescue.target. + + + + local-fs.target + + systemd automatically + adds dependencies of type + After to all mount units that + refer to local mount points + for this target unit. In + addition, systemd adds + dependencies of type Wants to + this target unit for those + mounts listed in + /etc/fstab + that have the + and + + mount options set. + + systemd automatically + adds dependencies of type + After for this target unit to + all SysV init script service + units with an LSB header + referring to the + $local_fs + facility. + + + + local-fs-pre.target + + This target unit is + automatically ordered before + all local mount points marked + with + (see above). It can be used to + execute certain units before + all local mounts. + + + + mail-transfer-agent.target + + The mail transfer agent + (MTA) service. Usually this + should pull-in all units + necessary for + sending/receiving mails on the + local host. + + systemd automatically + adds dependencies of type + After for this target unit to + all SysV init script service + units with an LSB header + referring to the + $mail-transfer-agent + or + $mail-transport-agent + facilities, for compatibility + with Debian. + + + + multi-user.target + + A special target unit + for setting up a multi-user + system (non-graphical). This + is pulled in by + graphical.target. + + Units that are needed + for a multi-user system shall + add Wants dependencies to + this unit for their unit during + installation. + + + + network.target + + systemd automatically + adds dependencies of type + After for this target unit to + all SysV init script service + units with an LSB header + referring to the + $network + facility. + + + + nss-lookup.target + + systemd automatically + adds dependencies of type + After for this target unit to + all SysV init script service + units with an LSB header + referring to the + $named + facility. + + + + poweroff.target + + A special target unit + for shutting down and powering off the system. + + Applications wanting to + power off the system should start + this unit. + + runlevel0.target + is an alias for this target + unit, for compatibility with SysV. + + + + reboot.target + + A special target unit + for shutting down and rebooting the system. + + Applications wanting to + reboot the system should start + this unit. + + runlevel6.target + is an alias for this target + unit, for compatibility with SysV. + + + + remote-fs.target + + Similar to + local-fs.target, + but for remote mount + points. + + systemd automatically + adds dependencies of type + After for this target unit to + all SysV init script service + units with an LSB header + referring to the + $remote_fs + facility. + + + + remote-fs-pre.target + + This target unit is + automatically ordered before + all remote mount points marked + with + (see above). It can be used to + execute certain units before + all remote mounts. + + + + rescue.target + + A special target unit + for setting up the base system + and a rescue shell. + + runlevel1.target + is an alias for this target + unit, for compatibility with SysV. + + + + rpcbind.target + + systemd automatically + adds dependencies of type + After for this target unit to + all SysV init script service + units with an LSB header + referring to the + $rpcbind + facility. + + + + runlevel2.target + + This is a target that is + called whenever the SysV + compatibility code asks for + runlevel 2. It is a good idea + to make this an alias for + (i.e. symlink to) + multi-user.target. + + + + runlevel3.target + + This is a target that is + called whenever the SysV + compatibility code asks for + runlevel 3. It is a good idea + to make this an alias for + (i.e. symlink to) + multi-user.target + or + graphical.target. + + + + runlevel4.target + + This is a target that is + called whenever the SysV + compatibility code asks for + runlevel 4. It is a good idea + to make this an alias for + (i.e. symlink to) + multi-user.target + or + graphical.target. + + + + runlevel5.target + + This is a target that is + called whenever the SysV + compatibility code asks for + runlevel 5. It is a good idea + to make this an alias for + (i.e. symlink to) + multi-user.target + or + graphical.target. + + + + shutdown.target + + A special target unit + that terminates the services + on system shutdown. + + Services that shall be + terminated on system shutdown + shall add Conflicts + dependencies to this unit for + their service unit, which is + implicitly done when + DefaultDependencies=yes + is set (the default). + + systemd automatically + adds dependencies of type + Conflicts to this target unit + for all SysV init script + service units that shall be + terminated in SysV runlevels 0 + or 6. + + + + sigpwr.target + + A special target that is + started when systemd receives + the SIGPWR process signal, + which is normally sent by the + kernel or UPS daemons when + power fails. + + + + sockets.target + + A special target unit + that sets up all service + sockets. + + Services that can be + socket-activated shall add + Wants dependencies to this + unit for their socket unit + during installation. + + + + swap.target + + Similar to + local-fs.target, but for swap + partitions and swap + files. + + + + sysinit.target + + A special target unit + covering early boot-up scripts. + systemd automatically + adds dependencies of the types + Wants and After for all + SysV service units configured + for runlevels that are not 0 + to 6 to this target unit. + This covers the special + boot-up runlevels some + distributions have, such as S + or b. + + + + syslog.target + + systemd automatically + adds dependencies of type + After for this target unit to + all SysV init script service + units with an LSB header + referring to the + $syslog + facility. + + + + systemd-initctl.service + + This provides + compatibility with the SysV + /dev/initctl file system FIFO + for communication with the + init system. + This is a + socket-activated service, see + system-initctl.socket. + + + + systemd-initctl.socket + + Socket activation unit + for + system-initctl.service. + + + + systemd-stdout-syslog-bridge.service + + This is internally used + by systemd to provide syslog + logging to the processes it + maintains. + This is a + socket-activated service, see + system-stdout-syslog-bridge.socket. + + + + systemd-stdout-syslog-bridge.socket + + Socket activation unit + for + system-stdout-syslog-bridge.service. systemd + will automatically add + dependencies of types Requires + and After to all units that + have been configured for + stdout or stderr to be + connected to syslog or the + kernel log buffer. + + + + systemd-shutdownd.service + + This is internally used + by + shutdown8 + to implement delayed shutdowns. + This is a + socket-activated service, see + system-shutdownd.socket. + + + + systemd-shutdownd.socket + + Socket activation unit + for + system-shutdownd.service. + + + + time-sync.target + + systemd automatically + adds dependencies of type + After for this target unit to + all SysV init script service + units with an LSB header + referring to the + $time + facility. + + + + umount.target + + A special target unit + that umounts all mount and + automount points on system + shutdown. + + Mounts that shall be + unmounted on system shutdown + shall add Conflicts + dependencies to this unit for + their mount unit, which is + implicitly done when + DefaultDependencies=yes + is set (the default). + + + + + + + Special User Units + + When systemd runs as a user instance, the + following special units are available, which have + similar definitions as their system counterparts: + default.target, + local-fs.target, + remote-fs.target, + shutdown.target, + sockets.target, + swap.target. + + In addition the following special unit is + understood only when systemd runs as service instance: + + + + exit.service + + A special service unit + for shutting down the + user service manager. + + Applications wanting to + terminate the user service + manager should start this + unit. If systemd receives + SIGTERM or SIGINT when running + as user service daemon it will + start this unit. + + Normally, this pulls in + shutdown.target + which in turn should be + conflicted by all units that + want to be shut down on + user service manager exit. + + + + + + + See Also + + systemd.unit5, + systemd.service5, + systemd.socket5, + systemd.target5 + + + + diff --git a/man/systemd.swap.xml b/man/systemd.swap.xml new file mode 100644 index 000000000..ab00f9f31 --- /dev/null +++ b/man/systemd.swap.xml @@ -0,0 +1,222 @@ + + + + + + + + + systemd.swap + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.swap + 5 + + + + systemd.swap + systemd swap configuration files + + + + systemd.swap + + + + Description + + A unit configuration file whose name ends in + .swap encodes information about a + swap device or file for memory paging controlled and + supervised by systemd. + + This man page lists the configuration options + specific to this unit type. See + systemd.unit5 + for the common options of all unit configuration + files. The common configuration items are configured + in the generic [Unit] and [Install] sections. The swap + specific configuration options are configured in the + [Swap] section. + + Swap units must be named after the devices + (resp. files) they control. Example: the swap device + /dev/sda5 must be configured in a + unit file dev-sda5.swap. For + details about the escaping logic used to convert a + file system path to a unit name see + systemd.unit5. + + All swap units automatically get the appropriate + dependencies on the devices (resp. on the mount points + of the files) they are activated from. + + Swap units with + DefaultDependencies= enabled + implicitly acquire a conflicting dependency to + umount.target so that they are + deactivated at shutdown. + + + + <filename>fstab</filename> + + Swap units may either be configured via unit + files, or via /etc/fstab (see + fstab5 + for details). + + If a swap device or file is configured in both + /etc/fstab and a unit file the + configuration in the latter takes precedence. + + Unless the option is set + for them all swap units configured in + /etc/fstab are also added as + requirements to swap.target, so + that they are waited for and activated during + boot. + + + + Options + + Swap files must include a [Swap] section, which + carries information about the swap device it + supervises. A number of options that may be used in + this section are shared with other unit types. These + options are documented in + systemd.exec5. The + options specific to the [Swap] section of swap units + are the following: + + + + + What= + Takes an absolute path + of a device node or file to use for + paging. See + swapon8 + for details. If this refers to a + device node, a dependency on the + respective device unit is + automatically created. (See + systemd.device5 + for more information.) If this refers + to a file, a dependency on the + respective mount unit is automatically + created. (See + systemd.mount5 + for more information.) This option is + mandatory. + + + + Priority= + + Swap priority to use + when activating the swap device or + file. This takes an integer. This + setting is optional. + + + + TimeoutSec= + Configures the time to + wait for the swapon command to + finish. If a command does not exit + within the configured time the swap + will be considered failed and be shut + down again. All commands still running + will be terminated forcibly via + SIGTERM, and after another delay of + this time with SIGKILL. (See + below.) + Takes a unit-less value in seconds, or + a time span value such as "5min + 20s". Pass 0 to disable the timeout + logic. Defaults to + 90s. + + + + KillMode= + Specifies how + processes of this swap shall be + killed. One of + , + , + . + + This option is mostly equivalent + to the + option of service files. See + systemd.service5 + for details. + + + + KillSignal= + Specifies which signal + to use when killing a process of this + swap. Defaults to SIGTERM. + + + + + SendSIGKILL= + Specifies whether to + send SIGKILL to remaining processes + after a timeout, if the normal + shutdown procedure left processes of + the swap around. Takes a boolean + value. Defaults to "yes". + + + + + + + See Also + + systemd1, + systemctl8, + systemd.unit5, + systemd.exec5, + systemd.device5, + systemd.mount5, + swapon8 + + + + diff --git a/man/systemd.target.xml b/man/systemd.target.xml new file mode 100644 index 000000000..6b1dbfbde --- /dev/null +++ b/man/systemd.target.xml @@ -0,0 +1,108 @@ + + + + + + + + + systemd.target + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.target + 5 + + + + systemd.target + systemd target configuration files + + + + systemd.target + + + + Description + + A unit configuration file whose name ends in + .target encodes information about + a target unit of systemd, which is used for grouping + units and as well-known synchronization points during + start-up. + + This unit type has no specific options. See + systemd.unit5 + for the common options of all unit configuration + files. The common configuration items are configured + in the generic [Unit] and [Install] sections. A + separate [Target] section does not exist, since no + target-specific options may be configured. + + Target units do not offer any additional + functionality on top of the generic functionality + provided by units. They exist merely to group units via dependencies + (useful as boot targets), and to establish + standardized names for synchronization points used in + dependencies between units. Among other things, target + units are a more flexible replacement for SysV + runlevels in the classic SysV init system. (And for + compatibility reasons special + target units such as + runlevel3.target exist which are used by + the SysV runlevel compatibility code in systemd. See + systemd.special7 + for details). + + Unless DefaultDependencies= + is set to , target units will + implicitly complement all configured dependencies of + type Wants=, + Requires=, + RequiresOverridable= with + dependencies of type After= if the + units in question also have + DefaultDependencies=true. + + + + + See Also + + systemd1, + systemctl8, + systemd.unit5, + systemd.special7 + + + + diff --git a/man/systemd.timer.xml b/man/systemd.timer.xml new file mode 100644 index 000000000..9b6b486bf --- /dev/null +++ b/man/systemd.timer.xml @@ -0,0 +1,192 @@ + + + + + + + + + systemd.timer + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.timer + 5 + + + + systemd.timer + systemd timer configuration files + + + + systemd.timer + + + + Description + + A unit configuration file whose name ends in + .timer encodes information about + a timer controlled and supervised by systemd, for + timer-based activation. + + This man page lists the configuration options + specific to this unit type. See + systemd.unit5 + for the common options of all unit configuration + files. The common configuration items are configured + in the generic [Unit] and [Install] sections. The + timer specific configuration options are configured in + the [Timer] section. + + For each timer file, a matching unit file must + exist, describing the unit to activate when the timer + elapses. By default, a service by the same name as the + timer (except for the suffix) is activated. Example: a + timer file foo.timer activates a + matching service foo.service. The + unit to activate may be controlled by + Unit= (see below). + + Unless DefaultDependencies= + is set to , timer units will + implicitly have dependencies of type + Conflicts= and + Before= on + shutdown.target. These ensure + that timer units are stopped cleanly prior to system + shutdown. Only timer units involved with early boot or + late system shutdown should disable this + option. + + + + Options + + Timer files must include a [Timer] section, + which carries information about the timer it + defines. The options specific to the [Timer] section + of timer units are the following: + + + + OnActiveSec= + OnBootSec= + OnStartupSec= + OnUnitActiveSec= + OnUnitInactiveSec= + + Defines timers + relative to different starting points: + OnActiveSec= defines a + timer relative to the moment the timer + itself is + activated. OnBootSec= + defines a timer relative to when the + machine was booted + up. OnStartupSec= + defines a timer relative to when + systemd was + started. OnUnitActiveSec= + defines a timer relative to when the + unit the timer is activating was last + activated. OnUnitInactiveSec= + defines a timer relative to when the + unit the timer is activating was last + deactivated. + + Multiple directives may be + combined of the same and of different + types. For example, by combining + OnBootSec= and + OnUnitActiveSec= it is + possible to define a timer that + elapses in regular intervals and + activates a specific service each + time. + + The arguments to the directives + are time spans configured in + seconds. Example: "OnBootSec=50" means + 50s after boot-up. The argument may + also include time units. Example: + "OnBootSec=5h 30min" means 5 hours and 30 + minutes after boot-up. For details + about the syntax of time spans see + systemd.unit5. + + If a timer configured with + OnBootSec= or + OnStartupSec= is + already in the past when the timer + unit is activated, it will immediately + elapse and the configured unit is + started. This is not the case for + timers defined in the other + directives. + + These are monotonic timers, + independent of wall-clock time and timezones. If the + computer is temporarily suspended, the + monotonic clock stops too. + + + + Unit= + + The unit to activate + when this timer elapses. The argument is a + unit name, whose suffix is not + .timer. If not + specified, this value defaults to a + service that has the same name as the + timer unit, except for the + suffix. (See above.) It is recommended + that the unit name that is activated + and the unit name of the timer unit + are named identically, except for the + suffix. + + + + + + See Also + + systemd1, + systemctl8, + systemd.unit5, + systemd.service5 + + + + diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml new file mode 100644 index 000000000..3cc126b12 --- /dev/null +++ b/man/systemd.unit.xml @@ -0,0 +1,969 @@ + + + + + + + + + systemd.unit + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd.unit + 5 + + + + systemd.unit + systemd unit configuration files + + + + systemd.service, + systemd.socket, + systemd.device, + systemd.mount, + systemd.automount, + systemd.swap, + systemd.target, + systemd.path, + systemd.timer, + systemd.snapshot + + + + Description + + A unit configuration file encodes information + about a service, a socket, a device, a mount point, an + automount point, a swap file or partition, a start-up + target, a file system path or a timer controlled and + supervised by + systemd1. The + syntax is inspired by XDG + Desktop Entry Specification .desktop files, which are in turn + inspired by Microsoft Windows + .ini files. + + This man pages lists the common configuration + options of all the unit types. These options need to + be configured in the [Unit] resp. [Install] + section of the unit files. + + In addition to the generic [Unit] and [Install] + sections described here, each unit should have a + type-specific section, e.g. [Service] for a service + unit. See the respective man pages for more + information. + + Unit files may contain additional options on top + of those listed here. If systemd encounters an unknown + option it will write a warning log message but + continue loading the unit. If an option is prefixed + with it is ignored completely by + systemd. Applications may use this to include + additional information in the unit files. + + Boolean arguments used in unit files can be + written in various formats. For positive settings the + strings , , + and are + equivalent. For negative settings the strings + , , + and are + equivalent. + + 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. + + 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. + + If a line starts with + followed by a file name, the specified file will be + parsed at this point. Make sure that the file that is + included has the appropiate section headers before + any directives. + + Along with a unit file + foo.service a directory + foo.service.wants/ may exist. All + units symlinked from such a directory are implicitly + added as dependencies of type + Wanted= to the unit. This is useful + to hook units into the start-up of other units, + without having to modify their unit configuration + files. For details about the semantics of + Wanted= see below. The preferred + way to create symlinks in the + .wants/ directory of a service is + with the enable command of the + systemctl1 + tool which reads information from the [Install] + section of unit files. (See below.) A similar + functionality exists for Requires= + type dependencies as well, the directory suffix is + .requires/ in this case. + + Note that while systemd offers a flexible + dependency system between units it is recommended to + use this functionality only sparsely and instead rely + on techniques such as bus-based or socket-based + activation which makes dependencies implicit, which + both results in a simpler and more flexible + system. + + Some unit names reflect paths existing in the + file system name space. Example: a device unit + dev-sda.device refers to a device + with the device node /dev/sda in + the file system namespace. If this applies a special + way to escape the path name is used, so that the + result is usable as part of a file name. Basically, + given a path, "/" is replaced by "-", and all + unprintable characters and the "-" are replaced by + C-style "\x20" escapes. The root directory "/" is + encoded as single dash, while otherwise the initial + and ending "/" is removed from all paths during + transformation. This escaping is reversible. + + Optionally, units may be instantiated from a + template file at runtime. This allows creation of + multiple units from a single configuration file. If + systemd looks for a unit configuration file it will + first search for the literal unit name in the + filesystem. If that yields no success and the unit + name contains an @ character, systemd will look for a + unit template that shares the same name but with the + instance string (i.e. the part between the @ character + and the suffix) removed. Example: if a service + getty@tty3.service is requested + and no file by that name is found, systemd will look + for getty@.service and + instantiate a service from that configuration file if + it is found. + + To refer to the instance string from + within the configuration file you may use the special + %i specifier in many of the + configuration options. Other specifiers exist, the + full list is: + + + Specifiers available in unit files + + + + + + + Specifier + Meaning + Details + + + + + %n + Full unit name + + + + %N + Unescaped full unit name + + + + %p + Prefix name + This refers to the string before the @, i.e. "getty" in the example above, where "tty3" is the instance name. + + + %P + Unescaped prefix name + + + + %i + Instance name + This is the string between the @ character and the suffix. + + + %I + Unescaped instance name + + + + %f + Unescaped file name + This is either the unescaped instance name (if set) with / prepended (if necessary), or the prefix name similarly prepended with /. + + + %c + Control group path of the unit + + + + %r + Root control group path of systemd + + + + %R + Parent directory of the root control group path of systemd + + + + %t + Runtime socket dir + This is either /run (for the system manager) or $XDG_RUNTIME_DIR (for user managers). + + + +
+ + If a unit file is empty (i.e. has the file size + 0) or is symlinked to /dev/null + its configuration will not be loaded and it appears + with a load state of masked, and + cannot be activated. Use this as an effective way to + fully disable a unit, making it impossible to start it + even manually. + + The unit file format is covered by the + Interface + Stability Promise. +
+ + + Options + + Unit file may include a [Unit] section, which + carries generic information about the unit that is not + dependent on the type of unit: + + + + + Description= + A free-form string + describing the unit. This is intended + for use in UIs to show descriptive + information along with the unit + name. + + + + Requires= + + Configures requirement + dependencies on other units. If this + unit gets activated, the units listed + here will be activated as well. If one + of the other units gets deactivated or + its activation fails, this unit will + be deactivated. This option may be + specified more than once, in which + case requirement dependencies for all + listed names are created. Note that + requirement dependencies do not + influence the order in which services + are started or stopped. This has to be + configured independently with the + After= or + Before= options. If + a unit + foo.service + requires a unit + bar.service as + configured with + Requires= and no + ordering is configured with + After= or + Before=, then both + units will be started simultaneously + and without any delay between them if + foo.service is + activated. Often it is a better choice + to use Wants= + instead of + Requires= in order + to achieve a system that is more + robust when dealing with failing + services. + + + + RequiresOverridable= + + Similar to + Requires=. + Dependencies listed in + RequiresOverridable= + which cannot be fulfilled or fail to + start are ignored if the startup was + explicitly requested by the user. If + the start-up was pulled in indirectly + by some dependency or automatic + start-up of units that is not + requested by the user this dependency + must be fulfilled and otherwise the + transaction fails. Hence, this option + may be used to configure dependencies + that are normally honored unless the + user explicitly starts up the unit, in + which case whether they failed or not + is irrelevant. + + + + Requisite= + RequisiteOverridable= + + Similar to + Requires= + resp. RequiresOverridable=. However, + if a unit listed here is not started + already it will not be started and the + transaction fails + immediately. + + + + Wants= + + A weaker version of + Requires=. A unit + listed in this option will be started + if the configuring unit is. However, + if the listed unit fails to start up + or cannot be added to the transaction + this has no impact on the validity of + the transaction as a whole. This is + the recommended way to hook start-up + of one unit to the start-up of another + unit. Note that dependencies of this + type may also be configured outside of + the unit configuration file by + adding a symlink to a + .wants/ directory + accompanying the unit file. For + details see above. + + + + BindTo= + + Configures requirement + dependencies, very similar in style to + Requires=, however + in addition to this behaviour it also + declares that this unit is stopped + when any of the units listed suddenly + disappears. Units can suddenly, + unexpectedly disappear if a service + terminates on its own choice, a device + is unplugged or a mount point + unmounted without involvement of + systemd. + + + + Conflicts= + + Configures negative + requirement dependencies. If a unit + has a + Conflicts= setting + on another unit, starting the former + will stop the latter and vice + versa. Note that this setting is + independent of and orthogonal to the + After= and + Before= ordering + dependencies. + + If a unit A that conflicts with + a unit B is scheduled to be started at + the same time as B, the transaction + will either fail (in case both are + required part of the transaction) or + be modified to be fixed (in case one + or both jobs are not a required part + of the transaction). In the latter + case the job that is not the required + will be removed, or in case both are + not required the unit that conflicts + will be started and the unit that is + conflicted is + stopped. + + + + Before= + After= + + Configures ordering + dependencies between units. If a unit + foo.service + contains a setting + + and both units are being started, + bar.service's + start-up is delayed until + foo.service is + started up. Note that this setting is + independent of and orthogonal to the + requirement dependencies as configured + by Requires=. It is + a common pattern to include a unit + name in both the + After= and + Requires= option in + which case the unit listed will be + started before the unit that is + configured with these options. This + option may be specified more than + once, in which case ordering + dependencies for all listed names are + created. After= is + the inverse of + Before=, i.e. while + After= ensures that + the configured unit is started after + the listed unit finished starting up, + Before= ensures the + opposite, i.e. that the configured + unit is fully started up before the + listed unit is started. Note that when + two units with an ordering dependency + between them are shut down, the + inverse of the start-up order is + applied. i.e. if a unit is configured + with After= on + another unit, the former is stopped + before the latter if both are shut + down. If one unit with an ordering + dependency on another unit is shut + down while the latter is started up, + the shut down is ordered before the + start-up regardless whether the + ordering dependency is actually of + type After= or + Before=. If two + units have no ordering dependencies + between them they are shut down + resp. started up simultaneously, and + no ordering takes + place. + + + + OnFailure= + + Lists one or more + units that are activated when this + unit enters the + 'failed' + state. + + + + PropagateReloadTo= + PropagateReloadFrom= + + Lists one or more + units where reload requests on the + unit will be propagated to/on the + other unit will be propagated + from. Issuing a reload request on a + unit will automatically also enqueue a + reload request on all units that the + reload request shall be propagated to + via these two + settings. + + + + OnFailureIsolate= + + Takes a boolean + argument. If the + unit listed in + OnFailure= will be + enqueued in isolation mode, i.e. all + units that are not its dependency will + be stopped. If this is set only a + single unit may be listed in + OnFailure=. Defaults + to + . + + + + IgnoreOnIsolate= + + Takes a boolean + argument. If + this unit will not be stopped when + isolating another unit. Defaults to + . + + + + IgnoreOnSnapshot= + + Takes a boolean + argument. If + this unit will not be included in + snapshots. Defaults to + for device and + snapshot units, + for the others. + + + + StopWhenUnneeded= + + Takes a boolean + argument. If + this unit will be stopped when it is + no longer used. Note that in order to + minimize the work to be executed, + systemd will not stop units by default + unless they are conflicting with other + units, or the user explicitly + requested their shut down. If this + option is set, a unit will be + automatically cleaned up if no other + active unit requires it. Defaults to + . + + + + RefuseManualStart= + RefuseManualStop= + + Takes a boolean + argument. If + this unit can only be activated + (resp. deactivated) indirectly. In + this case explicit start-up + (resp. termination) requested by the + user is denied, however if it is + started (resp. stopped) as a + dependency of another unit, start-up + (resp. termination) will succeed. This + is mostly a safety feature to ensure + that the user does not accidentally + activate units that are not intended + to be activated explicitly, and not + accidentally deactivate units that are + not intended to be deactivated. + These options default to + . + + + + AllowIsolate= + + Takes a boolean + argument. If + this unit may be used with the + systemctl isolate + command. Otherwise this will be + refused. It probably is a good idea to + leave this disabled except for target + units that shall be used similar to + runlevels in SysV init systems, just + as a precaution to avoid unusable + system states. This option defaults to + . + + + + DefaultDependencies= + + Takes a boolean + argument. If + (the default), a few default + dependencies will implicitly be + created for the unit. The actual + dependencies created depend on the + unit type. For example, for service + units, these dependencies ensure that + the service is started only after + basic system initialization is + completed and is properly terminated on + system shutdown. See the respective + man pages for details. Generally, only + services involved with early boot or + late shutdown should set this option + to . It is + highly recommended to leave this + option enabled for the majority of + common units. If set to + this option + does not disable all implicit + dependencies, just non-essential + ones. + + + + JobTimeoutSec= + + When clients are + waiting for a job of this unit to + complete, time out after the specified + time. If this time limit is reached + the job will be cancelled, the unit + however will not change state or even + enter the 'failed' + mode. This value defaults to 0 (job + timeouts disabled), except for device + units. NB: this timeout is independent + from any unit-specific timeout (for + example, the timeout set with + Timeout= in service + units) as the job timeout has no + effect on the unit itself, only on the + job that might be pending for it. Or + in other words: unit-specific timeouts + are useful to abort unit state + changes, and revert them. The job + timeout set with this option however + is useful to abort only the job + waiting for the unit state to + change. + + + + ConditionPathExists= + ConditionPathExistsGlob= + ConditionPathIsDirectory= + ConditionPathIsSymbolicLink= + ConditionPathIsMountPoint= + ConditionDirectoryNotEmpty= + ConditionFileIsExecutable= + ConditionKernelCommandLine= + ConditionVirtualization= + ConditionSecurity= + ConditionCapability= + ConditionNull= + + Before starting a unit + verify that the specified condition is + true. With + ConditionPathExists= + a file existence condition can be + checked before a unit is started. If + the specified absolute path name does + not exist, startup of a unit will not + actually happen, however the unit is + still useful for ordering purposes in + this case. The condition is checked at + the time the queued start job is to be + executed. If the absolute path name + passed to + ConditionPathExists= + is prefixed with an exclamation mark + (!), the test is negated, and the unit + is only started if the path does not + exist. + ConditionPathExistsGlob= + works in a similar way, but checks for + the existence of at least one file or + directory matching the specified + globbing + pattern. ConditionPathIsDirectory= + is similar to + ConditionPathExists= + but verifies whether a certain path + exists and is a + directory. ConditionPathIsSymbolicLink= + is similar to + ConditionPathExists= + but verifies whether a certain path + exists and is a symbolic + link. ConditionPathIsMountPoint= + is similar to + ConditionPathExists= + but verifies whether a certain path + exists and is a mount + point. ConditionFileIsExecutable= + is similar to + ConditionPathExists= + but verifies whether a certain path + exists, is a regular file and marked + executable. + ConditionDirectoryNotEmpty= + is similar to + ConditionPathExists= + but verifies whether a certain path + exists and is a non-empty + directory. Similarly + ConditionKernelCommandLine= + may be used to check whether a + specific kernel command line option is + set (or if prefixed with the + exclamation mark unset). The argument + must either be a single word, or an + assignment (i.e. two words, separated + by the equality sign). In the former + case the kernel command line is + searched for the word appearing as is, + or as left hand side of an + assignment. In the latter case the + exact assignment is looked for with + right and left hand side + matching. ConditionVirtualization= + may be used to check whether the + system is executed in a virtualized + environment and optionally test + whether it is a specific + implementation. Takes either boolean + value to check if being executed in + any virtualized environment, or one of + vm and + container to test + against a specific type of + virtualization solution, or one of + qemu, + kvm, + vmware, + microsoft, + oracle, + xen, + bochs, + chroot, + openvz, + lxc, + lxc-libvirt, + systemd-nspawn to test + against a specific implementation. If + multiple virtualization technologies + are nested only the innermost is + considered. The test may be negated by + prepending an exclamation mark. + ConditionSecurity= + may be used to check whether the given + security module is enabled on the + system. Currently the only recognized + value is selinux. + The test may be negated by prepending + an exclamation + mark. ConditionCapability= + may be used to check whether the given + capability exists in the capability + bounding set of the service manager + (i.e. this does not check whether + capability is actually available in + the permitted or effective sets, see + capabilities7 + for details). Pass a capability name + such as CAP_MKNOD, + possibly prefixed with an exclamation + mark to negate the check. Finally, + ConditionNull= may + be used to add a constant condition + check value to the unit. It takes a + boolean argument. If set to + false the condition + will always fail, otherwise + succeed. If multiple conditions are + specified the unit will be executed if + all of them apply (i.e. a logical AND + is applied). Condition checks can be + prefixed with a pipe symbol (|) in + which case a condition becomes a + triggering condition. If at least one + triggering condition is defined for a + unit then the unit will be executed if + at least one of the triggering + conditions apply and all of the + non-triggering conditions. If you + prefix an argument with the pipe + symbol and an exclamation mark the + pipe symbol must be passed first, the + exclamation second. Except for + ConditionPathIsSymbolicLink=, + all path checks follow + symlinks. + + + + Names= + + Additional names for + this unit. The names listed here must + have the same suffix (i.e. type) as + the unit file name. This option may be + specified more than once, in which + case all listed names are used. Note + that this option is different from the + Alias= option from + the [Install] section mentioned + below. See below for details. Note + that in almost all cases this option + is not what you want. A symlink alias + in the file system is generally + preferable since it can be used as + lookup key. If a unit with a symlinked + alias name is not loaded and needs to + be it is easily found via the + symlink. However, if a unit with an + alias name configured with this + setting is not loaded it will not be + discovered. This settings' only use is + in conjunction with service + instances. + + + + + Unit file may include a [Install] section, which + carries installation information for the unit. This + section is not interpreted by + systemd1 + during runtime. It is used exclusively by the + enable and + disable commands of the + systemctl1 + tool during installation of a unit: + + + + Alias= + + Additional names this + unit shall be installed under. The + names listed here must have the same + suffix (i.e. type) as the unit file + name. This option may be specified + more than once, in which case all + listed names are used. At installation + time, + systemctl enable + will create symlinks from these names + to the unit file name. Note that this + is different from the + Names= option from + the [Unit] section mentioned above: + The names from + Names= apply + unconditionally if the unit is + loaded. The names from + Alias= apply only + if the unit has actually been + installed with the + systemctl enable + command. Also, if systemd searches for a + unit, it will discover symlinked alias + names as configured with + Alias=, but not + names configured with + Names= only. It is + a common pattern to list a name in + both options. In this case, a unit + will be active under all names if + installed, but also if not installed + but requested explicitly under its + main name. + + + + WantedBy= + + Installs a symlink in + the .wants/ + subdirectory for a unit. This has the + effect that when the listed unit name + is activated the unit listing it is + activated + too. WantedBy=foo.service + in a service + bar.service is + mostly equivalent to + Alias=foo.service.wants/bar.service + in the same file. + + + + Also= + + Additional units to + install when this unit is + installed. If the user requests + installation of a unit with this + option configured, + systemctl enable + will automatically install units + listed in this option as + well. + + + + + + + See Also + + systemd1, + systemctl8, + systemd.special7, + systemd.service5, + systemd.socket5, + systemd.device5, + systemd.mount5, + systemd.automount5, + systemd.swap5, + systemd.target5, + systemd.path5, + systemd.timer5, + systemd.snapshot5, + capabilities7 + + + +
diff --git a/man/systemd.xml b/man/systemd.xml new file mode 100644 index 000000000..aef65e30c --- /dev/null +++ b/man/systemd.xml @@ -0,0 +1,1167 @@ + + + + + + + + + systemd + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd + 1 + + + + systemd + init + systemd System and Service Manager + + + + + systemd OPTIONS + + + init OPTIONS COMMAND + + + + + Description + + systemd is a system and service manager for + Linux operating systems. When run as first process on + boot (as PID 1), it acts as init system that brings + up and maintains userspace services. + + For compatibility with SysV, if systemd is called + as init and a PID that is not + 1, it will execute telinit and pass + all command line arguments unmodified. That means + init and telinit + are mostly equivalent when invoked from normal login sessions. See + telinit8 + for more information. + + When run as system instance, systemd interprets + the configuration file + system.conf, otherwise + user.conf. See + systemd.conf5 + for more information. + + + + Options + + The following options are understood: + + + + + + + Prints a short help + text and exits. + + + + + Determine startup + sequence, dump it and exit. This is an + option useful for debugging + only. + + + + + Dump understood unit + configuration items. This outputs a + terse but complete list of + configuration items understood in unit + definition files. + + + + + Extract D-Bus + interface introspection data. This is + mostly useful at install time + to generate data suitable for the + D-Bus interfaces + repository. Optionally the interface + name for the introspection data may be + specified. If omitted, the + introspection data for all interfaces + is dumped. + + + + + Set default unit to + activate on startup. If not specified + defaults to + default.target. + + + + + + Tell systemd to run a + system instance (resp. user + instance), even if the process ID is + not 1 (resp. is 1), i.e. systemd is + not (resp. is) run as init process. + Normally it should not be necessary to + pass these options, as systemd + automatically detects the mode it is + started in. These options are hence of + little use except for debugging. Note + that it is not supported booting and + maintaining a full system with systemd + running in + mode, but PID not 1. In practice, + passing explicitly is + only useful in conjunction with + . + + + + + Dump core on + crash. This switch has no effect when + run as user + instance. + + + + + Run shell on + crash. This switch has no effect when + run as user + instance. + + + + + Ask for confirmation + when spawning processes. This switch + has no effect when run as user + instance. + + + + + Show terse service + status information while booting. This + switch has no effect when run as user + instance. Takes a boolean argument + which may be omitted which is + interpreted as + . + + + + + Controls whether + output of SysV init scripts will be + directed to the console. This switch + has no effect when run as user + instance. Takes a boolean argument + which may be omitted which is + interpreted as + . + + + + + Set log + target. Argument must be one of + , + , + , + , + , + , + . + + + + + Set log level. As + argument this accepts a numerical log + level or the well-known syslog3 + symbolic names (lowercase): + , + , + , + , + , + , + , + . + + + + + Highlight important + log messages. Argument is a boolean + value. If the argument is omitted it + defaults to + . + + + + + Include code location + in log messages. This is mostly + relevant for debugging + purposes. Argument is a boolean + value. If the argument is omitted + it defaults to + . + + + + + + Sets the default + output resp. error output for all + services and sockets, i.e. controls + the default for + + resp. + (see + systemd.exec5 + for details). Takes one of + , + , + , + , + , + , + , + , + . If the + argument is omitted + + defaults to + and + + to + . + + + + + + Concepts + + systemd provides a dependency system between + various entities called "units". Units encapsulate + various objects that are relevant for system boot-up + and maintenance. The majority of units are configured + in unit configuration files, whose syntax and basic + set of options is described in + systemd.unit5, + however some are created automatically from other + configuration or dynamically from system state. Units + may be 'active' (meaning started, bound, plugged in, + ... depending on the unit type, see below), or + 'inactive' (meaning stopped, unbound, unplugged, ...), + as well as in the process of being activated or + deactivated, i.e. between the two states (these states + are called 'activating', 'deactivating'). A special + 'failed' state is available as well which is very + similar to 'inactive' and is entered when the service + failed in some way (process returned error code on + exit, or crashed, or an operation timed out). If this + state is entered the cause will be logged, for later + reference. Note that the various unit types may have a + number of additional substates, which are mapped to + the five generalized unit states described + here. + + The following unit types are available: + + + Service units, which control + daemons and the processes they consist of. For + details see + systemd.service5. + + Socket units, which + encapsulate local IPC or network sockets in + the system, useful for socket-based + activation. For details about socket units see + systemd.socket5, + for details on socket-based activation and + other forms of activation, see + daemon7. + + Target units are useful to + group units, or provide well-known + synchronization points during boot-up, see + systemd.target5. + + Device units expose kernel + devices in systemd and may be used to + implement device-based activation. For details + see + systemd.device5. + + Mount units control mount + points in the file system, for details see + systemd.mount5. + + Automount units provide + automount capabilities, for on-demand mounting + of file systems as well as parallelized + boot-up. See + systemd.automount5. + + Snapshot units can be used to + temporarily save the state of the set of + systemd units, which later may be restored by + activating the saved snapshot unit. For more + information see + systemd.snapshot5. + + Timer units are useful for + triggering activation of other units based on + timers. You may find details in + systemd.timer5. + + Swap units are very similar to + mount units and encapsulate memory swap + partitions or files of the operating + system. They are described in systemd.swap5. + + Path units may be used + to activate other services when file system + objects change or are modified. See + systemd.path5. + + + + Units are named as their configuration + files. Some units have special semantics. A detailed + list is available in + systemd.special7. + + systemd knows various kinds of dependencies, + including positive and negative requirement + dependencies (i.e. Requires= and + Conflicts=) as well as ordering + dependencies (After= and + Before=). NB: ordering and + requirement dependencies are orthogonal. If only a + requirement dependency exists between two units + (e.g. foo.service requires + bar.service), but no ordering + dependency (e.g. foo.service + after bar.service) and both are + requested to start, they will be started in + parallel. It is a common pattern that both requirement + and ordering dependencies are placed between two + units. Also note that the majority of dependencies are + implicitly created and maintained by systemd. In most + cases it should be unnecessary to declare additional + dependencies manually, however it is possible to do + this. + + Application programs and units (via + dependencies) may request state changes of units. In + systemd, these requests are encapsulated as 'jobs' and + maintained in a job queue. Jobs may succeed or can + fail, their execution is ordered based on the ordering + dependencies of the units they have been scheduled + for. + + On boot systemd activates the target unit + default.target whose job is to + activate on-boot services and other on-boot units by + pulling them in via dependencies. Usually the unit + name is just an alias (symlink) for either + graphical.target (for + fully-featured boots into the UI) or + multi-user.target (for limited + console-only boots for use in embedded or server + environments, or similar; a subset of + graphical.target). However it is at the discretion of + the administrator to configure it as an alias to any + other target unit. See + systemd.special7 + for details about these target units. + + Processes systemd spawns are placed in + individual Linux control groups named after the unit + which they belong to in the private systemd + hierarchy. (see cgroups.txt + for more information about control groups, or short + "cgroups"). systemd uses this to effectively keep + track of processes. Control group information is + maintained in the kernel, and is accessible via the + file system hierarchy (beneath + /sys/fs/cgroup/systemd/), or in tools + such as + ps1 + (ps xawf -eo pid,user,cgroup,args + is particularly useful to list all processes and the + systemd units they belong to.). + + systemd is compatible with the SysV init system + to a large degree: SysV init scripts are supported and + simply read as an alternative (though limited) + configuration file format. The SysV + /dev/initctl interface is + provided, and compatibility implementations of the + various SysV client tools are available. In addition to + that, various established Unix functionality such as + /etc/fstab or the + utmp database are + supported. + + systemd has a minimal transaction system: if a + unit is requested to start up or shut down it will add + it and all its dependencies to a temporary + transaction. Then, it will verify if the transaction + is consistent (i.e. whether the ordering of all units + is cycle-free). If it is not, systemd will try to fix + it up, and removes non-essential jobs from the + transaction that might remove the loop. Also, systemd + tries to suppress non-essential jobs in the + transaction that would stop a running service. Finally + it is checked whether the jobs of the transaction + contradict jobs that have already been queued, and + optionally the transaction is aborted then. If all + worked out and the transaction is consistent and + minimized in its impact it is merged with all already + outstanding jobs and added to the run + queue. Effectively this means that before executing a + requested operation, systemd will verify that it makes + sense, fixing it if possible, and only failing if it + really cannot work. + + Systemd contains native implementations of + various tasks that need to be executed as part of the + boot process. For example, it sets the host name or + configures the loopback network device. It also sets + up and mounts various API file systems, such as + /sys or + /proc. + + For more information about the concepts and + ideas behind systemd please refer to the Original + Design Document. + + Note that some but not all interfaces provided + by systemd are covered by the Interface + Stability Promise. + + + + Directories + + + + System unit directories + + The systemd system + manager reads unit configuration from + various directories. Packages that + want to install unit files shall place + them in the directory returned by + pkg-config systemd + --variable=systemdsystemunitdir. Other + directories checked are + /usr/local/lib/systemd/system + and + /usr/lib/systemd/system. User + configuration always takes + precedence. pkg-config + systemd + --variable=systemdsystemconfdir + returns the path of the system + configuration directory. Packages + should alter the content of these + directories only with the + enable and + disable commands of + the + systemctl1 + tool. + + + + + + User unit directories + + Similar rules apply + for the user unit + directories. However, here the XDG + Base Directory specification + is followed to find + units. Applications should place their + unit files in the directory returned + by pkg-config systemd + --variable=systemduserunitdir. Global + configuration is done in the directory + reported by pkg-config + systemd + --variable=systemduserconfdir. The + enable and + disable commands of + the + systemctl1 + tool can handle both global (i.e. for + all users) and private (for one user) + enabling/disabling of + units. + + + + + + SysV init scripts directory + + The location of the + SysV init script directory varies + between distributions. If systemd + cannot find a native unit file for a + requested service, it will look for a + SysV init script of the same name + (with the + .service suffix + removed). + + + + + + SysV runlevel link farm directory + + The location of the + SysV runlevel link farm directory + varies between distributions. systemd + will take the link farm into account + when figuring out whether a service + shall be enabled. Note that a service + unit with a native unit configuration + file cannot be started by activating it + in the SysV runlevel link + farm. + + + + + + Signals + + + + SIGTERM + + Upon receiving this + signal the systemd system manager + serializes its state, reexecutes + itself and deserializes the saved + state again. This is mostly equivalent + to systemctl + daemon-reexec. + + systemd user managers will + start the + exit.target unit + when this signal is received. This is + mostly equivalent to + systemctl --user start + exit.target. + + + + SIGINT + + Upon receiving this + signal the systemd system manager will + start the + ctrl-alt-del.target unit. This + is mostly equivalent to + systemctl start + ctl-alt-del.target. + + systemd user managers + treat this signal the same way as + SIGTERM. + + + + SIGWINCH + + When this signal is + received the systemd system manager + will start the + kbrequest.target + unit. This is mostly equivalent to + systemctl start + kbrequest.target. + + This signal is ignored by + systemd user + managers. + + + + SIGPWR + + When this signal is + received the systemd manager + will start the + sigpwr.target + unit. This is mostly equivalent to + systemctl start + sigpwr.target. + + + + SIGUSR1 + + When this signal is + received the systemd manager will try + to reconnect to the D-Bus + bus. + + + + SIGUSR2 + + When this signal is + received the systemd manager will log + its complete state in human readable + form. The data logged is the same as + printed by systemctl + dump. + + + + SIGHUP + + Reloads the complete + daemon configuration. This is mostly + equivalent to systemctl + daemon-reload. + + + + SIGRTMIN+0 + + Enters default mode, starts the + default.target + unit. This is mostly equivalent to + systemctl start + default.target. + + + + SIGRTMIN+1 + + Enters rescue mode, + starts the + rescue.target + unit. This is mostly equivalent to + systemctl isolate + rescue.target. + + + + SIGRTMIN+2 + + Enters emergency mode, + starts the + emergency.service + unit. This is mostly equivalent to + systemctl isolate + emergency.service. + + + + SIGRTMIN+3 + + Halts the machine, + starts the + halt.target + unit. This is mostly equivalent to + systemctl start + halt.target. + + + + SIGRTMIN+4 + + Powers off the machine, + starts the + poweroff.target + unit. This is mostly equivalent to + systemctl start + poweroff.target. + + + + SIGRTMIN+5 + + Reboots the machine, + starts the + reboot.target + unit. This is mostly equivalent to + systemctl start + reboot.target. + + + + SIGRTMIN+6 + + Reboots the machine via kexec, + starts the + kexec.target + unit. This is mostly equivalent to + systemctl start + kexec.target. + + + + SIGRTMIN+13 + + Immediately halts the machine. + + + + SIGRTMIN+14 + + Immediately powers off the machine. + + + + SIGRTMIN+15 + + Immediately reboots the machine. + + + + SIGRTMIN+16 + + Immediately reboots the machine with kexec. + + + + SIGRTMIN+20 + + Enables display of + status messages on the console, as + controlled via + systemd.show_status=1 + on the kernel command + line. + + + + SIGRTMIN+21 + + Disables display of + status messages on the console, as + controlled via + systemd.show_status=0 + on the kernel command + line. + + + + SIGRTMIN+22 + SIGRTMIN+23 + + Sets the log level to + debug + (resp. info on + SIGRTMIN+23), as + controlled via + systemd.log_level=debug + (resp. systemd.log_level=info + on SIGRTMIN+23) on + the kernel command + line. + + + + SIGRTMIN+26 + SIGRTMIN+27 + SIGRTMIN+28 + SIGRTMIN+29 + + Sets the log level to + journal-or-kmsg + (resp. console on + SIGRTMIN+27; + resp. kmsg on + SIGRTMIN+28; + resp. syslog-or-kmsg + on SIGRTMIN+29), as + controlled via + systemd.log_target=journal-or-kmsg + (resp. systemd.log_target=console + on SIGRTMIN+27; + resp. systemd.log_target=kmsg + on SIGRTMIN+28; + resp + systemd.log_target=syslog-or-kmsg + on SIGRTMIN+29) on + the kernel command + line. + + + + + + Environment + + + + $SYSTEMD_LOG_LEVEL + systemd reads the + log level from this environment + variable. This can be overridden with + . + + + + $SYSTEMD_LOG_TARGET + systemd reads the + log target from this environment + variable. This can be overridden with + . + + + + $SYSTEMD_LOG_COLOR + Controls whether + systemd highlights important log + messages. This can be overridden with + . + + + + $SYSTEMD_LOG_LOCATION + Controls whether + systemd prints the code location along + with log messages. This can be + overridden with + . + + + + $XDG_CONFIG_HOME + $XDG_CONFIG_DIRS + $XDG_DATA_HOME + $XDG_DATA_DIRS + + The systemd user + manager uses these variables in + accordance to the XDG + Base Directory specification + to find its configuration. + + + + $SYSTEMD_UNIT_PATH + + Controls where systemd + looks for unit + files. + + + + $SYSTEMD_SYSVINIT_PATH + + Controls where systemd + looks for SysV init scripts. + + + + $SYSTEMD_SYSVRCND_PATH + + Controls where systemd + looks for SysV init script runlevel link + farms. + + + + $LISTEN_PID + $LISTEN_FDS + + Set by systemd for + supervised processes during + socket-based activation. See + sd_listen_fds3 + for more information. + + + + + $NOTIFY_SOCKET + + Set by systemd for + supervised processes for status and + start-up completion notification. See + sd_notify3 + for more information. + + + + + + + Kernel Command Line + + When run as system instance systemd parses a + number of kernel command line + argumentsIf run inside a Linux + container these arguments may be passed as command + line arguments to systemd itself, next to any of the + command line options listed in the Options section + above. If run outside of Linux containers, these + arguments are parsed from + /proc/cmdline + instead.: + + + + systemd.unit= + + Overrides the unit to + activate on boot. Defaults to + default.target. This + may be used to temporarily boot into a + different boot unit, for example + rescue.target or + emergency.service. See + systemd.special7 + for details about these + units. + + + + systemd.dump_core= + + Takes a boolean + argument. If + systemd dumps core when it + crashes. Otherwise no core dump is + created. Defaults to + . + + + + systemd.crash_shell= + + Takes a boolean + argument. If + systemd spawns a shell when it + crashes. Otherwise no shell is + spawned. Defaults to + , for security + reasons, as the shell is not protected + by any password + authentication. + + + + systemd.crash_chvt= + + Takes an integer + argument. If positive systemd + activates the specified virtual + terminal when it crashes. Defaults to + -1. + + + + systemd.confirm_spawn= + + Takes a boolean + argument. If + asks for confirmation when spawning + processes. Defaults to + . + + + + systemd.show_status= + + Takes a boolean + argument. If + shows terse service status updates on + the console during bootup. Defaults to + . + + + + systemd.sysv_console= + + Takes a boolean + argument. If + output of SysV init scripts will be + directed to the console. Defaults to + , unless + is passed as + kernel command line option in which + case it defaults to + . + + + + systemd.log_target= + systemd.log_level= + systemd.log_color= + systemd.log_location= + + Controls log output, + with the same effect as the + $SYSTEMD_LOG_TARGET, $SYSTEMD_LOG_LEVEL, $SYSTEMD_LOG_COLOR, $SYSTEMD_LOG_LOCATION + environment variables described above. + + + + systemd.default_standard_output= + systemd.default_standard_error= + Controls default + standard output/error output for + services, with the same effect as the + + resp. + command line arguments described + above. + + + + systemd.setenv= + + Takes a string + argument in the form + VARIABLE=VALUE. May be used to set + environment variables for the init + process and all its children at boot + time. May be used more than once to + set multiple variables. If the equal + sign and variable are missing unsets + an environment variable which might be + passed in from the initial ram + disk. + + + + + + + Sockets and FIFOs + + + + /run/systemd/notify + + Daemon status + notification socket. This is an + AF_UNIX datagram socket and is used to + implement the daemon notification + logic as implemented by + sd_notify3. + + + + + /run/systemd/shutdownd + + Used internally by the + shutdown8 + tool to implement delayed + shutdowns. This is an AF_UNIX datagram + socket. + + + + /run/systemd/private + + Used internally as + communication channel between + systemctl1 + and the systemd process. This is an + AF_UNIX stream socket. This interface + is private to systemd and should not + be used in external + projects. + + + + /dev/initctl + + Limited compatibility + support for the SysV client interface, + as implemented by the + systemd-initctl.service + unit. This is a named pipe in the file + system. This interface is obsolete and + should not be used in new + applications. + + + + + + See Also + + systemctl1, + systemadm1, + systemd-notify1, + daemon7, + sd-daemon7, + systemd.unit5, + systemd.special5, + pkg-config1 + + + + diff --git a/man/telinit.xml b/man/telinit.xml new file mode 100644 index 000000000..fec059aa6 --- /dev/null +++ b/man/telinit.xml @@ -0,0 +1,195 @@ + + + + + + + + + telinit + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + telinit + 8 + + + + telinit + Change SysV runlevel + + + + + telinit OPTIONS COMMAND + + + + + Description + + telinit may be used to change + the SysV system runlevel. Since the concept of SysV + runlevels is obsolete the runlevel requests + will be transparently translated into systemd unit + activation requests. + + + + + Options + + The following options are understood: + + + + + + Prints a short help + text and exits. + + + + + + Don't send wall + message before + reboot/halt/power-off. + + + + The following commands are understood: + + + + 0 + + Power-off the + machine. This is translated into an + activation request for + poweroff.target + and is equivalent to + systemctl + poweroff. + + + + 6 + + Reboot the + machine. This is translated into an + activation request for + reboot.target and + is equivalent to systemctl + reboot. + + + + 2 + 3 + 4 + 5 + + Change the SysV + runlevel. This is translated into an + activation request for + runlevel2.target, + runlevel3.target, + ... and is equivalent to + systemctl isolate + runlevel2.target, + systemctl isolate + runlevel3.target, + ... + + + + 1 + s + S + + Change into system + rescue mode. This is translated into + an activation request for + rescue.target and + is equivalent to systemctl + rescue. + + + + q + Q + + Reload daemon + configuration. This is equivalent to + systemctl + daemon-reload. + + + + u + U + + Serialize state, + reexecute daemon and deserialize state + again. This is equivalent to + systemctl + daemon-reexec. + + + + + + + Exit status + + On success 0 is returned, a non-zero failure + code otherwise. + + + + Notes + + This is a legacy command available for compatibility + only. It should not be used anymore, as the concept of + runlevels is obsolete. + + + + See Also + + systemd1, + systemctl1, + wall1 + + + + diff --git a/man/timezone.xml b/man/timezone.xml new file mode 100644 index 000000000..4e3327915 --- /dev/null +++ b/man/timezone.xml @@ -0,0 +1,90 @@ + + + + + + + + + /etc/timezone + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + timezone + 5 + + + + timezone + Local time zone configuration file + + + + /etc/timezone + + + + Description + + The /etc/timezone file + configures the system-wide time zone of the local + system that is used by applications for presentation + to the user. It should contain a single + newline-terminated line consisting of a time zone + identifier such as + Europe/Berlin. The file + /etc/localtime corresponds with + /etc/timezone and contains the + binary time zone data for the time zone. These files + should always be changed simultaneously and kept in + sync. + + The time zone may be overridden for individual + programs by using the TZ environment variable. See + environ7. + + + + History + + The simple configuration file format of + /etc/timezone originates from + Debian GNU/Linux. + + + + See Also + + systemd1 + + + + diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml new file mode 100644 index 000000000..f70bf0ef9 --- /dev/null +++ b/man/tmpfiles.d.xml @@ -0,0 +1,300 @@ + + + + + + + + tmpfiles.d + systemd + + + + Documentation + Brandon + Philips + brandon@ifup.org + + + + + + tmpfiles.d + 5 + + + + tmpfiles.d + Configuration for creation, deletion and + cleaning of volatile and temporary files + + + + /etc/tmpfiles.d/*.conf + /run/tmpfiles.d/*.conf + /usr/lib/tmpfiles.d/*.conf + + + + Description + + systemd-tmpfiles uses the + configuration files from the above directories to describe the + creation, cleaning and removal of volatile and + temporary files and directories which usually reside + in directories such as /run + or /tmp. + + + + Configuration Format + + Each configuration file is named in the style of + <program>.conf. Files in + /etc/ override files with the + same name in /usr/lib/. Files in + /run override files with the same + name in /etc/ and + /usr/lib/. Packages should + install their configuration files in + /usr/lib/, files in + /etc/ are reserved for the local + administrator, who may choose to override the + configurations installed from packages. The list of + configuration files are sorted by their filename in + alphabetical order, regardless in which of the + directories they reside, to guarantee that a + configuration file takes precedence over another + configuration file with an alphabetically later + name. + + The configuration format is one line per path + containing action, path, mode, ownership, age and argument + fields: + + Type Path Mode UID GID Age Argument +d /run/user 0755 root root 10d - +L /tmp/foobar - - - - /dev/null + + + Type + + + f + Create a file if it doesn't exist yet (optionally writing a short string into it, if the argument parameter is passed) + + + + F + Create or truncate a file (optionally writing a short string into it, if the argument parameter is passed) + + + + w + Write the argument parameter to a file, if it exists. + + + + d + Create a directory if it doesn't exist yet + + + + D + Create or empty a directory + + + + p + Create a named pipe (FIFO) if it doesn't exist yet + + + + L + Create a symlink if it doesn't exist yet + + + + c + Create a character device node if it doesn't exist yet + + + + b + Create a block device node if it doesn't exist yet + + + + x + Ignore a path + during cleaning. Use this type + to exclude paths from clean-up + as controlled with the Age + parameter. Note that lines of + this type do not influence the + effect of r or R lines. Lines + of this type accept + shell-style globs in place of + of normal path + names. + + + + r + Remove a file + or directory if it + exists. This may not be used + to remove non-empty + directories, use R for + that. Lines of this type + accept shell-style globs in + place of normal path + names. + + + + R + Recursively + remove a path and all its + subdirectories (if it is a + directory). Lines of this type + accept shell-style globs in + place of normal path + names. + + + + z + Restore + SELinux security context label + and set ownership and access + mode of a file or directory if + it exists. Lines of this type + accept shell-style globs in + place of normal path names. + + + + + Z + Recursively + restore SELinux security + context label and set + ownership and access mode of a + path and all its + subdirectories (if it is a + directory). Lines of this type + accept shell-style globs in + place of normal path + names. + + + + + + Mode + + The file access mode to use when + creating this file or directory. If omitted or + when set to - the default is used: 0755 for + directories, 0644 for all other file + objects. For z, Z lines if omitted or when set + to - the file access mode will not be + modified. This parameter is ignored for x, r, + R, L lines. + + + + UID, GID + + The user and group to use for this file + or directory. This may either be a numeric + user/group ID or a user or group name. If + omitted or when set to - the default 0 (root) + is used. For z, Z lines when omitted or when set to - + the file ownership will not be modified. + These parameters are ignored for x, r, R, L lines. + + + + Age + The date field, when set, is used to + decide what files to delete when cleaning. If + a file or directory is older than the current + time minus the age field it is deleted. The + field format is a series of integers each + followed by one of the following + postfixes for the respective time units: + + + + s + min + h + d + w + ms + m + us + + + If multiple integers and units are specified the time + values are summed up. + + The age field only applies to lines starting with + d, D and x. If omitted or set to - no automatic clean-up + is done. + + + + Argument + + For L lines determines the destination + path of the symlink. For c, b determines the + major/minor of the device node, with major and + minor formatted as integers, separated by :, + e.g. "1:3". For f, F, w may be used to specify + a short string that is written to the file, + suffixed by a newline. Ignored for all other + lines. + + + + + + Example + + /etc/tmpfiles.d/screen.conf example + screen needs two directories created at boot with specific modes and ownership. + + d /var/run/screens 1777 root root 10d +d /var/run/uscreens 0755 root root 10d12h + + + + + See Also + + systemd1, + systemd-tmpfiles8 + + + + diff --git a/man/vconsole.conf.xml b/man/vconsole.conf.xml new file mode 100644 index 000000000..a73db00d1 --- /dev/null +++ b/man/vconsole.conf.xml @@ -0,0 +1,146 @@ + + + + + + + + + vconsole.conf + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + vconsole.conf + 5 + + + + vconsole.conf + configuration file for the virtual console + + + + /etc/vconsole.conf + + + + Description + + The /etc/vconsole.conf file + configures the virtual console, i.e. keyboard mapping + and console font. + + The basic file format of the + vconsole.conf is a + newline-separated list environment-like + shell-compatible variable assignments. It is possible + to source the configuration from shell scripts, + however, beyond mere variable assignments no shell + features are supported, allowing applications to read + the file without implementing a shell compatible + execution engine. + + Note that the kernel command line options + vconsole.keymap=, + vconsole.keymap.toggle=, + vconsole.font=, + vconsole.font.map=, + vconsole.font.unimap= may be used + to override the console settings at boot. + + Depending on the operating system other + configuration files might be checked for configuration + of the virtual console as well, however only as + fallback. + + + + Options + + The following options are understood: + + + + + KEYMAP= + KEYMAP_TOGGLE= + + Configures the key + mapping table of for they + keyboard. KEYMAP= + defaults to us if + not set. The + KEYMAP_TOGGLE= can + be used to configured a second toggle + keymap and is by default + unset. + + + + FONT= + FONT_MAP= + FONT_UNIMAP= + + Configures the console + font, the console map and the unicode + font map. FONT= + defaults to + latarcyrheb-sun16. + + + + + + + Example + + + German keyboard and console + + /etc/vconsole.conf: + + KEYMAP=de-latin1 +FONT=latarcyrheb-sun16 + + + + + + See Also + + systemd1, + loadkeys1, + setfont8, + locale.conf5 + + + + diff --git a/po/.gitignore b/po/.gitignore new file mode 100644 index 000000000..1fa8d3fd5 --- /dev/null +++ b/po/.gitignore @@ -0,0 +1,5 @@ +POTFILES +Makefile.in.in +.intltool-merge-cache +Makefile +systemd.pot diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 000000000..ed2c308db --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,4 @@ +src/hostname/org.freedesktop.hostname1.policy.in +src/locale/org.freedesktop.locale1.policy.in +src/login/org.freedesktop.login1.policy.in +src/timedate/org.freedesktop.timedate1.policy.in diff --git a/po/POTFILES.skip b/po/POTFILES.skip new file mode 100644 index 000000000..5a5d02749 --- /dev/null +++ b/po/POTFILES.skip @@ -0,0 +1,19 @@ +src/dbus-automount.c +src/dbus-device.c +src/dbus-job.c +src/dbus-manager.c +src/dbus-mount.c +src/dbus-path.c +src/dbus-service.c +src/dbus-snapshot.c +src/dbus-socket.c +src/dbus-swap.c +src/dbus-target.c +src/dbus-timer.c +src/dbus-unit.c +src/hostname/hostnamed.c +src/locale/localed.c +src/org.freedesktop.systemd1.policy.in.in +src/timedate/timedated.c +units/systemd-readahead-done.service.in +units/user@.service.in diff --git a/po/pl.po b/po/pl.po new file mode 100644 index 000000000..2581d01fc --- /dev/null +++ b/po/pl.po @@ -0,0 +1,175 @@ +# translation of pl.po to Polish +# Piotr Drąg , 2011. +# Zbigniew Jędrzejewski-Szmek , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: systemd\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2011-10-14 16:18+0200\n" +"PO-Revision-Date: 2011-10-14 16:20+0200\n" +"Last-Translator: Piotr Drąg \n" +"Language-Team: Polish \n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../src/org.freedesktop.hostname1.policy.in.h:1 +msgid "Authentication is required to set local machine information." +msgstr "" +"Wymagane jest uwierzytelnienie, aby ustawić informacje o lokalnym komputerze." + +#: ../src/org.freedesktop.hostname1.policy.in.h:2 +msgid "Authentication is required to set the local host name." +msgstr "Wymagane jest uwierzytelnienie, aby ustawić nazwę lokalnego komputera." + +#: ../src/org.freedesktop.hostname1.policy.in.h:3 +msgid "" +"Authentication is required to set the statically configured local host name, " +"as well as the pretty host name." +msgstr "" +"Wymagane jest uwierzytelnienie, aby ustawić statycznie skonfigurowaną nazwę " +"lokalnego komputera, a także jego ładną nazwę." + +#: ../src/org.freedesktop.hostname1.policy.in.h:4 +msgid "Set host name" +msgstr "Ustawienie nazwy komputera" + +#: ../src/org.freedesktop.hostname1.policy.in.h:5 +msgid "Set machine information" +msgstr "Ustawienie informacji o komputerze" + +#: ../src/org.freedesktop.hostname1.policy.in.h:6 +msgid "Set static host name" +msgstr "Ustawienie statycznej nazwy komputera" + +#: ../src/org.freedesktop.locale1.policy.in.h:1 +msgid "Authentication is required to set the system keyboard settings." +msgstr "Wymagane jest uwierzytelnienie, aby ustawić klawiaturę systemu." + +#: ../src/org.freedesktop.locale1.policy.in.h:2 +msgid "Authentication is required to set the system locale." +msgstr "Wymagane jest uwierzytelnienie, aby ustawić lokalizację systemu." + +#: ../src/org.freedesktop.locale1.policy.in.h:3 +msgid "Set system keyboard settings" +msgstr "Ustawienie klawiatury systemu" + +#: ../src/org.freedesktop.locale1.policy.in.h:4 +msgid "Set system locale" +msgstr "Ustawienie lokalizacji systemu" + +#: ../src/org.freedesktop.login1.policy.in.h:1 +msgid "Allow attaching devices to seats" +msgstr "Zezwolenie na podłączanie urządzeń do stanowisk" + +#: ../src/org.freedesktop.login1.policy.in.h:2 +msgid "Allow non-logged-in users to run programs" +msgstr "Zezwolenie niezalogowanym użytkownikom na uruchamianie programów" + +#: ../src/org.freedesktop.login1.policy.in.h:3 +msgid "" +"Authentication is required to allow a non-logged-in user to run programs" +msgstr "" +"Wymagane jest uwierzytelnienie, aby ustawić zezwolić niezalogowanym " +"użytkownikom na uruchamianie programów" + +#: ../src/org.freedesktop.login1.policy.in.h:4 +msgid "Authentication is required to allow attaching a device to a seat" +msgstr "" +"Wymagane jest uwierzytelnienie, aby zezwolić na podłączenie urządzenia do " +"stanowiska" + +#: ../src/org.freedesktop.login1.policy.in.h:5 +msgid "Authentication is required to allow powering off the system" +msgstr "Wymagane jest uwierzytelnienie, aby zezwolić na wyłączanie systemu" + +#: ../src/org.freedesktop.login1.policy.in.h:6 +msgid "" +"Authentication is required to allow powering off the system while other " +"users are logged in" +msgstr "" +"Wymagane jest uwierzytelnienie, aby zezwolić na wyłączanie systemu, kiedy są " +"zalogowani inni użytkownicy" + +#: ../src/org.freedesktop.login1.policy.in.h:7 +msgid "Authentication is required to allow rebooting the system" +msgstr "" +"Wymagane jest uwierzytelnienie, aby zezwolić na ponowne uruchamianie systemu" + +#: ../src/org.freedesktop.login1.policy.in.h:8 +msgid "" +"Authentication is required to allow rebooting the system while other users " +"are logged in" +msgstr "" +"Wymagane jest uwierzytelnienie, aby zezwolić na ponowne uruchamianie " +"systemu, kiedy są zalogowani inni użytkownicy" + +#: ../src/org.freedesktop.login1.policy.in.h:9 +msgid "" +"Authentication is required to allow resetting how devices are attached to " +"seats" +msgstr "" +"Wymagane jest uwierzytelnienie, aby zezwolić na ponowne ustawianie sposobu " +"podłączenia urządzeń do stanowisk" + +#: ../src/org.freedesktop.login1.policy.in.h:10 +msgid "Flush device to seat attachments" +msgstr "Usunięcie podłączenia urządzeń do stanowisk" + +#: ../src/org.freedesktop.login1.policy.in.h:11 +msgid "Power off the system" +msgstr "Wyłączenie systemu" + +#: ../src/org.freedesktop.login1.policy.in.h:12 +msgid "Power off the system when other users are logged in" +msgstr "Wyłączenie systemu, kiedy są zalogowani inni użytkownicy" + +#: ../src/org.freedesktop.login1.policy.in.h:13 +msgid "Reboot the system" +msgstr "Ponowne uruchomienie systemu" + +#: ../src/org.freedesktop.login1.policy.in.h:14 +msgid "Reboot the system when other users are logged in" +msgstr "Ponowne uruchomienie systemu, kiedy są zalogowani inni użytkownicy" + +#: ../src/org.freedesktop.timedate1.policy.in.h:1 +msgid "" +"Authentication is required to control whether network time synchronization " +"shall be enabled." +msgstr "" +"Wymagane jest uwierzytelnienie, aby kontrolować, czy włączyć synchronizację " +"czasu przez sieć." + +#: ../src/org.freedesktop.timedate1.policy.in.h:2 +msgid "" +"Authentication is required to control whether the RTC stores the local or " +"UTC time." +msgstr "" +"Wymagane jest uwierzytelnienie, aby kontrolować, czy RTC przechowuje czas " +"lokalny lub czas UTC." + +#: ../src/org.freedesktop.timedate1.policy.in.h:3 +msgid "Authentication is required to set the system time." +msgstr "Wymagane jest uwierzytelnienie, aby ustawić czas systemu." + +#: ../src/org.freedesktop.timedate1.policy.in.h:4 +msgid "Authentication is required to set the system timezone." +msgstr "Wymagane jest uwierzytelnienie, aby ustawić strefę czasową systemu." + +#: ../src/org.freedesktop.timedate1.policy.in.h:5 +msgid "Set RTC to local timezone or UTC" +msgstr "Ustawienie RTC na lokalną strefę czasową lub strefę UTC" + +#: ../src/org.freedesktop.timedate1.policy.in.h:6 +msgid "Set system time" +msgstr "Ustawienie czasu systemu" + +#: ../src/org.freedesktop.timedate1.policy.in.h:7 +msgid "Set system timezone" +msgstr "Ustawienie strefy czasowej systemu" + +#: ../src/org.freedesktop.timedate1.policy.in.h:8 +msgid "Turn network time synchronization on or off" +msgstr "Włączenie lub wyłączenie synchronizacji czasu przez sieć" diff --git a/src/.gitignore b/src/.gitignore index beb8604bc..4b123f86d 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,5 +1,11 @@ -*.[78] -*.html -udev.pc -libudev.pc -udev*.service +/*.pc +load-fragment-gperf-nulstr.c +load-fragment-gperf.c +load-fragment-gperf.gperf +org.freedesktop.systemd1.policy.in +org.freedesktop.systemd1.policy +gnome-ask-password-agent.c +systemd-interfaces.c +systemadm.c +wraplabel.c +99-systemd.rules diff --git a/src/99-systemd.rules.in b/src/99-systemd.rules.in new file mode 100644 index 000000000..d306f71b6 --- /dev/null +++ b/src/99-systemd.rules.in @@ -0,0 +1,55 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +ACTION=="remove", GOTO="systemd_end" + +SUBSYSTEM=="tty", KERNEL=="tty[0-9]|tty1[0-2]", TAG+="systemd" +SUBSYSTEM=="tty", KERNEL=="tty[a-zA-Z]*|hvc*|xvc*|hvsi*", TAG+="systemd" + +KERNEL=="vport*", TAG+="systemd" + +SUBSYSTEM=="block", KERNEL!="ram*|loop*", TAG+="systemd" +SUBSYSTEM=="block", KERNEL!="ram*|loop*", ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}=="1", ENV{SYSTEMD_READY}="0" + +# Ignore encrypted devices with no identified superblock on it, since +# we are probably still calling mke2fs or mkswap on it. + +SUBSYSTEM=="block", KERNEL!="ram*|loop*", ENV{DM_UUID}=="CRYPT-*", ENV{ID_PART_TABLE_TYPE}=="", ENV{ID_FS_USAGE}=="", ENV{SYSTEMD_READY}="0" + +# We need a hardware independent way to identify network devices. We +# use the /sys/subsystem path for this. Current vanilla kernels don't +# actually support that hierarchy right now, however upcoming kernels +# will. HAL and udev internally support /sys/subsystem already, hence +# it should be safe to use this here, too. This is mostly just an +# identification string for systemd, so whether the path actually is +# accessible or not does not matter as long as it is unique and in the +# filesystem namespace. +# +# http://git.kernel.org/?p=linux/hotplug/udev.git;a=blob;f=libudev/libudev-enumerate.c;h=da831449dcaf5e936a14409e8e68ab12d30a98e2;hb=HEAD#l742 + +SUBSYSTEM=="net", KERNEL!="lo", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/sys/subsystem/net/devices/$name" +SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/sys/subsystem/bluetooth/devices/%k" + +SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_WANTS}="bluetooth.target" +ENV{ID_SMARTCARD_READER}=="*?", TAG+="systemd", ENV{SYSTEMD_WANTS}="smartcard.target" +SUBSYSTEM=="sound", KERNEL=="card*", TAG+="systemd", ENV{SYSTEMD_WANTS}="sound.target" + +SUBSYSTEM=="printer", TAG+="systemd", ENV{SYSTEMD_WANTS}="printer.target" +SUBSYSTEM=="usb", KERNEL=="lp*", TAG+="systemd", ENV{SYSTEMD_WANTS}="printer.target" +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0701??:*", TAG+="systemd", ENV{SYSTEMD_WANTS}="printer.target" + +# Apply sysctl variables to network devices (and only to those) as they appear. + +SUBSYSTEM=="net", KERNEL!="lo", RUN+="@rootlibexecdir@/systemd-sysctl --prefix=/proc/sys/net/ipv4/conf/$name --prefix=/proc/sys/net/ipv4/neigh/$name --prefix=/proc/sys/net/ipv6/conf/$name --prefix=/proc/sys/net/ipv6/neigh/$name" + +# Asynchronously mount file systems implemented by these modules as +# soon as they are loaded. + +SUBSYSTEM=="module", KERNEL=="fuse", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}="sys-fs-fuse-connections.mount" +SUBSYSTEM=="module", KERNEL=="configfs", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}="sys-kernel-config.mount" + +LABEL="systemd_end" diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 000000000..bc7e9fa36 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,28 @@ +# This file is part of systemd. +# +# Copyright 2010 Lennart Poettering +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with systemd; If not, see . + +# This file is a dirty trick to simplify compilation from within +# emacs. This file is not intended to be distributed. So, don't touch +# it, even better ignore it! + +all: + $(MAKE) -C .. + +clean: + $(MAKE) -C .. clean + +.PHONY: all clean diff --git a/src/ac-power.c b/src/ac-power.c new file mode 100644 index 000000000..24a68e717 --- /dev/null +++ b/src/ac-power.c @@ -0,0 +1,111 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "util.h" + +static int on_ac_power(void) { + int r; + + struct udev *udev; + struct udev_enumerate *e = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + bool found_offline = false, found_online = false; + + if (!(udev = udev_new())) { + r = -ENOMEM; + goto finish; + } + + if (!(e = udev_enumerate_new(udev))) { + r = -ENOMEM; + goto finish; + } + + if (udev_enumerate_add_match_subsystem(e, "power_supply") < 0) { + r = -EIO; + goto finish; + } + + if (udev_enumerate_scan_devices(e) < 0) { + r = -EIO; + goto finish; + } + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + struct udev_device *d; + const char *type, *online; + + if (!(d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)))) { + r = -ENOMEM; + goto finish; + } + + if (!(type = udev_device_get_sysattr_value(d, "type"))) + goto next; + + if (!streq(type, "Mains")) + goto next; + + if (!(online = udev_device_get_sysattr_value(d, "online"))) + goto next; + + if (streq(online, "1")) { + found_online = true; + break; + } else if (streq(online, "0")) + found_offline = true; + + next: + udev_device_unref(d); + } + + r = found_online || !found_offline; + +finish: + if (e) + udev_enumerate_unref(e); + + if (udev) + udev_unref(udev); + + return r; +} + +int main(int argc, char *argv[]) { + int r; + + /* This is mostly intended to be used for scripts which want + * to detect whether AC power is plugged in or not. */ + + if ((r = on_ac_power()) < 0) { + log_error("Failed to read AC status: %s", strerror(-r)); + return EXIT_FAILURE; + } + + return r == 0; +} diff --git a/src/acl-util.c b/src/acl-util.c new file mode 100644 index 000000000..a2a9f9a22 --- /dev/null +++ b/src/acl-util.c @@ -0,0 +1,68 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "acl-util.h" + +int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry) { + acl_entry_t i; + int found; + + assert(acl); + assert(entry); + + for (found = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); + found > 0; + found = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) { + + acl_tag_t tag; + uid_t *u; + bool b; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (tag != ACL_USER) + continue; + + u = acl_get_qualifier(i); + if (!u) + return -errno; + + b = *u == uid; + acl_free(u); + + if (b) { + *entry = i; + return 1; + } + } + + if (found < 0) + return -errno; + + return 0; +} diff --git a/src/acl-util.h b/src/acl-util.h new file mode 100644 index 000000000..798ce4336 --- /dev/null +++ b/src/acl-util.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooaclutilhfoo +#define fooaclutilhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry); + +#endif diff --git a/src/ask-password-api.c b/src/ask-password-api.c new file mode 100644 index 000000000..ce2f3cbe7 --- /dev/null +++ b/src/ask-password-api.c @@ -0,0 +1,576 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "strv.h" + +#include "ask-password-api.h" + +static void backspace_chars(int ttyfd, size_t p) { + + if (ttyfd < 0) + return; + + while (p > 0) { + p--; + + loop_write(ttyfd, "\b \b", 3, false); + } +} + +int ask_password_tty( + const char *message, + usec_t until, + const char *flag_file, + char **_passphrase) { + + struct termios old_termios, new_termios; + char passphrase[LINE_MAX]; + size_t p = 0; + int r, ttyfd = -1, notify = -1; + struct pollfd pollfd[2]; + bool reset_tty = false; + bool silent_mode = false; + bool dirty = false; + enum { + POLL_TTY, + POLL_INOTIFY + }; + + assert(message); + assert(_passphrase); + + if (flag_file) { + if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) { + r = -errno; + goto finish; + } + + if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) { + r = -errno; + goto finish; + } + } + + if ((ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC)) >= 0) { + + if (tcgetattr(ttyfd, &old_termios) < 0) { + r = -errno; + goto finish; + } + + loop_write(ttyfd, ANSI_HIGHLIGHT_ON, sizeof(ANSI_HIGHLIGHT_ON)-1, false); + loop_write(ttyfd, message, strlen(message), false); + loop_write(ttyfd, " ", 1, false); + loop_write(ttyfd, ANSI_HIGHLIGHT_OFF, sizeof(ANSI_HIGHLIGHT_OFF)-1, false); + + new_termios = old_termios; + new_termios.c_lflag &= ~(ICANON|ECHO); + new_termios.c_cc[VMIN] = 1; + new_termios.c_cc[VTIME] = 0; + + if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) { + r = -errno; + goto finish; + } + + reset_tty = true; + } + + zero(pollfd); + + pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO; + pollfd[POLL_TTY].events = POLLIN; + pollfd[POLL_INOTIFY].fd = notify; + pollfd[POLL_INOTIFY].events = POLLIN; + + for (;;) { + char c; + int sleep_for = -1, k; + ssize_t n; + + if (until > 0) { + usec_t y; + + y = now(CLOCK_MONOTONIC); + + if (y > until) { + r = -ETIME; + goto finish; + } + + sleep_for = (int) ((until - y) / USEC_PER_MSEC); + } + + if (flag_file) + if (access(flag_file, F_OK) < 0) { + r = -errno; + goto finish; + } + + if ((k = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) { + + if (errno == EINTR) + continue; + + r = -errno; + goto finish; + } else if (k == 0) { + r = -ETIME; + goto finish; + } + + if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0) + flush_fd(notify); + + if (pollfd[POLL_TTY].revents == 0) + continue; + + if ((n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1)) < 0) { + + if (errno == EINTR || errno == EAGAIN) + continue; + + r = -errno; + goto finish; + + } else if (n == 0) + break; + + if (c == '\n') + break; + else if (c == 21) { /* C-u */ + + if (!silent_mode) + backspace_chars(ttyfd, p); + p = 0; + + } else if (c == '\b' || c == 127) { + + if (p > 0) { + + if (!silent_mode) + backspace_chars(ttyfd, 1); + + p--; + } else if (!dirty && !silent_mode) { + + silent_mode = true; + + /* There are two ways to enter silent + * mode. Either by pressing backspace + * as first key (and only as first key), + * or ... */ + if (ttyfd >= 0) + loop_write(ttyfd, "(no echo) ", 10, false); + + } else if (ttyfd >= 0) + loop_write(ttyfd, "\a", 1, false); + + } else if (c == '\t' && !silent_mode) { + + backspace_chars(ttyfd, p); + silent_mode = true; + + /* ... or by pressing TAB at any time. */ + + if (ttyfd >= 0) + loop_write(ttyfd, "(no echo) ", 10, false); + } else { + passphrase[p++] = c; + + if (!silent_mode && ttyfd >= 0) + loop_write(ttyfd, "*", 1, false); + + dirty = true; + } + } + + passphrase[p] = 0; + + if (!(*_passphrase = strdup(passphrase))) { + r = -ENOMEM; + goto finish; + } + + r = 0; + +finish: + if (notify >= 0) + close_nointr_nofail(notify); + + if (ttyfd >= 0) { + + if (reset_tty) { + loop_write(ttyfd, "\n", 1, false); + tcsetattr(ttyfd, TCSADRAIN, &old_termios); + } + + close_nointr_nofail(ttyfd); + } + + return r; +} + +static int create_socket(char **name) { + int fd; + union { + struct sockaddr sa; + struct sockaddr_un un; + } sa; + int one = 1, r; + char *c; + mode_t u; + + assert(name); + + if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) { + log_error("socket() failed: %m"); + return -errno; + } + + zero(sa); + sa.un.sun_family = AF_UNIX; + snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%llu", random_ull()); + + u = umask(0177); + r = bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)); + umask(u); + + if (r < 0) { + r = -errno; + log_error("bind() failed: %m"); + goto fail; + } + + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) { + r = -errno; + log_error("SO_PASSCRED failed: %m"); + goto fail; + } + + if (!(c = strdup(sa.un.sun_path))) { + r = -ENOMEM; + log_error("Out of memory"); + goto fail; + } + + *name = c; + return fd; + +fail: + close_nointr_nofail(fd); + + return r; +} + +int ask_password_agent( + const char *message, + const char *icon, + usec_t until, + bool accept_cached, + char ***_passphrases) { + + enum { + FD_SOCKET, + FD_SIGNAL, + _FD_MAX + }; + + char temp[] = "/run/systemd/ask-password/tmp.XXXXXX"; + char final[sizeof(temp)] = ""; + int fd = -1, r; + FILE *f = NULL; + char *socket_name = NULL; + int socket_fd = -1, signal_fd = -1; + sigset_t mask, oldmask; + struct pollfd pollfd[_FD_MAX]; + mode_t u; + + assert(_passphrases); + + assert_se(sigemptyset(&mask) == 0); + sigset_add_many(&mask, SIGINT, SIGTERM, -1); + assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0); + + mkdir_p("/run/systemd/ask-password", 0755); + + u = umask(0022); + fd = mkostemp(temp, O_CLOEXEC|O_CREAT|O_WRONLY); + umask(u); + + if (fd < 0) { + log_error("Failed to create password file: %m"); + r = -errno; + goto finish; + } + + fchmod(fd, 0644); + + if (!(f = fdopen(fd, "w"))) { + log_error("Failed to allocate FILE: %m"); + r = -errno; + goto finish; + } + + fd = -1; + + if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) { + log_error("signalfd(): %m"); + r = -errno; + goto finish; + } + + if ((socket_fd = create_socket(&socket_name)) < 0) { + r = socket_fd; + goto finish; + } + + fprintf(f, + "[Ask]\n" + "PID=%lu\n" + "Socket=%s\n" + "AcceptCached=%i\n" + "NotAfter=%llu\n", + (unsigned long) getpid(), + socket_name, + accept_cached ? 1 : 0, + (unsigned long long) until); + + if (message) + fprintf(f, "Message=%s\n", message); + + if (icon) + fprintf(f, "Icon=%s\n", icon); + + fflush(f); + + if (ferror(f)) { + log_error("Failed to write query file: %m"); + r = -errno; + goto finish; + } + + memcpy(final, temp, sizeof(temp)); + + final[sizeof(final)-11] = 'a'; + final[sizeof(final)-10] = 's'; + final[sizeof(final)-9] = 'k'; + + if (rename(temp, final) < 0) { + log_error("Failed to rename query file: %m"); + r = -errno; + goto finish; + } + + zero(pollfd); + pollfd[FD_SOCKET].fd = socket_fd; + pollfd[FD_SOCKET].events = POLLIN; + pollfd[FD_SIGNAL].fd = signal_fd; + pollfd[FD_SIGNAL].events = POLLIN; + + for (;;) { + char passphrase[LINE_MAX+1]; + struct msghdr msghdr; + struct iovec iovec; + struct ucred *ucred; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; + } control; + ssize_t n; + int k; + usec_t t; + + t = now(CLOCK_MONOTONIC); + + if (until > 0 && until <= t) { + log_notice("Timed out"); + r = -ETIME; + goto finish; + } + + if ((k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1)) < 0) { + + if (errno == EINTR) + continue; + + log_error("poll() failed: %m"); + r = -errno; + goto finish; + } + + if (k <= 0) { + log_notice("Timed out"); + r = -ETIME; + goto finish; + } + + if (pollfd[FD_SIGNAL].revents & POLLIN) { + r = -EINTR; + goto finish; + } + + if (pollfd[FD_SOCKET].revents != POLLIN) { + log_error("Unexpected poll() event."); + r = -EIO; + goto finish; + } + + zero(iovec); + iovec.iov_base = passphrase; + iovec.iov_len = sizeof(passphrase); + + zero(control); + zero(msghdr); + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + if ((n = recvmsg(socket_fd, &msghdr, 0)) < 0) { + + if (errno == EAGAIN || + errno == EINTR) + continue; + + log_error("recvmsg() failed: %m"); + r = -errno; + goto finish; + } + + if (n <= 0) { + log_error("Message too short"); + continue; + } + + if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || + control.cmsghdr.cmsg_level != SOL_SOCKET || + control.cmsghdr.cmsg_type != SCM_CREDENTIALS || + control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { + log_warning("Received message without credentials. Ignoring."); + continue; + } + + ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); + if (ucred->uid != 0) { + log_warning("Got request from unprivileged user. Ignoring."); + continue; + } + + if (passphrase[0] == '+') { + char **l; + + if (n == 1) + l = strv_new("", NULL); + else + l = strv_parse_nulstr(passphrase+1, n-1); + /* An empty message refers to the empty password */ + + if (!l) { + r = -ENOMEM; + goto finish; + } + + if (strv_length(l) <= 0) { + strv_free(l); + log_error("Invalid packet"); + continue; + } + + *_passphrases = l; + + } else if (passphrase[0] == '-') { + r = -ECANCELED; + goto finish; + } else { + log_error("Invalid packet"); + continue; + } + + break; + } + + r = 0; + +finish: + if (fd >= 0) + close_nointr_nofail(fd); + + if (socket_name) { + unlink(socket_name); + free(socket_name); + } + + if (socket_fd >= 0) + close_nointr_nofail(socket_fd); + + if (signal_fd >= 0) + close_nointr_nofail(signal_fd); + + if (f) + fclose(f); + + unlink(temp); + + if (final[0]) + unlink(final); + + assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0); + + return r; +} + +int ask_password_auto(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases) { + assert(message); + assert(_passphrases); + + if (isatty(STDIN_FILENO)) { + int r; + char *s = NULL, **l = NULL; + + if ((r = ask_password_tty(message, until, NULL, &s)) < 0) + return r; + + l = strv_new(s, NULL); + free(s); + + if (!l) + return -ENOMEM; + + *_passphrases = l; + return r; + + } else + return ask_password_agent(message, icon, until, accept_cached, _passphrases); +} diff --git a/src/ask-password-api.h b/src/ask-password-api.h new file mode 100644 index 000000000..fec8625a0 --- /dev/null +++ b/src/ask-password-api.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooaskpasswordapihfoo +#define fooaskpasswordapihfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "util.h" + +int ask_password_tty(const char *message, usec_t until, const char *flag_file, char **_passphrase); + +int ask_password_agent(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases); + +int ask_password_auto(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases); + +#endif diff --git a/src/ask-password.c b/src/ask-password.c new file mode 100644 index 000000000..5162f62ee --- /dev/null +++ b/src/ask-password.c @@ -0,0 +1,184 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "macro.h" +#include "util.h" +#include "strv.h" +#include "ask-password-api.h" +#include "def.h" + +static const char *arg_icon = NULL; +static const char *arg_message = NULL; +static bool arg_use_tty = true; +static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC; +static bool arg_accept_cached = false; +static bool arg_multiple = false; + +static int help(void) { + + printf("%s [OPTIONS...] MESSAGE\n\n" + "Query the user for a system passphrase, via the TTY or an UI agent.\n\n" + " -h --help Show this help\n" + " --icon=NAME Icon name\n" + " --timeout=SEC Timeout in sec\n" + " --no-tty Ask question via agent even on TTY\n" + " --accept-cached Accept cached passwords\n" + " --multiple List multiple passwords if available\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_ICON = 0x100, + ARG_TIMEOUT, + ARG_NO_TTY, + ARG_ACCEPT_CACHED, + ARG_MULTIPLE + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "icon", required_argument, NULL, ARG_ICON }, + { "timeout", required_argument, NULL, ARG_TIMEOUT }, + { "no-tty", no_argument, NULL, ARG_NO_TTY }, + { "accept-cached", no_argument, NULL, ARG_ACCEPT_CACHED }, + { "multiple", no_argument, NULL, ARG_MULTIPLE }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_ICON: + arg_icon = optarg; + break; + + case ARG_TIMEOUT: + if (parse_usec(optarg, &arg_timeout) < 0) { + log_error("Failed to parse --timeout parameter %s", optarg); + return -EINVAL; + } + break; + + case ARG_NO_TTY: + arg_use_tty = false; + break; + + case ARG_ACCEPT_CACHED: + arg_accept_cached = true; + break; + + case ARG_MULTIPLE: + arg_multiple = true; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (optind != argc-1) { + help(); + return -EINVAL; + } + + arg_message = argv[optind]; + return 1; +} + +int main(int argc, char *argv[]) { + int r; + usec_t timeout; + + log_parse_environment(); + log_open(); + + if ((r = parse_argv(argc, argv)) <= 0) + goto finish; + + if (arg_timeout > 0) + timeout = now(CLOCK_MONOTONIC) + arg_timeout; + else + timeout = 0; + + if (arg_use_tty && isatty(STDIN_FILENO)) { + char *password = NULL; + + if ((r = ask_password_tty(arg_message, timeout, NULL, &password)) >= 0) { + puts(password); + free(password); + } + + } else { + char **l; + + if ((r = ask_password_agent(arg_message, arg_icon, timeout, arg_accept_cached, &l)) >= 0) { + char **p; + + STRV_FOREACH(p, l) { + puts(*p); + + if (!arg_multiple) + break; + } + + strv_free(l); + } + } + +finish: + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/automount.c b/src/automount.c new file mode 100644 index 000000000..cf2fb60cd --- /dev/null +++ b/src/automount.c @@ -0,0 +1,887 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unit.h" +#include "automount.h" +#include "load-fragment.h" +#include "load-dropin.h" +#include "unit-name.h" +#include "dbus-automount.h" +#include "bus-errors.h" +#include "special.h" +#include "label.h" + +static const UnitActiveState state_translation_table[_AUTOMOUNT_STATE_MAX] = { + [AUTOMOUNT_DEAD] = UNIT_INACTIVE, + [AUTOMOUNT_WAITING] = UNIT_ACTIVE, + [AUTOMOUNT_RUNNING] = UNIT_ACTIVE, + [AUTOMOUNT_FAILED] = UNIT_FAILED +}; + +static int open_dev_autofs(Manager *m); + +static void automount_init(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + a->pipe_watch.fd = a->pipe_fd = -1; + a->pipe_watch.type = WATCH_INVALID; + + a->directory_mode = 0755; + + UNIT(a)->ignore_on_isolate = true; +} + +static void repeat_unmout(const char *path) { + assert(path); + + for (;;) { + /* If there are multiple mounts on a mount point, this + * removes them all */ + + if (umount2(path, MNT_DETACH) >= 0) + continue; + + if (errno != EINVAL) + log_error("Failed to unmount: %m"); + + break; + } +} + +static void unmount_autofs(Automount *a) { + assert(a); + + if (a->pipe_fd < 0) + return; + + automount_send_ready(a, -EHOSTDOWN); + + unit_unwatch_fd(UNIT(a), &a->pipe_watch); + close_nointr_nofail(a->pipe_fd); + a->pipe_fd = -1; + + /* If we reload/reexecute things we keep the mount point + * around */ + if (a->where && + (UNIT(a)->manager->exit_code != MANAGER_RELOAD && + UNIT(a)->manager->exit_code != MANAGER_REEXECUTE)) + repeat_unmout(a->where); +} + +static void automount_done(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + unmount_autofs(a); + unit_ref_unset(&a->mount); + + free(a->where); + a->where = NULL; + + set_free(a->tokens); + a->tokens = NULL; +} + +int automount_add_one_mount_link(Automount *a, Mount *m) { + int r; + + assert(a); + assert(m); + + if (UNIT(a)->load_state != UNIT_LOADED || + UNIT(m)->load_state != UNIT_LOADED) + return 0; + + if (!path_startswith(a->where, m->where)) + return 0; + + if (path_equal(a->where, m->where)) + return 0; + + if ((r = unit_add_two_dependencies(UNIT(a), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0) + return r; + + return 0; +} + +static int automount_add_mount_links(Automount *a) { + Unit *other; + int r; + + assert(a); + + LIST_FOREACH(units_by_type, other, UNIT(a)->manager->units_by_type[UNIT_MOUNT]) + if ((r = automount_add_one_mount_link(a, MOUNT(other))) < 0) + return r; + + return 0; +} + +static int automount_add_default_dependencies(Automount *a) { + int r; + + assert(a); + + if (UNIT(a)->manager->running_as == MANAGER_SYSTEM) { + + if ((r = unit_add_dependency_by_name(UNIT(a), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0) + return r; + + if ((r = unit_add_two_dependencies_by_name(UNIT(a), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0) + return r; + } + + return 0; +} + +static int automount_verify(Automount *a) { + bool b; + char *e; + assert(a); + + if (UNIT(a)->load_state != UNIT_LOADED) + return 0; + + if (path_equal(a->where, "/")) { + log_error("Cannot have an automount unit for the root directory. Refusing."); + return -EINVAL; + } + + if (!(e = unit_name_from_path(a->where, ".automount"))) + return -ENOMEM; + + b = unit_has_name(UNIT(a), e); + free(e); + + if (!b) { + log_error("%s's Where setting doesn't match unit name. Refusing.", UNIT(a)->id); + return -EINVAL; + } + + return 0; +} + +static int automount_load(Unit *u) { + int r; + Automount *a = AUTOMOUNT(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + /* Load a .automount file */ + if ((r = unit_load_fragment_and_dropin_optional(u)) < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + Unit *x; + + if (!a->where) + if (!(a->where = unit_name_to_path(u->id))) + return -ENOMEM; + + path_kill_slashes(a->where); + + if ((r = automount_add_mount_links(a)) < 0) + return r; + + r = unit_load_related_unit(u, ".mount", &x); + if (r < 0) + return r; + + unit_ref_set(&a->mount, x); + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(a->mount), true); + if (r < 0) + return r; + + if (UNIT(a)->default_dependencies) + if ((r = automount_add_default_dependencies(a)) < 0) + return r; + } + + return automount_verify(a); +} + +static void automount_set_state(Automount *a, AutomountState state) { + AutomountState old_state; + assert(a); + + old_state = a->state; + a->state = state; + + if (state != AUTOMOUNT_WAITING && + state != AUTOMOUNT_RUNNING) + unmount_autofs(a); + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(a)->id, + automount_state_to_string(old_state), + automount_state_to_string(state)); + + unit_notify(UNIT(a), state_translation_table[old_state], state_translation_table[state], true); +} + +static int automount_coldplug(Unit *u) { + Automount *a = AUTOMOUNT(u); + int r; + + assert(a); + assert(a->state == AUTOMOUNT_DEAD); + + if (a->deserialized_state != a->state) { + + if ((r = open_dev_autofs(u->manager)) < 0) + return r; + + if (a->deserialized_state == AUTOMOUNT_WAITING || + a->deserialized_state == AUTOMOUNT_RUNNING) { + + assert(a->pipe_fd >= 0); + + if ((r = unit_watch_fd(UNIT(a), a->pipe_fd, EPOLLIN, &a->pipe_watch)) < 0) + return r; + } + + automount_set_state(a, a->deserialized_state); + } + + return 0; +} + +static void automount_dump(Unit *u, FILE *f, const char *prefix) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + fprintf(f, + "%sAutomount State: %s\n" + "%sResult: %s\n" + "%sWhere: %s\n" + "%sDirectoryMode: %04o\n", + prefix, automount_state_to_string(a->state), + prefix, automount_result_to_string(a->result), + prefix, a->where, + prefix, a->directory_mode); +} + +static void automount_enter_dead(Automount *a, AutomountResult f) { + assert(a); + + if (f != AUTOMOUNT_SUCCESS) + a->result = f; + + automount_set_state(a, a->result != AUTOMOUNT_SUCCESS ? AUTOMOUNT_FAILED : AUTOMOUNT_DEAD); +} + +static int open_dev_autofs(Manager *m) { + struct autofs_dev_ioctl param; + + assert(m); + + if (m->dev_autofs_fd >= 0) + return m->dev_autofs_fd; + + label_fix("/dev/autofs", false); + + if ((m->dev_autofs_fd = open("/dev/autofs", O_CLOEXEC|O_RDONLY)) < 0) { + log_error("Failed to open /dev/autofs: %s", strerror(errno)); + return -errno; + } + + init_autofs_dev_ioctl(¶m); + if (ioctl(m->dev_autofs_fd, AUTOFS_DEV_IOCTL_VERSION, ¶m) < 0) { + close_nointr_nofail(m->dev_autofs_fd); + m->dev_autofs_fd = -1; + return -errno; + } + + log_debug("Autofs kernel version %i.%i", param.ver_major, param.ver_minor); + + return m->dev_autofs_fd; +} + +static int open_ioctl_fd(int dev_autofs_fd, const char *where, dev_t devid) { + struct autofs_dev_ioctl *param; + size_t l; + int r; + + assert(dev_autofs_fd >= 0); + assert(where); + + l = sizeof(struct autofs_dev_ioctl) + strlen(where) + 1; + + if (!(param = malloc(l))) + return -ENOMEM; + + init_autofs_dev_ioctl(param); + param->size = l; + param->ioctlfd = -1; + param->openmount.devid = devid; + strcpy(param->path, where); + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_OPENMOUNT, param) < 0) { + r = -errno; + goto finish; + } + + if (param->ioctlfd < 0) { + r = -EIO; + goto finish; + } + + fd_cloexec(param->ioctlfd, true); + r = param->ioctlfd; + +finish: + free(param); + return r; +} + +static int autofs_protocol(int dev_autofs_fd, int ioctl_fd) { + uint32_t major, minor; + struct autofs_dev_ioctl param; + + assert(dev_autofs_fd >= 0); + assert(ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOVER, ¶m) < 0) + return -errno; + + major = param.protover.version; + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOSUBVER, ¶m) < 0) + return -errno; + + minor = param.protosubver.sub_version; + + log_debug("Autofs protocol version %i.%i", major, minor); + return 0; +} + +static int autofs_set_timeout(int dev_autofs_fd, int ioctl_fd, time_t sec) { + struct autofs_dev_ioctl param; + + assert(dev_autofs_fd >= 0); + assert(ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + param.timeout.timeout = sec; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_TIMEOUT, ¶m) < 0) + return -errno; + + return 0; +} + +static int autofs_send_ready(int dev_autofs_fd, int ioctl_fd, uint32_t token, int status) { + struct autofs_dev_ioctl param; + + assert(dev_autofs_fd >= 0); + assert(ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + if (status) { + param.fail.token = token; + param.fail.status = status; + } else + param.ready.token = token; + + if (ioctl(dev_autofs_fd, status ? AUTOFS_DEV_IOCTL_FAIL : AUTOFS_DEV_IOCTL_READY, ¶m) < 0) + return -errno; + + return 0; +} + +int automount_send_ready(Automount *a, int status) { + int ioctl_fd, r; + unsigned token; + + assert(a); + assert(status <= 0); + + if (set_isempty(a->tokens)) + return 0; + + if ((ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id)) < 0) { + r = ioctl_fd; + goto fail; + } + + if (status) + log_debug("Sending failure: %s", strerror(-status)); + else + log_debug("Sending success."); + + r = 0; + + /* Autofs thankfully does not hand out 0 as a token */ + while ((token = PTR_TO_UINT(set_steal_first(a->tokens)))) { + int k; + + /* Autofs fun fact II: + * + * if you pass a positive status code here, the kernel will + * freeze! Yay! */ + + if ((k = autofs_send_ready(UNIT(a)->manager->dev_autofs_fd, + ioctl_fd, + token, + status)) < 0) + r = k; + } + +fail: + if (ioctl_fd >= 0) + close_nointr_nofail(ioctl_fd); + + return r; +} + +static void automount_enter_waiting(Automount *a) { + int p[2] = { -1, -1 }; + char name[32], options[128]; + bool mounted = false; + int r, ioctl_fd = -1, dev_autofs_fd; + struct stat st; + + assert(a); + assert(a->pipe_fd < 0); + assert(a->where); + + if (a->tokens) + set_clear(a->tokens); + + if ((dev_autofs_fd = open_dev_autofs(UNIT(a)->manager)) < 0) { + r = dev_autofs_fd; + goto fail; + } + + /* We knowingly ignore the results of this call */ + mkdir_p(a->where, 0555); + + if (pipe2(p, O_NONBLOCK|O_CLOEXEC) < 0) { + r = -errno; + goto fail; + } + + snprintf(options, sizeof(options), "fd=%i,pgrp=%u,minproto=5,maxproto=5,direct", p[1], (unsigned) getpgrp()); + char_array_0(options); + + snprintf(name, sizeof(name), "systemd-%u", (unsigned) getpid()); + char_array_0(name); + + if (mount(name, a->where, "autofs", 0, options) < 0) { + r = -errno; + goto fail; + } + + mounted = true; + + close_nointr_nofail(p[1]); + p[1] = -1; + + if (stat(a->where, &st) < 0) { + r = -errno; + goto fail; + } + + if ((ioctl_fd = open_ioctl_fd(dev_autofs_fd, a->where, st.st_dev)) < 0) { + r = ioctl_fd; + goto fail; + } + + if ((r = autofs_protocol(dev_autofs_fd, ioctl_fd)) < 0) + goto fail; + + if ((r = autofs_set_timeout(dev_autofs_fd, ioctl_fd, 300)) < 0) + goto fail; + + /* Autofs fun fact: + * + * Unless we close the ioctl fd here, for some weird reason + * the direct mount will not receive events from the + * kernel. */ + + close_nointr_nofail(ioctl_fd); + ioctl_fd = -1; + + if ((r = unit_watch_fd(UNIT(a), p[0], EPOLLIN, &a->pipe_watch)) < 0) + goto fail; + + a->pipe_fd = p[0]; + a->dev_id = st.st_dev; + + automount_set_state(a, AUTOMOUNT_WAITING); + + return; + +fail: + assert_se(close_pipe(p) == 0); + + if (ioctl_fd >= 0) + close_nointr_nofail(ioctl_fd); + + if (mounted) + repeat_unmout(a->where); + + log_error("Failed to initialize automounter: %s", strerror(-r)); + automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); +} + +static void automount_enter_runnning(Automount *a) { + int r; + struct stat st; + DBusError error; + + assert(a); + assert(UNIT_DEREF(a->mount)); + + dbus_error_init(&error); + + /* We don't take mount requests anymore if we are supposed to + * shut down anyway */ + if (unit_pending_inactive(UNIT(a))) { + log_debug("Suppressing automount request on %s since unit stop is scheduled.", UNIT(a)->id); + automount_send_ready(a, -EHOSTDOWN); + return; + } + + mkdir_p(a->where, a->directory_mode); + + /* Before we do anything, let's see if somebody is playing games with us? */ + if (lstat(a->where, &st) < 0) { + log_warning("%s failed to stat automount point: %m", UNIT(a)->id); + goto fail; + } + + if (!S_ISDIR(st.st_mode) || st.st_dev != a->dev_id) + log_info("%s's automount point already active?", UNIT(a)->id); + else if ((r = manager_add_job(UNIT(a)->manager, JOB_START, UNIT_DEREF(a->mount), JOB_REPLACE, true, &error, NULL)) < 0) { + log_warning("%s failed to queue mount startup job: %s", UNIT(a)->id, bus_error(&error, r)); + goto fail; + } + + automount_set_state(a, AUTOMOUNT_RUNNING); + return; + +fail: + automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); + dbus_error_free(&error); +} + +static int automount_start(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_FAILED); + + if (path_is_mount_point(a->where, false)) { + log_error("Path %s is already a mount point, refusing start for %s", a->where, u->id); + return -EEXIST; + } + + if (UNIT_DEREF(a->mount)->load_state != UNIT_LOADED) + return -ENOENT; + + a->result = AUTOMOUNT_SUCCESS; + automount_enter_waiting(a); + return 0; +} + +static int automount_stop(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + assert(a->state == AUTOMOUNT_WAITING || a->state == AUTOMOUNT_RUNNING); + + automount_enter_dead(a, AUTOMOUNT_SUCCESS); + return 0; +} + +static int automount_serialize(Unit *u, FILE *f, FDSet *fds) { + Automount *a = AUTOMOUNT(u); + void *p; + Iterator i; + + assert(a); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", automount_state_to_string(a->state)); + unit_serialize_item(u, f, "result", automount_result_to_string(a->result)); + unit_serialize_item_format(u, f, "dev-id", "%u", (unsigned) a->dev_id); + + SET_FOREACH(p, a->tokens, i) + unit_serialize_item_format(u, f, "token", "%u", PTR_TO_UINT(p)); + + if (a->pipe_fd >= 0) { + int copy; + + if ((copy = fdset_put_dup(fds, a->pipe_fd)) < 0) + return copy; + + unit_serialize_item_format(u, f, "pipe-fd", "%i", copy); + } + + return 0; +} + +static int automount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Automount *a = AUTOMOUNT(u); + int r; + + assert(a); + assert(fds); + + if (streq(key, "state")) { + AutomountState state; + + if ((state = automount_state_from_string(value)) < 0) + log_debug("Failed to parse state value %s", value); + else + a->deserialized_state = state; + } else if (streq(key, "result")) { + AutomountResult f; + + f = automount_result_from_string(value); + if (f < 0) + log_debug("Failed to parse result value %s", value); + else if (f != AUTOMOUNT_SUCCESS) + a->result = f; + + } else if (streq(key, "dev-id")) { + unsigned d; + + if (safe_atou(value, &d) < 0) + log_debug("Failed to parse dev-id value %s", value); + else + a->dev_id = (unsigned) d; + } else if (streq(key, "token")) { + unsigned token; + + if (safe_atou(value, &token) < 0) + log_debug("Failed to parse token value %s", value); + else { + if (!a->tokens) + if (!(a->tokens = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + if ((r = set_put(a->tokens, UINT_TO_PTR(token))) < 0) + return r; + } + } else if (streq(key, "pipe-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse pipe-fd value %s", value); + else { + if (a->pipe_fd >= 0) + close_nointr_nofail(a->pipe_fd); + + a->pipe_fd = fdset_remove(fds, fd); + } + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState automount_active_state(Unit *u) { + assert(u); + + return state_translation_table[AUTOMOUNT(u)->state]; +} + +static const char *automount_sub_state_to_string(Unit *u) { + assert(u); + + return automount_state_to_string(AUTOMOUNT(u)->state); +} + +static bool automount_check_gc(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + if (!UNIT_DEREF(a->mount)) + return false; + + return UNIT_VTABLE(UNIT_DEREF(a->mount))->check_gc(UNIT_DEREF(a->mount)); +} + +static void automount_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { + Automount *a = AUTOMOUNT(u); + union autofs_v5_packet_union packet; + ssize_t l; + int r; + + assert(a); + assert(fd == a->pipe_fd); + + if (events != EPOLLIN) { + log_error("Got invalid poll event on pipe."); + goto fail; + } + + if ((l = loop_read(a->pipe_fd, &packet, sizeof(packet), true)) != sizeof(packet)) { + log_error("Invalid read from pipe: %s", l < 0 ? strerror(-l) : "short read"); + goto fail; + } + + switch (packet.hdr.type) { + + case autofs_ptype_missing_direct: + + if (packet.v5_packet.pid > 0) { + char *p = NULL; + + get_process_comm(packet.v5_packet.pid, &p); + log_debug("Got direct mount request for %s, triggered by %lu (%s)", packet.v5_packet.name, (unsigned long) packet.v5_packet.pid, strna(p)); + free(p); + + } else + log_debug("Got direct mount request for %s", packet.v5_packet.name); + + if (!a->tokens) + if (!(a->tokens = set_new(trivial_hash_func, trivial_compare_func))) { + log_error("Failed to allocate token set."); + goto fail; + } + + if ((r = set_put(a->tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token))) < 0) { + log_error("Failed to remember token: %s", strerror(-r)); + goto fail; + } + + automount_enter_runnning(a); + break; + + default: + log_error("Received unknown automount request %i", packet.hdr.type); + break; + } + + return; + +fail: + automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); +} + +static void automount_shutdown(Manager *m) { + assert(m); + + if (m->dev_autofs_fd >= 0) + close_nointr_nofail(m->dev_autofs_fd); +} + +static void automount_reset_failed(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + if (a->state == AUTOMOUNT_FAILED) + automount_set_state(a, AUTOMOUNT_DEAD); + + a->result = AUTOMOUNT_SUCCESS; +} + +static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = { + [AUTOMOUNT_DEAD] = "dead", + [AUTOMOUNT_WAITING] = "waiting", + [AUTOMOUNT_RUNNING] = "running", + [AUTOMOUNT_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(automount_state, AutomountState); + +static const char* const automount_result_table[_AUTOMOUNT_RESULT_MAX] = { + [AUTOMOUNT_SUCCESS] = "success", + [AUTOMOUNT_FAILURE_RESOURCES] = "resources" +}; + +DEFINE_STRING_TABLE_LOOKUP(automount_result, AutomountResult); + +const UnitVTable automount_vtable = { + .suffix = ".automount", + .object_size = sizeof(Automount), + .sections = + "Unit\0" + "Automount\0" + "Install\0", + + .no_alias = true, + .no_instances = true, + + .init = automount_init, + .load = automount_load, + .done = automount_done, + + .coldplug = automount_coldplug, + + .dump = automount_dump, + + .start = automount_start, + .stop = automount_stop, + + .serialize = automount_serialize, + .deserialize_item = automount_deserialize_item, + + .active_state = automount_active_state, + .sub_state_to_string = automount_sub_state_to_string, + + .check_gc = automount_check_gc, + + .fd_event = automount_fd_event, + + .reset_failed = automount_reset_failed, + + .bus_interface = "org.freedesktop.systemd1.Automount", + .bus_message_handler = bus_automount_message_handler, + .bus_invalidating_properties = bus_automount_invalidating_properties, + + .shutdown = automount_shutdown +}; diff --git a/src/automount.h b/src/automount.h new file mode 100644 index 000000000..19baee208 --- /dev/null +++ b/src/automount.h @@ -0,0 +1,76 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooautomounthfoo +#define fooautomounthfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Automount Automount; + +#include "unit.h" + +typedef enum AutomountState { + AUTOMOUNT_DEAD, + AUTOMOUNT_WAITING, + AUTOMOUNT_RUNNING, + AUTOMOUNT_FAILED, + _AUTOMOUNT_STATE_MAX, + _AUTOMOUNT_STATE_INVALID = -1 +} AutomountState; + +typedef enum AutomountResult { + AUTOMOUNT_SUCCESS, + AUTOMOUNT_FAILURE_RESOURCES, + _AUTOMOUNT_RESULT_MAX, + _AUTOMOUNT_RESULT_INVALID = -1 +} AutomountResult; + +struct Automount { + Unit meta; + + AutomountState state, deserialized_state; + + char *where; + + UnitRef mount; + + int pipe_fd; + mode_t directory_mode; + Watch pipe_watch; + dev_t dev_id; + + Set *tokens; + + AutomountResult result; +}; + +extern const UnitVTable automount_vtable; + +int automount_send_ready(Automount *a, int status); + +int automount_add_one_mount_link(Automount *a, Mount *m); + +const char* automount_state_to_string(AutomountState i); +AutomountState automount_state_from_string(const char *s); + +const char* automount_result_to_string(AutomountResult i); +AutomountResult automount_result_from_string(const char *s); + +#endif diff --git a/src/binfmt/Makefile b/src/binfmt/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/binfmt/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c new file mode 100644 index 000000000..3c8d815d6 --- /dev/null +++ b/src/binfmt/binfmt.c @@ -0,0 +1,169 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "hashmap.h" +#include "strv.h" +#include "util.h" + +static int delete_rule(const char *rule) { + char *x, *fn = NULL, *e; + int r; + + assert(rule[0]); + + if (!(x = strdup(rule))) + return -ENOMEM; + + e = strchrnul(x+1, x[0]); + *e = 0; + + asprintf(&fn, "/proc/sys/fs/binfmt_misc/%s", x+1); + free(x); + + if (!fn) + return -ENOMEM; + + r = write_one_line_file(fn, "-1"); + free(fn); + + return r; +} + +static int apply_rule(const char *rule) { + int r; + + delete_rule(rule); + + if ((r = write_one_line_file("/proc/sys/fs/binfmt_misc/register", rule)) < 0) { + log_error("Failed to add binary format: %s", strerror(-r)); + return r; + } + + return 0; +} + +static int apply_file(const char *path, bool ignore_enoent) { + FILE *f; + int r = 0; + + assert(path); + + if (!(f = fopen(path, "re"))) { + if (ignore_enoent && errno == ENOENT) + return 0; + + log_error("Failed to open file '%s', ignoring: %m", path); + return -errno; + } + + log_debug("apply: %s\n", path); + while (!feof(f)) { + char l[LINE_MAX], *p; + int k; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + log_error("Failed to read file '%s', ignoring: %m", path); + r = -errno; + goto finish; + } + + p = strstrip(l); + + if (!*p) + continue; + + if (strchr(COMMENTS, *p)) + continue; + + if ((k = apply_rule(p)) < 0 && r == 0) + r = k; + } + +finish: + fclose(f); + + return r; +} + +int main(int argc, char *argv[]) { + int r = 0; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc > 1) { + int i; + + for (i = 1; i < argc; i++) { + int k; + + k = apply_file(argv[i], false); + if (k < 0 && r == 0) + r = k; + } + } else { + char **files, **f; + + r = conf_files_list(&files, ".conf", + "/etc/binfmt.d", + "/run/binfmt.d", + "/usr/local/lib/binfmt.d", + "/usr/lib/binfmt.d", +#ifdef HAVE_SPLIT_USR + "/lib/binfmt.d", +#endif + NULL); + if (r < 0) { + log_error("Failed to enumerate binfmt.d files: %s", strerror(-r)); + goto finish; + } + + /* Flush out all rules */ + write_one_line_file("/proc/sys/fs/binfmt_misc/status", "-1"); + + STRV_FOREACH(f, files) { + int k; + + k = apply_file(*f, true); + if (k < 0 && r == 0) + r = k; + } + + strv_free(files); + } +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/bridge.c b/src/bridge.c new file mode 100644 index 000000000..bfb38a8bb --- /dev/null +++ b/src/bridge.c @@ -0,0 +1,367 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "util.h" +#include "socket-util.h" + +#define BUFFER_SIZE (64*1024) +#define EXTRA_SIZE 16 + +static bool initial_nul = false; +static bool auth_over = false; + +static void format_uid(char *buf, size_t l) { + char text[20 + 1]; /* enough space for a 64bit integer plus NUL */ + unsigned j; + + assert(l > 0); + + snprintf(text, sizeof(text)-1, "%llu", (unsigned long long) geteuid()); + text[sizeof(text)-1] = 0; + + memset(buf, 0, l); + + for (j = 0; text[j] && j*2+2 < l; j++) { + buf[j*2] = hexchar(text[j] >> 4); + buf[j*2+1] = hexchar(text[j] & 0xF); + } + + buf[j*2] = 0; +} + +static size_t patch_in_line(char *line, size_t l, size_t left) { + size_t r; + + if (line[0] == 0 && !initial_nul) { + initial_nul = true; + line += 1; + l -= 1; + r = 1; + } else + r = 0; + + if (l == 5 && strncmp(line, "BEGIN", 5) == 0) { + r += l; + auth_over = true; + + } else if (l == 17 && strncmp(line, "NEGOTIATE_UNIX_FD", 17) == 0) { + memmove(line + 13, line + 17, left); + memcpy(line, "NEGOTIATE_NOP", 13); + r += 13; + + } else if (l >= 14 && strncmp(line, "AUTH EXTERNAL ", 14) == 0) { + char uid[20*2 + 1]; + size_t len; + + format_uid(uid, sizeof(uid)); + len = strlen(uid); + assert(len <= EXTRA_SIZE); + + memmove(line + 14 + len, line + l, left); + memcpy(line + 14, uid, len); + + r += 14 + len; + } else + r += l; + + return r; +} + +static size_t patch_in_buffer(char* in_buffer, size_t *in_buffer_full) { + size_t i, good = 0; + + if (*in_buffer_full <= 0) + return *in_buffer_full; + + /* If authentication is done, we don't touch anything anymore */ + if (auth_over) + return *in_buffer_full; + + if (*in_buffer_full < 2) + return 0; + + for (i = 0; i <= *in_buffer_full - 2; i ++) { + + /* Fully lines can be send on */ + if (in_buffer[i] == '\r' && in_buffer[i+1] == '\n') { + if (i > good) { + size_t old_length, new_length; + + old_length = i - good; + new_length = patch_in_line(in_buffer+good, old_length, *in_buffer_full - i); + *in_buffer_full = *in_buffer_full + new_length - old_length; + + good += new_length + 2; + + } else + good = i+2; + } + + if (auth_over) + break; + } + + return good; +} + +int main(int argc, char *argv[]) { + int r = EXIT_FAILURE, fd = -1, ep = -1; + union sockaddr_union sa; + char in_buffer[BUFFER_SIZE+EXTRA_SIZE], out_buffer[BUFFER_SIZE+EXTRA_SIZE]; + size_t in_buffer_full = 0, out_buffer_full = 0; + struct epoll_event stdin_ev, stdout_ev, fd_ev; + bool stdin_readable = false, stdout_writable = false, fd_readable = false, fd_writable = false; + bool stdin_rhup = false, stdout_whup = false, fd_rhup = false, fd_whup = false; + + if (argc > 1) { + log_error("This program takes no argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + log_parse_environment(); + log_open(); + + if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) { + log_error("Failed to create socket: %s", strerror(errno)); + goto finish; + } + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, "/run/dbus/system_bus_socket", sizeof(sa.un.sun_path)); + + if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { + log_error("Failed to connect: %m"); + goto finish; + } + + fd_nonblock(STDIN_FILENO, 1); + fd_nonblock(STDOUT_FILENO, 1); + + if ((ep = epoll_create1(EPOLL_CLOEXEC)) < 0) { + log_error("Failed to create epoll: %m"); + goto finish; + } + + zero(stdin_ev); + stdin_ev.events = EPOLLIN|EPOLLET; + stdin_ev.data.fd = STDIN_FILENO; + + zero(stdout_ev); + stdout_ev.events = EPOLLOUT|EPOLLET; + stdout_ev.data.fd = STDOUT_FILENO; + + zero(fd_ev); + fd_ev.events = EPOLLIN|EPOLLOUT|EPOLLET; + fd_ev.data.fd = fd; + + if (epoll_ctl(ep, EPOLL_CTL_ADD, STDIN_FILENO, &stdin_ev) < 0 || + epoll_ctl(ep, EPOLL_CTL_ADD, STDOUT_FILENO, &stdout_ev) < 0 || + epoll_ctl(ep, EPOLL_CTL_ADD, fd, &fd_ev) < 0) { + log_error("Failed to regiser fds in epoll: %m"); + goto finish; + } + + do { + struct epoll_event ev[16]; + ssize_t k; + int i, nfds; + + if ((nfds = epoll_wait(ep, ev, ELEMENTSOF(ev), -1)) < 0) { + + if (errno == EINTR || errno == EAGAIN) + continue; + + log_error("epoll_wait(): %m"); + goto finish; + } + + assert(nfds >= 1); + + for (i = 0; i < nfds; i++) { + if (ev[i].data.fd == STDIN_FILENO) { + + if (!stdin_rhup && (ev[i].events & (EPOLLHUP|EPOLLIN))) + stdin_readable = true; + + } else if (ev[i].data.fd == STDOUT_FILENO) { + + if (ev[i].events & EPOLLHUP) { + stdout_writable = false; + stdout_whup = true; + } + + if (!stdout_whup && (ev[i].events & EPOLLOUT)) + stdout_writable = true; + + } else if (ev[i].data.fd == fd) { + + if (ev[i].events & EPOLLHUP) { + fd_writable = false; + fd_whup = true; + } + + if (!fd_rhup && (ev[i].events & (EPOLLHUP|EPOLLIN))) + fd_readable = true; + + if (!fd_whup && (ev[i].events & EPOLLOUT)) + fd_writable = true; + } + } + + while ((stdin_readable && in_buffer_full <= 0) || + (fd_writable && patch_in_buffer(in_buffer, &in_buffer_full) > 0) || + (fd_readable && out_buffer_full <= 0) || + (stdout_writable && out_buffer_full > 0)) { + + size_t in_buffer_good = 0; + + if (stdin_readable && in_buffer_full < BUFFER_SIZE) { + + if ((k = read(STDIN_FILENO, in_buffer + in_buffer_full, BUFFER_SIZE - in_buffer_full)) < 0) { + + if (errno == EAGAIN) + stdin_readable = false; + else if (errno == EPIPE || errno == ECONNRESET) + k = 0; + else { + log_error("read(): %m"); + goto finish; + } + } else + in_buffer_full += (size_t) k; + + if (k == 0) { + stdin_rhup = true; + stdin_readable = false; + shutdown(STDIN_FILENO, SHUT_RD); + close_nointr_nofail(STDIN_FILENO); + } + } + + in_buffer_good = patch_in_buffer(in_buffer, &in_buffer_full); + + if (fd_writable && in_buffer_good > 0) { + + if ((k = write(fd, in_buffer, in_buffer_good)) < 0) { + + if (errno == EAGAIN) + fd_writable = false; + else if (errno == EPIPE || errno == ECONNRESET) { + fd_whup = true; + fd_writable = false; + shutdown(fd, SHUT_WR); + } else { + log_error("write(): %m"); + goto finish; + } + + } else { + assert(in_buffer_full >= (size_t) k); + memmove(in_buffer, in_buffer + k, in_buffer_full - k); + in_buffer_full -= k; + } + } + + if (fd_readable && out_buffer_full < BUFFER_SIZE) { + + if ((k = read(fd, out_buffer + out_buffer_full, BUFFER_SIZE - out_buffer_full)) < 0) { + + if (errno == EAGAIN) + fd_readable = false; + else if (errno == EPIPE || errno == ECONNRESET) + k = 0; + else { + log_error("read(): %m"); + goto finish; + } + } else + out_buffer_full += (size_t) k; + + if (k == 0) { + fd_rhup = true; + fd_readable = false; + shutdown(fd, SHUT_RD); + } + } + + if (stdout_writable && out_buffer_full > 0) { + + if ((k = write(STDOUT_FILENO, out_buffer, out_buffer_full)) < 0) { + + if (errno == EAGAIN) + stdout_writable = false; + else if (errno == EPIPE || errno == ECONNRESET) { + stdout_whup = true; + stdout_writable = false; + shutdown(STDOUT_FILENO, SHUT_WR); + close_nointr(STDOUT_FILENO); + } else { + log_error("write(): %m"); + goto finish; + } + + } else { + assert(out_buffer_full >= (size_t) k); + memmove(out_buffer, out_buffer + k, out_buffer_full - k); + out_buffer_full -= k; + } + } + } + + if (stdin_rhup && in_buffer_full <= 0 && !fd_whup) { + fd_whup = true; + fd_writable = false; + shutdown(fd, SHUT_WR); + } + + if (fd_rhup && out_buffer_full <= 0 && !stdout_whup) { + stdout_whup = true; + stdout_writable = false; + shutdown(STDOUT_FILENO, SHUT_WR); + close_nointr(STDOUT_FILENO); + } + + } while (!stdout_whup || !fd_whup); + + r = EXIT_SUCCESS; + +finish: + if (fd >= 0) + close_nointr_nofail(fd); + + if (ep >= 0) + close_nointr_nofail(ep); + + return r; +} diff --git a/src/build.h b/src/build.h new file mode 100644 index 000000000..061901314 --- /dev/null +++ b/src/build.h @@ -0,0 +1,69 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foobuildhfoo +#define foobuildhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#ifdef HAVE_PAM +#define _PAM_FEATURE_ "+PAM" +#else +#define _PAM_FEATURE_ "-PAM" +#endif + +#ifdef HAVE_LIBWRAP +#define _LIBWRAP_FEATURE_ "+LIBWRAP" +#else +#define _LIBWRAP_FEATURE_ "-LIBWRAP" +#endif + +#ifdef HAVE_AUDIT +#define _AUDIT_FEATURE_ "+AUDIT" +#else +#define _AUDIT_FEATURE_ "-AUDIT" +#endif + +#ifdef HAVE_SELINUX +#define _SELINUX_FEATURE_ "+SELINUX" +#else +#define _SELINUX_FEATURE_ "-SELINUX" +#endif + +#ifdef HAVE_IMA +#define _IMA_FEATURE_ "+IMA" +#else +#define _IMA_FEATURE_ "-IMA" +#endif + +#ifdef HAVE_SYSV_COMPAT +#define _SYSVINIT_FEATURE_ "+SYSVINIT" +#else +#define _SYSVINIT_FEATURE_ "-SYSVINIT" +#endif + +#ifdef HAVE_LIBCRYPTSETUP +#define _LIBCRYPTSETUP_FEATURE_ "+LIBCRYPTSETUP" +#else +#define _LIBCRYPTSETUP_FEATURE_ "-LIBCRYPTSETUP" +#endif + +#define SYSTEMD_FEATURES _PAM_FEATURE_ " " _LIBWRAP_FEATURE_ " " _AUDIT_FEATURE_ " " _SELINUX_FEATURE_ " " _IMA_FEATURE_ " " _SYSVINIT_FEATURE_ " " _LIBCRYPTSETUP_FEATURE_ + +#endif diff --git a/src/bus-errors.h b/src/bus-errors.h new file mode 100644 index 000000000..82d4e99ee --- /dev/null +++ b/src/bus-errors.h @@ -0,0 +1,58 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foobuserrorshfoo +#define foobuserrorshfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#define BUS_ERROR_NO_SUCH_UNIT "org.freedesktop.systemd1.NoSuchUnit" +#define BUS_ERROR_NO_SUCH_JOB "org.freedesktop.systemd1.NoSuchJob" +#define BUS_ERROR_NOT_SUBSCRIBED "org.freedesktop.systemd1.NotSubscribed" +#define BUS_ERROR_INVALID_PATH "org.freedesktop.systemd1.InvalidPath" +#define BUS_ERROR_INVALID_NAME "org.freedesktop.systemd1.InvalidName" +#define BUS_ERROR_UNIT_TYPE_MISMATCH "org.freedesktop.systemd1.UnitTypeMismatch" +#define BUS_ERROR_UNIT_EXISTS "org.freedesktop.systemd1.UnitExists" +#define BUS_ERROR_NOT_SUPPORTED "org.freedesktop.systemd1.NotSupported" +#define BUS_ERROR_INVALID_JOB_MODE "org.freedesktop.systemd1.InvalidJobMode" +#define BUS_ERROR_ONLY_BY_DEPENDENCY "org.freedesktop.systemd1.OnlyByDependency" +#define BUS_ERROR_NO_ISOLATION "org.freedesktop.systemd1.NoIsolation" +#define BUS_ERROR_LOAD_FAILED "org.freedesktop.systemd1.LoadFailed" +#define BUS_ERROR_MASKED "org.freedesktop.systemd1.Masked" +#define BUS_ERROR_JOB_TYPE_NOT_APPLICABLE "org.freedesktop.systemd1.JobTypeNotApplicable" +#define BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE "org.freedesktop.systemd1.TransactionIsDestructive" +#define BUS_ERROR_TRANSACTION_JOBS_CONFLICTING "org.freedesktop.systemd1.TransactionJobsConflicting" +#define BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC "org.freedesktop.systemd1.TransactionOrderIsCyclic" +#define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown" +#define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess" + +static inline const char *bus_error(const DBusError *e, int r) { + if (e && e->message) + return e->message; + + if (r >= 0) + return strerror(r); + + return strerror(-r); +} + +#endif diff --git a/src/cgls.c b/src/cgls.c new file mode 100644 index 000000000..d5417f950 --- /dev/null +++ b/src/cgls.c @@ -0,0 +1,164 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include + +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "log.h" +#include "util.h" +#include "pager.h" + +static bool arg_no_pager = false; +static bool arg_kernel_threads = false; + +static void help(void) { + + printf("%s [OPTIONS...] [CGROUP...]\n\n" + "Recursively show control group contents.\n\n" + " -h --help Show this help\n" + " --no-pager Do not pipe output into a pager\n" + " -k Include kernel threads in output\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_NO_PAGER = 0x100 + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 1); + assert(argv); + + while ((c = getopt_long(argc, argv, "hk", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 'k': + arg_kernel_threads = true; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r = 0, retval = EXIT_FAILURE; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r < 0) + goto finish; + else if (r == 0) { + retval = EXIT_SUCCESS; + goto finish; + } + + if (!arg_no_pager) + pager_open(); + + if (optind < argc) { + unsigned i; + + for (i = (unsigned) optind; i < (unsigned) argc; i++) { + int q; + printf("%s:\n", argv[i]); + + q = show_cgroup_by_path(argv[i], NULL, 0, arg_kernel_threads); + if (q < 0) + r = q; + } + + } else { + char *p; + + p = get_current_dir_name(); + if (!p) { + log_error("Cannot determine current working directory: %m"); + goto finish; + } + + if (path_startswith(p, "/sys/fs/cgroup")) { + printf("Working Directory %s:\n", p); + r = show_cgroup_by_path(p, NULL, 0, arg_kernel_threads); + } else { + char *root = NULL; + const char *t = NULL; + + r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 1, &root); + if (r < 0) + t = "/"; + else { + if (endswith(root, "/system")) + root[strlen(root)-7] = 0; + + t = root[0] ? root : "/"; + } + + r = show_cgroup(SYSTEMD_CGROUP_CONTROLLER, t, NULL, 0, arg_kernel_threads); + free(root); + } + + free(p); + } + + if (r < 0) + log_error("Failed to list cgroup tree: %s", strerror(-r)); + + retval = EXIT_SUCCESS; + +finish: + pager_close(); + + return retval; +} diff --git a/src/cgroup-attr.c b/src/cgroup-attr.c new file mode 100644 index 000000000..474a6865c --- /dev/null +++ b/src/cgroup-attr.c @@ -0,0 +1,102 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "cgroup-attr.h" +#include "cgroup-util.h" +#include "list.h" + +int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b) { + int r; + char *path = NULL; + char *v = NULL; + + assert(a); + + b = cgroup_bonding_find_list(b, a->controller); + if (!b) + return 0; + + if (a->map_callback) { + r = a->map_callback(a->controller, a->name, a->value, &v); + if (r < 0) + return r; + } + + r = cg_get_path(a->controller, b->path, a->name, &path); + if (r < 0) { + free(v); + return r; + } + + r = write_one_line_file(path, v ? v : a->value); + if (r < 0) + log_warning("Failed to write '%s' to %s: %s", v ? v : a->value, path, strerror(-r)); + + free(path); + free(v); + + return r; +} + +int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b) { + CGroupAttribute *a; + int r = 0; + + LIST_FOREACH(by_unit, a, first) { + int k; + + k = cgroup_attribute_apply(a, b); + if (r == 0) + r = k; + } + + return r; +} + +CGroupAttribute *cgroup_attribute_find_list(CGroupAttribute *first, const char *controller, const char *name) { + CGroupAttribute *a; + + assert(controller); + assert(name); + + LIST_FOREACH(by_unit, a, first) + if (streq(a->controller, controller) && + streq(a->name, name)) + return a; + + return NULL; +} + +static void cgroup_attribute_free(CGroupAttribute *a) { + assert(a); + + free(a->controller); + free(a->name); + free(a->value); + free(a); +} + +void cgroup_attribute_free_list(CGroupAttribute *first) { + CGroupAttribute *a, *n; + + LIST_FOREACH_SAFE(by_unit, a, n, first) + cgroup_attribute_free(a); +} diff --git a/src/cgroup-attr.h b/src/cgroup-attr.h new file mode 100644 index 000000000..63a73b810 --- /dev/null +++ b/src/cgroup-attr.h @@ -0,0 +1,49 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foocgroupattrhfoo +#define foocgroupattrhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct CGroupAttribute CGroupAttribute; + +typedef int (*CGroupAttributeMapCallback)(const char *controller, const char*name, const char *value, char **ret); + +#include "unit.h" +#include "cgroup.h" + +struct CGroupAttribute { + char *controller; + char *name; + char *value; + + CGroupAttributeMapCallback map_callback; + + LIST_FIELDS(CGroupAttribute, by_unit); +}; + +int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b); +int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b); + +CGroupAttribute *cgroup_attribute_find_list(CGroupAttribute *first, const char *controller, const char *name); + +void cgroup_attribute_free_list(CGroupAttribute *first); + +#endif diff --git a/src/cgroup-show.c b/src/cgroup-show.c new file mode 100644 index 000000000..ee2a241c9 --- /dev/null +++ b/src/cgroup-show.c @@ -0,0 +1,261 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "util.h" +#include "macro.h" +#include "cgroup-util.h" +#include "cgroup-show.h" + +static int compare(const void *a, const void *b) { + const pid_t *p = a, *q = b; + + if (*p < *q) + return -1; + if (*p > *q) + return 1; + return 0; +} + +static unsigned ilog10(unsigned long ul) { + int n = 0; + + while (ul > 0) { + n++; + ul /= 10; + } + + return n; +} + +static int show_cgroup_one_by_path(const char *path, const char *prefix, unsigned n_columns, bool more, bool kernel_threads) { + char *fn; + FILE *f; + size_t n = 0, n_allocated = 0; + pid_t *pids = NULL; + char *p; + pid_t pid, biggest = 0; + int r; + + if (n_columns <= 0) + n_columns = columns(); + + if (!prefix) + prefix = ""; + + if ((r = cg_fix_path(path, &p)) < 0) + return r; + + r = asprintf(&fn, "%s/cgroup.procs", p); + free(p); + + if (r < 0) + return -ENOMEM; + + f = fopen(fn, "re"); + free(fn); + + if (!f) + return -errno; + + while ((r = cg_read_pid(f, &pid)) > 0) { + + if (!kernel_threads && is_kernel_thread(pid) > 0) + continue; + + if (n >= n_allocated) { + pid_t *npids; + + n_allocated = MAX(16U, n*2U); + + if (!(npids = realloc(pids, sizeof(pid_t) * n_allocated))) { + r = -ENOMEM; + goto finish; + } + + pids = npids; + } + + assert(n < n_allocated); + pids[n++] = pid; + + if (pid > biggest) + biggest = pid; + } + + if (r < 0) + goto finish; + + if (n > 0) { + unsigned i, m; + + /* Filter duplicates */ + m = 0; + for (i = 0; i < n; i++) { + unsigned j; + + for (j = i+1; j < n; j++) + if (pids[i] == pids[j]) + break; + + if (j >= n) + pids[m++] = pids[i]; + } + n = m; + + /* And sort */ + qsort(pids, n, sizeof(pid_t), compare); + + if (n_columns > 8) + n_columns -= 8; + else + n_columns = 20; + + for (i = 0; i < n; i++) { + char *t = NULL; + + get_process_cmdline(pids[i], n_columns, true, &t); + + printf("%s%s %*lu %s\n", + prefix, + (more || i < n-1) ? "\342\224\234" : "\342\224\224", + (int) ilog10(biggest), + (unsigned long) pids[i], + strna(t)); + + free(t); + } + } + + r = 0; + +finish: + free(pids); + + if (f) + fclose(f); + + return r; +} + +int show_cgroup_by_path(const char *path, const char *prefix, unsigned n_columns, bool kernel_threads) { + DIR *d; + char *last = NULL; + char *p1 = NULL, *p2 = NULL, *fn = NULL, *gn = NULL; + bool shown_pids = false; + int r; + + if (n_columns <= 0) + n_columns = columns(); + + if (!prefix) + prefix = ""; + + if ((r = cg_fix_path(path, &fn)) < 0) + return r; + + if (!(d = opendir(fn))) { + free(fn); + return -errno; + } + + while ((r = cg_read_subgroup(d, &gn)) > 0) { + + if (!shown_pids) { + show_cgroup_one_by_path(path, prefix, n_columns, true, kernel_threads); + shown_pids = true; + } + + if (last) { + printf("%s\342\224\234 %s\n", prefix, file_name_from_path(last)); + + if (!p1) + if (!(p1 = strappend(prefix, "\342\224\202 "))) { + r = -ENOMEM; + goto finish; + } + + show_cgroup_by_path(last, p1, n_columns-2, kernel_threads); + + free(last); + last = NULL; + } + + r = asprintf(&last, "%s/%s", fn, gn); + free(gn); + + if (r < 0) { + r = -ENOMEM; + goto finish; + } + } + + if (r < 0) + goto finish; + + if (!shown_pids) + show_cgroup_one_by_path(path, prefix, n_columns, !!last, kernel_threads); + + if (last) { + printf("%s\342\224\224 %s\n", prefix, file_name_from_path(last)); + + if (!p2) + if (!(p2 = strappend(prefix, " "))) { + r = -ENOMEM; + goto finish; + } + + show_cgroup_by_path(last, p2, n_columns-2, kernel_threads); + } + + r = 0; + +finish: + free(p1); + free(p2); + free(last); + free(fn); + + closedir(d); + + return r; +} + +int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads) { + char *p; + int r; + + assert(controller); + assert(path); + + r = cg_get_path(controller, path, NULL, &p); + if (r < 0) + return r; + + r = show_cgroup_by_path(p, prefix, n_columns, kernel_threads); + free(p); + + return r; +} diff --git a/src/cgroup-show.h b/src/cgroup-show.h new file mode 100644 index 000000000..992e17b98 --- /dev/null +++ b/src/cgroup-show.h @@ -0,0 +1,30 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foocgroupshowhfoo +#define foocgroupshowhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +int show_cgroup_by_path(const char *path, const char *prefix, unsigned columns, bool kernel_threads); +int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned columns, bool kernel_threads); + +#endif diff --git a/src/cgroup-util.c b/src/cgroup-util.c new file mode 100644 index 000000000..904d30095 --- /dev/null +++ b/src/cgroup-util.c @@ -0,0 +1,1111 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cgroup-util.h" +#include "log.h" +#include "set.h" +#include "macro.h" +#include "util.h" + +int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) { + char *fs; + int r; + FILE *f; + + assert(controller); + assert(path); + assert(_f); + + if ((r = cg_get_path(controller, path, "cgroup.procs", &fs)) < 0) + return r; + + f = fopen(fs, "re"); + free(fs); + + if (!f) + return -errno; + + *_f = f; + return 0; +} + +int cg_enumerate_tasks(const char *controller, const char *path, FILE **_f) { + char *fs; + int r; + FILE *f; + + assert(controller); + assert(path); + assert(_f); + + if ((r = cg_get_path(controller, path, "tasks", &fs)) < 0) + return r; + + f = fopen(fs, "re"); + free(fs); + + if (!f) + return -errno; + + *_f = f; + return 0; +} + +int cg_read_pid(FILE *f, pid_t *_pid) { + unsigned long ul; + + /* Note that the cgroup.procs might contain duplicates! See + * cgroups.txt for details. */ + + errno = 0; + if (fscanf(f, "%lu", &ul) != 1) { + + if (feof(f)) + return 0; + + return errno ? -errno : -EIO; + } + + if (ul <= 0) + return -EIO; + + *_pid = (pid_t) ul; + return 1; +} + +int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) { + char *fs; + int r; + DIR *d; + + assert(controller); + assert(path); + assert(_d); + + /* This is not recursive! */ + + if ((r = cg_get_path(controller, path, NULL, &fs)) < 0) + return r; + + d = opendir(fs); + free(fs); + + if (!d) + return -errno; + + *_d = d; + return 0; +} + +int cg_read_subgroup(DIR *d, char **fn) { + struct dirent *de; + + assert(d); + + errno = 0; + while ((de = readdir(d))) { + char *b; + + if (de->d_type != DT_DIR) + continue; + + if (streq(de->d_name, ".") || + streq(de->d_name, "..")) + continue; + + if (!(b = strdup(de->d_name))) + return -ENOMEM; + + *fn = b; + return 1; + } + + if (errno) + return -errno; + + return 0; +} + +int cg_rmdir(const char *controller, const char *path, bool honour_sticky) { + char *p; + int r; + + r = cg_get_path(controller, path, NULL, &p); + if (r < 0) + return r; + + if (honour_sticky) { + char *tasks; + + /* If the sticky bit is set don't remove the directory */ + + tasks = strappend(p, "/tasks"); + if (!tasks) { + free(p); + return -ENOMEM; + } + + r = file_is_priv_sticky(tasks); + free(tasks); + + if (r > 0) { + free(p); + return 0; + } + } + + r = rmdir(p); + free(p); + + return (r < 0 && errno != ENOENT) ? -errno : 0; +} + +int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) { + bool done = false; + int r, ret = 0; + pid_t my_pid; + FILE *f = NULL; + Set *allocated_set = NULL; + + assert(controller); + assert(path); + assert(sig >= 0); + + /* This goes through the tasks list and kills them all. This + * is repeated until no further processes are added to the + * tasks list, to properly handle forking processes */ + + if (!s) + if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + my_pid = getpid(); + + do { + pid_t pid = 0; + done = true; + + if ((r = cg_enumerate_processes(controller, path, &f)) < 0) { + if (ret >= 0 && r != -ENOENT) + ret = r; + + goto finish; + } + + while ((r = cg_read_pid(f, &pid)) > 0) { + + if (pid == my_pid && ignore_self) + continue; + + if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid)) + continue; + + /* If we haven't killed this process yet, kill + * it */ + if (kill(pid, sig) < 0) { + if (ret >= 0 && errno != ESRCH) + ret = -errno; + } else if (ret == 0) { + + if (sigcont) + kill(pid, SIGCONT); + + ret = 1; + } + + done = false; + + if ((r = set_put(s, LONG_TO_PTR(pid))) < 0) { + if (ret >= 0) + ret = r; + + goto finish; + } + } + + if (r < 0) { + if (ret >= 0) + ret = r; + + goto finish; + } + + fclose(f); + f = NULL; + + /* To avoid racing against processes which fork + * quicker than we can kill them we repeat this until + * no new pids need to be killed. */ + + } while (!done); + +finish: + if (allocated_set) + set_free(allocated_set); + + if (f) + fclose(f); + + return ret; +} + +int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) { + int r, ret = 0; + DIR *d = NULL; + char *fn; + Set *allocated_set = NULL; + + assert(path); + assert(controller); + assert(sig >= 0); + + if (!s) + if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + ret = cg_kill(controller, path, sig, sigcont, ignore_self, s); + + if ((r = cg_enumerate_subgroups(controller, path, &d)) < 0) { + if (ret >= 0 && r != -ENOENT) + ret = r; + + goto finish; + } + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + char *p = NULL; + + r = asprintf(&p, "%s/%s", path, fn); + free(fn); + + if (r < 0) { + if (ret >= 0) + ret = -ENOMEM; + + goto finish; + } + + r = cg_kill_recursive(controller, p, sig, sigcont, ignore_self, rem, s); + free(p); + + if (r != 0 && ret >= 0) + ret = r; + } + + if (r < 0 && ret >= 0) + ret = r; + + if (rem) + if ((r = cg_rmdir(controller, path, true)) < 0) { + if (ret >= 0 && + r != -ENOENT && + r != -EBUSY) + ret = r; + } + +finish: + if (d) + closedir(d); + + if (allocated_set) + set_free(allocated_set); + + return ret; +} + +int cg_kill_recursive_and_wait(const char *controller, const char *path, bool rem) { + unsigned i; + + assert(path); + assert(controller); + + /* This safely kills all processes; first it sends a SIGTERM, + * then checks 8 times after 200ms whether the group is now + * empty, then kills everything that is left with SIGKILL and + * finally checks 5 times after 200ms each whether the group + * is finally empty. */ + + for (i = 0; i < 15; i++) { + int sig, r; + + if (i <= 0) + sig = SIGTERM; + else if (i == 9) + sig = SIGKILL; + else + sig = 0; + + if ((r = cg_kill_recursive(controller, path, sig, true, true, rem, NULL)) <= 0) + return r; + + usleep(200 * USEC_PER_MSEC); + } + + return 0; +} + +int cg_migrate(const char *controller, const char *from, const char *to, bool ignore_self) { + bool done = false; + Set *s; + int r, ret = 0; + pid_t my_pid; + FILE *f = NULL; + + assert(controller); + assert(from); + assert(to); + + if (!(s = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + my_pid = getpid(); + + do { + pid_t pid = 0; + done = true; + + if ((r = cg_enumerate_tasks(controller, from, &f)) < 0) { + if (ret >= 0 && r != -ENOENT) + ret = r; + + goto finish; + } + + while ((r = cg_read_pid(f, &pid)) > 0) { + + /* This might do weird stuff if we aren't a + * single-threaded program. However, we + * luckily know we are not */ + if (pid == my_pid && ignore_self) + continue; + + if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid)) + continue; + + if ((r = cg_attach(controller, to, pid)) < 0) { + if (ret >= 0 && r != -ESRCH) + ret = r; + } else if (ret == 0) + ret = 1; + + done = false; + + if ((r = set_put(s, LONG_TO_PTR(pid))) < 0) { + if (ret >= 0) + ret = r; + + goto finish; + } + } + + if (r < 0) { + if (ret >= 0) + ret = r; + + goto finish; + } + + fclose(f); + f = NULL; + + } while (!done); + +finish: + set_free(s); + + if (f) + fclose(f); + + return ret; +} + +int cg_migrate_recursive(const char *controller, const char *from, const char *to, bool ignore_self, bool rem) { + int r, ret = 0; + DIR *d = NULL; + char *fn; + + assert(controller); + assert(from); + assert(to); + + ret = cg_migrate(controller, from, to, ignore_self); + + if ((r = cg_enumerate_subgroups(controller, from, &d)) < 0) { + if (ret >= 0 && r != -ENOENT) + ret = r; + goto finish; + } + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + char *p = NULL; + + r = asprintf(&p, "%s/%s", from, fn); + free(fn); + + if (r < 0) { + if (ret >= 0) + ret = -ENOMEM; + + goto finish; + } + + r = cg_migrate_recursive(controller, p, to, ignore_self, rem); + free(p); + + if (r != 0 && ret >= 0) + ret = r; + } + + if (r < 0 && ret >= 0) + ret = r; + + if (rem) + if ((r = cg_rmdir(controller, from, true)) < 0) { + if (ret >= 0 && + r != -ENOENT && + r != -EBUSY) + ret = r; + } + +finish: + if (d) + closedir(d); + + return ret; +} + +int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) { + const char *p; + char *t; + static __thread bool good = false; + + assert(controller); + assert(fs); + + if (_unlikely_(!good)) { + int r; + + r = path_is_mount_point("/sys/fs/cgroup", false); + if (r <= 0) + return r < 0 ? r : -ENOENT; + + /* Cache this to save a few stat()s */ + good = true; + } + + if (isempty(controller)) + return -EINVAL; + + /* This is a very minimal lookup from controller names to + * paths. Since we have mounted most hierarchies ourselves + * should be kinda safe, but eventually we might want to + * extend this to have a fallback to actually check + * /proc/mounts. Might need caching then. */ + + if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) + p = "systemd"; + else if (startswith(controller, "name=")) + p = controller + 5; + else + p = controller; + + if (path && suffix) + t = join("/sys/fs/cgroup/", p, "/", path, "/", suffix, NULL); + else if (path) + t = join("/sys/fs/cgroup/", p, "/", path, NULL); + else if (suffix) + t = join("/sys/fs/cgroup/", p, "/", suffix, NULL); + else + t = join("/sys/fs/cgroup/", p, NULL); + + if (!t) + return -ENOMEM; + + path_kill_slashes(t); + + *fs = t; + return 0; +} + +static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { + char *p; + bool is_sticky; + + if (typeflag != FTW_DP) + return 0; + + if (ftwbuf->level < 1) + return 0; + + p = strappend(path, "/tasks"); + if (!p) { + errno = ENOMEM; + return 1; + } + + is_sticky = file_is_priv_sticky(p) > 0; + free(p); + + if (is_sticky) + return 0; + + rmdir(path); + return 0; +} + +int cg_trim(const char *controller, const char *path, bool delete_root) { + char *fs; + int r = 0; + + assert(controller); + assert(path); + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + errno = 0; + if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) < 0) + r = errno ? -errno : -EIO; + + if (delete_root) { + bool is_sticky; + char *p; + + p = strappend(fs, "/tasks"); + if (!p) { + free(fs); + return -ENOMEM; + } + + is_sticky = file_is_priv_sticky(p) > 0; + free(p); + + if (!is_sticky) + if (rmdir(fs) < 0 && errno != ENOENT) { + if (r == 0) + r = -errno; + } + } + + free(fs); + + return r; +} + +int cg_delete(const char *controller, const char *path) { + char *parent; + int r; + + assert(controller); + assert(path); + + if ((r = parent_of_path(path, &parent)) < 0) + return r; + + r = cg_migrate_recursive(controller, path, parent, false, true); + free(parent); + + return r == -ENOENT ? 0 : r; +} + +int cg_create(const char *controller, const char *path) { + char *fs; + int r; + + assert(controller); + assert(path); + + if ((r = cg_get_path(controller, path, NULL, &fs)) < 0) + return r; + + r = mkdir_parents(fs, 0755); + + if (r >= 0) { + if (mkdir(fs, 0755) >= 0) + r = 1; + else if (errno == EEXIST) + r = 0; + else + r = -errno; + } + + free(fs); + + return r; +} + +int cg_attach(const char *controller, const char *path, pid_t pid) { + char *fs; + int r; + char c[32]; + + assert(controller); + assert(path); + assert(pid >= 0); + + if ((r = cg_get_path(controller, path, "tasks", &fs)) < 0) + return r; + + if (pid == 0) + pid = getpid(); + + snprintf(c, sizeof(c), "%lu\n", (unsigned long) pid); + char_array_0(c); + + r = write_one_line_file(fs, c); + free(fs); + + return r; +} + +int cg_create_and_attach(const char *controller, const char *path, pid_t pid) { + int r, q; + + assert(controller); + assert(path); + assert(pid >= 0); + + if ((r = cg_create(controller, path)) < 0) + return r; + + if ((q = cg_attach(controller, path, pid)) < 0) + return q; + + /* This does not remove the cgroup on failure */ + + return r; +} + +int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid) { + char *fs; + int r; + + assert(controller); + assert(path); + + if (mode != (mode_t) -1) + mode &= 0777; + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + r = chmod_and_chown(fs, mode, uid, gid); + free(fs); + + return r; +} + +int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid, int sticky) { + char *fs; + int r; + + assert(controller); + assert(path); + + if (mode == (mode_t) -1 && uid == (uid_t) -1 && gid == (gid_t) -1 && sticky < 0) + return 0; + + if (mode != (mode_t) -1) + mode &= 0666; + + r = cg_get_path(controller, path, "tasks", &fs); + if (r < 0) + return r; + + if (sticky >= 0 && mode != (mode_t) -1) + /* Both mode and sticky param are passed */ + mode |= (sticky ? S_ISVTX : 0); + else if ((sticky >= 0 && mode == (mode_t) -1) || + (mode != (mode_t) -1 && sticky < 0)) { + struct stat st; + + /* Only one param is passed, hence read the current + * mode from the file itself */ + + r = lstat(fs, &st); + if (r < 0) { + free(fs); + return -errno; + } + + if (mode == (mode_t) -1) + /* No mode set, we just shall set the sticky bit */ + mode = (st.st_mode & ~S_ISVTX) | (sticky ? S_ISVTX : 0); + else + /* Only mode set, leave sticky bit untouched */ + mode = (st.st_mode & ~0777) | mode; + } + + r = chmod_and_chown(fs, mode, uid, gid); + free(fs); + + return r; +} + +int cg_get_by_pid(const char *controller, pid_t pid, char **path) { + int r; + char *p = NULL; + FILE *f; + char *fs; + size_t cs; + + assert(controller); + assert(path); + assert(pid >= 0); + + if (pid == 0) + pid = getpid(); + + if (asprintf(&fs, "/proc/%lu/cgroup", (unsigned long) pid) < 0) + return -ENOMEM; + + f = fopen(fs, "re"); + free(fs); + + if (!f) + return errno == ENOENT ? -ESRCH : -errno; + + cs = strlen(controller); + + while (!feof(f)) { + char line[LINE_MAX]; + char *l; + + errno = 0; + if (!(fgets(line, sizeof(line), f))) { + if (feof(f)) + break; + + r = errno ? -errno : -EIO; + goto finish; + } + + truncate_nl(line); + + if (!(l = strchr(line, ':'))) + continue; + + l++; + if (strncmp(l, controller, cs) != 0) + continue; + + if (l[cs] != ':') + continue; + + if (!(p = strdup(l + cs + 1))) { + r = -ENOMEM; + goto finish; + } + + *path = p; + r = 0; + goto finish; + } + + r = -ENOENT; + +finish: + fclose(f); + + return r; +} + +int cg_install_release_agent(const char *controller, const char *agent) { + char *fs = NULL, *contents = NULL, *line = NULL, *sc; + int r; + + assert(controller); + assert(agent); + + if ((r = cg_get_path(controller, NULL, "release_agent", &fs)) < 0) + return r; + + if ((r = read_one_line_file(fs, &contents)) < 0) + goto finish; + + sc = strstrip(contents); + if (sc[0] == 0) { + + if (asprintf(&line, "%s\n", agent) < 0) { + r = -ENOMEM; + goto finish; + } + + if ((r = write_one_line_file(fs, line)) < 0) + goto finish; + + } else if (!streq(sc, agent)) { + r = -EEXIST; + goto finish; + } + + free(fs); + fs = NULL; + if ((r = cg_get_path(controller, NULL, "notify_on_release", &fs)) < 0) + goto finish; + + free(contents); + contents = NULL; + if ((r = read_one_line_file(fs, &contents)) < 0) + goto finish; + + sc = strstrip(contents); + + if (streq(sc, "0")) { + if ((r = write_one_line_file(fs, "1\n")) < 0) + goto finish; + + r = 1; + } else if (!streq(sc, "1")) { + r = -EIO; + goto finish; + } else + r = 0; + +finish: + free(fs); + free(contents); + free(line); + + return r; +} + +int cg_is_empty(const char *controller, const char *path, bool ignore_self) { + pid_t pid = 0; + int r; + FILE *f = NULL; + bool found = false; + + assert(controller); + assert(path); + + if ((r = cg_enumerate_tasks(controller, path, &f)) < 0) + return r == -ENOENT ? 1 : r; + + while ((r = cg_read_pid(f, &pid)) > 0) { + + if (ignore_self && pid == getpid()) + continue; + + found = true; + break; + } + + fclose(f); + + if (r < 0) + return r; + + return !found; +} + +int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self) { + int r; + DIR *d = NULL; + char *fn; + + assert(controller); + assert(path); + + if ((r = cg_is_empty(controller, path, ignore_self)) <= 0) + return r; + + if ((r = cg_enumerate_subgroups(controller, path, &d)) < 0) + return r == -ENOENT ? 1 : r; + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + char *p = NULL; + + r = asprintf(&p, "%s/%s", path, fn); + free(fn); + + if (r < 0) { + r = -ENOMEM; + goto finish; + } + + r = cg_is_empty_recursive(controller, p, ignore_self); + free(p); + + if (r <= 0) + goto finish; + } + + if (r >= 0) + r = 1; + +finish: + + if (d) + closedir(d); + + return r; +} + +int cg_split_spec(const char *spec, char **controller, char **path) { + const char *e; + char *t = NULL, *u = NULL; + + assert(spec); + assert(controller || path); + + if (*spec == '/') { + + if (path) { + if (!(t = strdup(spec))) + return -ENOMEM; + + *path = t; + } + + if (controller) + *controller = NULL; + + return 0; + } + + if (!(e = strchr(spec, ':'))) { + + if (strchr(spec, '/') || spec[0] == 0) + return -EINVAL; + + if (controller) { + if (!(t = strdup(spec))) + return -ENOMEM; + + *controller = t; + } + + if (path) + *path = NULL; + + return 0; + } + + if (e[1] != '/' || + e == spec || + memchr(spec, '/', e-spec)) + return -EINVAL; + + if (controller) + if (!(t = strndup(spec, e-spec))) + return -ENOMEM; + + if (path) + if (!(u = strdup(e+1))) { + free(t); + return -ENOMEM; + } + + if (controller) + *controller = t; + + if (path) + *path = u; + + return 0; +} + +int cg_join_spec(const char *controller, const char *path, char **spec) { + assert(controller); + assert(path); + + if (!path_is_absolute(path) || + controller[0] == 0 || + strchr(controller, ':') || + strchr(controller, '/')) + return -EINVAL; + + if (asprintf(spec, "%s:%s", controller, path) < 0) + return -ENOMEM; + + return 0; +} + +int cg_fix_path(const char *path, char **result) { + char *t, *c, *p; + int r; + + assert(path); + assert(result); + + /* First check if it already is a filesystem path */ + if (path_is_absolute(path) && + path_startswith(path, "/sys/fs/cgroup") && + access(path, F_OK) >= 0) { + + if (!(t = strdup(path))) + return -ENOMEM; + + *result = t; + return 0; + } + + /* Otherwise treat it as cg spec */ + if ((r = cg_split_spec(path, &c, &p)) < 0) + return r; + + r = cg_get_path(c ? c : SYSTEMD_CGROUP_CONTROLLER, p ? p : "/", NULL, result); + free(c); + free(p); + + return r; +} + +int cg_get_user_path(char **path) { + char *root, *p; + + assert(path); + + /* Figure out the place to put user cgroups below. We use the + * same as PID 1 has but with the "/system" suffix replaced by + * "/user" */ + + if (cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 1, &root) < 0) + p = strdup("/user"); + else { + if (endswith(root, "/system")) + root[strlen(root) - 7] = 0; + else if (streq(root, "/")) + root[0] = 0; + + p = strappend(root, "/user"); + free(root); + } + + if (!p) + return -ENOMEM; + + *path = p; + return 0; +} diff --git a/src/cgroup-util.h b/src/cgroup-util.h new file mode 100644 index 000000000..37e4255a9 --- /dev/null +++ b/src/cgroup-util.h @@ -0,0 +1,72 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foocgrouputilhfoo +#define foocgrouputilhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "set.h" +#include "def.h" + +int cg_enumerate_processes(const char *controller, const char *path, FILE **_f); +int cg_enumerate_tasks(const char *controller, const char *path, FILE **_f); +int cg_read_pid(FILE *f, pid_t *_pid); + +int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d); +int cg_read_subgroup(DIR *d, char **fn); + +int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s); +int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool remove, Set *s); +int cg_kill_recursive_and_wait(const char *controller, const char *path, bool remove); + +int cg_migrate(const char *controller, const char *from, const char *to, bool ignore_self); +int cg_migrate_recursive(const char *controller, const char *from, const char *to, bool ignore_self, bool remove); + +int cg_split_spec(const char *spec, char **controller, char **path); +int cg_join_spec(const char *controller, const char *path, char **spec); +int cg_fix_path(const char *path, char **result); + +int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs); +int cg_get_by_pid(const char *controller, pid_t pid, char **path); + +int cg_trim(const char *controller, const char *path, bool delete_root); + +int cg_rmdir(const char *controller, const char *path, bool honour_sticky); +int cg_delete(const char *controller, const char *path); + +int cg_create(const char *controller, const char *path); +int cg_attach(const char *controller, const char *path, pid_t pid); +int cg_create_and_attach(const char *controller, const char *path, pid_t pid); + +int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); +int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid, int sticky); + +int cg_install_release_agent(const char *controller, const char *agent); + +int cg_is_empty(const char *controller, const char *path, bool ignore_self); +int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self); + +int cg_get_user_path(char **path); + +#endif diff --git a/src/cgroup.c b/src/cgroup.c new file mode 100644 index 000000000..1f6139e25 --- /dev/null +++ b/src/cgroup.c @@ -0,0 +1,556 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "cgroup.h" +#include "cgroup-util.h" +#include "log.h" + +int cgroup_bonding_realize(CGroupBonding *b) { + int r; + + assert(b); + assert(b->path); + assert(b->controller); + + r = cg_create(b->controller, b->path); + if (r < 0) { + log_warning("Failed to create cgroup %s:%s: %s", b->controller, b->path, strerror(-r)); + return r; + } + + b->realized = true; + + return 0; +} + +int cgroup_bonding_realize_list(CGroupBonding *first) { + CGroupBonding *b; + int r; + + LIST_FOREACH(by_unit, b, first) + if ((r = cgroup_bonding_realize(b)) < 0 && b->essential) + return r; + + return 0; +} + +void cgroup_bonding_free(CGroupBonding *b, bool trim) { + assert(b); + + if (b->unit) { + CGroupBonding *f; + + LIST_REMOVE(CGroupBonding, by_unit, b->unit->cgroup_bondings, b); + + if (streq(b->controller, SYSTEMD_CGROUP_CONTROLLER)) { + assert_se(f = hashmap_get(b->unit->manager->cgroup_bondings, b->path)); + LIST_REMOVE(CGroupBonding, by_path, f, b); + + if (f) + hashmap_replace(b->unit->manager->cgroup_bondings, b->path, f); + else + hashmap_remove(b->unit->manager->cgroup_bondings, b->path); + } + } + + if (b->realized && b->ours && trim) + cg_trim(b->controller, b->path, false); + + free(b->controller); + free(b->path); + free(b); +} + +void cgroup_bonding_free_list(CGroupBonding *first, bool remove_or_trim) { + CGroupBonding *b, *n; + + LIST_FOREACH_SAFE(by_unit, b, n, first) + cgroup_bonding_free(b, remove_or_trim); +} + +void cgroup_bonding_trim(CGroupBonding *b, bool delete_root) { + assert(b); + + if (b->realized && b->ours) + cg_trim(b->controller, b->path, delete_root); +} + +void cgroup_bonding_trim_list(CGroupBonding *first, bool delete_root) { + CGroupBonding *b; + + LIST_FOREACH(by_unit, b, first) + cgroup_bonding_trim(b, delete_root); +} + +int cgroup_bonding_install(CGroupBonding *b, pid_t pid) { + int r; + + assert(b); + assert(pid >= 0); + + if ((r = cg_create_and_attach(b->controller, b->path, pid)) < 0) + return r; + + b->realized = true; + return 0; +} + +int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid) { + CGroupBonding *b; + int r; + + LIST_FOREACH(by_unit, b, first) + if ((r = cgroup_bonding_install(b, pid)) < 0 && b->essential) + return r; + + return 0; +} + +int cgroup_bonding_set_group_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid) { + assert(b); + + if (!b->realized) + return -EINVAL; + + return cg_set_group_access(b->controller, b->path, mode, uid, gid); +} + +int cgroup_bonding_set_group_access_list(CGroupBonding *first, mode_t mode, uid_t uid, gid_t gid) { + CGroupBonding *b; + int r; + + LIST_FOREACH(by_unit, b, first) { + r = cgroup_bonding_set_group_access(b, mode, uid, gid); + if (r < 0) + return r; + } + + return 0; +} + +int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky) { + assert(b); + + if (!b->realized) + return -EINVAL; + + return cg_set_task_access(b->controller, b->path, mode, uid, gid, sticky); +} + +int cgroup_bonding_set_task_access_list(CGroupBonding *first, mode_t mode, uid_t uid, gid_t gid, int sticky) { + CGroupBonding *b; + int r; + + LIST_FOREACH(by_unit, b, first) { + r = cgroup_bonding_set_task_access(b, mode, uid, gid, sticky); + if (r < 0) + return r; + } + + return 0; +} + +int cgroup_bonding_kill(CGroupBonding *b, int sig, bool sigcont, Set *s) { + assert(b); + assert(sig >= 0); + + /* Don't kill cgroups that aren't ours */ + if (!b->ours) + return 0; + + return cg_kill_recursive(b->controller, b->path, sig, sigcont, true, false, s); +} + +int cgroup_bonding_kill_list(CGroupBonding *first, int sig, bool sigcont, Set *s) { + CGroupBonding *b; + Set *allocated_set = NULL; + int ret = -EAGAIN, r; + + if (!first) + return 0; + + if (!s) + if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + LIST_FOREACH(by_unit, b, first) { + if ((r = cgroup_bonding_kill(b, sig, sigcont, s)) < 0) { + if (r == -EAGAIN || r == -ESRCH) + continue; + + ret = r; + goto finish; + } + + if (ret < 0 || r > 0) + ret = r; + } + +finish: + if (allocated_set) + set_free(allocated_set); + + return ret; +} + +/* Returns 1 if the group is empty, 0 if it is not, -EAGAIN if we + * cannot know */ +int cgroup_bonding_is_empty(CGroupBonding *b) { + int r; + + assert(b); + + if ((r = cg_is_empty_recursive(b->controller, b->path, true)) < 0) + return r; + + /* If it is empty it is empty */ + if (r > 0) + return 1; + + /* It's not only us using this cgroup, so we just don't know */ + return b->ours ? 0 : -EAGAIN; +} + +int cgroup_bonding_is_empty_list(CGroupBonding *first) { + CGroupBonding *b; + + LIST_FOREACH(by_unit, b, first) { + int r; + + if ((r = cgroup_bonding_is_empty(b)) < 0) { + /* If this returned -EAGAIN, then we don't know if the + * group is empty, so let's see if another group can + * tell us */ + + if (r != -EAGAIN) + return r; + } else + return r; + } + + return -EAGAIN; +} + +int manager_setup_cgroup(Manager *m) { + char *current = NULL, *path = NULL; + int r; + char suffix[32]; + + assert(m); + + /* 0. Be nice to Ingo Molnar #628004 */ + if (path_is_mount_point("/sys/fs/cgroup/systemd", false) <= 0) { + log_warning("No control group support available, not creating root group."); + return 0; + } + + /* 1. Determine hierarchy */ + if ((r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 0, ¤t)) < 0) { + log_error("Cannot determine cgroup we are running in: %s", strerror(-r)); + goto finish; + } + + if (m->running_as == MANAGER_SYSTEM) + strcpy(suffix, "/system"); + else { + snprintf(suffix, sizeof(suffix), "/systemd-%lu", (unsigned long) getpid()); + char_array_0(suffix); + } + + free(m->cgroup_hierarchy); + if (endswith(current, suffix)) { + /* We probably got reexecuted and can continue to use our root cgroup */ + m->cgroup_hierarchy = current; + current = NULL; + + } else { + /* We need a new root cgroup */ + m->cgroup_hierarchy = NULL; + if (asprintf(&m->cgroup_hierarchy, "%s%s", streq(current, "/") ? "" : current, suffix) < 0) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + } + + /* 2. Show data */ + if ((r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy, NULL, &path)) < 0) { + log_error("Cannot find cgroup mount point: %s", strerror(-r)); + goto finish; + } + + log_debug("Using cgroup controller " SYSTEMD_CGROUP_CONTROLLER ". File system hierarchy is at %s.", path); + + /* 3. Install agent */ + if ((r = cg_install_release_agent(SYSTEMD_CGROUP_CONTROLLER, SYSTEMD_CGROUP_AGENT_PATH)) < 0) + log_warning("Failed to install release agent, ignoring: %s", strerror(-r)); + else if (r > 0) + log_debug("Installed release agent."); + else + log_debug("Release agent already installed."); + + /* 4. Realize the group */ + if ((r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy, 0)) < 0) { + log_error("Failed to create root cgroup hierarchy: %s", strerror(-r)); + goto finish; + } + + /* 5. And pin it, so that it cannot be unmounted */ + if (m->pin_cgroupfs_fd >= 0) + close_nointr_nofail(m->pin_cgroupfs_fd); + + if ((m->pin_cgroupfs_fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY|O_NONBLOCK)) < 0) { + log_error("Failed to open pin file: %m"); + r = -errno; + goto finish; + } + + log_debug("Created root group."); + +finish: + free(current); + free(path); + + return r; +} + +void manager_shutdown_cgroup(Manager *m, bool delete) { + assert(m); + + if (delete && m->cgroup_hierarchy) + cg_delete(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy); + + if (m->pin_cgroupfs_fd >= 0) { + close_nointr_nofail(m->pin_cgroupfs_fd); + m->pin_cgroupfs_fd = -1; + } + + free(m->cgroup_hierarchy); + m->cgroup_hierarchy = NULL; +} + +int cgroup_bonding_get(Manager *m, const char *cgroup, CGroupBonding **bonding) { + CGroupBonding *b; + char *p; + + assert(m); + assert(cgroup); + assert(bonding); + + b = hashmap_get(m->cgroup_bondings, cgroup); + if (b) { + *bonding = b; + return 1; + } + + p = strdup(cgroup); + if (!p) + return -ENOMEM; + + for (;;) { + char *e; + + e = strrchr(p, '/'); + if (!e || e == p) { + free(p); + *bonding = NULL; + return 0; + } + + *e = 0; + + b = hashmap_get(m->cgroup_bondings, p); + if (b) { + free(p); + *bonding = b; + return 1; + } + } +} + +int cgroup_notify_empty(Manager *m, const char *group) { + CGroupBonding *l, *b; + int r; + + assert(m); + assert(group); + + r = cgroup_bonding_get(m, group, &l); + if (r <= 0) + return r; + + LIST_FOREACH(by_path, b, l) { + int t; + + if (!b->unit) + continue; + + t = cgroup_bonding_is_empty_list(b); + if (t < 0) { + + /* If we don't know, we don't know */ + if (t != -EAGAIN) + log_warning("Failed to check whether cgroup is empty: %s", strerror(errno)); + + continue; + } + + if (t > 0) { + /* If it is empty, let's delete it */ + cgroup_bonding_trim_list(b->unit->cgroup_bondings, true); + + if (UNIT_VTABLE(b->unit)->cgroup_notify_empty) + UNIT_VTABLE(b->unit)->cgroup_notify_empty(b->unit); + } + } + + return 0; +} + +Unit* cgroup_unit_by_pid(Manager *m, pid_t pid) { + CGroupBonding *l, *b; + char *group = NULL; + + assert(m); + + if (pid <= 1) + return NULL; + + if (cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, pid, &group) < 0) + return NULL; + + l = hashmap_get(m->cgroup_bondings, group); + + if (!l) { + char *slash; + + while ((slash = strrchr(group, '/'))) { + if (slash == group) + break; + + *slash = 0; + + if ((l = hashmap_get(m->cgroup_bondings, group))) + break; + } + } + + free(group); + + LIST_FOREACH(by_path, b, l) { + + if (!b->unit) + continue; + + if (b->ours) + return b->unit; + } + + return NULL; +} + +CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller) { + CGroupBonding *b; + + assert(controller); + + LIST_FOREACH(by_unit, b, first) + if (streq(b->controller, controller)) + return b; + + return NULL; +} + +char *cgroup_bonding_to_string(CGroupBonding *b) { + char *r; + + assert(b); + + if (asprintf(&r, "%s:%s", b->controller, b->path) < 0) + return NULL; + + return r; +} + +pid_t cgroup_bonding_search_main_pid(CGroupBonding *b) { + FILE *f; + pid_t pid = 0, npid, mypid; + + assert(b); + + if (!b->ours) + return 0; + + if (cg_enumerate_processes(b->controller, b->path, &f) < 0) + return 0; + + mypid = getpid(); + + while (cg_read_pid(f, &npid) > 0) { + pid_t ppid; + + if (npid == pid) + continue; + + /* Ignore processes that aren't our kids */ + if (get_parent_of_pid(npid, &ppid) >= 0 && ppid != mypid) + continue; + + if (pid != 0) { + /* Dang, there's more than one daemonized PID + in this group, so we don't know what process + is the main process. */ + pid = 0; + break; + } + + pid = npid; + } + + fclose(f); + + return pid; +} + +pid_t cgroup_bonding_search_main_pid_list(CGroupBonding *first) { + CGroupBonding *b; + pid_t pid; + + /* Try to find a main pid from this cgroup, but checking if + * there's only one PID in the cgroup and returning it. Later + * on we might want to add additional, smarter heuristics + * here. */ + + LIST_FOREACH(by_unit, b, first) + if ((pid = cgroup_bonding_search_main_pid(b)) != 0) + return pid; + + return 0; + +} diff --git a/src/cgroup.h b/src/cgroup.h new file mode 100644 index 000000000..5faa7dc0f --- /dev/null +++ b/src/cgroup.h @@ -0,0 +1,94 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foocgrouphfoo +#define foocgrouphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct CGroupBonding CGroupBonding; + +#include "unit.h" + +/* Binds a cgroup to a name */ +struct CGroupBonding { + char *controller; + char *path; + + Unit *unit; + + /* For the Unit::cgroup_bondings list */ + LIST_FIELDS(CGroupBonding, by_unit); + + /* For the Manager::cgroup_bondings hashmap */ + LIST_FIELDS(CGroupBonding, by_path); + + /* When shutting down, remove cgroup? Are our own tasks the + * only ones in this group?*/ + bool ours:1; + + /* If we cannot create this group, or add a process to it, is this fatal? */ + bool essential:1; + + /* This cgroup is realized */ + bool realized:1; +}; + +int cgroup_bonding_realize(CGroupBonding *b); +int cgroup_bonding_realize_list(CGroupBonding *first); + +void cgroup_bonding_free(CGroupBonding *b, bool trim); +void cgroup_bonding_free_list(CGroupBonding *first, bool trim); + +int cgroup_bonding_install(CGroupBonding *b, pid_t pid); +int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid); + +int cgroup_bonding_set_group_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid); +int cgroup_bonding_set_group_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid); + +int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky); +int cgroup_bonding_set_task_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky); + +int cgroup_bonding_kill(CGroupBonding *b, int sig, bool sigcont, Set *s); +int cgroup_bonding_kill_list(CGroupBonding *first, int sig, bool sigcont, Set *s); + +void cgroup_bonding_trim(CGroupBonding *first, bool delete_root); +void cgroup_bonding_trim_list(CGroupBonding *first, bool delete_root); + +int cgroup_bonding_is_empty(CGroupBonding *b); +int cgroup_bonding_is_empty_list(CGroupBonding *first); + +CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller); + +char *cgroup_bonding_to_string(CGroupBonding *b); + +pid_t cgroup_bonding_search_main_pid(CGroupBonding *b); +pid_t cgroup_bonding_search_main_pid_list(CGroupBonding *b); + +#include "manager.h" + +int manager_setup_cgroup(Manager *m); +void manager_shutdown_cgroup(Manager *m, bool delete); + +int cgroup_bonding_get(Manager *m, const char *cgroup, CGroupBonding **bonding); +int cgroup_notify_empty(Manager *m, const char *group); + +Unit* cgroup_unit_by_pid(Manager *m, pid_t pid); + +#endif diff --git a/src/cgroups-agent.c b/src/cgroups-agent.c new file mode 100644 index 000000000..1bbc8827d --- /dev/null +++ b/src/cgroups-agent.c @@ -0,0 +1,101 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include + +#include "log.h" +#include "dbus-common.h" + +int main(int argc, char *argv[]) { + DBusError error; + DBusConnection *bus = NULL; + DBusMessage *m = NULL; + int r = EXIT_FAILURE; + + dbus_error_init(&error); + + if (argc != 2) { + log_error("Incorrect number of arguments."); + goto finish; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + /* We send this event to the private D-Bus socket and then the + * system instance will forward this to the system bus. We do + * this to avoid an activation loop when we start dbus when we + * are called when the dbus service is shut down. */ + + if (!(bus = dbus_connection_open_private("unix:path=/run/systemd/private", &error))) { +#ifndef LEGACY + dbus_error_free(&error); + + /* Retry with the pre v21 socket name, to ease upgrades */ + if (!(bus = dbus_connection_open_private("unix:abstract=/org/freedesktop/systemd1/private", &error))) { +#endif + log_error("Failed to get D-Bus connection: %s", bus_error_message(&error)); + goto finish; + } +#ifndef LEGACY + } +#endif + + if (bus_check_peercred(bus) < 0) { + log_error("Bus owner not root."); + goto finish; + } + + if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1/agent", "org.freedesktop.systemd1.Agent", "Released"))) { + log_error("Could not allocate signal message."); + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &argv[1], + DBUS_TYPE_INVALID)) { + log_error("Could not attach group information to signal message."); + goto finish; + } + + if (!dbus_connection_send(bus, m, NULL)) { + log_error("Failed to send signal message on private connection."); + goto finish; + } + + r = EXIT_SUCCESS; + +finish: + if (bus) { + dbus_connection_flush(bus); + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + return r; +} diff --git a/src/cgtop.c b/src/cgtop.c new file mode 100644 index 000000000..8b8617dc1 --- /dev/null +++ b/src/cgtop.c @@ -0,0 +1,729 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "hashmap.h" +#include "cgroup-util.h" + +typedef struct Group { + char *path; + + bool n_tasks_valid:1; + bool cpu_valid:1; + bool memory_valid:1; + bool io_valid:1; + + unsigned n_tasks; + + unsigned cpu_iteration; + uint64_t cpu_usage; + struct timespec cpu_timestamp; + double cpu_fraction; + + uint64_t memory; + + unsigned io_iteration; + uint64_t io_input, io_output; + struct timespec io_timestamp; + uint64_t io_input_bps, io_output_bps; +} Group; + +static unsigned arg_depth = 2; +static usec_t arg_delay = 1*USEC_PER_SEC; + +static enum { + ORDER_PATH, + ORDER_TASKS, + ORDER_CPU, + ORDER_MEMORY, + ORDER_IO +} arg_order = ORDER_CPU; + +static void group_free(Group *g) { + assert(g); + + free(g->path); + free(g); +} + +static void group_hashmap_clear(Hashmap *h) { + Group *g; + + while ((g = hashmap_steal_first(h))) + group_free(g); +} + +static void group_hashmap_free(Hashmap *h) { + group_hashmap_clear(h); + hashmap_free(h); +} + +static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) { + Group *g; + int r; + FILE *f; + pid_t pid; + unsigned n; + + assert(controller); + assert(path); + assert(a); + + g = hashmap_get(a, path); + if (!g) { + g = hashmap_get(b, path); + if (!g) { + g = new0(Group, 1); + if (!g) + return -ENOMEM; + + g->path = strdup(path); + if (!g->path) { + group_free(g); + return -ENOMEM; + } + + r = hashmap_put(a, g->path, g); + if (r < 0) { + group_free(g); + return r; + } + } else { + assert_se(hashmap_move_one(a, b, path) == 0); + g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false; + } + } + + /* Regardless which controller, let's find the maximum number + * of processes in any of it */ + + r = cg_enumerate_tasks(controller, path, &f); + if (r < 0) + return r; + + n = 0; + while (cg_read_pid(f, &pid) > 0) + n++; + fclose(f); + + if (n > 0) { + if (g->n_tasks_valid) + g->n_tasks = MAX(g->n_tasks, n); + else + g->n_tasks = n; + + g->n_tasks_valid = true; + } + + if (streq(controller, "cpuacct")) { + uint64_t new_usage; + char *p, *v; + struct timespec ts; + + r = cg_get_path(controller, path, "cpuacct.usage", &p); + if (r < 0) + return r; + + r = read_one_line_file(p, &v); + free(p); + if (r < 0) + return r; + + r = safe_atou64(v, &new_usage); + free(v); + if (r < 0) + return r; + + assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); + + if (g->cpu_iteration == iteration - 1) { + uint64_t x, y; + + x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) - + ((uint64_t) g->cpu_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->cpu_timestamp.tv_nsec); + + y = new_usage - g->cpu_usage; + + if (y > 0) { + g->cpu_fraction = (double) y / (double) x; + g->cpu_valid = true; + } + } + + g->cpu_usage = new_usage; + g->cpu_timestamp = ts; + g->cpu_iteration = iteration; + + } else if (streq(controller, "memory")) { + char *p, *v; + + r = cg_get_path(controller, path, "memory.usage_in_bytes", &p); + if (r < 0) + return r; + + r = read_one_line_file(p, &v); + free(p); + if (r < 0) + return r; + + r = safe_atou64(v, &g->memory); + free(v); + if (r < 0) + return r; + + if (g->memory > 0) + g->memory_valid = true; + + } else if (streq(controller, "blkio")) { + char *p; + uint64_t wr = 0, rd = 0; + struct timespec ts; + + r = cg_get_path(controller, path, "blkio.io_service_bytes", &p); + if (r < 0) + return r; + + f = fopen(p, "re"); + free(p); + + if (!f) + return -errno; + + for (;;) { + char line[LINE_MAX], *l; + uint64_t k, *q; + + if (!fgets(line, sizeof(line), f)) + break; + + l = strstrip(line); + l += strcspn(l, WHITESPACE); + l += strspn(l, WHITESPACE); + + if (first_word(l, "Read")) { + l += 4; + q = &rd; + } else if (first_word(l, "Write")) { + l += 5; + q = ≀ + } else + continue; + + l += strspn(l, WHITESPACE); + r = safe_atou64(l, &k); + if (r < 0) + continue; + + *q += k; + } + + fclose(f); + + assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); + + if (g->io_iteration == iteration - 1) { + uint64_t x, yr, yw; + + x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) - + ((uint64_t) g->io_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->io_timestamp.tv_nsec); + + yr = rd - g->io_input; + yw = wr - g->io_output; + + if (yr > 0 || yw > 0) { + g->io_input_bps = (yr * 1000000000ULL) / x; + g->io_output_bps = (yw * 1000000000ULL) / x; + g->io_valid = true; + + } + } + + g->io_input = rd; + g->io_output = wr; + g->io_timestamp = ts; + g->io_iteration = iteration; + } + + return 0; +} + +static int refresh_one( + const char *controller, + const char *path, + Hashmap *a, + Hashmap *b, + unsigned iteration, + unsigned depth) { + + DIR *d = NULL; + int r; + + assert(controller); + assert(path); + assert(a); + + if (depth > arg_depth) + return 0; + + r = process(controller, path, a, b, iteration); + if (r < 0) + return r; + + r = cg_enumerate_subgroups(controller, path, &d); + if (r < 0) { + if (r == ENOENT) + return 0; + + return r; + } + + for (;;) { + char *fn, *p; + + r = cg_read_subgroup(d, &fn); + if (r <= 0) + goto finish; + + p = join(path, "/", fn, NULL); + free(fn); + + if (!p) { + r = -ENOMEM; + goto finish; + } + + path_kill_slashes(p); + + r = refresh_one(controller, p, a, b, iteration, depth + 1); + free(p); + + if (r < 0) + goto finish; + } + +finish: + if (d) + closedir(d); + + return r; +} + +static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) { + int r; + + assert(a); + + r = refresh_one("name=systemd", "/", a, b, iteration, 0); + if (r < 0) + return r; + + r = refresh_one("cpuacct", "/", a, b, iteration, 0); + if (r < 0) + return r; + + r = refresh_one("memory", "/", a, b, iteration, 0); + if (r < 0) + return r; + + return refresh_one("blkio", "/", a, b, iteration, 0); +} + +static int group_compare(const void*a, const void *b) { + const Group *x = *(Group**)a, *y = *(Group**)b; + + if (path_startswith(y->path, x->path)) + return -1; + if (path_startswith(x->path, y->path)) + return 1; + + if (arg_order == ORDER_CPU) { + if (x->cpu_valid && y->cpu_valid) { + + if (x->cpu_fraction > y->cpu_fraction) + return -1; + else if (x->cpu_fraction < y->cpu_fraction) + return 1; + } else if (x->cpu_valid) + return -1; + else if (y->cpu_valid) + return 1; + } + + if (arg_order == ORDER_TASKS) { + + if (x->n_tasks_valid && y->n_tasks_valid) { + if (x->n_tasks > y->n_tasks) + return -1; + else if (x->n_tasks < y->n_tasks) + return 1; + } else if (x->n_tasks_valid) + return -1; + else if (y->n_tasks_valid) + return 1; + } + + if (arg_order == ORDER_MEMORY) { + if (x->memory_valid && y->memory_valid) { + if (x->memory > y->memory) + return -1; + else if (x->memory < y->memory) + return 1; + } else if (x->memory_valid) + return -1; + else if (y->memory_valid) + return 1; + } + + if (arg_order == ORDER_IO) { + if (x->io_valid && y->io_valid) { + if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps) + return -1; + else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps) + return 1; + } else if (x->io_valid) + return -1; + else if (y->io_valid) + return 1; + } + + return strcmp(x->path, y->path); +} + +static int display(Hashmap *a) { + Iterator i; + Group *g; + Group **array; + unsigned rows, n = 0, j; + + assert(a); + + /* Set cursor to top left corner and clear screen */ + fputs("\033[H" + "\033[2J", stdout); + + array = alloca(sizeof(Group*) * hashmap_size(a)); + + HASHMAP_FOREACH(g, a, i) + if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid) + array[n++] = g; + + qsort(array, n, sizeof(Group*), group_compare); + + rows = fd_lines(STDOUT_FILENO); + if (rows <= 0) + rows = 25; + + printf("%s%-37s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n", + arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_ON : "", "Path", arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_OFF : "", + arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_ON : "", "Tasks", arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_OFF : "", + arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_ON : "", "%CPU", arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_OFF : "", + arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory", arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "", + arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Input/s", arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "", + arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Output/s", arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : ""); + + for (j = 0; j < n; j++) { + char *p; + char m[FORMAT_BYTES_MAX]; + + if (j + 5 > rows) + break; + + g = array[j]; + + p = ellipsize(g->path, 37, 33); + printf("%-37s", p ? p : g->path); + free(p); + + if (g->n_tasks_valid) + printf(" %7u", g->n_tasks); + else + fputs(" -", stdout); + + if (g->cpu_valid) + printf(" %6.1f", g->cpu_fraction*100); + else + fputs(" -", stdout); + + if (g->memory_valid) + printf(" %8s", format_bytes(m, sizeof(m), g->memory)); + else + fputs(" -", stdout); + + if (g->io_valid) { + printf(" %8s", + format_bytes(m, sizeof(m), g->io_input_bps)); + printf(" %8s", + format_bytes(m, sizeof(m), g->io_output_bps)); + } else + fputs(" - -", stdout); + + putchar('\n'); + } + + return 0; +} + +static void help(void) { + + printf("%s [OPTIONS...]\n\n" + "Show top control groups by their resource usage.\n\n" + " -h --help Show this help\n" + " -p Order by path\n" + " -t Order by number of tasks\n" + " -c Order by CPU load\n" + " -m Order by memory load\n" + " -i Order by IO load\n" + " -d --delay=DELAY Specify delay\n" + " --depth=DEPTH Maximum traversal depth (default: 2)\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_DEPTH = 0x100 + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "delay", required_argument, NULL, 'd' }, + { "depth", required_argument, NULL, ARG_DEPTH }, + { NULL, 0, NULL, 0 } + }; + + int c; + int r; + + assert(argc >= 1); + assert(argv); + + while ((c = getopt_long(argc, argv, "hptcmid:", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_DEPTH: + r = safe_atou(optarg, &arg_depth); + if (r < 0) { + log_error("Failed to parse depth parameter."); + return -EINVAL; + } + + break; + + case 'd': + r = parse_usec(optarg, &arg_delay); + if (r < 0 || arg_delay <= 0) { + log_error("Failed to parse delay parameter."); + return -EINVAL; + } + + break; + + case 'p': + arg_order = ORDER_PATH; + break; + + case 't': + arg_order = ORDER_TASKS; + break; + + case 'c': + arg_order = ORDER_CPU; + break; + + case 'm': + arg_order = ORDER_MEMORY; + break; + + case 'i': + arg_order = ORDER_IO; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (optind < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r; + Hashmap *a = NULL, *b = NULL; + unsigned iteration = 0; + usec_t last_refresh = 0; + bool quit = false, immediate_refresh = false; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + a = hashmap_new(string_hash_func, string_compare_func); + b = hashmap_new(string_hash_func, string_compare_func); + if (!a || !b) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + while (!quit) { + Hashmap *c; + usec_t t; + char key; + char h[FORMAT_TIMESPAN_MAX]; + + t = now(CLOCK_MONOTONIC); + + if (t >= last_refresh + arg_delay || immediate_refresh) { + + r = refresh(a, b, iteration++); + if (r < 0) + goto finish; + + group_hashmap_clear(b); + + c = a; + a = b; + b = c; + + last_refresh = t; + immediate_refresh = false; + } + + r = display(b); + if (r < 0) + goto finish; + + r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL); + if (r == -ETIMEDOUT) + continue; + if (r < 0) { + log_error("Couldn't read key: %s", strerror(-r)); + goto finish; + } + + fputs("\r \r", stdout); + fflush(stdout); + + switch (key) { + + case ' ': + immediate_refresh = true; + break; + + case 'q': + quit = true; + break; + + case 'p': + arg_order = ORDER_PATH; + break; + + case 't': + arg_order = ORDER_TASKS; + break; + + case 'c': + arg_order = ORDER_CPU; + break; + + case 'm': + arg_order = ORDER_MEMORY; + break; + + case 'i': + arg_order = ORDER_IO; + break; + + case '+': + if (arg_delay < USEC_PER_SEC) + arg_delay += USEC_PER_MSEC*250; + else + arg_delay += USEC_PER_SEC; + + fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay)); + fflush(stdout); + sleep(1); + break; + + case '-': + if (arg_delay <= USEC_PER_MSEC*500) + arg_delay = USEC_PER_MSEC*250; + else if (arg_delay < USEC_PER_MSEC*1250) + arg_delay -= USEC_PER_MSEC*250; + else + arg_delay -= USEC_PER_SEC; + + fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay)); + fflush(stdout); + sleep(1); + break; + + case '?': + case 'h': + fprintf(stdout, + "\t<" ANSI_HIGHLIGHT_ON "P" ANSI_HIGHLIGHT_OFF "> By path; <" ANSI_HIGHLIGHT_ON "T" ANSI_HIGHLIGHT_OFF "> By tasks; <" ANSI_HIGHLIGHT_ON "C" ANSI_HIGHLIGHT_OFF "> By CPU; <" ANSI_HIGHLIGHT_ON "M" ANSI_HIGHLIGHT_OFF "> By memory; <" ANSI_HIGHLIGHT_ON "I" ANSI_HIGHLIGHT_OFF "> By I/O\n" + "\t<" ANSI_HIGHLIGHT_ON "Q" ANSI_HIGHLIGHT_OFF "> Quit; <" ANSI_HIGHLIGHT_ON "+" ANSI_HIGHLIGHT_OFF "> Increase delay; <" ANSI_HIGHLIGHT_ON "-" ANSI_HIGHLIGHT_OFF "> Decrease delay; <" ANSI_HIGHLIGHT_ON "SPACE" ANSI_HIGHLIGHT_OFF "> Refresh"); + fflush(stdout); + sleep(3); + break; + + default: + fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key); + fflush(stdout); + sleep(1); + break; + } + } + + log_info("Exiting."); + + r = 0; + +finish: + group_hashmap_free(a); + group_hashmap_free(b); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/condition.c b/src/condition.c new file mode 100644 index 000000000..2b51a16f1 --- /dev/null +++ b/src/condition.c @@ -0,0 +1,323 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#ifdef HAVE_SELINUX +#include +#endif + +#include "util.h" +#include "condition.h" +#include "virt.h" + +Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) { + Condition *c; + + assert(type < _CONDITION_TYPE_MAX); + + c = new0(Condition, 1); + if (!c) + return NULL; + + c->type = type; + c->trigger = trigger; + c->negate = negate; + + if (parameter) { + c->parameter = strdup(parameter); + if (!c->parameter) { + free(c); + return NULL; + } + } + + return c; +} + +void condition_free(Condition *c) { + assert(c); + + free(c->parameter); + free(c); +} + +void condition_free_list(Condition *first) { + Condition *c, *n; + + LIST_FOREACH_SAFE(conditions, c, n, first) + condition_free(c); +} + +static bool test_kernel_command_line(const char *parameter) { + char *line, *w, *state, *word = NULL; + bool equal; + int r; + size_t l, pl; + bool found = false; + + assert(parameter); + + if (detect_container(NULL) > 0) + return false; + + r = read_one_line_file("/proc/cmdline", &line); + if (r < 0) { + log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r)); + return false; + } + + equal = !!strchr(parameter, '='); + pl = strlen(parameter); + + FOREACH_WORD_QUOTED(w, l, line, state) { + + free(word); + word = strndup(w, l); + if (!word) + break; + + if (equal) { + if (streq(word, parameter)) { + found = true; + break; + } + } else { + if (startswith(word, parameter) && (word[pl] == '=' || word[pl] == 0)) { + found = true; + break; + } + } + + } + + free(word); + free(line); + + return found; +} + +static bool test_virtualization(const char *parameter) { + int b; + Virtualization v; + const char *id; + + assert(parameter); + + v = detect_virtualization(&id); + if (v < 0) { + log_warning("Failed to detect virtualization, ignoring: %s", strerror(-v)); + return false; + } + + /* First, compare with yes/no */ + b = parse_boolean(parameter); + + if (v > 0 && b > 0) + return true; + + if (v == 0 && b == 0) + return true; + + /* Then, compare categorization */ + if (v == VIRTUALIZATION_VM && streq(parameter, "vm")) + return true; + + if (v == VIRTUALIZATION_CONTAINER && streq(parameter, "container")) + return true; + + /* Finally compare id */ + return v > 0 && streq(parameter, id); +} + +static bool test_security(const char *parameter) { +#ifdef HAVE_SELINUX + if (streq(parameter, "selinux")) + return is_selinux_enabled() > 0; +#endif + return false; +} + +static bool test_capability(const char *parameter) { + cap_value_t value; + FILE *f; + char line[LINE_MAX]; + unsigned long long capabilities = (unsigned long long) -1; + + /* If it's an invalid capability, we don't have it */ + + if (cap_from_name(parameter, &value) < 0) + return false; + + /* If it's a valid capability we default to assume + * that we have it */ + + f = fopen("/proc/self/status", "re"); + if (!f) + return true; + + while (fgets(line, sizeof(line), f)) { + truncate_nl(line); + + if (startswith(line, "CapBnd:")) { + (void) sscanf(line+7, "%llx", &capabilities); + break; + } + } + + fclose(f); + + return !!(capabilities & (1ULL << value)); +} + +bool condition_test(Condition *c) { + assert(c); + + switch(c->type) { + + case CONDITION_PATH_EXISTS: + return (access(c->parameter, F_OK) >= 0) == !c->negate; + + case CONDITION_PATH_EXISTS_GLOB: + return (glob_exists(c->parameter) > 0) == !c->negate; + + case CONDITION_PATH_IS_DIRECTORY: { + struct stat st; + + if (stat(c->parameter, &st) < 0) + return c->negate; + return S_ISDIR(st.st_mode) == !c->negate; + } + + case CONDITION_PATH_IS_SYMBOLIC_LINK: { + struct stat st; + + if (lstat(c->parameter, &st) < 0) + return c->negate; + return S_ISLNK(st.st_mode) == !c->negate; + } + + case CONDITION_PATH_IS_MOUNT_POINT: + return (path_is_mount_point(c->parameter, true) > 0) == !c->negate; + + case CONDITION_DIRECTORY_NOT_EMPTY: { + int k; + + k = dir_is_empty(c->parameter); + return !(k == -ENOENT || k > 0) == !c->negate; + } + + case CONDITION_FILE_IS_EXECUTABLE: { + struct stat st; + + if (stat(c->parameter, &st) < 0) + return c->negate; + + return (S_ISREG(st.st_mode) && (st.st_mode & 0111)) == !c->negate; + } + + case CONDITION_KERNEL_COMMAND_LINE: + return test_kernel_command_line(c->parameter) == !c->negate; + + case CONDITION_VIRTUALIZATION: + return test_virtualization(c->parameter) == !c->negate; + + case CONDITION_SECURITY: + return test_security(c->parameter) == !c->negate; + + case CONDITION_CAPABILITY: + return test_capability(c->parameter) == !c->negate; + + case CONDITION_NULL: + return !c->negate; + + default: + assert_not_reached("Invalid condition type."); + } +} + +bool condition_test_list(Condition *first) { + Condition *c; + int triggered = -1; + + /* If the condition list is empty, then it is true */ + if (!first) + return true; + + /* Otherwise, if all of the non-trigger conditions apply and + * if any of the trigger conditions apply (unless there are + * none) we return true */ + LIST_FOREACH(conditions, c, first) { + bool b; + + b = condition_test(c); + + if (!c->trigger && !b) + return false; + + if (c->trigger && triggered <= 0) + triggered = b; + } + + return triggered != 0; +} + +void condition_dump(Condition *c, FILE *f, const char *prefix) { + assert(c); + assert(f); + + if (!prefix) + prefix = ""; + + fprintf(f, + "%s\t%s: %s%s%s\n", + prefix, + condition_type_to_string(c->type), + c->trigger ? "|" : "", + c->negate ? "!" : "", + c->parameter); +} + +void condition_dump_list(Condition *first, FILE *f, const char *prefix) { + Condition *c; + + LIST_FOREACH(conditions, c, first) + condition_dump(c, f, prefix); +} + +static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { + [CONDITION_PATH_EXISTS] = "ConditionPathExists", + [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob", + [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory", + [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink", + [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint", + [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty", + [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", + [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", + [CONDITION_SECURITY] = "ConditionSecurity", + [CONDITION_NULL] = "ConditionNull" +}; + +DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType); diff --git a/src/condition.h b/src/condition.h new file mode 100644 index 000000000..71b1c6761 --- /dev/null +++ b/src/condition.h @@ -0,0 +1,69 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooconditionhfoo +#define fooconditionhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "list.h" + +typedef enum ConditionType { + CONDITION_PATH_EXISTS, + CONDITION_PATH_EXISTS_GLOB, + CONDITION_PATH_IS_DIRECTORY, + CONDITION_PATH_IS_SYMBOLIC_LINK, + CONDITION_PATH_IS_MOUNT_POINT, + CONDITION_DIRECTORY_NOT_EMPTY, + CONDITION_FILE_IS_EXECUTABLE, + CONDITION_KERNEL_COMMAND_LINE, + CONDITION_VIRTUALIZATION, + CONDITION_SECURITY, + CONDITION_CAPABILITY, + CONDITION_NULL, + _CONDITION_TYPE_MAX, + _CONDITION_TYPE_INVALID = -1 +} ConditionType; + +typedef struct Condition { + ConditionType type; + char *parameter; + + bool trigger:1; + bool negate:1; + + LIST_FIELDS(struct Condition, conditions); +} Condition; + +Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate); +void condition_free(Condition *c); +void condition_free_list(Condition *c); + +bool condition_test(Condition *c); +bool condition_test_list(Condition *c); + +void condition_dump(Condition *c, FILE *f, const char *prefix); +void condition_dump_list(Condition *c, FILE *f, const char *prefix); + +const char* condition_type_to_string(ConditionType t); +int condition_type_from_string(const char *s); + +#endif diff --git a/src/conf-parser.c b/src/conf-parser.c new file mode 100644 index 000000000..a9b01135e --- /dev/null +++ b/src/conf-parser.c @@ -0,0 +1,852 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "conf-parser.h" +#include "util.h" +#include "macro.h" +#include "strv.h" +#include "log.h" +#include "utf8.h" + +int config_item_table_lookup( + void *table, + const char *section, + const char *lvalue, + ConfigParserCallback *func, + int *ltype, + void **data, + void *userdata) { + + ConfigTableItem *t; + + assert(table); + assert(lvalue); + assert(func); + assert(ltype); + assert(data); + + for (t = table; t->lvalue; t++) { + + if (!streq(lvalue, t->lvalue)) + continue; + + if (!streq_ptr(section, t->section)) + continue; + + *func = t->parse; + *ltype = t->ltype; + *data = t->data; + return 1; + } + + return 0; +} + +int config_item_perf_lookup( + void *table, + const char *section, + const char *lvalue, + ConfigParserCallback *func, + int *ltype, + void **data, + void *userdata) { + + ConfigPerfItemLookup lookup = (ConfigPerfItemLookup) table; + const ConfigPerfItem *p; + + assert(table); + assert(lvalue); + assert(func); + assert(ltype); + assert(data); + + if (!section) + p = lookup(lvalue, strlen(lvalue)); + else { + char *key; + + key = join(section, ".", lvalue, NULL); + if (!key) + return -ENOMEM; + + p = lookup(key, strlen(key)); + free(key); + } + + if (!p) + return 0; + + *func = p->parse; + *ltype = p->ltype; + *data = (uint8_t*) userdata + p->offset; + return 1; +} + +/* Run the user supplied parser for an assignment */ +static int next_assignment( + const char *filename, + unsigned line, + ConfigItemLookup lookup, + void *table, + const char *section, + const char *lvalue, + const char *rvalue, + bool relaxed, + void *userdata) { + + ConfigParserCallback func = NULL; + int ltype = 0; + void *data = NULL; + int r; + + assert(filename); + assert(line > 0); + assert(lookup); + assert(lvalue); + assert(rvalue); + + r = lookup(table, section, lvalue, &func, <ype, &data, userdata); + if (r < 0) + return r; + + if (r > 0) { + if (func) + return func(filename, line, section, lvalue, ltype, rvalue, data, userdata); + + return 0; + } + + /* Warn about unknown non-extension fields. */ + if (!relaxed && !startswith(lvalue, "X-")) + log_info("[%s:%u] Unknown lvalue '%s' in section '%s'. Ignoring.", filename, line, lvalue, section); + + return 0; +} + +/* Parse a variable assignment line */ +static int parse_line( + const char *filename, + unsigned line, + const char *sections, + ConfigItemLookup lookup, + void *table, + bool relaxed, + char **section, + char *l, + void *userdata) { + + char *e; + + assert(filename); + assert(line > 0); + assert(lookup); + assert(l); + + l = strstrip(l); + + if (!*l) + return 0; + + if (strchr(COMMENTS, *l)) + return 0; + + if (startswith(l, ".include ")) { + char *fn; + int r; + + fn = file_in_same_dir(filename, strstrip(l+9)); + if (!fn) + return -ENOMEM; + + r = config_parse(fn, NULL, sections, lookup, table, relaxed, userdata); + free(fn); + + return r; + } + + if (*l == '[') { + size_t k; + char *n; + + k = strlen(l); + assert(k > 0); + + if (l[k-1] != ']') { + log_error("[%s:%u] Invalid section header.", filename, line); + return -EBADMSG; + } + + n = strndup(l+1, k-2); + if (!n) + return -ENOMEM; + + if (sections && !nulstr_contains(sections, n)) { + + if (!relaxed) + log_info("[%s:%u] Unknown section '%s'. Ignoring.", filename, line, n); + + free(n); + *section = NULL; + } else { + free(*section); + *section = n; + } + + return 0; + } + + if (sections && !*section) { + + if (!relaxed) + log_info("[%s:%u] Assignment outside of section. Ignoring.", filename, line); + + return 0; + } + + e = strchr(l, '='); + if (!e) { + log_error("[%s:%u] Missing '='.", filename, line); + return -EBADMSG; + } + + *e = 0; + e++; + + return next_assignment( + filename, + line, + lookup, + table, + *section, + strstrip(l), + strstrip(e), + relaxed, + userdata); +} + +/* Go through the file and parse each line */ +int config_parse( + const char *filename, + FILE *f, + const char *sections, + ConfigItemLookup lookup, + void *table, + bool relaxed, + void *userdata) { + + unsigned line = 0; + char *section = NULL; + int r; + bool ours = false; + char *continuation = NULL; + + assert(filename); + assert(lookup); + + if (!f) { + f = fopen(filename, "re"); + if (!f) { + r = -errno; + log_error("Failed to open configuration file '%s': %s", filename, strerror(-r)); + goto finish; + } + + ours = true; + } + + while (!feof(f)) { + char l[LINE_MAX], *p, *c = NULL, *e; + bool escaped = false; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + r = -errno; + log_error("Failed to read configuration file '%s': %s", filename, strerror(-r)); + goto finish; + } + + truncate_nl(l); + + if (continuation) { + c = strappend(continuation, l); + if (!c) { + r = -ENOMEM; + goto finish; + } + + free(continuation); + continuation = NULL; + p = c; + } else + p = l; + + for (e = p; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + } + + if (escaped) { + *(e-1) = ' '; + + if (c) + continuation = c; + else { + continuation = strdup(l); + if (!continuation) { + r = -ENOMEM; + goto finish; + } + } + + continue; + } + + r = parse_line(filename, + ++line, + sections, + lookup, + table, + relaxed, + §ion, + p, + userdata); + free(c); + + if (r < 0) + goto finish; + } + + r = 0; + +finish: + free(section); + free(continuation); + + if (f && ours) + fclose(f); + + return r; +} + +int config_parse_int( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int *i = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((r = safe_atoi(rvalue, i)) < 0) { + log_error("[%s:%u] Failed to parse numeric value, ingoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +int config_parse_long( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + long *i = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((r = safe_atoli(rvalue, i)) < 0) { + log_error("[%s:%u] Failed to parse numeric value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +int config_parse_uint64( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + uint64_t *u = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((r = safe_atou64(rvalue, u)) < 0) { + log_error("[%s:%u] Failed to parse numeric value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +int config_parse_unsigned( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + unsigned *u = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((r = safe_atou(rvalue, u)) < 0) { + log_error("[%s:%u] Failed to parse numeric value: %s", filename, line, rvalue); + return r; + } + + return 0; +} + +int config_parse_bytes_size( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + size_t *sz = data; + off_t o; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (parse_bytes(rvalue, &o) < 0 || (off_t) (size_t) o != o) { + log_error("[%s:%u] Failed to parse byte value, ignoring: %s", filename, line, rvalue); + return 0; + } + + *sz = (size_t) o; + return 0; +} + + +int config_parse_bytes_off( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + off_t *bytes = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + assert_cc(sizeof(off_t) == sizeof(uint64_t)); + + if (parse_bytes(rvalue, bytes) < 0) { + log_error("[%s:%u] Failed to parse bytes value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +int config_parse_bool( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int k; + bool *b = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((k = parse_boolean(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse boolean value, ignoring: %s", filename, line, rvalue); + return 0; + } + + *b = !!k; + return 0; +} + +int config_parse_tristate( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int k; + int *b = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* Tristates are like booleans, but can also take the 'default' value, i.e. "-1" */ + + k = parse_boolean(rvalue); + if (k < 0) { + log_error("[%s:%u] Failed to parse boolean value, ignoring: %s", filename, line, rvalue); + return 0; + } + + *b = !!k; + return 0; +} + +int config_parse_string( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char **s = data; + char *n; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + n = cunescape(rvalue); + if (!n) + return -ENOMEM; + + if (!utf8_is_valid(n)) { + log_error("[%s:%u] String is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue); + free(n); + return 0; + } + + free(*s); + if (*n) + *s = n; + else { + free(n); + *s = NULL; + } + + return 0; +} + +int config_parse_path( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char **s = data; + char *n; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (!utf8_is_valid(rvalue)) { + log_error("[%s:%u] Path is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue); + return 0; + } + + if (!path_is_absolute(rvalue)) { + log_error("[%s:%u] Not an absolute path, ignoring: %s", filename, line, rvalue); + return 0; + } + + n = strdup(rvalue); + if (!n) + return -ENOMEM; + + path_kill_slashes(n); + + free(*s); + *s = n; + + return 0; +} + +int config_parse_strv( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char*** sv = data; + char **n; + char *w; + unsigned k; + size_t l; + char *state; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + k = strv_length(*sv); + FOREACH_WORD_QUOTED(w, l, rvalue, state) + k++; + + n = new(char*, k+1); + if (!n) + return -ENOMEM; + + if (*sv) + for (k = 0; (*sv)[k]; k++) + n[k] = (*sv)[k]; + else + k = 0; + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + n[k] = cunescape_length(w, l); + if (!n[k]) { + r = -ENOMEM; + goto fail; + } + + if (!utf8_is_valid(n[k])) { + log_error("[%s:%u] String is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue); + free(n[k]); + continue; + } + + k++; + } + + n[k] = NULL; + free(*sv); + *sv = n; + + return 0; + +fail: + for (; k > 0; k--) + free(n[k-1]); + free(n); + + return r; +} + +int config_parse_path_strv( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char*** sv = data; + char **n; + char *w; + unsigned k; + size_t l; + char *state; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + k = strv_length(*sv); + FOREACH_WORD_QUOTED(w, l, rvalue, state) + k++; + + n = new(char*, k+1); + if (!n) + return -ENOMEM; + + k = 0; + if (*sv) + for (; (*sv)[k]; k++) + n[k] = (*sv)[k]; + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + n[k] = strndup(w, l); + if (!n[k]) { + r = -ENOMEM; + goto fail; + } + + if (!utf8_is_valid(n[k])) { + log_error("[%s:%u] Path is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue); + free(n[k]); + continue; + } + + if (!path_is_absolute(n[k])) { + log_error("[%s:%u] Not an absolute path, ignoring: %s", filename, line, rvalue); + free(n[k]); + continue; + } + + path_kill_slashes(n[k]); + k++; + } + + n[k] = NULL; + free(*sv); + *sv = n; + + return 0; + +fail: + for (; k > 0; k--) + free(n[k-1]); + free(n); + + return r; +} + +int config_parse_usec( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + usec_t *usec = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (parse_usec(rvalue, usec) < 0) { + log_error("[%s:%u] Failed to parse time value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +int config_parse_mode( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + mode_t *m = data; + long l; + char *x = NULL; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + errno = 0; + l = strtol(rvalue, &x, 8); + if (!x || *x || errno) { + log_error("[%s:%u] Failed to parse mode value, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (l < 0000 || l > 07777) { + log_error("[%s:%u] mode value out of range, ignoring: %s", filename, line, rvalue); + return 0; + } + + *m = (mode_t) l; + return 0; +} diff --git a/src/conf-parser.h b/src/conf-parser.h new file mode 100644 index 000000000..be7d70817 --- /dev/null +++ b/src/conf-parser.h @@ -0,0 +1,135 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooconfparserhfoo +#define fooconfparserhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +/* An abstract parser for simple, line based, shallow configuration + * files consisting of variable assignments only. */ + +/* Prototype for a parser for a specific configuration setting */ +typedef int (*ConfigParserCallback)( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata); + +/* Wraps information for parsing a specific configuration variable, to + * be stored in a simple array */ +typedef struct ConfigTableItem { + const char *section; /* Section */ + const char *lvalue; /* Name of the variable */ + ConfigParserCallback parse; /* Function that is called to parse the variable's value */ + int ltype; /* Distinguish different variables passed to the same callback */ + void *data; /* Where to store the variable's data */ +} ConfigTableItem; + +/* Wraps information for parsing a specific configuration variable, to + * ve srored in a gperf perfect hashtable */ +typedef struct ConfigPerfItem { + const char *section_and_lvalue; /* Section + "." + name of the variable */ + ConfigParserCallback parse; /* Function that is called to parse the variable's value */ + int ltype; /* Distinguish different variables passed to the same callback */ + size_t offset; /* Offset where to store data, from the beginning of userdata */ +} ConfigPerfItem; + +/* Prototype for a low-level gperf lookup function */ +typedef const ConfigPerfItem* (*ConfigPerfItemLookup)(const char *section_and_lvalue, unsigned length); + +/* Prototype for a generic high-level lookup function */ +typedef int (*ConfigItemLookup)( + void *table, + const char *section, + const char *lvalue, + ConfigParserCallback *func, + int *ltype, + void **data, + void *userdata); + +/* Linear table search implementation of ConfigItemLookup, based on + * ConfigTableItem arrays */ +int config_item_table_lookup(void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); + +/* gperf implementation of ConfigItemLookup, based on gperf + * ConfigPerfItem tables */ +int config_item_perf_lookup(void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); + +int config_parse( + const char *filename, + FILE *f, + const char *sections, /* nulstr */ + ConfigItemLookup lookup, + void *table, + bool relaxed, + void *userdata); + +/* Generic parsers */ +int config_parse_int(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unsigned(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_long(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_uint64(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_bytes_size(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_bytes_off(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_bool(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_tristate(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_string(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_path(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_strv(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_path_strv(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_usec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_mode(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); + +#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \ + int function( \ + const char *filename, \ + unsigned line, \ + const char *section, \ + const char *lvalue, \ + int ltype, \ + const char *rvalue, \ + void *data, \ + void *userdata) { \ + \ + type *i = data, x; \ + \ + assert(filename); \ + assert(lvalue); \ + assert(rvalue); \ + assert(data); \ + \ + if ((x = name##_from_string(rvalue)) < 0) { \ + log_error("[%s:%u] " msg ", ignoring: %s", filename, line, rvalue); \ + return 0; \ + } \ + \ + *i = x; \ + \ + return 0; \ + } + +#endif diff --git a/src/cryptsetup/Makefile b/src/cryptsetup/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/cryptsetup/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c new file mode 100644 index 000000000..ba59b49b0 --- /dev/null +++ b/src/cryptsetup/cryptsetup-generator.c @@ -0,0 +1,298 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "log.h" +#include "util.h" +#include "unit-name.h" + +const char *arg_dest = "/tmp"; + +static bool has_option(const char *haystack, const char *needle) { + const char *f = haystack; + size_t l; + + assert(needle); + + if (!haystack) + return false; + + l = strlen(needle); + + while ((f = strstr(f, needle))) { + + if (f > haystack && f[-1] != ',') { + f++; + continue; + } + + if (f[l] != 0 && f[l] != ',') { + f++; + continue; + } + + return true; + } + + return false; +} + +static int create_disk( + const char *name, + const char *device, + const char *password, + const char *options) { + + char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *from = NULL, *to = NULL, *e = NULL; + int r; + FILE *f = NULL; + bool noauto, nofail; + + assert(name); + assert(device); + + noauto = has_option(options, "noauto"); + nofail = has_option(options, "nofail"); + + if (!(n = unit_name_build_escape("cryptsetup", name, ".service"))) { + r = -ENOMEM; + log_error("Failed to allocate unit name."); + goto fail; + } + + if (asprintf(&p, "%s/%s", arg_dest, n) < 0) { + r = -ENOMEM; + log_error("Failed to allocate unit file name."); + goto fail; + } + + if (!(u = fstab_node_to_udev_node(device))) { + r = -ENOMEM; + log_error("Failed to allocate device node."); + goto fail; + } + + if (!(d = unit_name_from_path(u, ".device"))) { + r = -ENOMEM; + log_error("Failed to allocate device name."); + goto fail; + } + + if (!(f = fopen(p, "wxe"))) { + r = -errno; + log_error("Failed to create unit file: %m"); + goto fail; + } + + fprintf(f, + "[Unit]\n" + "Description=Cryptography Setup for %%I\n" + "Conflicts=umount.target\n" + "DefaultDependencies=no\n" + "BindTo=%s dev-mapper-%%i.device\n" + "After=systemd-readahead-collect.service systemd-readahead-replay.service %s\n" + "Before=umount.target\n", + d, d); + + if (!nofail) + fprintf(f, + "Before=cryptsetup.target\n"); + + if (password && (streq(password, "/dev/urandom") || + streq(password, "/dev/random") || + streq(password, "/dev/hw_random"))) + fprintf(f, + "After=systemd-random-seed-load.service\n"); + else + fprintf(f, + "Before=local-fs.target\n"); + + fprintf(f, + "\n[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=yes\n" + "TimeoutSec=0\n" /* the binary handles timeouts anyway */ + "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n" + "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", + name, u, strempty(password), strempty(options), + name); + + if (has_option(options, "tmp")) + fprintf(f, + "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n", + name); + + if (has_option(options, "swap")) + fprintf(f, + "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n", + name); + + fflush(f); + + if (ferror(f)) { + r = -errno; + log_error("Failed to write file: %m"); + goto fail; + } + + if (asprintf(&from, "../%s", n) < 0) { + r = -ENOMEM; + goto fail; + } + + if (!noauto) { + + if (asprintf(&to, "%s/%s.wants/%s", arg_dest, d, n) < 0) { + r = -ENOMEM; + goto fail; + } + + mkdir_parents(to, 0755); + + if (symlink(from, to) < 0) { + log_error("Failed to create symlink '%s' to '%s': %m", from, to); + r = -errno; + goto fail; + } + + free(to); + to = NULL; + + if (!nofail) + asprintf(&to, "%s/cryptsetup.target.requires/%s", arg_dest, n); + else + asprintf(&to, "%s/cryptsetup.target.wants/%s", arg_dest, n); + + if (!to) { + r = -ENOMEM; + goto fail; + } + + mkdir_parents(to, 0755); + + if (symlink(from, to) < 0) { + log_error("Failed to create symlink '%s' to '%s': %m", from, to); + r = -errno; + goto fail; + } + } + + free(to); + to = NULL; + + e = unit_name_escape(name); + if (asprintf(&to, "%s/dev-mapper-%s.device.requires/%s", arg_dest, e, n) < 0) { + r = -ENOMEM; + goto fail; + } + + mkdir_parents(to, 0755); + + if (symlink(from, to) < 0) { + log_error("Failed to create symlink '%s' to '%s': %m", from, to); + r = -errno; + goto fail; + } + + r = 0; + +fail: + free(p); + free(n); + free(d); + free(e); + + free(from); + free(to); + + if (f) + fclose(f); + + return r; +} + +int main(int argc, char *argv[]) { + FILE *f; + int r = EXIT_SUCCESS; + unsigned n = 0; + + if (argc > 2) { + log_error("This program takes one or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[1]; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (!(f = fopen("/etc/crypttab", "re"))) { + + if (errno == ENOENT) + r = EXIT_SUCCESS; + else { + r = EXIT_FAILURE; + log_error("Failed to open /etc/crypttab: %m"); + } + + goto finish; + } + + for (;;) { + char line[LINE_MAX], *l; + char *name = NULL, *device = NULL, *password = NULL, *options = NULL; + int k; + + if (!(fgets(line, sizeof(line), f))) + break; + + n++; + + l = strstrip(line); + if (*l == '#' || *l == 0) + continue; + + if ((k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options)) < 2 || k > 4) { + log_error("Failed to parse /etc/crypttab:%u, ignoring.", n); + r = EXIT_FAILURE; + goto next; + } + + if (create_disk(name, device, password, options) < 0) + r = EXIT_FAILURE; + + next: + free(name); + free(device); + free(password); + free(options); + } + +finish: + return r; +} diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c new file mode 100644 index 000000000..ac7b6d6c3 --- /dev/null +++ b/src/cryptsetup/cryptsetup.c @@ -0,0 +1,529 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include +#include + +#include "log.h" +#include "util.h" +#include "strv.h" +#include "ask-password-api.h" +#include "def.h" + +static const char *opt_type = NULL; /* LUKS1 or PLAIN */ +static char *opt_cipher = NULL; +static unsigned opt_key_size = 0; +static char *opt_hash = NULL; +static unsigned opt_tries = 0; +static bool opt_readonly = false; +static bool opt_verify = false; +static usec_t opt_timeout = DEFAULT_TIMEOUT_USEC; + +/* Options Debian's crypttab knows we don't: + + offset= + skip= + precheck= + check= + checkargs= + noearly= + loud= + keyscript= +*/ + +static int parse_one_option(const char *option) { + assert(option); + + /* Handled outside of this tool */ + if (streq(option, "noauto")) + return 0; + + if (startswith(option, "cipher=")) { + char *t; + + if (!(t = strdup(option+7))) + return -ENOMEM; + + free(opt_cipher); + opt_cipher = t; + + } else if (startswith(option, "size=")) { + + if (safe_atou(option+5, &opt_key_size) < 0) { + log_error("size= parse failure, ignoring."); + return 0; + } + + } else if (startswith(option, "hash=")) { + char *t; + + if (!(t = strdup(option+5))) + return -ENOMEM; + + free(opt_hash); + opt_hash = t; + + } else if (startswith(option, "tries=")) { + + if (safe_atou(option+6, &opt_tries) < 0) { + log_error("tries= parse failure, ignoring."); + return 0; + } + + } else if (streq(option, "readonly")) + opt_readonly = true; + else if (streq(option, "verify")) + opt_verify = true; + else if (streq(option, "luks")) + opt_type = CRYPT_LUKS1; + else if (streq(option, "plain") || + streq(option, "swap") || + streq(option, "tmp")) + opt_type = CRYPT_PLAIN; + else if (startswith(option, "timeout=")) { + + if (parse_usec(option+8, &opt_timeout) < 0) { + log_error("timeout= parse failure, ignoring."); + return 0; + } + + } else if (!streq(option, "none")) + log_error("Encountered unknown /etc/crypttab option '%s', ignoring.", option); + + return 0; +} + +static int parse_options(const char *options) { + char *state; + char *w; + size_t l; + + assert(options); + + FOREACH_WORD_SEPARATOR(w, l, options, ",", state) { + char *o; + int r; + + if (!(o = strndup(w, l))) + return -ENOMEM; + + r = parse_one_option(o); + free(o); + + if (r < 0) + return r; + } + + return 0; +} + +static void log_glue(int level, const char *msg, void *usrptr) { + log_debug("%s", msg); +} + +static char *disk_description(const char *path) { + struct udev *udev = NULL; + struct udev_device *device = NULL; + struct stat st; + char *description = NULL; + const char *model; + + assert(path); + + if (stat(path, &st) < 0) + return NULL; + + if (!S_ISBLK(st.st_mode)) + return NULL; + + if (!(udev = udev_new())) + return NULL; + + if (!(device = udev_device_new_from_devnum(udev, 'b', st.st_rdev))) + goto finish; + + if ((model = udev_device_get_property_value(device, "ID_MODEL_FROM_DATABASE")) || + (model = udev_device_get_property_value(device, "ID_MODEL")) || + (model = udev_device_get_property_value(device, "DM_NAME"))) + description = strdup(model); + +finish: + if (device) + udev_device_unref(device); + + if (udev) + udev_unref(udev); + + return description; +} + +static char *disk_mount_point(const char *label) { + char *mp = NULL, *device = NULL; + FILE *f = NULL; + struct mntent *m; + + /* Yeah, we don't support native systemd unit files here for now */ + + if (asprintf(&device, "/dev/mapper/%s", label) < 0) + goto finish; + + if (!(f = setmntent("/etc/fstab", "r"))) + goto finish; + + while ((m = getmntent(f))) + if (path_equal(m->mnt_fsname, device)) { + mp = strdup(m->mnt_dir); + break; + } + +finish: + if (f) + endmntent(f); + + free(device); + + return mp; +} + +static int help(void) { + + printf("%s attach VOLUME SOURCEDEVICE [PASSWORD] [OPTIONS]\n" + "%s detach VOLUME\n\n" + "Attaches or detaches an encrypted block device.\n", + program_invocation_short_name, + program_invocation_short_name); + + return 0; +} + +int main(int argc, char *argv[]) { + int r = EXIT_FAILURE; + struct crypt_device *cd = NULL; + char **passwords = NULL, *truncated_cipher = NULL; + const char *cipher = NULL, *cipher_mode = NULL, *hash = NULL, *name = NULL; + char *description = NULL, *name_buffer = NULL, *mount_point = NULL; + unsigned keyfile_size = 0; + + if (argc <= 1) { + help(); + return EXIT_SUCCESS; + } + + if (argc < 3) { + log_error("This program requires at least two arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (streq(argv[1], "attach")) { + uint32_t flags = 0; + int k; + unsigned try; + const char *key_file = NULL; + usec_t until; + crypt_status_info status; + + /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [PASSWORD] [OPTIONS] */ + + if (argc < 4) { + log_error("attach requires at least two arguments."); + goto finish; + } + + if (argc >= 5 && + argv[4][0] && + !streq(argv[4], "-") && + !streq(argv[4], "none")) { + + if (!path_is_absolute(argv[4])) + log_error("Password file path %s is not absolute. Ignoring.", argv[4]); + else + key_file = argv[4]; + } + + if (argc >= 6 && argv[5][0] && !streq(argv[5], "-")) + parse_options(argv[5]); + + /* A delicious drop of snake oil */ + mlockall(MCL_FUTURE); + + description = disk_description(argv[3]); + mount_point = disk_mount_point(argv[2]); + + if (description && streq(argv[2], description)) { + /* If the description string is simply the + * volume name, then let's not show this + * twice */ + free(description); + description = NULL; + } + + if (mount_point && description) + asprintf(&name_buffer, "%s (%s) on %s", description, argv[2], mount_point); + else if (mount_point) + asprintf(&name_buffer, "%s on %s", argv[2], mount_point); + else if (description) + asprintf(&name_buffer, "%s (%s)", description, argv[2]); + + name = name_buffer ? name_buffer : argv[2]; + + if ((k = crypt_init(&cd, argv[3]))) { + log_error("crypt_init() failed: %s", strerror(-k)); + goto finish; + } + + crypt_set_log_callback(cd, log_glue, NULL); + + status = crypt_status(cd, argv[2]); + if (status == CRYPT_ACTIVE || status == CRYPT_BUSY) { + log_info("Volume %s already active.", argv[2]); + r = EXIT_SUCCESS; + goto finish; + } + + if (opt_readonly) + flags |= CRYPT_ACTIVATE_READONLY; + + if (opt_timeout > 0) + until = now(CLOCK_MONOTONIC) + opt_timeout; + else + until = 0; + + opt_tries = opt_tries > 0 ? opt_tries : 3; + opt_key_size = (opt_key_size > 0 ? opt_key_size : 256); + hash = opt_hash ? opt_hash : "ripemd160"; + + if (opt_cipher) { + size_t l; + + l = strcspn(opt_cipher, "-"); + + if (!(truncated_cipher = strndup(opt_cipher, l))) { + log_error("Out of memory"); + goto finish; + } + + cipher = truncated_cipher; + cipher_mode = opt_cipher[l] ? opt_cipher+l+1 : "plain"; + } else { + cipher = "aes"; + cipher_mode = "cbc-essiv:sha256"; + } + + for (try = 0; try < opt_tries; try++) { + bool pass_volume_key = false; + + strv_free(passwords); + passwords = NULL; + + if (!key_file) { + char *text; + char **p; + + if (asprintf(&text, "Please enter passphrase for disk %s!", name) < 0) { + log_error("Out of memory"); + goto finish; + } + + k = ask_password_auto(text, "drive-harddisk", until, try == 0 && !opt_verify, &passwords); + free(text); + + if (k < 0) { + log_error("Failed to query password: %s", strerror(-k)); + goto finish; + } + + if (opt_verify) { + char **passwords2 = NULL; + + assert(strv_length(passwords) == 1); + + if (asprintf(&text, "Please enter passphrase for disk %s! (verification)", name) < 0) { + log_error("Out of memory"); + goto finish; + } + + k = ask_password_auto(text, "drive-harddisk", until, false, &passwords2); + free(text); + + if (k < 0) { + log_error("Failed to query verification password: %s", strerror(-k)); + goto finish; + } + + assert(strv_length(passwords2) == 1); + + if (!streq(passwords[0], passwords2[0])) { + log_warning("Passwords did not match, retrying."); + strv_free(passwords2); + continue; + } + + strv_free(passwords2); + } + + strv_uniq(passwords); + + STRV_FOREACH(p, passwords) { + char *c; + + if (strlen(*p)+1 >= opt_key_size) + continue; + + /* Pad password if necessary */ + if (!(c = new(char, opt_key_size))) { + log_error("Out of memory."); + goto finish; + } + + strncpy(c, *p, opt_key_size); + free(*p); + *p = c; + } + } + + k = 0; + + if (!opt_type || streq(opt_type, CRYPT_LUKS1)) + k = crypt_load(cd, CRYPT_LUKS1, NULL); + + if ((!opt_type && k < 0) || streq_ptr(opt_type, CRYPT_PLAIN)) { + struct crypt_params_plain params; + + zero(params); + params.hash = hash; + + /* In contrast to what the name + * crypt_setup() might suggest this + * doesn't actually format anything, + * it just configures encryption + * parameters when used for plain + * mode. */ + k = crypt_format(cd, CRYPT_PLAIN, + cipher, + cipher_mode, + NULL, + NULL, + opt_key_size / 8, + ¶ms); + + pass_volume_key = streq(hash, "plain"); + + /* for CRYPT_PLAIN limit reads + * from keyfile to key length */ + keyfile_size = opt_key_size / 8; + } + + if (k < 0) { + log_error("Loading of cryptographic parameters failed: %s", strerror(-k)); + goto finish; + } + + log_info("Set cipher %s, mode %s, key size %i bits for device %s.", + crypt_get_cipher(cd), + crypt_get_cipher_mode(cd), + crypt_get_volume_key_size(cd)*8, + argv[3]); + + if (key_file) + k = crypt_activate_by_keyfile(cd, argv[2], CRYPT_ANY_SLOT, key_file, keyfile_size, flags); + else { + char **p; + + STRV_FOREACH(p, passwords) { + + if (pass_volume_key) + k = crypt_activate_by_volume_key(cd, argv[2], *p, opt_key_size, flags); + else + k = crypt_activate_by_passphrase(cd, argv[2], CRYPT_ANY_SLOT, *p, strlen(*p), flags); + + if (k >= 0) + break; + } + } + + if (k >= 0) + break; + + if (k != -EPERM) { + log_error("Failed to activate: %s", strerror(-k)); + goto finish; + } + + log_warning("Invalid passphrase."); + } + + if (try >= opt_tries) { + log_error("Too many attempts."); + r = EXIT_FAILURE; + goto finish; + } + + } else if (streq(argv[1], "detach")) { + int k; + + if ((k = crypt_init_by_name(&cd, argv[2]))) { + log_error("crypt_init() failed: %s", strerror(-k)); + goto finish; + } + + crypt_set_log_callback(cd, log_glue, NULL); + + if ((k = crypt_deactivate(cd, argv[2])) < 0) { + log_error("Failed to deactivate: %s", strerror(-k)); + goto finish; + } + + } else { + log_error("Unknown verb %s.", argv[1]); + goto finish; + } + + r = EXIT_SUCCESS; + +finish: + + if (cd) + crypt_free(cd); + + free(opt_cipher); + free(opt_hash); + + free(truncated_cipher); + + strv_free(passwords); + + free(description); + free(mount_point); + free(name_buffer); + + return r; +} diff --git a/src/dbus-automount.c b/src/dbus-automount.c new file mode 100644 index 000000000..8e45f81fc --- /dev/null +++ b/src/dbus-automount.c @@ -0,0 +1,72 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-automount.h" +#include "dbus-common.h" + +#define BUS_AUTOMOUNT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_AUTOMOUNT_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Automount\0" + +const char bus_automount_interface[] _introspect_("Automount") = BUS_AUTOMOUNT_INTERFACE; + +const char bus_automount_invalidating_properties[] = + "Result\0"; + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_automount_append_automount_result, automount_result, AutomountResult); + +static const BusProperty bus_automount_properties[] = { + { "Where", bus_property_append_string, "s", offsetof(Automount, where), true }, + { "DirectoryMode", bus_property_append_mode, "u", offsetof(Automount, directory_mode) }, + { "Result", bus_automount_append_automount_result, "s", offsetof(Automount, result) }, + { NULL, } +}; + +DBusHandlerResult bus_automount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Automount *am = AUTOMOUNT(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Automount", bus_automount_properties, am }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/dbus-automount.h b/src/dbus-automount.h new file mode 100644 index 000000000..2fc834504 --- /dev/null +++ b/src/dbus-automount.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusautomounthfoo +#define foodbusautomounthfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_automount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_automount_interface[]; +extern const char bus_automount_invalidating_properties[]; + +#endif diff --git a/src/dbus-common.c b/src/dbus-common.c new file mode 100644 index 000000000..2905ac3c8 --- /dev/null +++ b/src/dbus-common.c @@ -0,0 +1,1092 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "dbus-common.h" +#include "util.h" +#include "def.h" +#include "strv.h" + +int bus_check_peercred(DBusConnection *c) { + int fd; + struct ucred ucred; + socklen_t l; + + assert(c); + + assert_se(dbus_connection_get_unix_fd(c, &fd)); + + l = sizeof(struct ucred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0) { + log_error("SO_PEERCRED failed: %m"); + return -errno; + } + + if (l != sizeof(struct ucred)) { + log_error("SO_PEERCRED returned wrong size."); + return -E2BIG; + } + + if (ucred.uid != 0 && ucred.uid != geteuid()) + return -EPERM; + + return 1; +} + +static int sync_auth(DBusConnection *bus, DBusError *error) { + usec_t begin, tstamp; + + assert(bus); + + /* This complexity should probably move into D-Bus itself: + * + * https://bugs.freedesktop.org/show_bug.cgi?id=35189 */ + + begin = tstamp = now(CLOCK_MONOTONIC); + for (;;) { + + if (tstamp > begin + DEFAULT_TIMEOUT_USEC) + break; + + if (dbus_connection_get_is_authenticated(bus)) + break; + + if (!dbus_connection_read_write_dispatch(bus, ((begin + DEFAULT_TIMEOUT_USEC - tstamp) + USEC_PER_MSEC - 1) / USEC_PER_MSEC)) + break; + + tstamp = now(CLOCK_MONOTONIC); + } + + if (!dbus_connection_get_is_connected(bus)) { + dbus_set_error_const(error, DBUS_ERROR_NO_SERVER, "Connection terminated during authentication."); + return -ECONNREFUSED; + } + + if (!dbus_connection_get_is_authenticated(bus)) { + dbus_set_error_const(error, DBUS_ERROR_TIMEOUT, "Failed to authenticate in time."); + return -EACCES; + } + + return 0; +} + +int bus_connect(DBusBusType t, DBusConnection **_bus, bool *_private, DBusError *error) { + DBusConnection *bus = NULL; + int r; + bool private = true; + + assert(_bus); + + if (geteuid() == 0 && t == DBUS_BUS_SYSTEM) { + /* If we are root, then let's talk directly to the + * system instance, instead of going via the bus */ + + bus = dbus_connection_open_private("unix:path=/run/systemd/private", error); + if (!bus) + return -EIO; + + } else { + if (t == DBUS_BUS_SESSION) { + const char *e; + + /* If we are supposed to talk to the instance, + * try via XDG_RUNTIME_DIR first, then + * fallback to normal bus access */ + + e = getenv("XDG_RUNTIME_DIR"); + if (e) { + char *p; + + if (asprintf(&p, "unix:path=%s/systemd/private", e) < 0) + return -ENOMEM; + + bus = dbus_connection_open_private(p, NULL); + free(p); + } + } + + if (!bus) { + bus = dbus_bus_get_private(t, error); + if (!bus) + return -EIO; + + private = false; + } + } + + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + if (private) { + if (bus_check_peercred(bus) < 0) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + + dbus_set_error_const(error, DBUS_ERROR_ACCESS_DENIED, "Failed to verify owner of bus."); + return -EACCES; + } + } + + r = sync_auth(bus, error); + if (r < 0) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + return r; + } + + if (_private) + *_private = private; + + *_bus = bus; + return 0; +} + +int bus_connect_system_ssh(const char *user, const char *host, DBusConnection **_bus, DBusError *error) { + DBusConnection *bus; + char *p = NULL; + int r; + + assert(_bus); + assert(user || host); + + if (user && host) + asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s@%s,argv3=systemd-stdio-bridge", user, host); + else if (user) + asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s@localhost,argv3=systemd-stdio-bridge", user); + else if (host) + asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s,argv3=systemd-stdio-bridge", host); + + if (!p) { + dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL); + return -ENOMEM; + } + + bus = dbus_connection_open_private(p, error); + free(p); + + if (!bus) + return -EIO; + + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + if ((r = sync_auth(bus, error)) < 0) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + return r; + } + + if (!dbus_bus_register(bus, error)) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + return r; + } + + *_bus = bus; + return 0; +} + +int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error) { + DBusConnection *bus; + int r; + + assert(_bus); + + /* Don't bother with PolicyKit if we are root */ + if (geteuid() == 0) + return bus_connect(DBUS_BUS_SYSTEM, _bus, NULL, error); + + bus = dbus_connection_open_private("unixexec:path=pkexec,argv1=" SYSTEMD_STDIO_BRIDGE_BINARY_PATH, error); + if (!bus) + return -EIO; + + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + if ((r = sync_auth(bus, error)) < 0) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + return r; + } + + if (!dbus_bus_register(bus, error)) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + return r; + } + + *_bus = bus; + return 0; +} + +const char *bus_error_message(const DBusError *error) { + assert(error); + + /* Sometimes the D-Bus server is a little bit too verbose with + * its error messages, so let's override them here */ + if (dbus_error_has_name(error, DBUS_ERROR_ACCESS_DENIED)) + return "Access denied"; + + return error->message; +} + +DBusHandlerResult bus_default_message_handler( + DBusConnection *c, + DBusMessage *message, + const char *introspection, + const char *interfaces, + const BusBoundProperties *bound_properties) { + + DBusError error; + DBusMessage *reply = NULL; + int r; + + assert(c); + assert(message); + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect") && introspection) { + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Get") && bound_properties) { + const char *interface, *property; + const BusBoundProperties *bp; + const BusProperty *p; + void *data; + DBusMessageIter iter, sub; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(c, message, &error, -EINVAL); + + for (bp = bound_properties; bp->interface; bp++) { + if (!streq(bp->interface, interface)) + continue; + + for (p = bp->properties; p->property; p++) + if (streq(p->property, property)) + goto get_prop; + } + + /* no match */ + if (!nulstr_contains(interfaces, interface)) + dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface"); + else + dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); + + return bus_send_error_reply(c, message, &error, -EINVAL); + +get_prop: + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, p->signature, &sub)) + goto oom; + + data = (char*)bp->base + p->offset; + if (p->indirect) + data = *(void**)data; + r = p->append(&sub, property, data); + if (r < 0) { + if (r == -ENOMEM) + goto oom; + + dbus_message_unref(reply); + return bus_send_error_reply(c, message, NULL, r); + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "GetAll") && bound_properties) { + const char *interface; + const BusBoundProperties *bp; + const BusProperty *p; + DBusMessageIter iter, sub, sub2, sub3; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(c, message, &error, -EINVAL); + + if (interface[0] && !nulstr_contains(interfaces, interface)) { + dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface"); + return bus_send_error_reply(c, message, &error, -EINVAL); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)) + goto oom; + + for (bp = bound_properties; bp->interface; bp++) { + if (interface[0] && !streq(bp->interface, interface)) + continue; + + for (p = bp->properties; p->property; p++) { + void *data; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &p->property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, p->signature, &sub3)) + goto oom; + + data = (char*)bp->base + p->offset; + if (p->indirect) + data = *(void**)data; + r = p->append(&sub3, p->property, data); + if (r < 0) { + if (r == -ENOMEM) + goto oom; + + dbus_message_unref(reply); + return bus_send_error_reply(c, message, NULL, r); + } + + if (!dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + goto oom; + } + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Set") && bound_properties) { + const char *interface, *property; + DBusMessageIter iter; + const BusBoundProperties *bp; + const BusProperty *p; + DBusMessageIter sub; + char *sig; + + if (!dbus_message_iter_init(message, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return bus_send_error_reply(c, message, NULL, -EINVAL); + + dbus_message_iter_get_basic(&iter, &interface); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return bus_send_error_reply(c, message, NULL, -EINVAL); + + dbus_message_iter_get_basic(&iter, &property); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT || + dbus_message_iter_has_next(&iter)) + return bus_send_error_reply(c, message, NULL, -EINVAL); + + for (bp = bound_properties; bp->interface; bp++) { + if (!streq(bp->interface, interface)) + continue; + + for (p = bp->properties; p->property; p++) + if (streq(p->property, property)) + goto set_prop; + } + + /* no match */ + if (!nulstr_contains(interfaces, interface)) + dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface"); + else + dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); + + return bus_send_error_reply(c, message, &error, -EINVAL); + +set_prop: + if (!p->set) { + dbus_set_error_const(&error, DBUS_ERROR_PROPERTY_READ_ONLY, "Property read-only"); + return bus_send_error_reply(c, message, &error, -EINVAL); + } + + dbus_message_iter_recurse(&iter, &sub); + + sig = dbus_message_iter_get_signature(&sub); + if (!sig) + goto oom; + + if (!streq(sig, p->signature)) { + dbus_free(sig); + return bus_send_error_reply(c, message, NULL, -EINVAL); + } + + dbus_free(sig); + + r = p->set(&sub, property); + if (r < 0) { + if (r == -ENOMEM) + goto oom; + return bus_send_error_reply(c, message, NULL, r); + } + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + } else { + const char *interface = dbus_message_get_interface(message); + + if (!interface || !nulstr_contains(interfaces, interface)) { + dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface"); + return bus_send_error_reply(c, message, &error, -EINVAL); + } + } + + if (reply) { + if (!dbus_connection_send(c, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +int bus_property_append_string(DBusMessageIter *i, const char *property, void *data) { + const char *t = data; + + assert(i); + assert(property); + + if (!t) + t = ""; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_strv(DBusMessageIter *i, const char *property, void *data) { + char **t = data; + + assert(i); + assert(property); + + return bus_append_strv_iter(i, t); +} + +int bus_property_append_bool(DBusMessageIter *i, const char *property, void *data) { + bool *b = data; + dbus_bool_t db; + + assert(i); + assert(property); + assert(b); + + db = *b; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_tristate_false(DBusMessageIter *i, const char *property, void *data) { + int *b = data; + dbus_bool_t db; + + assert(i); + assert(property); + assert(b); + + db = *b > 0; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_uint64(DBusMessageIter *i, const char *property, void *data) { + assert(i); + assert(property); + assert(data); + + /* Let's ensure that usec_t is actually 64bit, and hence this + * function can be used for usec_t */ + assert_cc(sizeof(uint64_t) == sizeof(usec_t)); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, data)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_uint32(DBusMessageIter *i, const char *property, void *data) { + assert(i); + assert(property); + assert(data); + + /* Let's ensure that pid_t, mode_t, uid_t, gid_t are actually + * 32bit, and hence this function can be used for + * pid_t/mode_t/uid_t/gid_t */ + assert_cc(sizeof(uint32_t) == sizeof(pid_t)); + assert_cc(sizeof(uint32_t) == sizeof(mode_t)); + assert_cc(sizeof(uint32_t) == sizeof(unsigned)); + assert_cc(sizeof(uint32_t) == sizeof(uid_t)); + assert_cc(sizeof(uint32_t) == sizeof(gid_t)); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, data)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_int32(DBusMessageIter *i, const char *property, void *data) { + assert(i); + assert(property); + assert(data); + + assert_cc(sizeof(int32_t) == sizeof(int)); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, data)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_size(DBusMessageIter *i, const char *property, void *data) { + uint64_t u; + + assert(i); + assert(property); + assert(data); + + u = (uint64_t) *(size_t*) data; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_ul(DBusMessageIter *i, const char *property, void *data) { + uint64_t u; + + assert(i); + assert(property); + assert(data); + + u = (uint64_t) *(unsigned long*) data; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_long(DBusMessageIter *i, const char *property, void *data) { + int64_t l; + + assert(i); + assert(property); + assert(data); + + l = (int64_t) *(long*) data; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT64, &l)) + return -ENOMEM; + + return 0; +} + +const char *bus_errno_to_dbus(int error) { + + switch(error) { + + case -EINVAL: + return DBUS_ERROR_INVALID_ARGS; + + case -ENOMEM: + return DBUS_ERROR_NO_MEMORY; + + case -EPERM: + case -EACCES: + return DBUS_ERROR_ACCESS_DENIED; + + case -ESRCH: + return DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN; + + case -ENOENT: + return DBUS_ERROR_FILE_NOT_FOUND; + + case -EEXIST: + return DBUS_ERROR_FILE_EXISTS; + + case -ETIMEDOUT: + case -ETIME: + return DBUS_ERROR_TIMEOUT; + + case -EIO: + return DBUS_ERROR_IO_ERROR; + + case -ENETRESET: + case -ECONNABORTED: + case -ECONNRESET: + return DBUS_ERROR_DISCONNECTED; + } + + return DBUS_ERROR_FAILED; +} + +DBusHandlerResult bus_send_error_reply(DBusConnection *c, DBusMessage *message, DBusError *berror, int error) { + DBusMessage *reply = NULL; + const char *name, *text; + + if (berror && dbus_error_is_set(berror)) { + name = berror->name; + text = berror->message; + } else { + name = bus_errno_to_dbus(error); + text = strerror(-error); + } + + if (!(reply = dbus_message_new_error(message, name, text))) + goto oom; + + if (!dbus_connection_send(c, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + + if (berror) + dbus_error_free(berror); + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + if (berror) + dbus_error_free(berror); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +DBusMessage* bus_properties_changed_new(const char *path, const char *interface, const char *properties) { + DBusMessage *m; + DBusMessageIter iter, sub; + const char *i; + + assert(interface); + assert(properties); + + if (!(m = dbus_message_new_signal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged"))) + goto oom; + + dbus_message_iter_init_append(m, &iter); + + /* We won't send any property values, since they might be + * large and sometimes not cheap to generated */ + + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &interface) || + !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub) || + !dbus_message_iter_close_container(&iter, &sub) || + !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) + goto oom; + + NULSTR_FOREACH(i, properties) + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &i)) + goto oom; + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + return m; + +oom: + if (m) + dbus_message_unref(m); + + return NULL; +} + +uint32_t bus_flags_to_events(DBusWatch *bus_watch) { + unsigned flags; + uint32_t events = 0; + + assert(bus_watch); + + /* no watch flags for disabled watches */ + if (!dbus_watch_get_enabled(bus_watch)) + return 0; + + flags = dbus_watch_get_flags(bus_watch); + + if (flags & DBUS_WATCH_READABLE) + events |= EPOLLIN; + if (flags & DBUS_WATCH_WRITABLE) + events |= EPOLLOUT; + + return events | EPOLLHUP | EPOLLERR; +} + +unsigned bus_events_to_flags(uint32_t events) { + unsigned flags = 0; + + if (events & EPOLLIN) + flags |= DBUS_WATCH_READABLE; + if (events & EPOLLOUT) + flags |= DBUS_WATCH_WRITABLE; + if (events & EPOLLHUP) + flags |= DBUS_WATCH_HANGUP; + if (events & EPOLLERR) + flags |= DBUS_WATCH_ERROR; + + return flags; +} + +int bus_parse_strv(DBusMessage *m, char ***_l) { + DBusMessageIter iter; + + assert(m); + assert(_l); + + if (!dbus_message_iter_init(m, &iter)) + return -EINVAL; + + return bus_parse_strv_iter(&iter, _l); +} + +int bus_parse_strv_iter(DBusMessageIter *iter, char ***_l) { + DBusMessageIter sub; + unsigned n = 0, i = 0; + char **l; + + assert(iter); + assert(_l); + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_recurse(iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + n++; + dbus_message_iter_next(&sub); + } + + if (!(l = new(char*, n+1))) + return -ENOMEM; + + dbus_message_iter_recurse(iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *s; + + assert_se(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING); + dbus_message_iter_get_basic(&sub, &s); + + if (!(l[i++] = strdup(s))) { + strv_free(l); + return -ENOMEM; + } + + dbus_message_iter_next(&sub); + } + + assert(i == n); + l[i] = NULL; + + if (_l) + *_l = l; + + return 0; +} + +int bus_append_strv_iter(DBusMessageIter *iter, char **l) { + DBusMessageIter sub; + + assert(iter); + + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "s", &sub)) + return -ENOMEM; + + STRV_FOREACH(l, l) + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, l)) + return -ENOMEM; + + if (!dbus_message_iter_close_container(iter, &sub)) + return -ENOMEM; + + return 0; +} + +int bus_iter_get_basic_and_next(DBusMessageIter *iter, int type, void *data, bool next) { + + assert(iter); + assert(data); + + if (dbus_message_iter_get_arg_type(iter) != type) + return -EIO; + + dbus_message_iter_get_basic(iter, data); + + if (!dbus_message_iter_next(iter) != !next) + return -EIO; + + return 0; +} + +int generic_print_property(const char *name, DBusMessageIter *iter, bool all) { + assert(name); + assert(iter); + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_STRING: { + const char *s; + dbus_message_iter_get_basic(iter, &s); + + if (all || !isempty(s)) + printf("%s=%s\n", name, s); + + return 1; + } + + case DBUS_TYPE_BOOLEAN: { + dbus_bool_t b; + + dbus_message_iter_get_basic(iter, &b); + printf("%s=%s\n", name, yes_no(b)); + + return 1; + } + + case DBUS_TYPE_UINT64: { + uint64_t u; + dbus_message_iter_get_basic(iter, &u); + + /* Yes, heuristics! But we can change this check + * should it turn out to not be sufficient */ + + if (endswith(name, "Timestamp")) { + char timestamp[FORMAT_TIMESTAMP_MAX], *t; + + t = format_timestamp(timestamp, sizeof(timestamp), u); + if (t || all) + printf("%s=%s\n", name, strempty(t)); + + } else if (strstr(name, "USec")) { + char timespan[FORMAT_TIMESPAN_MAX]; + + printf("%s=%s\n", name, format_timespan(timespan, sizeof(timespan), u)); + } else + printf("%s=%llu\n", name, (unsigned long long) u); + + return 1; + } + + case DBUS_TYPE_UINT32: { + uint32_t u; + dbus_message_iter_get_basic(iter, &u); + + if (strstr(name, "UMask") || strstr(name, "Mode")) + printf("%s=%04o\n", name, u); + else + printf("%s=%u\n", name, (unsigned) u); + + return 1; + } + + case DBUS_TYPE_INT32: { + int32_t i; + dbus_message_iter_get_basic(iter, &i); + + printf("%s=%i\n", name, (int) i); + return 1; + } + + case DBUS_TYPE_DOUBLE: { + double d; + dbus_message_iter_get_basic(iter, &d); + + printf("%s=%g\n", name, d); + return 1; + } + + case DBUS_TYPE_ARRAY: + + if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) { + DBusMessageIter sub; + bool space = false; + + dbus_message_iter_recurse(iter, &sub); + if (all || + dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + printf("%s=", name); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *s; + + assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING); + dbus_message_iter_get_basic(&sub, &s); + printf("%s%s", space ? " " : "", s); + + space = true; + dbus_message_iter_next(&sub); + } + + puts(""); + } + + return 1; + + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_BYTE) { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + if (all || + dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + printf("%s=", name); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + uint8_t u; + + assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_BYTE); + dbus_message_iter_get_basic(&sub, &u); + printf("%02x", u); + + dbus_message_iter_next(&sub); + } + + puts(""); + } + + return 1; + } + + break; + } + + return 0; +} + +static void release_name_pending_cb(DBusPendingCall *pending, void *userdata) { + DBusMessage *reply; + DBusConnection *bus = userdata; + + assert_se(reply = dbus_pending_call_steal_reply(pending)); + dbus_message_unref(reply); + + dbus_connection_close(bus); +} + +void bus_async_unregister_and_exit(DBusConnection *bus, const char *name) { + DBusMessage *m = NULL; + DBusPendingCall *pending = NULL; + + assert(bus); + + /* We unregister the name here, but we continue to process + * requests, until we get the response for it, so that all + * requests are guaranteed to be processed. */ + + m = dbus_message_new_method_call( + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "ReleaseName"); + if (!m) + goto oom; + + if (!dbus_message_append_args( + m, + DBUS_TYPE_STRING, + &name, + DBUS_TYPE_INVALID)) + goto oom; + + if (!dbus_connection_send_with_reply(bus, m, &pending, -1)) + goto oom; + + if (!dbus_pending_call_set_notify(pending, release_name_pending_cb, bus, NULL)) + goto oom; + + dbus_message_unref(m); + dbus_pending_call_unref(pending); + + return; + +oom: + log_error("Out of memory"); + + if (pending) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + } + + if (m) + dbus_message_unref(m); +} + +DBusHandlerResult bus_exit_idle_filter(DBusConnection *bus, DBusMessage *m, void *userdata) { + usec_t *remain_until = userdata; + + assert(bus); + assert(m); + assert(remain_until); + + /* Everytime we get a new message we reset out timeout */ + *remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC; + + if (dbus_message_is_signal(m, DBUS_INTERFACE_LOCAL, "Disconnected")) + dbus_connection_close(bus); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} diff --git a/src/dbus-common.h b/src/dbus-common.h new file mode 100644 index 000000000..15811a7e5 --- /dev/null +++ b/src/dbus-common.h @@ -0,0 +1,183 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbuscommonhfoo +#define foodbuscommonhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#ifndef DBUS_ERROR_UNKNOWN_OBJECT +#define DBUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject" +#endif + +#ifndef DBUS_ERROR_UNKNOWN_INTERFACE +#define DBUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface" +#endif + +#ifndef DBUS_ERROR_UNKNOWN_PROPERTY +#define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" +#endif + +#ifndef DBUS_ERROR_PROPERTY_READ_ONLY +#define DBUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly" +#endif + +#define BUS_PROPERTIES_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_INTROSPECTABLE_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_PEER_INTERFACE \ + "\n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + "\n" + +#define BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.DBus.Properties\0" \ + "org.freedesktop.DBus.Introspectable\0" \ + "org.freedesktop.DBus.Peer\0" + +int bus_check_peercred(DBusConnection *c); + +int bus_connect(DBusBusType t, DBusConnection **_bus, bool *private_bus, DBusError *error); + +int bus_connect_system_ssh(const char *user, const char *host, DBusConnection **_bus, DBusError *error); +int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error); + +const char *bus_error_message(const DBusError *error); + +typedef int (*BusPropertyCallback)(DBusMessageIter *iter, const char *property, void *data); +typedef int (*BusPropertySetCallback)(DBusMessageIter *iter, const char *property); + +typedef struct BusProperty { + const char *property; /* name of the property */ + BusPropertyCallback append; /* Function that is called to serialize this property */ + const char *signature; + const uint16_t offset; /* Offset from BusBoundProperties::base address to the property data. + * uint16_t is sufficient, because we have no structs too big. + * -Werror=overflow will catch it if this does not hold. */ + bool indirect; /* data is indirect, ie. not base+offset, but *(base+offset) */ + BusPropertySetCallback set; /* Optional: Function that is called to set this property */ +} BusProperty; + +typedef struct BusBoundProperties { + const char *interface; /* interface of the properties */ + const BusProperty *properties; /* array of properties, ended by a NULL-filled element */ + const void *const base; /* base pointer to which the offset must be added to reach data */ +} BusBoundProperties; + +DBusHandlerResult bus_send_error_reply( + DBusConnection *c, + DBusMessage *message, + DBusError *bus_error, + int error); + +DBusHandlerResult bus_default_message_handler( + DBusConnection *c, + DBusMessage *message, + const char *introspection, + const char *interfaces, + const BusBoundProperties *bound_properties); + +int bus_property_append_string(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_strv(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_bool(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_tristate_false(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_int32(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_uint32(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_uint64(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_size(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_ul(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_long(DBusMessageIter *i, const char *property, void *data); + +#define bus_property_append_int bus_property_append_int32 +#define bus_property_append_pid bus_property_append_uint32 +#define bus_property_append_uid bus_property_append_uint32 +#define bus_property_append_gid bus_property_append_uint32 +#define bus_property_append_mode bus_property_append_uint32 +#define bus_property_append_unsigned bus_property_append_uint32 +#define bus_property_append_usec bus_property_append_uint64 + +#define DEFINE_BUS_PROPERTY_APPEND_ENUM(function,name,type) \ + int function(DBusMessageIter *i, const char *property, void *data) { \ + const char *value; \ + type *field = data; \ + \ + assert(i); \ + assert(property); \ + \ + value = name##_to_string(*field); \ + \ + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &value)) \ + return -ENOMEM; \ + \ + return 0; \ + } + +const char *bus_errno_to_dbus(int error); + +DBusMessage* bus_properties_changed_new(const char *path, const char *interface, const char *properties); + +uint32_t bus_flags_to_events(DBusWatch *bus_watch); +unsigned bus_events_to_flags(uint32_t events); + +int bus_parse_strv(DBusMessage *m, char ***_l); +int bus_parse_strv_iter(DBusMessageIter *iter, char ***_l); + +int bus_append_strv_iter(DBusMessageIter *iter, char **l); + +int bus_iter_get_basic_and_next(DBusMessageIter *iter, int type, void *data, bool next); + +int generic_print_property(const char *name, DBusMessageIter *iter, bool all); + +void bus_async_unregister_and_exit(DBusConnection *bus, const char *name); + +DBusHandlerResult bus_exit_idle_filter(DBusConnection *bus, DBusMessage *m, void *userdata); + +#endif diff --git a/src/dbus-device.c b/src/dbus-device.c new file mode 100644 index 000000000..b39fb9d38 --- /dev/null +++ b/src/dbus-device.c @@ -0,0 +1,65 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "dbus-unit.h" +#include "dbus-device.h" +#include "dbus-common.h" + +#define BUS_DEVICE_INTERFACE \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_DEVICE_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Device\0" + +const char bus_device_interface[] _introspect_("Device") = BUS_DEVICE_INTERFACE; + +const char bus_device_invalidating_properties[] = + "SysFSPath\0"; + +static const BusProperty bus_device_properties[] = { + { "SysFSPath", bus_property_append_string, "s", offsetof(Device, sysfs), true }, + { NULL, } +}; + + +DBusHandlerResult bus_device_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Device *d = DEVICE(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Device", bus_device_properties, d }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/dbus-device.h b/src/dbus-device.h new file mode 100644 index 000000000..fba270b4e --- /dev/null +++ b/src/dbus-device.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusdevicehfoo +#define foodbusdevicehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_device_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_device_interface[]; +extern const char bus_device_invalidating_properties[]; + +#endif diff --git a/src/dbus-execute.c b/src/dbus-execute.c new file mode 100644 index 000000000..1fd2b2133 --- /dev/null +++ b/src/dbus-execute.c @@ -0,0 +1,422 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "dbus-execute.h" +#include "missing.h" +#include "ioprio.h" +#include "strv.h" +#include "dbus-common.h" + +DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_kill_mode, kill_mode, KillMode); + +DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_input, exec_input, ExecInput); +DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_output, exec_output, ExecOutput); + +int bus_execute_append_env_files(DBusMessageIter *i, const char *property, void *data) { + char **env_files = data, **j; + DBusMessageIter sub, sub2; + + assert(i); + assert(property); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sb)", &sub)) + return -ENOMEM; + + STRV_FOREACH(j, env_files) { + dbus_bool_t b = false; + char *fn = *j; + + if (fn[0] == '-') { + b = true; + fn++; + } + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &fn) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_BOOLEAN, &b) || + !dbus_message_iter_close_container(&sub, &sub2)) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + int32_t n; + + assert(i); + assert(property); + assert(c); + + if (c->oom_score_adjust_set) + n = c->oom_score_adjust; + else { + char *t; + + n = 0; + if (read_one_line_file("/proc/self/oom_score_adj", &t) >= 0) { + safe_atoi(t, &n); + free(t); + } else if (read_one_line_file("/proc/self/oom_adj", &t) >= 0) { + safe_atoi(t, &n); + free(t); + + if (n == OOM_ADJUST_MAX) + n = OOM_SCORE_ADJ_MAX; + else + n = (n * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE; + } + } + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_nice(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + int32_t n; + + assert(i); + assert(property); + assert(c); + + if (c->nice_set) + n = c->nice; + else + n = getpriority(PRIO_PROCESS, 0); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_ioprio(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + int32_t n; + + assert(i); + assert(property); + assert(c); + + if (c->ioprio_set) + n = c->ioprio; + else + n = ioprio_get(IOPRIO_WHO_PROCESS, 0); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_cpu_sched_policy(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + int32_t n; + + assert(i); + assert(property); + assert(c); + + if (c->cpu_sched_set) + n = c->cpu_sched_policy; + else + n = sched_getscheduler(0); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_cpu_sched_priority(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + int32_t n; + + assert(i); + assert(property); + assert(c); + + if (c->cpu_sched_set) + n = c->cpu_sched_priority; + else { + struct sched_param p; + n = 0; + + zero(p); + if (sched_getparam(0, &p) >= 0) + n = p.sched_priority; + } + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_affinity(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + dbus_bool_t b; + DBusMessageIter sub; + + assert(i); + assert(property); + assert(c); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "y", &sub)) + return -ENOMEM; + + if (c->cpuset) + b = dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_BYTE, &c->cpuset, CPU_ALLOC_SIZE(c->cpuset_ncpus)); + else + b = dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_BYTE, &c->cpuset, 0); + + if (!b) + return -ENOMEM; + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_timer_slack_nsec(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + uint64_t u; + + assert(i); + assert(property); + assert(c); + + if (c->timer_slack_nsec_set) + u = (uint64_t) c->timer_slack_nsec; + else + u = (uint64_t) prctl(PR_GET_TIMERSLACK); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_capability_bs(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + uint64_t normal, inverted; + + assert(i); + assert(property); + assert(c); + + /* We store this negated internally, to match the kernel, but + * we expose it normalized. */ + + normal = *(uint64_t*) data; + inverted = ~normal; + + return bus_property_append_uint64(i, property, &inverted); +} + +int bus_execute_append_capabilities(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + char *t = NULL; + const char *s; + dbus_bool_t b; + + assert(i); + assert(property); + assert(c); + + if (c->capabilities) + s = t = cap_to_text(c->capabilities, NULL); + else + s = ""; + + if (!s) + return -ENOMEM; + + b = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &s); + + if (t) + cap_free(t); + + if (!b) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_rlimits(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + int r; + uint64_t u; + + assert(i); + assert(property); + assert(c); + + assert_se((r = rlimit_from_string(property)) >= 0); + + if (c->rlimit[r]) + u = (uint64_t) c->rlimit[r]->rlim_max; + else { + struct rlimit rl; + + zero(rl); + getrlimit(r, &rl); + + u = (uint64_t) rl.rlim_max; + } + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_command(DBusMessageIter *i, const char *property, void *data) { + ExecCommand *c = data; + DBusMessageIter sub, sub2, sub3; + + assert(i); + assert(property); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sasbttttuii)", &sub)) + return -ENOMEM; + + LIST_FOREACH(command, c, c) { + char **l; + uint32_t pid; + int32_t code, status; + dbus_bool_t b; + + if (!c->path) + continue; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &c->path) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_ARRAY, "s", &sub3)) + return -ENOMEM; + + STRV_FOREACH(l, c->argv) + if (!dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, l)) + return -ENOMEM; + + pid = (uint32_t) c->exec_status.pid; + code = (int32_t) c->exec_status.code; + status = (int32_t) c->exec_status.status; + + b = !!c->ignore; + + if (!dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_BOOLEAN, &b) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.start_timestamp.realtime) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.start_timestamp.monotonic) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.exit_timestamp.realtime) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.exit_timestamp.monotonic) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &pid) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_INT32, &code) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_INT32, &status)) + return -ENOMEM; + + if (!dbus_message_iter_close_container(&sub, &sub2)) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +const BusProperty bus_exec_context_properties[] = { + { "Environment", bus_property_append_strv, "as", offsetof(ExecContext, environment), true }, + { "EnvironmentFiles", bus_execute_append_env_files, "a(sb)", offsetof(ExecContext, environment_files), true }, + { "UMask", bus_property_append_mode, "u", offsetof(ExecContext, umask) }, + { "LimitCPU", bus_execute_append_rlimits, "t", 0 }, + { "LimitFSIZE", bus_execute_append_rlimits, "t", 0 }, + { "LimitDATA", bus_execute_append_rlimits, "t", 0 }, + { "LimitSTACK", bus_execute_append_rlimits, "t", 0 }, + { "LimitCORE", bus_execute_append_rlimits, "t", 0 }, + { "LimitRSS", bus_execute_append_rlimits, "t", 0 }, + { "LimitNOFILE", bus_execute_append_rlimits, "t", 0 }, + { "LimitAS", bus_execute_append_rlimits, "t", 0 }, + { "LimitNPROC", bus_execute_append_rlimits, "t", 0 }, + { "LimitMEMLOCK", bus_execute_append_rlimits, "t", 0 }, + { "LimitLOCKS", bus_execute_append_rlimits, "t", 0 }, + { "LimitSIGPENDING", bus_execute_append_rlimits, "t", 0 }, + { "LimitMSGQUEUE", bus_execute_append_rlimits, "t", 0 }, + { "LimitNICE", bus_execute_append_rlimits, "t", 0 }, + { "LimitRTPRIO", bus_execute_append_rlimits, "t", 0 }, + { "LimitRTTIME", bus_execute_append_rlimits, "t", 0 }, + { "WorkingDirectory", bus_property_append_string, "s", offsetof(ExecContext, working_directory), true }, + { "RootDirectory", bus_property_append_string, "s", offsetof(ExecContext, root_directory), true }, + { "OOMScoreAdjust", bus_execute_append_oom_score_adjust, "i", 0 }, + { "Nice", bus_execute_append_nice, "i", 0 }, + { "IOScheduling", bus_execute_append_ioprio, "i", 0 }, + { "CPUSchedulingPolicy", bus_execute_append_cpu_sched_policy, "i", 0 }, + { "CPUSchedulingPriority", bus_execute_append_cpu_sched_priority, "i", 0 }, + { "CPUAffinity", bus_execute_append_affinity, "ay", 0 }, + { "TimerSlackNSec", bus_execute_append_timer_slack_nsec, "t", 0 }, + { "CPUSchedulingResetOnFork", bus_property_append_bool, "b", offsetof(ExecContext, cpu_sched_reset_on_fork) }, + { "NonBlocking", bus_property_append_bool, "b", offsetof(ExecContext, non_blocking) }, + { "StandardInput", bus_execute_append_input, "s", offsetof(ExecContext, std_input) }, + { "StandardOutput", bus_execute_append_output, "s", offsetof(ExecContext, std_output) }, + { "StandardError", bus_execute_append_output, "s", offsetof(ExecContext, std_error) }, + { "TTYPath", bus_property_append_string, "s", offsetof(ExecContext, tty_path), true }, + { "TTYReset", bus_property_append_bool, "b", offsetof(ExecContext, tty_reset) }, + { "TTYVHangup", bus_property_append_bool, "b", offsetof(ExecContext, tty_vhangup) }, + { "TTYVTDisallocate", bus_property_append_bool, "b", offsetof(ExecContext, tty_vt_disallocate) }, + { "SyslogPriority", bus_property_append_int, "i", offsetof(ExecContext, syslog_priority) }, + { "SyslogIdentifier", bus_property_append_string, "s", offsetof(ExecContext, syslog_identifier), true }, + { "SyslogLevelPrefix", bus_property_append_bool, "b", offsetof(ExecContext, syslog_level_prefix) }, + { "Capabilities", bus_execute_append_capabilities, "s", 0 }, + { "SecureBits", bus_property_append_int, "i", offsetof(ExecContext, secure_bits) }, + { "CapabilityBoundingSet", bus_execute_append_capability_bs, "t", offsetof(ExecContext, capability_bounding_set_drop) }, + { "User", bus_property_append_string, "s", offsetof(ExecContext, user), true }, + { "Group", bus_property_append_string, "s", offsetof(ExecContext, group), true }, + { "SupplementaryGroups", bus_property_append_strv, "as", offsetof(ExecContext, supplementary_groups), true }, + { "TCPWrapName", bus_property_append_string, "s", offsetof(ExecContext, tcpwrap_name), true }, + { "PAMName", bus_property_append_string, "s", offsetof(ExecContext, pam_name), true }, + { "ReadWriteDirectories", bus_property_append_strv, "as", offsetof(ExecContext, read_write_dirs), true }, + { "ReadOnlyDirectories", bus_property_append_strv, "as", offsetof(ExecContext, read_only_dirs), true }, + { "InaccessibleDirectories", bus_property_append_strv, "as", offsetof(ExecContext, inaccessible_dirs), true }, + { "MountFlags", bus_property_append_ul, "t", offsetof(ExecContext, mount_flags) }, + { "PrivateTmp", bus_property_append_bool, "b", offsetof(ExecContext, private_tmp) }, + { "PrivateNetwork", bus_property_append_bool, "b", offsetof(ExecContext, private_network) }, + { "SameProcessGroup", bus_property_append_bool, "b", offsetof(ExecContext, same_pgrp) }, + { "KillMode", bus_execute_append_kill_mode, "s", offsetof(ExecContext, kill_mode) }, + { "KillSignal", bus_property_append_int, "i", offsetof(ExecContext, kill_signal) }, + { "UtmpIdentifier", bus_property_append_string, "s", offsetof(ExecContext, utmp_id), true }, + { "ControlGroupModify", bus_property_append_bool, "b", offsetof(ExecContext, control_group_modify) }, + { "ControlGroupPersistent", bus_property_append_tristate_false, "b", offsetof(ExecContext, control_group_persistent) }, + { "IgnoreSIGPIPE", bus_property_append_bool, "b", offsetof(ExecContext, ignore_sigpipe ) }, + { NULL, } +}; diff --git a/src/dbus-execute.h b/src/dbus-execute.h new file mode 100644 index 000000000..03cd69d12 --- /dev/null +++ b/src/dbus-execute.h @@ -0,0 +1,125 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusexecutehfoo +#define foodbusexecutehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "manager.h" +#include "dbus-common.h" + +#define BUS_EXEC_STATUS_INTERFACE(prefix) \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_EXEC_CONTEXT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_EXEC_COMMAND_INTERFACE(name) \ + " \n" + +extern const BusProperty bus_exec_context_properties[]; + +#define BUS_EXEC_COMMAND_PROPERTY(name, command, indirect) \ + { name, bus_execute_append_command, "a(sasbttttuii)", (command), (indirect), NULL } + +int bus_execute_append_output(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_input(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_nice(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_ioprio(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_cpu_sched_policy(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_cpu_sched_priority(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_affinity(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_timer_slack_nsec(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_capabilities(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_capability_bs(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_rlimits(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_command(DBusMessageIter *u, const char *property, void *data); +int bus_execute_append_kill_mode(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_env_files(DBusMessageIter *i, const char *property, void *data); + +#endif diff --git a/src/dbus-job.c b/src/dbus-job.c new file mode 100644 index 000000000..ab6d61024 --- /dev/null +++ b/src/dbus-job.c @@ -0,0 +1,354 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus.h" +#include "log.h" +#include "dbus-job.h" +#include "dbus-common.h" + +#define BUS_JOB_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_JOB_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +const char bus_job_interface[] _introspect_("Job") = BUS_JOB_INTERFACE; + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.systemd1.Job\0" + +#define INVALIDATING_PROPERTIES \ + "State\0" + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_state, job_state, JobState); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_type, job_type, JobType); + +static int bus_job_append_unit(DBusMessageIter *i, const char *property, void *data) { + Job *j = data; + DBusMessageIter sub; + char *p; + + assert(i); + assert(property); + assert(j); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub)) + return -ENOMEM; + + if (!(p = unit_dbus_path(j->unit))) + return -ENOMEM; + + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &j->unit->id) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + return -ENOMEM; + } + + free(p); + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static const BusProperty bus_job_properties[] = { + { "Id", bus_property_append_uint32, "u", offsetof(Job, id) }, + { "State", bus_job_append_state, "s", offsetof(Job, state) }, + { "JobType", bus_job_append_type, "s", offsetof(Job, type) }, + { "Unit", bus_job_append_unit, "(so)", 0 }, + { NULL, } +}; + +static DBusHandlerResult bus_job_message_dispatch(Job *j, DBusConnection *connection, DBusMessage *message) { + DBusMessage *reply = NULL; + + if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Job", "Cancel")) { + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + job_finish_and_invalidate(j, JOB_CANCELED); + + } else { + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Job", bus_job_properties, j }, + { NULL, } + }; + return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); + } + + if (reply) { + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static DBusHandlerResult bus_job_message_handler(DBusConnection *connection, DBusMessage *message, void *data) { + Manager *m = data; + Job *j; + int r; + DBusMessage *reply; + + assert(connection); + assert(message); + assert(m); + + if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/job")) { + /* Be nice to gdbus and return introspection data for our mid-level paths */ + + if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { + char *introspection = NULL; + FILE *f; + Iterator i; + size_t size; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + /* We roll our own introspection code here, instead of + * relying on bus_default_message_handler() because we + * need to generate our introspection string + * dynamically. */ + + if (!(f = open_memstream(&introspection, &size))) + goto oom; + + fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "\n", f); + + fputs(BUS_INTROSPECTABLE_INTERFACE, f); + fputs(BUS_PEER_INTERFACE, f); + + HASHMAP_FOREACH(j, m->jobs, i) + fprintf(f, "", (unsigned long) j->id); + + fputs("\n", f); + + if (ferror(f)) { + fclose(f); + free(introspection); + goto oom; + } + + fclose(f); + + if (!introspection) + goto oom; + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { + free(introspection); + goto oom; + } + + free(introspection); + + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if ((r = manager_get_job_from_dbus_path(m, dbus_message_get_path(message), &j)) < 0) { + + if (r == -ENOMEM) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (r == -ENOENT) { + DBusError e; + + dbus_error_init(&e); + dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown job"); + return bus_send_error_reply(connection, message, &e, r); + } + + return bus_send_error_reply(connection, message, NULL, r); + } + + return bus_job_message_dispatch(j, connection, message); + +oom: + if (reply) + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +const DBusObjectPathVTable bus_job_vtable = { + .message_function = bus_job_message_handler +}; + +static int job_send_message(Job *j, DBusMessage *m) { + int r; + + assert(j); + assert(m); + + if (bus_has_subscriber(j->manager)) { + if ((r = bus_broadcast(j->manager, m)) < 0) + return r; + + } else if (j->bus_client) { + /* If nobody is subscribed, we just send the message + * to the client which created the job */ + + assert(j->bus); + + if (!dbus_message_set_destination(m, j->bus_client)) + return -ENOMEM; + + if (!dbus_connection_send(j->bus, m, NULL)) + return -ENOMEM; + } + + return 0; +} + +void bus_job_send_change_signal(Job *j) { + char *p = NULL; + DBusMessage *m = NULL; + + assert(j); + + if (j->in_dbus_queue) { + LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j); + j->in_dbus_queue = false; + } + + if (!bus_has_subscriber(j->manager) && !j->bus_client) { + j->sent_dbus_new_signal = true; + return; + } + + if (!(p = job_dbus_path(j))) + goto oom; + + if (j->sent_dbus_new_signal) { + /* Send a properties changed signal */ + + if (!(m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Job", INVALIDATING_PROPERTIES))) + goto oom; + + } else { + /* Send a new signal */ + + if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew"))) + goto oom; + + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &j->id, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID)) + goto oom; + } + + if (job_send_message(j, m) < 0) + goto oom; + + free(p); + dbus_message_unref(m); + + j->sent_dbus_new_signal = true; + + return; + +oom: + free(p); + + if (m) + dbus_message_unref(m); + + log_error("Failed to allocate job change signal."); +} + +void bus_job_send_removed_signal(Job *j) { + char *p = NULL; + DBusMessage *m = NULL; + const char *r; + + assert(j); + + if (!bus_has_subscriber(j->manager) && !j->bus_client) + return; + + if (!j->sent_dbus_new_signal) + bus_job_send_change_signal(j); + + if (!(p = job_dbus_path(j))) + goto oom; + + if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved"))) + goto oom; + + r = job_result_to_string(j->result); + + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &j->id, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_STRING, &r, + DBUS_TYPE_INVALID)) + goto oom; + + if (job_send_message(j, m) < 0) + goto oom; + + free(p); + dbus_message_unref(m); + + return; + +oom: + free(p); + + if (m) + dbus_message_unref(m); + + log_error("Failed to allocate job remove signal."); +} diff --git a/src/dbus-job.h b/src/dbus-job.h new file mode 100644 index 000000000..103c2ff3e --- /dev/null +++ b/src/dbus-job.h @@ -0,0 +1,36 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusjobhfoo +#define foodbusjobhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "job.h" + +void bus_job_send_change_signal(Job *j); +void bus_job_send_removed_signal(Job *j); + +extern const DBusObjectPathVTable bus_job_vtable; + +extern const char bus_job_interface[]; + +#endif diff --git a/src/dbus-loop.c b/src/dbus-loop.c new file mode 100644 index 000000000..8eb1d171a --- /dev/null +++ b/src/dbus-loop.c @@ -0,0 +1,263 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "dbus-loop.h" +#include "dbus-common.h" +#include "util.h" + +/* Minimal implementation of the dbus loop which integrates all dbus + * events into a single epoll fd which we can triviall integrate with + * other loops. Note that this is not used in the main systemd daemon + * since we run a more elaborate mainloop there. */ + +typedef struct EpollData { + int fd; + void *object; + bool is_timeout:1; + bool fd_is_dupped:1; +} EpollData; + +static dbus_bool_t add_watch(DBusWatch *watch, void *data) { + EpollData *e; + struct epoll_event ev; + + assert(watch); + + e = new0(EpollData, 1); + if (!e) + return FALSE; + + e->fd = dbus_watch_get_unix_fd(watch); + e->object = watch; + e->is_timeout = false; + + zero(ev); + ev.events = bus_flags_to_events(watch); + ev.data.ptr = e; + + if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) { + + if (errno != EEXIST) { + free(e); + return FALSE; + } + + /* Hmm, bloody D-Bus creates multiple watches on the + * same fd. epoll() does not like that. As a dirty + * hack we simply dup() the fd and hence get a second + * one we can safely add to the epoll(). */ + + e->fd = dup(e->fd); + if (e->fd < 0) { + free(e); + return FALSE; + } + + if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) { + close_nointr_nofail(e->fd); + free(e); + return FALSE; + } + + e->fd_is_dupped = true; + } + + dbus_watch_set_data(watch, e, NULL); + + return TRUE; +} + +static void remove_watch(DBusWatch *watch, void *data) { + EpollData *e; + + assert(watch); + + e = dbus_watch_get_data(watch); + if (!e) + return; + + assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_DEL, e->fd, NULL) >= 0); + + if (e->fd_is_dupped) + close_nointr_nofail(e->fd); + + free(e); +} + +static void toggle_watch(DBusWatch *watch, void *data) { + EpollData *e; + struct epoll_event ev; + + assert(watch); + + e = dbus_watch_get_data(watch); + if (!e) + return; + + zero(ev); + ev.events = bus_flags_to_events(watch); + ev.data.ptr = e; + + assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_MOD, e->fd, &ev) == 0); +} + +static int timeout_arm(EpollData *e) { + struct itimerspec its; + + assert(e); + assert(e->is_timeout); + + zero(its); + + if (dbus_timeout_get_enabled(e->object)) { + timespec_store(&its.it_value, dbus_timeout_get_interval(e->object) * USEC_PER_MSEC); + its.it_interval = its.it_value; + } + + if (timerfd_settime(e->fd, 0, &its, NULL) < 0) + return -errno; + + return 0; +} + +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) { + EpollData *e; + struct epoll_event ev; + + assert(timeout); + + e = new0(EpollData, 1); + if (!e) + return FALSE; + + e->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); + if (e->fd < 0) + goto fail; + + e->object = timeout; + e->is_timeout = true; + + if (timeout_arm(e) < 0) + goto fail; + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = e; + + if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) + goto fail; + + dbus_timeout_set_data(timeout, e, NULL); + + return TRUE; + +fail: + if (e->fd >= 0) + close_nointr_nofail(e->fd); + + free(e); + return FALSE; +} + +static void remove_timeout(DBusTimeout *timeout, void *data) { + EpollData *e; + + assert(timeout); + + e = dbus_timeout_get_data(timeout); + if (!e) + return; + + assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_DEL, e->fd, NULL) >= 0); + close_nointr_nofail(e->fd); + free(e); +} + +static void toggle_timeout(DBusTimeout *timeout, void *data) { + EpollData *e; + int r; + + assert(timeout); + + e = dbus_timeout_get_data(timeout); + if (!e) + return; + + r = timeout_arm(e); + if (r < 0) + log_error("Failed to rearm timer: %s", strerror(-r)); +} + +int bus_loop_open(DBusConnection *c) { + int fd; + + assert(c); + + fd = epoll_create1(EPOLL_CLOEXEC); + if (fd < 0) + return -errno; + + if (!dbus_connection_set_watch_functions(c, add_watch, remove_watch, toggle_watch, INT_TO_PTR(fd), NULL) || + !dbus_connection_set_timeout_functions(c, add_timeout, remove_timeout, toggle_timeout, INT_TO_PTR(fd), NULL)) { + close_nointr_nofail(fd); + return -ENOMEM; + } + + return fd; +} + +int bus_loop_dispatch(int fd) { + int n; + struct epoll_event event; + EpollData *d; + + assert(fd >= 0); + + zero(event); + + n = epoll_wait(fd, &event, 1, 0); + if (n < 0) + return errno == EAGAIN || errno == EINTR ? 0 : -errno; + + assert_se(d = event.data.ptr); + + if (d->is_timeout) { + DBusTimeout *t = d->object; + + if (dbus_timeout_get_enabled(t)) + dbus_timeout_handle(t); + } else { + DBusWatch *w = d->object; + + if (dbus_watch_get_enabled(w)) + dbus_watch_handle(w, bus_events_to_flags(event.events)); + } + + return 0; +} diff --git a/src/dbus-loop.h b/src/dbus-loop.h new file mode 100644 index 000000000..0bbdfe592 --- /dev/null +++ b/src/dbus-loop.h @@ -0,0 +1,30 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusloophfoo +#define foodbusloophfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +int bus_loop_open(DBusConnection *c); +int bus_loop_dispatch(int fd); + +#endif diff --git a/src/dbus-manager.c b/src/dbus-manager.c new file mode 100644 index 000000000..6d272cb32 --- /dev/null +++ b/src/dbus-manager.c @@ -0,0 +1,1537 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "dbus.h" +#include "log.h" +#include "dbus-manager.h" +#include "strv.h" +#include "bus-errors.h" +#include "build.h" +#include "dbus-common.h" +#include "install.h" + +#define BUS_MANAGER_INTERFACE_BEGIN \ + " \n" + +#define BUS_MANAGER_INTERFACE_METHODS \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_MANAGER_INTERFACE_SIGNALS \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " " \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " " \ + " \n" + +#define BUS_MANAGER_INTERFACE_PROPERTIES_GENERAL \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#ifdef HAVE_SYSV_COMPAT +#define BUS_MANAGER_INTERFACE_PROPERTIES_SYSV \ + " \n" \ + " \n" \ + " \n" +#else +#define BUS_MANAGER_INTERFACE_PROPERTIES_SYSV +#endif + +#define BUS_MANAGER_INTERFACE_END \ + " \n" + +#define BUS_MANAGER_INTERFACE \ + BUS_MANAGER_INTERFACE_BEGIN \ + BUS_MANAGER_INTERFACE_METHODS \ + BUS_MANAGER_INTERFACE_SIGNALS \ + BUS_MANAGER_INTERFACE_PROPERTIES_GENERAL \ + BUS_MANAGER_INTERFACE_PROPERTIES_SYSV \ + BUS_MANAGER_INTERFACE_END + +#define INTROSPECTION_BEGIN \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_MANAGER_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE + +#define INTROSPECTION_END \ + "\n" + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.systemd1.Manager\0" + +const char bus_manager_interface[] _introspect_("Manager") = BUS_MANAGER_INTERFACE; + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_manager_append_running_as, manager_running_as, ManagerRunningAs); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_manager_append_exec_output, exec_output, ExecOutput); + +static int bus_manager_append_tainted(DBusMessageIter *i, const char *property, void *data) { + const char *t; + Manager *m = data; + char buf[LINE_MAX] = "", *e = buf, *p = NULL; + + assert(i); + assert(property); + assert(m); + + if (m->taint_usr) + e = stpcpy(e, "usr-separate-fs "); + + if (readlink_malloc("/etc/mtab", &p) < 0) + e = stpcpy(e, "etc-mtab-not-symlink "); + else + free(p); + + if (access("/proc/cgroups", F_OK) < 0) + stpcpy(e, "cgroups-missing "); + + t = strstrip(buf); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) + return -ENOMEM; + + return 0; +} + +static int bus_manager_append_log_target(DBusMessageIter *i, const char *property, void *data) { + const char *t; + + assert(i); + assert(property); + + t = log_target_to_string(log_get_target()); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) + return -ENOMEM; + + return 0; +} + +static int bus_manager_set_log_target(DBusMessageIter *i, const char *property) { + const char *t; + + assert(i); + assert(property); + + dbus_message_iter_get_basic(i, &t); + + return log_set_target_from_string(t); +} + +static int bus_manager_append_log_level(DBusMessageIter *i, const char *property, void *data) { + const char *t; + + assert(i); + assert(property); + + t = log_level_to_string(log_get_max_level()); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) + return -ENOMEM; + + return 0; +} + +static int bus_manager_set_log_level(DBusMessageIter *i, const char *property) { + const char *t; + + assert(i); + assert(property); + + dbus_message_iter_get_basic(i, &t); + + return log_set_max_level_from_string(t); +} + +static int bus_manager_append_n_names(DBusMessageIter *i, const char *property, void *data) { + Manager *m = data; + uint32_t u; + + assert(i); + assert(property); + assert(m); + + u = hashmap_size(m->units); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, &u)) + return -ENOMEM; + + return 0; +} + +static int bus_manager_append_n_jobs(DBusMessageIter *i, const char *property, void *data) { + Manager *m = data; + uint32_t u; + + assert(i); + assert(property); + assert(m); + + u = hashmap_size(m->jobs); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, &u)) + return -ENOMEM; + + return 0; +} + +static int bus_manager_append_progress(DBusMessageIter *i, const char *property, void *data) { + double d; + Manager *m = data; + + assert(i); + assert(property); + assert(m); + + if (dual_timestamp_is_set(&m->finish_timestamp)) + d = 1.0; + else + d = 1.0 - ((double) hashmap_size(m->jobs) / (double) m->n_installed_jobs); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_DOUBLE, &d)) + return -ENOMEM; + + return 0; +} + +static const char *message_get_sender_with_fallback(DBusMessage *m) { + const char *s; + + assert(m); + + if ((s = dbus_message_get_sender(m))) + return s; + + /* When the message came in from a direct connection the + * message will have no sender. We fix that here. */ + + return ":no-sender"; +} + +static DBusMessage *message_from_file_changes( + DBusMessage *m, + UnitFileChange *changes, + unsigned n_changes, + int carries_install_info) { + + DBusMessageIter iter, sub, sub2; + DBusMessage *reply; + unsigned i; + + reply = dbus_message_new_method_return(m); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + if (carries_install_info >= 0) { + dbus_bool_t b; + + b = !!carries_install_info; + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &b)) + goto oom; + } + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sss)", &sub)) + goto oom; + + for (i = 0; i < n_changes; i++) { + const char *type, *path, *source; + + type = unit_file_change_type_to_string(changes[i].type); + path = strempty(changes[i].path); + source = strempty(changes[i].source); + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &type) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &path) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &source) || + !dbus_message_iter_close_container(&sub, &sub2)) + goto oom; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + return reply; + +oom: + dbus_message_unref(reply); + return NULL; +} + +static int bus_manager_send_unit_files_changed(Manager *m) { + DBusMessage *s; + int r; + + s = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitFilesChanged"); + if (!s) + return -ENOMEM; + + r = bus_broadcast(m, s); + dbus_message_unref(s); + + return r; +} + +static const char systemd_property_string[] = PACKAGE_STRING "\0" DISTRIBUTION "\0" SYSTEMD_FEATURES; + +static const BusProperty bus_systemd_properties[] = { + { "Version", bus_property_append_string, "s", 0 }, + { "Distribution", bus_property_append_string, "s", sizeof(PACKAGE_STRING) }, + { "Features", bus_property_append_string, "s", sizeof(PACKAGE_STRING) + sizeof(DISTRIBUTION) }, + { NULL, } +}; + +static const BusProperty bus_manager_properties[] = { + { "RunningAs", bus_manager_append_running_as, "s", offsetof(Manager, running_as) }, + { "Tainted", bus_manager_append_tainted, "s", 0 }, + { "InitRDTimestamp", bus_property_append_uint64, "t", offsetof(Manager, initrd_timestamp.realtime) }, + { "InitRDTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, initrd_timestamp.monotonic) }, + { "StartupTimestamp", bus_property_append_uint64, "t", offsetof(Manager, startup_timestamp.realtime) }, + { "StartupTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, startup_timestamp.monotonic) }, + { "FinishTimestamp", bus_property_append_uint64, "t", offsetof(Manager, finish_timestamp.realtime) }, + { "FinishTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, finish_timestamp.monotonic) }, + { "LogLevel", bus_manager_append_log_level, "s", 0, 0, bus_manager_set_log_level }, + { "LogTarget", bus_manager_append_log_target, "s", 0, 0, bus_manager_set_log_target }, + { "NNames", bus_manager_append_n_names, "u", 0 }, + { "NJobs", bus_manager_append_n_jobs, "u", 0 }, + { "NInstalledJobs",bus_property_append_uint32, "u", offsetof(Manager, n_installed_jobs) }, + { "NFailedJobs", bus_property_append_uint32, "u", offsetof(Manager, n_failed_jobs) }, + { "Progress", bus_manager_append_progress, "d", 0 }, + { "Environment", bus_property_append_strv, "as", offsetof(Manager, environment), true }, + { "ConfirmSpawn", bus_property_append_bool, "b", offsetof(Manager, confirm_spawn) }, + { "ShowStatus", bus_property_append_bool, "b", offsetof(Manager, show_status) }, + { "UnitPath", bus_property_append_strv, "as", offsetof(Manager, lookup_paths.unit_path), true }, + { "NotifySocket", bus_property_append_string, "s", offsetof(Manager, notify_socket), true }, + { "ControlGroupHierarchy", bus_property_append_string, "s", offsetof(Manager, cgroup_hierarchy), true }, + { "MountAuto", bus_property_append_bool, "b", offsetof(Manager, mount_auto) }, + { "SwapAuto", bus_property_append_bool, "b", offsetof(Manager, swap_auto) }, + { "DefaultControllers", bus_property_append_strv, "as", offsetof(Manager, default_controllers), true }, + { "DefaultStandardOutput", bus_manager_append_exec_output, "s", offsetof(Manager, default_std_output) }, + { "DefaultStandardError", bus_manager_append_exec_output, "s", offsetof(Manager, default_std_error) }, +#ifdef HAVE_SYSV_COMPAT + { "SysVConsole", bus_property_append_bool, "b", offsetof(Manager, sysv_console) }, + { "SysVInitPath", bus_property_append_strv, "as", offsetof(Manager, lookup_paths.sysvinit_path), true }, + { "SysVRcndPath", bus_property_append_strv, "as", offsetof(Manager, lookup_paths.sysvrcnd_path), true }, +#endif + { NULL, } +}; + +static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, DBusMessage *message, void *data) { + Manager *m = data; + + int r; + DBusError error; + DBusMessage *reply = NULL; + char * path = NULL; + JobType job_type = _JOB_TYPE_INVALID; + bool reload_if_possible = false; + const char *member; + + assert(connection); + assert(message); + assert(m); + + dbus_error_init(&error); + + member = dbus_message_get_member(message); + + if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnit")) { + const char *name; + Unit *u; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!(u = manager_get_unit(m, name))) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = unit_dbus_path(u))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitByPID")) { + Unit *u; + uint32_t pid; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &pid, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!(u = cgroup_unit_by_pid(m, (pid_t) pid))) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "No unit for PID %lu is loaded.", (unsigned long) pid); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = unit_dbus_path(u))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "LoadUnit")) { + const char *name; + Unit *u; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if ((r = manager_load_unit(m, name, NULL, &error, &u)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = unit_dbus_path(u))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartUnit")) + job_type = JOB_START; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartUnitReplace")) + job_type = JOB_START; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StopUnit")) + job_type = JOB_STOP; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadUnit")) + job_type = JOB_RELOAD; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "RestartUnit")) + job_type = JOB_RESTART; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "TryRestartUnit")) + job_type = JOB_TRY_RESTART; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadOrRestartUnit")) { + reload_if_possible = true; + job_type = JOB_RESTART; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadOrTryRestartUnit")) { + reload_if_possible = true; + job_type = JOB_TRY_RESTART; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "KillUnit")) { + const char *name, *swho, *smode; + int32_t signo; + Unit *u; + KillMode mode; + KillWho who; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &swho, + DBUS_TYPE_STRING, &smode, + DBUS_TYPE_INT32, &signo, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if (isempty(smode)) + mode = KILL_CONTROL_GROUP; + else { + mode = kill_mode_from_string(smode); + if (mode < 0) + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if (signo <= 0 || signo >= _NSIG) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!(u = manager_get_unit(m, name))) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + if ((r = unit_kill(u, who, mode, signo, &error)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetJob")) { + uint32_t id; + Job *j; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!(j = manager_get_job(m, id))) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = job_dbus_path(j))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ClearJobs")) { + + manager_clear_jobs(m); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ResetFailed")) { + + manager_reset_failed(m); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ResetFailedUnit")) { + const char *name; + Unit *u; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!(u = manager_get_unit(m, name))) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + unit_reset_failed(u); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListUnits")) { + DBusMessageIter iter, sub; + Iterator i; + Unit *u; + const char *k; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ssssssouso)", &sub)) + goto oom; + + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + char *u_path, *j_path; + const char *description, *load_state, *active_state, *sub_state, *sjob_type, *following; + DBusMessageIter sub2; + uint32_t job_id; + Unit *f; + + if (k != u->id) + continue; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) + goto oom; + + description = unit_description(u); + load_state = unit_load_state_to_string(u->load_state); + active_state = unit_active_state_to_string(unit_active_state(u)); + sub_state = unit_sub_state_to_string(u); + + f = unit_following(u); + following = f ? f->id : ""; + + if (!(u_path = unit_dbus_path(u))) + goto oom; + + if (u->job) { + job_id = (uint32_t) u->job->id; + + if (!(j_path = job_dbus_path(u->job))) { + free(u_path); + goto oom; + } + + sjob_type = job_type_to_string(u->job->type); + } else { + job_id = 0; + j_path = u_path; + sjob_type = ""; + } + + if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &u->id) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &description) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &load_state) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &active_state) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &sub_state) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &following) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &u_path) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &job_id) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &sjob_type) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &j_path)) { + free(u_path); + if (u->job) + free(j_path); + goto oom; + } + + free(u_path); + if (u->job) + free(j_path); + + if (!dbus_message_iter_close_container(&sub, &sub2)) + goto oom; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListJobs")) { + DBusMessageIter iter, sub; + Iterator i; + Job *j; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(usssoo)", &sub)) + goto oom; + + HASHMAP_FOREACH(j, m->jobs, i) { + char *u_path, *j_path; + const char *state, *type; + uint32_t id; + DBusMessageIter sub2; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) + goto oom; + + id = (uint32_t) j->id; + state = job_state_to_string(j->state); + type = job_type_to_string(j->type); + + if (!(j_path = job_dbus_path(j))) + goto oom; + + if (!(u_path = unit_dbus_path(j->unit))) { + free(j_path); + goto oom; + } + + if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &id) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &j->unit->id) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &type) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &state) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &j_path) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &u_path)) { + free(j_path); + free(u_path); + goto oom; + } + + free(j_path); + free(u_path); + + if (!dbus_message_iter_close_container(&sub, &sub2)) + goto oom; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Subscribe")) { + char *client; + Set *s; + + if (!(s = BUS_CONNECTION_SUBSCRIBED(m, connection))) { + if (!(s = set_new(string_hash_func, string_compare_func))) + goto oom; + + if (!(dbus_connection_set_data(connection, m->subscribed_data_slot, s, NULL))) { + set_free(s); + goto oom; + } + } + + if (!(client = strdup(message_get_sender_with_fallback(message)))) + goto oom; + + if ((r = set_put(s, client)) < 0) { + free(client); + return bus_send_error_reply(connection, message, NULL, r); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Unsubscribe")) { + char *client; + + if (!(client = set_remove(BUS_CONNECTION_SUBSCRIBED(m, connection), (char*) message_get_sender_with_fallback(message)))) { + dbus_set_error(&error, BUS_ERROR_NOT_SUBSCRIBED, "Client is not subscribed."); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + free(client); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Dump")) { + FILE *f; + char *dump = NULL; + size_t size; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(f = open_memstream(&dump, &size))) + goto oom; + + manager_dump_units(m, f, NULL); + manager_dump_jobs(m, f, NULL); + + if (ferror(f)) { + fclose(f); + free(dump); + goto oom; + } + + fclose(f); + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &dump, DBUS_TYPE_INVALID)) { + free(dump); + goto oom; + } + + free(dump); + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "CreateSnapshot")) { + const char *name; + dbus_bool_t cleanup; + Snapshot *s; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_BOOLEAN, &cleanup, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (name && name[0] == 0) + name = NULL; + + if ((r = snapshot_create(m, name, cleanup, &error, &s)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = unit_dbus_path(UNIT(s)))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { + char *introspection = NULL; + FILE *f; + Iterator i; + Unit *u; + Job *j; + const char *k; + size_t size; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + /* We roll our own introspection code here, instead of + * relying on bus_default_message_handler() because we + * need to generate our introspection string + * dynamically. */ + + if (!(f = open_memstream(&introspection, &size))) + goto oom; + + fputs(INTROSPECTION_BEGIN, f); + + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + char *p; + + if (k != u->id) + continue; + + if (!(p = bus_path_escape(k))) { + fclose(f); + free(introspection); + goto oom; + } + + fprintf(f, "", p); + free(p); + } + + HASHMAP_FOREACH(j, m->jobs, i) + fprintf(f, "", (unsigned long) j->id); + + fputs(INTROSPECTION_END, f); + + if (ferror(f)) { + fclose(f); + free(introspection); + goto oom; + } + + fclose(f); + + if (!introspection) + goto oom; + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { + free(introspection); + goto oom; + } + + free(introspection); + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reload")) { + + assert(!m->queued_message); + + /* Instead of sending the reply back right away, we + * just remember that we need to and then send it + * after the reload is finished. That way the caller + * knows when the reload finished. */ + + if (!(m->queued_message = dbus_message_new_method_return(message))) + goto oom; + + m->queued_message_connection = connection; + m->exit_code = MANAGER_RELOAD; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reexecute")) { + + /* We don't send a reply back here, the client should + * just wait for us disconnecting. */ + + m->exit_code = MANAGER_REEXECUTE; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Exit")) { + + if (m->running_as == MANAGER_SYSTEM) { + dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Exit is only supported for user service managers."); + return bus_send_error_reply(connection, message, &error, -ENOTSUP); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + m->exit_code = MANAGER_EXIT; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reboot")) { + + if (m->running_as != MANAGER_SYSTEM) { + dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Reboot is only supported for system managers."); + return bus_send_error_reply(connection, message, &error, -ENOTSUP); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + m->exit_code = MANAGER_REBOOT; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "PowerOff")) { + + if (m->running_as != MANAGER_SYSTEM) { + dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Powering off is only supported for system managers."); + return bus_send_error_reply(connection, message, &error, -ENOTSUP); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + m->exit_code = MANAGER_POWEROFF; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Halt")) { + + if (m->running_as != MANAGER_SYSTEM) { + dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Halting is only supported for system managers."); + return bus_send_error_reply(connection, message, &error, -ENOTSUP); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + m->exit_code = MANAGER_HALT; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "KExec")) { + + if (m->running_as != MANAGER_SYSTEM) { + dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "kexec is only supported for system managers."); + return bus_send_error_reply(connection, message, &error, -ENOTSUP); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + m->exit_code = MANAGER_KEXEC; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetEnvironment")) { + char **l = NULL, **e = NULL; + + if ((r = bus_parse_strv(message, &l)) < 0) { + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + e = strv_env_merge(2, m->environment, l); + strv_free(l); + + if (!e) + goto oom; + + if (!(reply = dbus_message_new_method_return(message))) { + strv_free(e); + goto oom; + } + + strv_free(m->environment); + m->environment = e; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetEnvironment")) { + char **l = NULL, **e = NULL; + + if ((r = bus_parse_strv(message, &l)) < 0) { + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + e = strv_env_delete(m->environment, 1, l); + strv_free(l); + + if (!e) + goto oom; + + if (!(reply = dbus_message_new_method_return(message))) { + strv_free(e); + goto oom; + } + + strv_free(m->environment); + m->environment = e; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetAndSetEnvironment")) { + char **l_set = NULL, **l_unset = NULL, **e = NULL, **f = NULL; + DBusMessageIter iter; + + if (!dbus_message_iter_init(message, &iter)) + goto oom; + + r = bus_parse_strv_iter(&iter, &l_unset); + if (r < 0) { + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + if (!dbus_message_iter_next(&iter)) { + strv_free(l_unset); + return bus_send_error_reply(connection, message, NULL, -EINVAL); + } + + r = bus_parse_strv_iter(&iter, &l_set); + if (r < 0) { + strv_free(l_unset); + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + e = strv_env_delete(m->environment, 1, l_unset); + strv_free(l_unset); + + if (!e) { + strv_free(l_set); + goto oom; + } + + f = strv_env_merge(2, e, l_set); + strv_free(l_set); + strv_free(e); + + if (!f) + goto oom; + + if (!(reply = dbus_message_new_method_return(message))) { + strv_free(f); + goto oom; + } + + strv_free(m->environment); + m->environment = f; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListUnitFiles")) { + DBusMessageIter iter, sub, sub2; + Hashmap *h; + Iterator i; + UnitFileList *item; + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + h = hashmap_new(string_hash_func, string_compare_func); + if (!h) + goto oom; + + r = unit_file_get_list(m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, NULL, h); + if (r < 0) { + unit_file_list_free(h); + dbus_message_unref(reply); + return bus_send_error_reply(connection, message, NULL, r); + } + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ss)", &sub)) { + unit_file_list_free(h); + goto oom; + } + + HASHMAP_FOREACH(item, h, i) { + const char *state; + + state = unit_file_state_to_string(item->state); + assert(state); + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &item->path) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &state) || + !dbus_message_iter_close_container(&sub, &sub2)) { + unit_file_list_free(h); + goto oom; + } + } + + unit_file_list_free(h); + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitFileState")) { + const char *name; + UnitFileState state; + const char *s; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + state = unit_file_get_state(m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, NULL, name); + if (state < 0) + return bus_send_error_reply(connection, message, NULL, state); + + s = unit_file_state_to_string(state); + assert(s); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_STRING, &s, + DBUS_TYPE_INVALID)) + goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "EnableUnitFiles") || + dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReenableUnitFiles") || + dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "LinkUnitFiles") || + dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "PresetUnitFiles") || + dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "MaskUnitFiles")) { + + char **l = NULL; + DBusMessageIter iter; + UnitFileScope scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + dbus_bool_t runtime, force; + int carries_install_info = -1; + + if (!dbus_message_iter_init(message, &iter)) + goto oom; + + r = bus_parse_strv_iter(&iter, &l); + if (r < 0) { + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + if (!dbus_message_iter_next(&iter) || + bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &runtime, true) < 0 || + bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &force, false) < 0) { + strv_free(l); + return bus_send_error_reply(connection, message, NULL, -EIO); + } + + if (streq(member, "EnableUnitFiles")) { + r = unit_file_enable(scope, runtime, NULL, l, force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(member, "ReenableUnitFiles")) { + r = unit_file_reenable(scope, runtime, NULL, l, force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(member, "LinkUnitFiles")) + r = unit_file_link(scope, runtime, NULL, l, force, &changes, &n_changes); + else if (streq(member, "PresetUnitFiles")) { + r = unit_file_preset(scope, runtime, NULL, l, force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(member, "MaskUnitFiles")) + r = unit_file_mask(scope, runtime, NULL, l, force, &changes, &n_changes); + else + assert_not_reached("Uh? Wrong method"); + + strv_free(l); + bus_manager_send_unit_files_changed(m); + + if (r < 0) { + unit_file_changes_free(changes, n_changes); + return bus_send_error_reply(connection, message, NULL, r); + } + + reply = message_from_file_changes(message, changes, n_changes, carries_install_info); + unit_file_changes_free(changes, n_changes); + + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "DisableUnitFiles") || + dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnmaskUnitFiles")) { + + char **l = NULL; + DBusMessageIter iter; + UnitFileScope scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + dbus_bool_t runtime; + + if (!dbus_message_iter_init(message, &iter)) + goto oom; + + r = bus_parse_strv_iter(&iter, &l); + if (r < 0) { + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + if (!dbus_message_iter_next(&iter) || + bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &runtime, false) < 0) { + strv_free(l); + return bus_send_error_reply(connection, message, NULL, -EIO); + } + + if (streq(member, "DisableUnitFiles")) + r = unit_file_disable(scope, runtime, NULL, l, &changes, &n_changes); + else if (streq(member, "UnmaskUnitFiles")) + r = unit_file_unmask(scope, runtime, NULL, l, &changes, &n_changes); + else + assert_not_reached("Uh? Wrong method"); + + strv_free(l); + bus_manager_send_unit_files_changed(m); + + if (r < 0) { + unit_file_changes_free(changes, n_changes); + return bus_send_error_reply(connection, message, NULL, r); + } + + reply = message_from_file_changes(message, changes, n_changes, -1); + unit_file_changes_free(changes, n_changes); + + if (!reply) + goto oom; + + } else { + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Manager", bus_systemd_properties, systemd_property_string }, + { "org.freedesktop.systemd1.Manager", bus_manager_properties, m }, + { NULL, } + }; + return bus_default_message_handler(connection, message, NULL, INTERFACES_LIST, bps); + } + + if (job_type != _JOB_TYPE_INVALID) { + const char *name, *smode, *old_name = NULL; + JobMode mode; + Job *j; + Unit *u; + bool b; + + if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartUnitReplace")) + b = dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &old_name, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &smode, + DBUS_TYPE_INVALID); + else + b = dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &smode, + DBUS_TYPE_INVALID); + + if (!b) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (old_name) + if (!(u = manager_get_unit(m, old_name)) || + !u->job || + u->job->type != JOB_START) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_JOB, "No job queued for unit %s", old_name); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + + if ((mode = job_mode_from_string(smode)) == _JOB_MODE_INVALID) { + dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode); + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if ((r = manager_load_unit(m, name, NULL, &error, &u)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (reload_if_possible && unit_can_reload(u)) { + if (job_type == JOB_RESTART) + job_type = JOB_RELOAD_OR_START; + else if (job_type == JOB_TRY_RESTART) + job_type = JOB_RELOAD; + } + + if ((job_type == JOB_START && u->refuse_manual_start) || + (job_type == JOB_STOP && u->refuse_manual_stop) || + ((job_type == JOB_RESTART || job_type == JOB_TRY_RESTART) && + (u->refuse_manual_start || u->refuse_manual_stop))) { + dbus_set_error(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, may be requested by dependency only."); + return bus_send_error_reply(connection, message, &error, -EPERM); + } + + if ((r = manager_add_job(m, job_type, u, mode, true, &error, &j)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (!(j->bus_client = strdup(message_get_sender_with_fallback(message)))) + goto oom; + + j->bus = connection; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = job_dbus_path(j))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + } + + if (reply) { + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + free(path); + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + free(path); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +const DBusObjectPathVTable bus_manager_vtable = { + .message_function = bus_manager_message_handler +}; diff --git a/src/dbus-manager.h b/src/dbus-manager.h new file mode 100644 index 000000000..2eb2fbbed --- /dev/null +++ b/src/dbus-manager.h @@ -0,0 +1,31 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusmanagerhfoo +#define foodbusmanagerhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +extern const DBusObjectPathVTable bus_manager_vtable; + +extern const char bus_manager_interface[]; + +#endif diff --git a/src/dbus-mount.c b/src/dbus-mount.c new file mode 100644 index 000000000..35d6ea7a1 --- /dev/null +++ b/src/dbus-mount.c @@ -0,0 +1,168 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-mount.h" +#include "dbus-execute.h" +#include "dbus-common.h" + +#define BUS_MOUNT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + BUS_EXEC_COMMAND_INTERFACE("ExecMount") \ + BUS_EXEC_COMMAND_INTERFACE("ExecUnmount") \ + BUS_EXEC_COMMAND_INTERFACE("ExecRemount") \ + BUS_EXEC_CONTEXT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_MOUNT_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Mount\0" + +const char bus_mount_interface[] _introspect_("Mount") = BUS_MOUNT_INTERFACE; + +const char bus_mount_invalidating_properties[] = + "What\0" + "Options\0" + "Type\0" + "ExecMount\0" + "ExecUnmount\0" + "ExecRemount\0" + "ControlPID\0" + "Result\0"; + +static int bus_mount_append_what(DBusMessageIter *i, const char *property, void *data) { + Mount *m = data; + const char *d; + + assert(i); + assert(property); + assert(m); + + if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.what) + d = m->parameters_proc_self_mountinfo.what; + else if (m->from_fragment && m->parameters_fragment.what) + d = m->parameters_fragment.what; + else if (m->from_etc_fstab && m->parameters_etc_fstab.what) + d = m->parameters_etc_fstab.what; + else + d = ""; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) + return -ENOMEM; + + return 0; +} + +static int bus_mount_append_options(DBusMessageIter *i, const char *property, void *data) { + Mount *m = data; + const char *d; + + assert(i); + assert(property); + assert(m); + + if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.options) + d = m->parameters_proc_self_mountinfo.options; + else if (m->from_fragment && m->parameters_fragment.options) + d = m->parameters_fragment.options; + else if (m->from_etc_fstab && m->parameters_etc_fstab.options) + d = m->parameters_etc_fstab.options; + else + d = ""; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) + return -ENOMEM; + + return 0; +} + +static int bus_mount_append_type(DBusMessageIter *i, const char *property, void *data) { + Mount *m = data; + const char *d; + + assert(i); + assert(property); + assert(m); + + if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.fstype) + d = m->parameters_proc_self_mountinfo.fstype; + else if (m->from_fragment && m->parameters_fragment.fstype) + d = m->parameters_fragment.fstype; + else if (m->from_etc_fstab && m->parameters_etc_fstab.fstype) + d = m->parameters_etc_fstab.fstype; + else + d = ""; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) + return -ENOMEM; + + return 0; +} + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_mount_append_mount_result, mount_result, MountResult); + +static const BusProperty bus_mount_properties[] = { + { "Where", bus_property_append_string, "s", offsetof(Mount, where), true }, + { "What", bus_mount_append_what, "s", 0 }, + { "Options", bus_mount_append_options, "s", 0 }, + { "Type", bus_mount_append_type, "s", 0 }, + { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Mount, timeout_usec) }, + BUS_EXEC_COMMAND_PROPERTY("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), false), + BUS_EXEC_COMMAND_PROPERTY("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), false), + BUS_EXEC_COMMAND_PROPERTY("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), false), + { "ControlPID", bus_property_append_pid, "u", offsetof(Mount, control_pid) }, + { "DirectoryMode", bus_property_append_mode, "u", offsetof(Mount, directory_mode) }, + { "Result", bus_mount_append_mount_result, "s", offsetof(Mount, result) }, + { NULL, } +}; + +DBusHandlerResult bus_mount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Mount *m = MOUNT(u); + + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Mount", bus_mount_properties, m }, + { "org.freedesktop.systemd1.Mount", bus_exec_context_properties, &m->exec_context }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps ); +} diff --git a/src/dbus-mount.h b/src/dbus-mount.h new file mode 100644 index 000000000..b5613fa7b --- /dev/null +++ b/src/dbus-mount.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusmounthfoo +#define foodbusmounthfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_mount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_mount_interface[]; +extern const char bus_mount_invalidating_properties[]; + +#endif diff --git a/src/dbus-path.c b/src/dbus-path.c new file mode 100644 index 000000000..5506784c3 --- /dev/null +++ b/src/dbus-path.c @@ -0,0 +1,119 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-path.h" +#include "dbus-execute.h" +#include "dbus-common.h" + +#define BUS_PATH_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_PATH_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Path\0" + +const char bus_path_interface[] _introspect_("Path") = BUS_PATH_INTERFACE; + +const char bus_path_invalidating_properties[] = + "Result\0"; + +static int bus_path_append_paths(DBusMessageIter *i, const char *property, void *data) { + Path *p = data; + DBusMessageIter sub, sub2; + PathSpec *k; + + assert(i); + assert(property); + assert(p); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(ss)", &sub)) + return -ENOMEM; + + LIST_FOREACH(spec, k, p->specs) { + const char *t = path_type_to_string(k->type); + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &t) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &k->path) || + !dbus_message_iter_close_container(&sub, &sub2)) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_path_append_unit(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + Path *p = PATH(u); + const char *t; + + assert(i); + assert(property); + assert(u); + + t = UNIT_DEREF(p->unit) ? UNIT_DEREF(p->unit)->id : ""; + + return dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t) ? 0 : -ENOMEM; +} + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_path_append_path_result, path_result, PathResult); + +static const BusProperty bus_path_properties[] = { + { "Unit", bus_path_append_unit, "s", 0 }, + { "Paths", bus_path_append_paths, "a(ss)", 0 }, + { "MakeDirectory", bus_property_append_bool, "b", offsetof(Path, make_directory) }, + { "DirectoryMode", bus_property_append_mode, "u", offsetof(Path, directory_mode) }, + { "Result", bus_path_append_path_result, "s", offsetof(Path, result) }, + { NULL, } +}; + +DBusHandlerResult bus_path_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Path *p = PATH(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Path", bus_path_properties, p }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/dbus-path.h b/src/dbus-path.h new file mode 100644 index 000000000..2888400a1 --- /dev/null +++ b/src/dbus-path.h @@ -0,0 +1,35 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbuspathhfoo +#define foodbuspathhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_path_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_path_interface[]; + +extern const char bus_path_invalidating_properties[]; + +#endif diff --git a/src/dbus-service.c b/src/dbus-service.c new file mode 100644 index 000000000..780916419 --- /dev/null +++ b/src/dbus-service.c @@ -0,0 +1,168 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-execute.h" +#include "dbus-service.h" +#include "dbus-common.h" + +#ifdef HAVE_SYSV_COMPAT +#define BUS_SERVICE_SYSV_INTERFACE_FRAGMENT \ + " \n" \ + " \n" \ + " \n" +#else +#define BUS_SERVICE_SYSV_INTERFACE_FRAGMENT "" +#endif + +#define BUS_SERVICE_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + BUS_EXEC_COMMAND_INTERFACE("ExecStartPre") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStart") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStartPost") \ + BUS_EXEC_COMMAND_INTERFACE("ExecReload") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStop") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStopPost") \ + BUS_EXEC_CONTEXT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + BUS_EXEC_STATUS_INTERFACE("ExecMain") \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + BUS_SERVICE_SYSV_INTERFACE_FRAGMENT \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_SERVICE_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Service\0" + +const char bus_service_interface[] _introspect_("Service") = BUS_SERVICE_INTERFACE; + +const char bus_service_invalidating_properties[] = + "ExecStartPre\0" + "ExecStart\0" + "ExecStartPost\0" + "ExecReload\0" + "ExecStop\0" + "ExecStopPost\0" + "ExecMain\0" + "WatchdogTimestamp\0" + "WatchdogTimestampMonotonic\0" + "MainPID\0" + "ControlPID\0" + "StatusText\0" + "Result\0"; + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_type, service_type, ServiceType); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_restart, service_restart, ServiceRestart); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_notify_access, notify_access, NotifyAccess); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_service_result, service_result, ServiceResult); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_start_limit_action, start_limit_action, StartLimitAction); + +static const BusProperty bus_exec_main_status_properties[] = { + { "ExecMainStartTimestamp", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.realtime) }, + { "ExecMainStartTimestampMonotonic",bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.monotonic) }, + { "ExecMainExitTimestamp", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.realtime) }, + { "ExecMainExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.monotonic) }, + { "ExecMainPID", bus_property_append_pid, "u", offsetof(ExecStatus, pid) }, + { "ExecMainCode", bus_property_append_int, "i", offsetof(ExecStatus, code) }, + { "ExecMainStatus", bus_property_append_int, "i", offsetof(ExecStatus, status) }, + { NULL, } +}; + +static const BusProperty bus_service_properties[] = { + { "Type", bus_service_append_type, "s", offsetof(Service, type) }, + { "Restart", bus_service_append_restart, "s", offsetof(Service, restart) }, + { "PIDFile", bus_property_append_string, "s", offsetof(Service, pid_file), true }, + { "NotifyAccess", bus_service_append_notify_access, "s", offsetof(Service, notify_access) }, + { "RestartUSec", bus_property_append_usec, "t", offsetof(Service, restart_usec) }, + { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Service, timeout_usec) }, + { "WatchdogUSec", bus_property_append_usec, "t", offsetof(Service, watchdog_usec) }, + { "WatchdogTimestamp", bus_property_append_usec, "t", offsetof(Service, watchdog_timestamp.realtime) }, + { "WatchdogTimestampMonotonic",bus_property_append_usec, "t", offsetof(Service, watchdog_timestamp.monotonic) }, + { "StartLimitInterval", bus_property_append_usec, "t", offsetof(Service, start_limit.interval) }, + { "StartLimitBurst", bus_property_append_uint32, "u", offsetof(Service, start_limit.burst) }, + { "StartLimitAction", bus_service_append_start_limit_action,"s", offsetof(Service, start_limit_action) }, + BUS_EXEC_COMMAND_PROPERTY("ExecStartPre", offsetof(Service, exec_command[SERVICE_EXEC_START_PRE]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStart", offsetof(Service, exec_command[SERVICE_EXEC_START]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStartPost", offsetof(Service, exec_command[SERVICE_EXEC_START_POST]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecReload", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStop", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), true ), + { "PermissionsStartOnly", bus_property_append_bool, "b", offsetof(Service, permissions_start_only) }, + { "RootDirectoryStartOnly", bus_property_append_bool, "b", offsetof(Service, root_directory_start_only) }, + { "RemainAfterExit", bus_property_append_bool, "b", offsetof(Service, remain_after_exit) }, + { "GuessMainPID", bus_property_append_bool, "b", offsetof(Service, guess_main_pid) }, + { "MainPID", bus_property_append_pid, "u", offsetof(Service, main_pid) }, + { "ControlPID", bus_property_append_pid, "u", offsetof(Service, control_pid) }, + { "BusName", bus_property_append_string, "s", offsetof(Service, bus_name), true }, + { "StatusText", bus_property_append_string, "s", offsetof(Service, status_text), true }, +#ifdef HAVE_SYSV_COMPAT + { "SysVRunLevels", bus_property_append_string, "s", offsetof(Service, sysv_runlevels), true }, + { "SysVStartPriority", bus_property_append_int, "i", offsetof(Service, sysv_start_priority) }, + { "SysVPath", bus_property_append_string, "s", offsetof(Service, sysv_path), true }, +#endif + { "FsckPassNo", bus_property_append_int, "i", offsetof(Service, fsck_passno) }, + { "Result", bus_service_append_service_result,"s", offsetof(Service, result) }, + { NULL, } +}; + +DBusHandlerResult bus_service_message_handler(Unit *u, DBusConnection *connection, DBusMessage *message) { + Service *s = SERVICE(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Service", bus_service_properties, s }, + { "org.freedesktop.systemd1.Service", bus_exec_context_properties, &s->exec_context }, + { "org.freedesktop.systemd1.Service", bus_exec_main_status_properties, &s->main_exec_status }, + { NULL, } + }; + + return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/dbus-service.h b/src/dbus-service.h new file mode 100644 index 000000000..d6eab65c5 --- /dev/null +++ b/src/dbus-service.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusservicehfoo +#define foodbusservicehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_service_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_service_interface[]; +extern const char bus_service_invalidating_properties[]; + +#endif diff --git a/src/dbus-snapshot.c b/src/dbus-snapshot.c new file mode 100644 index 000000000..e69388a52 --- /dev/null +++ b/src/dbus-snapshot.c @@ -0,0 +1,93 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "dbus-unit.h" +#include "dbus-snapshot.h" +#include "dbus-common.h" + +#define BUS_SNAPSHOT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_SNAPSHOT_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Snapshot\0" + +const char bus_snapshot_interface[] _introspect_("Snapshot") = BUS_SNAPSHOT_INTERFACE; + +static const BusProperty bus_snapshot_properties[] = { + { "Cleanup", bus_property_append_bool, "b", offsetof(Snapshot, cleanup) }, + { NULL, } +}; + +DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Snapshot *s = SNAPSHOT(u); + + DBusMessage *reply = NULL; + DBusError error; + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Snapshot", "Remove")) { + + snapshot_remove(SNAPSHOT(u)); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else { + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Snapshot", bus_snapshot_properties, s }, + { NULL, } + }; + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); + } + + if (reply) { + if (!dbus_connection_send(c, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} diff --git a/src/dbus-snapshot.h b/src/dbus-snapshot.h new file mode 100644 index 000000000..0b82279f1 --- /dev/null +++ b/src/dbus-snapshot.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbussnapshothfoo +#define foodbussnapshothfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_snapshot_interface[]; + +#endif diff --git a/src/dbus-socket.c b/src/dbus-socket.c new file mode 100644 index 000000000..2e3342cb5 --- /dev/null +++ b/src/dbus-socket.c @@ -0,0 +1,139 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-socket.h" +#include "dbus-execute.h" +#include "dbus-common.h" + +#define BUS_SOCKET_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + BUS_EXEC_COMMAND_INTERFACE("ExecStartPre") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStartPost") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStopPre") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStopPost") \ + BUS_EXEC_CONTEXT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_SOCKET_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Socket\0" + +const char bus_socket_interface[] _introspect_("Socket") = BUS_SOCKET_INTERFACE; + +const char bus_socket_invalidating_properties[] = + "ExecStartPre\0" + "ExecStartPost\0" + "ExecStopPre\0" + "ExecStopPost\0" + "ControlPID\0" + "NAccepted\0" + "NConnections\0" + "Result\0"; + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_socket_append_bind_ipv6_only, socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_socket_append_socket_result, socket_result, SocketResult); + +static const BusProperty bus_socket_properties[] = { + { "BindIPv6Only", bus_socket_append_bind_ipv6_only, "s", offsetof(Socket, bind_ipv6_only) }, + { "Backlog", bus_property_append_unsigned, "u", offsetof(Socket, backlog) }, + { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Socket, timeout_usec) }, + BUS_EXEC_COMMAND_PROPERTY("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStopPost", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_POST]), true ), + { "ControlPID", bus_property_append_pid, "u", offsetof(Socket, control_pid) }, + { "BindToDevice", bus_property_append_string, "s", offsetof(Socket, bind_to_device), true }, + { "DirectoryMode", bus_property_append_mode, "u", offsetof(Socket, directory_mode) }, + { "SocketMode", bus_property_append_mode, "u", offsetof(Socket, socket_mode) }, + { "Accept", bus_property_append_bool, "b", offsetof(Socket, accept) }, + { "KeepAlive", bus_property_append_bool, "b", offsetof(Socket, keep_alive) }, + { "Priority", bus_property_append_int, "i", offsetof(Socket, priority) }, + { "ReceiveBuffer", bus_property_append_size, "t", offsetof(Socket, receive_buffer) }, + { "SendBuffer", bus_property_append_size, "t", offsetof(Socket, send_buffer) }, + { "IPTOS", bus_property_append_int, "i", offsetof(Socket, ip_tos) }, + { "IPTTL", bus_property_append_int, "i", offsetof(Socket, ip_ttl) }, + { "PipeSize", bus_property_append_size, "t", offsetof(Socket, pipe_size) }, + { "FreeBind", bus_property_append_bool, "b", offsetof(Socket, free_bind) }, + { "Transparent", bus_property_append_bool, "b", offsetof(Socket, transparent) }, + { "Broadcast", bus_property_append_bool, "b", offsetof(Socket, broadcast) }, + { "PassCredentials",bus_property_append_bool, "b", offsetof(Socket, pass_cred) }, + { "PassSecurity", bus_property_append_bool, "b", offsetof(Socket, pass_sec) }, + { "Mark", bus_property_append_int, "i", offsetof(Socket, mark) }, + { "MaxConnections", bus_property_append_unsigned, "u", offsetof(Socket, max_connections) }, + { "NConnections", bus_property_append_unsigned, "u", offsetof(Socket, n_connections) }, + { "NAccepted", bus_property_append_unsigned, "u", offsetof(Socket, n_accepted) }, + { "MessageQueueMaxMessages", bus_property_append_long, "x", offsetof(Socket, mq_maxmsg) }, + { "MessageQueueMessageSize", bus_property_append_long, "x", offsetof(Socket, mq_msgsize) }, + { "Result", bus_socket_append_socket_result, "s", offsetof(Socket, result) }, + { NULL, } +}; + +DBusHandlerResult bus_socket_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Socket *s = SOCKET(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Socket", bus_socket_properties, s }, + { "org.freedesktop.systemd1.Socket", bus_exec_context_properties, &s->exec_context }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/dbus-socket.h b/src/dbus-socket.h new file mode 100644 index 000000000..069a2f5df --- /dev/null +++ b/src/dbus-socket.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbussockethfoo +#define foodbussockethfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_socket_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_socket_interface[]; +extern const char bus_socket_invalidating_properties[]; + +#endif diff --git a/src/dbus-swap.c b/src/dbus-swap.c new file mode 100644 index 000000000..09cd1e8b9 --- /dev/null +++ b/src/dbus-swap.c @@ -0,0 +1,111 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Maarten Lankhorst + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-swap.h" +#include "dbus-execute.h" +#include "dbus-common.h" + +#define BUS_SWAP_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + BUS_EXEC_COMMAND_INTERFACE("ExecActivate") \ + BUS_EXEC_COMMAND_INTERFACE("ExecDeactivate") \ + BUS_EXEC_CONTEXT_INTERFACE \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_SWAP_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Swap\0" + +const char bus_swap_interface[] _introspect_("Swap") = BUS_SWAP_INTERFACE; + +const char bus_swap_invalidating_properties[] = + "What\0" + "Priority\0" + "ExecActivate\0" + "ExecDeactivate\0" + "ControlPID\0" + "Result\0"; + +static int bus_swap_append_priority(DBusMessageIter *i, const char *property, void *data) { + Swap *s = data; + dbus_int32_t j; + + assert(i); + assert(property); + assert(s); + + if (s->from_proc_swaps) + j = s->parameters_proc_swaps.priority; + else if (s->from_fragment) + j = s->parameters_fragment.priority; + else if (s->from_etc_fstab) + j = s->parameters_etc_fstab.priority; + else + j = -1; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &j)) + return -ENOMEM; + + return 0; +} + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_swap_append_swap_result, swap_result, SwapResult); + +static const BusProperty bus_swap_properties[] = { + { "What", bus_property_append_string, "s", offsetof(Swap, what), true }, + { "Priority", bus_swap_append_priority, "i", 0 }, + BUS_EXEC_COMMAND_PROPERTY("ExecActivate", offsetof(Swap, exec_command[SWAP_EXEC_ACTIVATE]), false), + BUS_EXEC_COMMAND_PROPERTY("ExecDeactivate", offsetof(Swap, exec_command[SWAP_EXEC_DEACTIVATE]), false), + { "ControlPID", bus_property_append_pid, "u", offsetof(Swap, control_pid) }, + { "Result", bus_swap_append_swap_result,"s", offsetof(Swap, result) }, + { NULL, } +}; + +DBusHandlerResult bus_swap_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Swap *s = SWAP(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Swap", bus_swap_properties, s }, + { "org.freedesktop.systemd1.Swap", bus_exec_context_properties, &s->exec_context }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/dbus-swap.h b/src/dbus-swap.h new file mode 100644 index 000000000..15b914726 --- /dev/null +++ b/src/dbus-swap.h @@ -0,0 +1,35 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusswaphfoo +#define foodbusswaphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Maarten Lankhorst + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_swap_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_swap_interface[]; +extern const char bus_swap_invalidating_properties[]; + +#endif diff --git a/src/dbus-target.c b/src/dbus-target.c new file mode 100644 index 000000000..55cf862de --- /dev/null +++ b/src/dbus-target.c @@ -0,0 +1,55 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-target.h" +#include "dbus-common.h" + +#define BUS_TARGET_INTERFACE \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_TARGET_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Target\0" + +const char bus_target_interface[] _introspect_("Target") = BUS_TARGET_INTERFACE; + +DBusHandlerResult bus_target_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/dbus-target.h b/src/dbus-target.h new file mode 100644 index 000000000..13d38764a --- /dev/null +++ b/src/dbus-target.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbustargethfoo +#define foodbustargethfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_target_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_target_interface[]; + +#endif diff --git a/src/dbus-timer.c b/src/dbus-timer.c new file mode 100644 index 000000000..b396aed04 --- /dev/null +++ b/src/dbus-timer.c @@ -0,0 +1,137 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-timer.h" +#include "dbus-execute.h" +#include "dbus-common.h" + +#define BUS_TIMER_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_TIMER_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Timer\0" + +const char bus_timer_interface[] _introspect_("Timer") = BUS_TIMER_INTERFACE; + +const char bus_timer_invalidating_properties[] = + "Timers\0" + "NextElapseUSec\0" + "Result\0"; + +static int bus_timer_append_timers(DBusMessageIter *i, const char *property, void *data) { + Timer *p = data; + DBusMessageIter sub, sub2; + TimerValue *k; + + assert(i); + assert(property); + assert(p); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(stt)", &sub)) + return -ENOMEM; + + LIST_FOREACH(value, k, p->values) { + char *buf; + const char *t; + size_t l; + bool b; + + t = timer_base_to_string(k->base); + assert(endswith(t, "Sec")); + + /* s/Sec/USec/ */ + l = strlen(t); + if (!(buf = new(char, l+2))) + return -ENOMEM; + + memcpy(buf, t, l-3); + memcpy(buf+l-3, "USec", 5); + + b = dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) && + dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &buf) && + dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &k->value) && + dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &k->next_elapse) && + dbus_message_iter_close_container(&sub, &sub2); + + free(buf); + if (!b) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_timer_append_unit(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + Timer *timer = TIMER(u); + const char *t; + + assert(i); + assert(property); + assert(u); + + t = UNIT_DEREF(timer->unit) ? UNIT_DEREF(timer->unit)->id : ""; + + return dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t) ? 0 : -ENOMEM; +} + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_timer_append_timer_result, timer_result, TimerResult); + +static const BusProperty bus_timer_properties[] = { + { "Unit", bus_timer_append_unit, "s", 0 }, + { "Timers", bus_timer_append_timers, "a(stt)", 0 }, + { "NextElapseUSec", bus_property_append_usec, "t", offsetof(Timer, next_elapse) }, + { "Result", bus_timer_append_timer_result,"s", offsetof(Timer, result) }, + { NULL, } +}; + +DBusHandlerResult bus_timer_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Timer *t = TIMER(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Timer", bus_timer_properties, t }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/dbus-timer.h b/src/dbus-timer.h new file mode 100644 index 000000000..e692e12fc --- /dev/null +++ b/src/dbus-timer.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbustimerhfoo +#define foodbustimerhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_timer_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_timer_interface[]; +extern const char bus_timer_invalidating_properties[]; + +#endif diff --git a/src/dbus-unit.c b/src/dbus-unit.c new file mode 100644 index 000000000..c7532c725 --- /dev/null +++ b/src/dbus-unit.c @@ -0,0 +1,850 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus.h" +#include "log.h" +#include "dbus-unit.h" +#include "bus-errors.h" +#include "dbus-common.h" + +const char bus_unit_interface[] _introspect_("Unit") = BUS_UNIT_INTERFACE; + +#define INVALIDATING_PROPERTIES \ + "LoadState\0" \ + "ActiveState\0" \ + "SubState\0" \ + "InactiveExitTimestamp\0" \ + "ActiveEnterTimestamp\0" \ + "ActiveExitTimestamp\0" \ + "InactiveEnterTimestamp\0" \ + "Job\0" \ + "NeedDaemonReload\0" + +static int bus_unit_append_names(DBusMessageIter *i, const char *property, void *data) { + char *t; + Iterator j; + DBusMessageIter sub; + Unit *u = data; + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub)) + return -ENOMEM; + + SET_FOREACH(t, u->names, j) + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t)) + return -ENOMEM; + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_following(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data, *f; + const char *d; + + assert(i); + assert(property); + assert(u); + + f = unit_following(u); + d = f ? f->id : ""; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_dependencies(DBusMessageIter *i, const char *property, void *data) { + Unit *u; + Iterator j; + DBusMessageIter sub; + Set *s = data; + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub)) + return -ENOMEM; + + SET_FOREACH(u, s, j) + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &u->id)) + return -ENOMEM; + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_description(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + const char *d; + + assert(i); + assert(property); + assert(u); + + d = unit_description(u); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) + return -ENOMEM; + + return 0; +} + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_unit_append_load_state, unit_load_state, UnitLoadState); + +static int bus_unit_append_active_state(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + const char *state; + + assert(i); + assert(property); + assert(u); + + state = unit_active_state_to_string(unit_active_state(u)); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_sub_state(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + const char *state; + + assert(i); + assert(property); + assert(u); + + state = unit_sub_state_to_string(u); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_file_state(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + const char *state; + + assert(i); + assert(property); + assert(u); + + state = strempty(unit_file_state_to_string(unit_get_unit_file_state(u))); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_can_start(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(u); + + b = unit_can_start(u) && + !u->refuse_manual_start; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_can_stop(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(u); + + /* On the lower levels we assume that every unit we can start + * we can also stop */ + + b = unit_can_start(u) && + !u->refuse_manual_stop; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_can_reload(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(u); + + b = unit_can_reload(u); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_can_isolate(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(u); + + b = unit_can_isolate(u) && + !u->refuse_manual_start; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_job(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + DBusMessageIter sub; + char *p; + + assert(i); + assert(property); + assert(u); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub)) + return -ENOMEM; + + if (u->job) { + + if (!(p = job_dbus_path(u->job))) + return -ENOMEM; + + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u->job->id) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + return -ENOMEM; + } + } else { + uint32_t id = 0; + + /* No job, so let's fill in some placeholder + * data. Since we need to fill in a valid path we + * simple point to ourselves. */ + + if (!(p = unit_dbus_path(u))) + return -ENOMEM; + + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &id) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + return -ENOMEM; + } + } + + free(p); + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_default_cgroup(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + char *t; + CGroupBonding *cgb; + bool success; + + assert(i); + assert(property); + assert(u); + + if ((cgb = unit_get_default_cgroup(u))) { + if (!(t = cgroup_bonding_to_string(cgb))) + return -ENOMEM; + } else + t = (char*) ""; + + success = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t); + + if (cgb) + free(t); + + return success ? 0 : -ENOMEM; +} + +static int bus_unit_append_cgroups(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + CGroupBonding *cgb; + DBusMessageIter sub; + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub)) + return -ENOMEM; + + LIST_FOREACH(by_unit, cgb, u->cgroup_bondings) { + char *t; + bool success; + + if (!(t = cgroup_bonding_to_string(cgb))) + return -ENOMEM; + + success = dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t); + free(t); + + if (!success) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_cgroup_attrs(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + CGroupAttribute *a; + DBusMessageIter sub, sub2; + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sss)", &sub)) + return -ENOMEM; + + LIST_FOREACH(by_unit, a, u->cgroup_attributes) { + char *v = NULL; + bool success; + + if (a->map_callback) + a->map_callback(a->controller, a->name, a->value, &v); + + success = + dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) && + dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->controller) && + dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->name) && + dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, v ? &v : &a->value) && + dbus_message_iter_close_container(&sub, &sub2); + + free(v); + + if (!success) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_need_daemon_reload(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(u); + + b = unit_need_daemon_reload(u); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_load_error(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + const char *name, *message; + DBusMessageIter sub; + + assert(i); + assert(property); + assert(u); + + if (u->load_error != 0) { + name = bus_errno_to_dbus(u->load_error); + message = strempty(strerror(-u->load_error)); + } else + name = message = ""; + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &message) || + !dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *connection, DBusMessage *message) { + DBusMessage *reply = NULL; + Manager *m = u->manager; + DBusError error; + JobType job_type = _JOB_TYPE_INVALID; + char *path = NULL; + bool reload_if_possible = false; + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Start")) + job_type = JOB_START; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Stop")) + job_type = JOB_STOP; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Reload")) + job_type = JOB_RELOAD; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Restart")) + job_type = JOB_RESTART; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "TryRestart")) + job_type = JOB_TRY_RESTART; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrRestart")) { + reload_if_possible = true; + job_type = JOB_RESTART; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrTryRestart")) { + reload_if_possible = true; + job_type = JOB_TRY_RESTART; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Kill")) { + const char *swho, *smode; + int32_t signo; + KillMode mode; + KillWho who; + int r; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &swho, + DBUS_TYPE_STRING, &smode, + DBUS_TYPE_INT32, &signo, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if (isempty(smode)) + mode = KILL_CONTROL_GROUP; + else { + mode = kill_mode_from_string(smode); + if (mode < 0) + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if (signo <= 0 || signo >= _NSIG) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if ((r = unit_kill(u, who, mode, signo, &error)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ResetFailed")) { + + unit_reset_failed(u); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (UNIT_VTABLE(u)->bus_message_handler) + return UNIT_VTABLE(u)->bus_message_handler(u, connection, message); + else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (job_type != _JOB_TYPE_INVALID) { + const char *smode; + JobMode mode; + Job *j; + int r; + + if ((job_type == JOB_START && u->refuse_manual_start) || + (job_type == JOB_STOP && u->refuse_manual_stop) || + ((job_type == JOB_RESTART || job_type == JOB_TRY_RESTART) && + (u->refuse_manual_start || u->refuse_manual_stop))) { + dbus_set_error(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, may be requested by dependency only."); + return bus_send_error_reply(connection, message, &error, -EPERM); + } + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &smode, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (reload_if_possible && unit_can_reload(u)) { + if (job_type == JOB_RESTART) + job_type = JOB_RELOAD_OR_START; + else if (job_type == JOB_TRY_RESTART) + job_type = JOB_RELOAD; + } + + if ((mode = job_mode_from_string(smode)) == _JOB_MODE_INVALID) { + dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode); + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if ((r = manager_add_job(m, job_type, u, mode, true, &error, &j)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = job_dbus_path(j))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + } + + if (reply) { + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + free(path); + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + free(path); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static DBusHandlerResult bus_unit_message_handler(DBusConnection *connection, DBusMessage *message, void *data) { + Manager *m = data; + Unit *u; + int r; + DBusMessage *reply; + + assert(connection); + assert(message); + assert(m); + + if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/unit")) { + /* Be nice to gdbus and return introspection data for our mid-level paths */ + + if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { + char *introspection = NULL; + FILE *f; + Iterator i; + const char *k; + size_t size; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + /* We roll our own introspection code here, instead of + * relying on bus_default_message_handler() because we + * need to generate our introspection string + * dynamically. */ + + if (!(f = open_memstream(&introspection, &size))) + goto oom; + + fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "\n", f); + + fputs(BUS_INTROSPECTABLE_INTERFACE, f); + fputs(BUS_PEER_INTERFACE, f); + + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + char *p; + + if (k != u->id) + continue; + + if (!(p = bus_path_escape(k))) { + fclose(f); + free(introspection); + goto oom; + } + + fprintf(f, "", p); + free(p); + } + + fputs("\n", f); + + if (ferror(f)) { + fclose(f); + free(introspection); + goto oom; + } + + fclose(f); + + if (!introspection) + goto oom; + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { + free(introspection); + goto oom; + } + + free(introspection); + + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if ((r = manager_get_unit_from_dbus_path(m, dbus_message_get_path(message), &u)) < 0) { + + if (r == -ENOMEM) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (r == -ENOENT) { + DBusError e; + + dbus_error_init(&e); + dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown unit"); + return bus_send_error_reply(connection, message, &e, r); + } + + return bus_send_error_reply(connection, message, NULL, r); + } + + return bus_unit_message_dispatch(u, connection, message); + +oom: + if (reply) + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +const DBusObjectPathVTable bus_unit_vtable = { + .message_function = bus_unit_message_handler +}; + +void bus_unit_send_change_signal(Unit *u) { + char *p = NULL; + DBusMessage *m = NULL; + + assert(u); + + if (u->in_dbus_queue) { + LIST_REMOVE(Unit, dbus_queue, u->manager->dbus_unit_queue, u); + u->in_dbus_queue = false; + } + + if (!u->id) + return; + + if (!bus_has_subscriber(u->manager)) { + u->sent_dbus_new_signal = true; + return; + } + + if (!(p = unit_dbus_path(u))) + goto oom; + + if (u->sent_dbus_new_signal) { + /* Send a properties changed signal. First for the + * specific type, then for the generic unit. The + * clients may rely on this order to get atomic + * behaviour if needed. */ + + if (UNIT_VTABLE(u)->bus_invalidating_properties) { + + if (!(m = bus_properties_changed_new(p, + UNIT_VTABLE(u)->bus_interface, + UNIT_VTABLE(u)->bus_invalidating_properties))) + goto oom; + + if (bus_broadcast(u->manager, m) < 0) + goto oom; + + dbus_message_unref(m); + } + + if (!(m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Unit", INVALIDATING_PROPERTIES))) + goto oom; + + } else { + /* Send a new signal */ + + if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitNew"))) + goto oom; + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &u->id, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID)) + goto oom; + } + + if (bus_broadcast(u->manager, m) < 0) + goto oom; + + free(p); + dbus_message_unref(m); + + u->sent_dbus_new_signal = true; + + return; + +oom: + free(p); + + if (m) + dbus_message_unref(m); + + log_error("Failed to allocate unit change/new signal."); +} + +void bus_unit_send_removed_signal(Unit *u) { + char *p = NULL; + DBusMessage *m = NULL; + + assert(u); + + if (!bus_has_subscriber(u->manager)) + return; + + if (!u->sent_dbus_new_signal) + bus_unit_send_change_signal(u); + + if (!u->id) + return; + + if (!(p = unit_dbus_path(u))) + goto oom; + + if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitRemoved"))) + goto oom; + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &u->id, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID)) + goto oom; + + if (bus_broadcast(u->manager, m) < 0) + goto oom; + + free(p); + dbus_message_unref(m); + + return; + +oom: + free(p); + + if (m) + dbus_message_unref(m); + + log_error("Failed to allocate unit remove signal."); +} + +const BusProperty bus_unit_properties[] = { + { "Id", bus_property_append_string, "s", offsetof(Unit, id), true }, + { "Names", bus_unit_append_names, "as", 0 }, + { "Following", bus_unit_append_following, "s", 0 }, + { "Requires", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES]), true }, + { "RequiresOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES_OVERRIDABLE]), true }, + { "Requisite", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE]), true }, + { "RequisiteOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE_OVERRIDABLE]), true }, + { "Wants", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTS]), true }, + { "BindTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BIND_TO]), true }, + { "RequiredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), true }, + { "RequiredByOverridable",bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY_OVERRIDABLE]), true }, + { "WantedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTED_BY]), true }, + { "BoundBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BOUND_BY]), true }, + { "Conflicts", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTS]), true }, + { "ConflictedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), true }, + { "Before", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BEFORE]), true }, + { "After", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_AFTER]), true }, + { "OnFailure", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_ON_FAILURE]), true }, + { "Triggers", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERS]), true }, + { "TriggeredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), true }, + { "PropagateReloadTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATE_RELOAD_TO]), true }, + { "PropagateReloadFrom", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATE_RELOAD_FROM]), true }, + { "Description", bus_unit_append_description, "s", 0 }, + { "LoadState", bus_unit_append_load_state, "s", offsetof(Unit, load_state) }, + { "ActiveState", bus_unit_append_active_state, "s", 0 }, + { "SubState", bus_unit_append_sub_state, "s", 0 }, + { "FragmentPath", bus_property_append_string, "s", offsetof(Unit, fragment_path), true }, + { "UnitFileState", bus_unit_append_file_state, "s", 0 }, + { "InactiveExitTimestamp",bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.realtime) }, + { "InactiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.monotonic) }, + { "ActiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.realtime) }, + { "ActiveEnterTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.monotonic) }, + { "ActiveExitTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.realtime) }, + { "ActiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.monotonic) }, + { "InactiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.realtime) }, + { "InactiveEnterTimestampMonotonic",bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.monotonic) }, + { "CanStart", bus_unit_append_can_start, "b", 0 }, + { "CanStop", bus_unit_append_can_stop, "b", 0 }, + { "CanReload", bus_unit_append_can_reload, "b", 0 }, + { "CanIsolate", bus_unit_append_can_isolate, "b", 0 }, + { "Job", bus_unit_append_job, "(uo)", 0 }, + { "StopWhenUnneeded", bus_property_append_bool, "b", offsetof(Unit, stop_when_unneeded) }, + { "RefuseManualStart", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_start) }, + { "RefuseManualStop", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_stop) }, + { "AllowIsolate", bus_property_append_bool, "b", offsetof(Unit, allow_isolate) }, + { "DefaultDependencies", bus_property_append_bool, "b", offsetof(Unit, default_dependencies) }, + { "OnFailureIsolate", bus_property_append_bool, "b", offsetof(Unit, on_failure_isolate) }, + { "IgnoreOnIsolate", bus_property_append_bool, "b", offsetof(Unit, ignore_on_isolate) }, + { "IgnoreOnSnapshot", bus_property_append_bool, "b", offsetof(Unit, ignore_on_snapshot) }, + { "DefaultControlGroup", bus_unit_append_default_cgroup, "s", 0 }, + { "ControlGroup", bus_unit_append_cgroups, "as", 0 }, + { "ControlGroupAttributes", bus_unit_append_cgroup_attrs,"a(sss)", 0 }, + { "NeedDaemonReload", bus_unit_append_need_daemon_reload, "b", 0 }, + { "JobTimeoutUSec", bus_property_append_usec, "t", offsetof(Unit, job_timeout) }, + { "ConditionTimestamp", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.realtime) }, + { "ConditionTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.monotonic) }, + { "ConditionResult", bus_property_append_bool, "b", offsetof(Unit, condition_result) }, + { "LoadError", bus_unit_append_load_error, "(ss)", 0 }, + { NULL, } +}; diff --git a/src/dbus-unit.h b/src/dbus-unit.h new file mode 100644 index 000000000..4f19a808b --- /dev/null +++ b/src/dbus-unit.h @@ -0,0 +1,139 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusunithfoo +#define foodbusunithfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "manager.h" +#include "dbus-common.h" + +#define BUS_UNIT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_UNIT_INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.systemd1.Unit\0" + +extern const BusProperty bus_unit_properties[]; + +void bus_unit_send_change_signal(Unit *u); +void bus_unit_send_removed_signal(Unit *u); + +extern const DBusObjectPathVTable bus_unit_vtable; + +extern const char bus_unit_interface[]; + +#endif diff --git a/src/dbus.c b/src/dbus.c new file mode 100644 index 000000000..8e6e9fd52 --- /dev/null +++ b/src/dbus.c @@ -0,0 +1,1482 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "dbus.h" +#include "log.h" +#include "strv.h" +#include "cgroup.h" +#include "dbus-unit.h" +#include "dbus-job.h" +#include "dbus-manager.h" +#include "dbus-service.h" +#include "dbus-socket.h" +#include "dbus-target.h" +#include "dbus-device.h" +#include "dbus-mount.h" +#include "dbus-automount.h" +#include "dbus-snapshot.h" +#include "dbus-swap.h" +#include "dbus-timer.h" +#include "dbus-path.h" +#include "bus-errors.h" +#include "special.h" +#include "dbus-common.h" + +#define CONNECTIONS_MAX 52 + +/* Well-known address (http://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-types) */ +#define DBUS_SYSTEM_BUS_DEFAULT_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" +/* Only used as a fallback */ +#define DBUS_SESSION_BUS_DEFAULT_ADDRESS "autolaunch:" + +static const char bus_properties_interface[] = BUS_PROPERTIES_INTERFACE; +static const char bus_introspectable_interface[] = BUS_INTROSPECTABLE_INTERFACE; + +const char *const bus_interface_table[] = { + "org.freedesktop.DBus.Properties", bus_properties_interface, + "org.freedesktop.DBus.Introspectable", bus_introspectable_interface, + "org.freedesktop.systemd1.Manager", bus_manager_interface, + "org.freedesktop.systemd1.Job", bus_job_interface, + "org.freedesktop.systemd1.Unit", bus_unit_interface, + "org.freedesktop.systemd1.Service", bus_service_interface, + "org.freedesktop.systemd1.Socket", bus_socket_interface, + "org.freedesktop.systemd1.Target", bus_target_interface, + "org.freedesktop.systemd1.Device", bus_device_interface, + "org.freedesktop.systemd1.Mount", bus_mount_interface, + "org.freedesktop.systemd1.Automount", bus_automount_interface, + "org.freedesktop.systemd1.Snapshot", bus_snapshot_interface, + "org.freedesktop.systemd1.Swap", bus_swap_interface, + "org.freedesktop.systemd1.Timer", bus_timer_interface, + "org.freedesktop.systemd1.Path", bus_path_interface, + NULL +}; + +static void bus_done_api(Manager *m); +static void bus_done_system(Manager *m); +static void bus_done_private(Manager *m); +static void shutdown_connection(Manager *m, DBusConnection *c); + +static void bus_dispatch_status(DBusConnection *bus, DBusDispatchStatus status, void *data) { + Manager *m = data; + + assert(bus); + assert(m); + + /* We maintain two sets, one for those connections where we + * requested a dispatch, and another where we didn't. And then, + * we move the connections between the two sets. */ + + if (status == DBUS_DISPATCH_COMPLETE) + set_move_one(m->bus_connections, m->bus_connections_for_dispatch, bus); + else + set_move_one(m->bus_connections_for_dispatch, m->bus_connections, bus); +} + +void bus_watch_event(Manager *m, Watch *w, int events) { + assert(m); + assert(w); + + /* This is called by the event loop whenever there is + * something happening on D-Bus' file handles. */ + + if (!dbus_watch_get_enabled(w->data.bus_watch)) + return; + + dbus_watch_handle(w->data.bus_watch, bus_events_to_flags(events)); +} + +static dbus_bool_t bus_add_watch(DBusWatch *bus_watch, void *data) { + Manager *m = data; + Watch *w; + struct epoll_event ev; + + assert(bus_watch); + assert(m); + + if (!(w = new0(Watch, 1))) + return FALSE; + + w->fd = dbus_watch_get_unix_fd(bus_watch); + w->type = WATCH_DBUS_WATCH; + w->data.bus_watch = bus_watch; + + zero(ev); + ev.events = bus_flags_to_events(bus_watch); + ev.data.ptr = w; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) { + + if (errno != EEXIST) { + free(w); + return FALSE; + } + + /* Hmm, bloody D-Bus creates multiple watches on the + * same fd. epoll() does not like that. As a dirty + * hack we simply dup() the fd and hence get a second + * one we can safely add to the epoll(). */ + + if ((w->fd = dup(w->fd)) < 0) { + free(w); + return FALSE; + } + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) { + close_nointr_nofail(w->fd); + free(w); + return FALSE; + } + + w->fd_is_dupped = true; + } + + dbus_watch_set_data(bus_watch, w, NULL); + + return TRUE; +} + +static void bus_remove_watch(DBusWatch *bus_watch, void *data) { + Manager *m = data; + Watch *w; + + assert(bus_watch); + assert(m); + + w = dbus_watch_get_data(bus_watch); + if (!w) + return; + + assert(w->type == WATCH_DBUS_WATCH); + assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); + + if (w->fd_is_dupped) + close_nointr_nofail(w->fd); + + free(w); +} + +static void bus_toggle_watch(DBusWatch *bus_watch, void *data) { + Manager *m = data; + Watch *w; + struct epoll_event ev; + + assert(bus_watch); + assert(m); + + w = dbus_watch_get_data(bus_watch); + if (!w) + return; + + assert(w->type == WATCH_DBUS_WATCH); + + zero(ev); + ev.events = bus_flags_to_events(bus_watch); + ev.data.ptr = w; + + assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_MOD, w->fd, &ev) == 0); +} + +static int bus_timeout_arm(Manager *m, Watch *w) { + struct itimerspec its; + + assert(m); + assert(w); + + zero(its); + + if (dbus_timeout_get_enabled(w->data.bus_timeout)) { + timespec_store(&its.it_value, dbus_timeout_get_interval(w->data.bus_timeout) * USEC_PER_MSEC); + its.it_interval = its.it_value; + } + + if (timerfd_settime(w->fd, 0, &its, NULL) < 0) + return -errno; + + return 0; +} + +void bus_timeout_event(Manager *m, Watch *w, int events) { + assert(m); + assert(w); + + /* This is called by the event loop whenever there is + * something happening on D-Bus' file handles. */ + + if (!(dbus_timeout_get_enabled(w->data.bus_timeout))) + return; + + dbus_timeout_handle(w->data.bus_timeout); +} + +static dbus_bool_t bus_add_timeout(DBusTimeout *timeout, void *data) { + Manager *m = data; + Watch *w; + struct epoll_event ev; + + assert(timeout); + assert(m); + + if (!(w = new0(Watch, 1))) + return FALSE; + + if ((w->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) + goto fail; + + w->type = WATCH_DBUS_TIMEOUT; + w->data.bus_timeout = timeout; + + if (bus_timeout_arm(m, w) < 0) + goto fail; + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = w; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) + goto fail; + + dbus_timeout_set_data(timeout, w, NULL); + + return TRUE; + +fail: + if (w->fd >= 0) + close_nointr_nofail(w->fd); + + free(w); + return FALSE; +} + +static void bus_remove_timeout(DBusTimeout *timeout, void *data) { + Manager *m = data; + Watch *w; + + assert(timeout); + assert(m); + + w = dbus_timeout_get_data(timeout); + if (!w) + return; + + assert(w->type == WATCH_DBUS_TIMEOUT); + + assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); + close_nointr_nofail(w->fd); + free(w); +} + +static void bus_toggle_timeout(DBusTimeout *timeout, void *data) { + Manager *m = data; + Watch *w; + int r; + + assert(timeout); + assert(m); + + w = dbus_timeout_get_data(timeout); + if (!w) + return; + + assert(w->type == WATCH_DBUS_TIMEOUT); + + if ((r = bus_timeout_arm(m, w)) < 0) + log_error("Failed to rearm timer: %s", strerror(-r)); +} + +static DBusHandlerResult api_bus_message_filter(DBusConnection *connection, DBusMessage *message, void *data) { + Manager *m = data; + DBusError error; + DBusMessage *reply = NULL; + + assert(connection); + assert(message); + assert(m); + + dbus_error_init(&error); + + if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL || + dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL) + log_debug("Got D-Bus request: %s.%s() on %s", + dbus_message_get_interface(message), + dbus_message_get_member(message), + dbus_message_get_path(message)); + + if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) { + log_debug("API D-Bus connection terminated."); + bus_done_api(m); + + } else if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { + const char *name, *old_owner, *new_owner; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID)) + log_error("Failed to parse NameOwnerChanged message: %s", bus_error_message(&error)); + else { + if (set_remove(BUS_CONNECTION_SUBSCRIBED(m, connection), (char*) name)) + log_debug("Subscription client vanished: %s (left: %u)", name, set_size(BUS_CONNECTION_SUBSCRIBED(m, connection))); + + if (old_owner[0] == 0) + old_owner = NULL; + + if (new_owner[0] == 0) + new_owner = NULL; + + manager_dispatch_bus_name_owner_changed(m, name, old_owner, new_owner); + } + } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Activator", "ActivationRequest")) { + const char *name; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + log_error("Failed to parse ActivationRequest message: %s", bus_error_message(&error)); + else { + int r; + Unit *u; + + log_debug("Got D-Bus activation request for %s", name); + + if (manager_unit_pending_inactive(m, SPECIAL_DBUS_SERVICE) || + manager_unit_pending_inactive(m, SPECIAL_DBUS_SOCKET)) { + r = -EADDRNOTAVAIL; + dbus_set_error(&error, BUS_ERROR_SHUTTING_DOWN, "Refusing activation, D-Bus is shutting down."); + } else { + r = manager_load_unit(m, name, NULL, &error, &u); + + if (r >= 0 && u->refuse_manual_start) + r = -EPERM; + + if (r >= 0) + r = manager_add_job(m, JOB_START, u, JOB_REPLACE, true, &error, NULL); + } + + if (r < 0) { + const char *id, *text; + + log_debug("D-Bus activation failed for %s: %s", name, strerror(-r)); + + if (!(reply = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Activator", "ActivationFailure"))) + goto oom; + + id = error.name ? error.name : bus_errno_to_dbus(r); + text = bus_error(&error, r); + + if (!dbus_message_set_destination(reply, DBUS_SERVICE_DBUS) || + !dbus_message_append_args(reply, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &id, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) + goto oom; + } + + /* On success we don't do anything, the service will be spawned now */ + } + } + + dbus_error_free(&error); + + if (reply) { + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static DBusHandlerResult system_bus_message_filter(DBusConnection *connection, DBusMessage *message, void *data) { + Manager *m = data; + DBusError error; + + assert(connection); + assert(message); + assert(m); + + dbus_error_init(&error); + + if (m->api_bus != m->system_bus && + (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL || + dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL)) + log_debug("Got D-Bus request on system bus: %s.%s() on %s", + dbus_message_get_interface(message), + dbus_message_get_member(message), + dbus_message_get_path(message)); + + if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) { + log_debug("System D-Bus connection terminated."); + bus_done_system(m); + + } else if (m->running_as != MANAGER_SYSTEM && + dbus_message_is_signal(message, "org.freedesktop.systemd1.Agent", "Released")) { + + const char *cgroup; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &cgroup, + DBUS_TYPE_INVALID)) + log_error("Failed to parse Released message: %s", bus_error_message(&error)); + else + cgroup_notify_empty(m, cgroup); + } + + dbus_error_free(&error); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult private_bus_message_filter(DBusConnection *connection, DBusMessage *message, void *data) { + Manager *m = data; + DBusError error; + + assert(connection); + assert(message); + assert(m); + + dbus_error_init(&error); + + if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL || + dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL) + log_debug("Got D-Bus request: %s.%s() on %s", + dbus_message_get_interface(message), + dbus_message_get_member(message), + dbus_message_get_path(message)); + + if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) + shutdown_connection(m, connection); + else if (m->running_as == MANAGER_SYSTEM && + dbus_message_is_signal(message, "org.freedesktop.systemd1.Agent", "Released")) { + + const char *cgroup; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &cgroup, + DBUS_TYPE_INVALID)) + log_error("Failed to parse Released message: %s", bus_error_message(&error)); + else + cgroup_notify_empty(m, cgroup); + + /* Forward the message to the system bus, so that user + * instances are notified as well */ + + if (m->system_bus) + dbus_connection_send(m->system_bus, message, NULL); + } + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +unsigned bus_dispatch(Manager *m) { + DBusConnection *c; + + assert(m); + + if (m->queued_message) { + /* If we cannot get rid of this message we won't + * dispatch any D-Bus messages, so that we won't end + * up wanting to queue another message. */ + + if (m->queued_message_connection) + if (!dbus_connection_send(m->queued_message_connection, m->queued_message, NULL)) + return 0; + + dbus_message_unref(m->queued_message); + m->queued_message = NULL; + m->queued_message_connection = NULL; + } + + if ((c = set_first(m->bus_connections_for_dispatch))) { + if (dbus_connection_dispatch(c) == DBUS_DISPATCH_COMPLETE) + set_move_one(m->bus_connections, m->bus_connections_for_dispatch, c); + + return 1; + } + + return 0; +} + +static void request_name_pending_cb(DBusPendingCall *pending, void *userdata) { + DBusMessage *reply; + DBusError error; + + dbus_error_init(&error); + + assert_se(reply = dbus_pending_call_steal_reply(pending)); + + switch (dbus_message_get_type(reply)) { + + case DBUS_MESSAGE_TYPE_ERROR: + + assert_se(dbus_set_error_from_message(&error, reply)); + log_warning("RequestName() failed: %s", bus_error_message(&error)); + break; + + case DBUS_MESSAGE_TYPE_METHOD_RETURN: { + uint32_t r; + + if (!dbus_message_get_args(reply, + &error, + DBUS_TYPE_UINT32, &r, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse RequestName() reply: %s", bus_error_message(&error)); + break; + } + + if (r == 1) + log_debug("Successfully acquired name."); + else + log_error("Name already owned."); + + break; + } + + default: + assert_not_reached("Invalid reply message"); + } + + dbus_message_unref(reply); + dbus_error_free(&error); +} + +static int request_name(Manager *m) { + const char *name = "org.freedesktop.systemd1"; + /* Allow replacing of our name, to ease implementation of + * reexecution, where we keep the old connection open until + * after the new connection is set up and the name installed + * to allow clients to synchronously wait for reexecution to + * finish */ + uint32_t flags = DBUS_NAME_FLAG_ALLOW_REPLACEMENT|DBUS_NAME_FLAG_REPLACE_EXISTING; + DBusMessage *message = NULL; + DBusPendingCall *pending = NULL; + + if (!(message = dbus_message_new_method_call( + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "RequestName"))) + goto oom; + + if (!dbus_message_append_args( + message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID)) + goto oom; + + if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1)) + goto oom; + + if (!dbus_pending_call_set_notify(pending, request_name_pending_cb, m, NULL)) + goto oom; + + dbus_message_unref(message); + dbus_pending_call_unref(pending); + + /* We simple ask for the name and don't wait for it. Sooner or + * later we'll have it. */ + + return 0; + +oom: + if (pending) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + } + + if (message) + dbus_message_unref(message); + + return -ENOMEM; +} + +static void query_name_list_pending_cb(DBusPendingCall *pending, void *userdata) { + DBusMessage *reply; + DBusError error; + Manager *m = userdata; + + assert(m); + + dbus_error_init(&error); + + assert_se(reply = dbus_pending_call_steal_reply(pending)); + + switch (dbus_message_get_type(reply)) { + + case DBUS_MESSAGE_TYPE_ERROR: + + assert_se(dbus_set_error_from_message(&error, reply)); + log_warning("ListNames() failed: %s", bus_error_message(&error)); + break; + + case DBUS_MESSAGE_TYPE_METHOD_RETURN: { + int r; + char **l; + + if ((r = bus_parse_strv(reply, &l)) < 0) + log_warning("Failed to parse ListNames() reply: %s", strerror(-r)); + else { + char **t; + + STRV_FOREACH(t, l) + /* This is a bit hacky, we say the + * owner of the name is the name + * itself, because we don't want the + * extra traffic to figure out the + * real owner. */ + manager_dispatch_bus_name_owner_changed(m, *t, NULL, *t); + + strv_free(l); + } + + break; + } + + default: + assert_not_reached("Invalid reply message"); + } + + dbus_message_unref(reply); + dbus_error_free(&error); +} + +static int query_name_list(Manager *m) { + DBusMessage *message = NULL; + DBusPendingCall *pending = NULL; + + /* Asks for the currently installed bus names */ + + if (!(message = dbus_message_new_method_call( + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "ListNames"))) + goto oom; + + if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1)) + goto oom; + + if (!dbus_pending_call_set_notify(pending, query_name_list_pending_cb, m, NULL)) + goto oom; + + dbus_message_unref(message); + dbus_pending_call_unref(pending); + + /* We simple ask for the list and don't wait for it. Sooner or + * later we'll get it. */ + + return 0; + +oom: + if (pending) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + } + + if (message) + dbus_message_unref(message); + + return -ENOMEM; +} + +static int bus_setup_loop(Manager *m, DBusConnection *bus) { + assert(m); + assert(bus); + + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + if (!dbus_connection_set_watch_functions(bus, bus_add_watch, bus_remove_watch, bus_toggle_watch, m, NULL) || + !dbus_connection_set_timeout_functions(bus, bus_add_timeout, bus_remove_timeout, bus_toggle_timeout, m, NULL)) { + log_error("Not enough memory"); + return -ENOMEM; + } + + if (set_put(m->bus_connections_for_dispatch, bus) < 0) { + log_error("Not enough memory"); + return -ENOMEM; + } + + dbus_connection_set_dispatch_status_function(bus, bus_dispatch_status, m, NULL); + return 0; +} + +static dbus_bool_t allow_only_same_user(DBusConnection *connection, unsigned long uid, void *data) { + return uid == 0 || uid == geteuid(); +} + +static void bus_new_connection( + DBusServer *server, + DBusConnection *new_connection, + void *data) { + + Manager *m = data; + + assert(m); + + if (set_size(m->bus_connections) >= CONNECTIONS_MAX) { + log_error("Too many concurrent connections."); + return; + } + + dbus_connection_set_unix_user_function(new_connection, allow_only_same_user, NULL, NULL); + + if (bus_setup_loop(m, new_connection) < 0) + return; + + if (!dbus_connection_register_object_path(new_connection, "/org/freedesktop/systemd1", &bus_manager_vtable, m) || + !dbus_connection_register_fallback(new_connection, "/org/freedesktop/systemd1/unit", &bus_unit_vtable, m) || + !dbus_connection_register_fallback(new_connection, "/org/freedesktop/systemd1/job", &bus_job_vtable, m) || + !dbus_connection_add_filter(new_connection, private_bus_message_filter, m, NULL)) { + log_error("Not enough memory."); + return; + } + + log_debug("Accepted connection on private bus."); + + dbus_connection_ref(new_connection); +} + +static int init_registered_system_bus(Manager *m) { + char *id; + + if (!dbus_connection_add_filter(m->system_bus, system_bus_message_filter, m, NULL)) { + log_error("Not enough memory"); + return -ENOMEM; + } + + if (m->running_as != MANAGER_SYSTEM) { + DBusError error; + + dbus_error_init(&error); + + dbus_bus_add_match(m->system_bus, + "type='signal'," + "interface='org.freedesktop.systemd1.Agent'," + "member='Released'," + "path='/org/freedesktop/systemd1/agent'", + &error); + + if (dbus_error_is_set(&error)) { + log_error("Failed to register match: %s", bus_error_message(&error)); + dbus_error_free(&error); + return -1; + } + } + + log_debug("Successfully connected to system D-Bus bus %s as %s", + strnull((id = dbus_connection_get_server_id(m->system_bus))), + strnull(dbus_bus_get_unique_name(m->system_bus))); + dbus_free(id); + + return 0; +} + +static int init_registered_api_bus(Manager *m) { + int r; + + if (!dbus_connection_register_object_path(m->api_bus, "/org/freedesktop/systemd1", &bus_manager_vtable, m) || + !dbus_connection_register_fallback(m->api_bus, "/org/freedesktop/systemd1/unit", &bus_unit_vtable, m) || + !dbus_connection_register_fallback(m->api_bus, "/org/freedesktop/systemd1/job", &bus_job_vtable, m) || + !dbus_connection_add_filter(m->api_bus, api_bus_message_filter, m, NULL)) { + log_error("Not enough memory"); + return -ENOMEM; + } + + /* Get NameOwnerChange messages */ + dbus_bus_add_match(m->api_bus, + "type='signal'," + "sender='"DBUS_SERVICE_DBUS"'," + "interface='"DBUS_INTERFACE_DBUS"'," + "member='NameOwnerChanged'," + "path='"DBUS_PATH_DBUS"'", + NULL); + + /* Get activation requests */ + dbus_bus_add_match(m->api_bus, + "type='signal'," + "sender='"DBUS_SERVICE_DBUS"'," + "interface='org.freedesktop.systemd1.Activator'," + "member='ActivationRequest'," + "path='"DBUS_PATH_DBUS"'", + NULL); + + r = request_name(m); + if (r < 0) + return r; + + r = query_name_list(m); + if (r < 0) + return r; + + if (m->running_as == MANAGER_USER) { + char *id; + log_debug("Successfully connected to API D-Bus bus %s as %s", + strnull((id = dbus_connection_get_server_id(m->api_bus))), + strnull(dbus_bus_get_unique_name(m->api_bus))); + dbus_free(id); + } else + log_debug("Successfully initialized API on the system bus"); + + return 0; +} + +static void bus_register_cb(DBusPendingCall *pending, void *userdata) { + Manager *m = userdata; + DBusConnection **conn; + DBusMessage *reply; + DBusError error; + const char *name; + int r = 0; + + dbus_error_init(&error); + + conn = dbus_pending_call_get_data(pending, m->conn_data_slot); + assert(conn == &m->system_bus || conn == &m->api_bus); + + reply = dbus_pending_call_steal_reply(pending); + + switch (dbus_message_get_type(reply)) { + case DBUS_MESSAGE_TYPE_ERROR: + assert_se(dbus_set_error_from_message(&error, reply)); + log_warning("Failed to register to bus: %s", bus_error_message(&error)); + r = -1; + break; + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse Hello reply: %s", bus_error_message(&error)); + r = -1; + break; + } + + log_debug("Received name %s in reply to Hello", name); + if (!dbus_bus_set_unique_name(*conn, name)) { + log_error("Failed to set unique name"); + r = -1; + break; + } + + if (conn == &m->system_bus) { + r = init_registered_system_bus(m); + if (r == 0 && m->running_as == MANAGER_SYSTEM) + r = init_registered_api_bus(m); + } else + r = init_registered_api_bus(m); + + break; + default: + assert_not_reached("Invalid reply message"); + } + + dbus_message_unref(reply); + dbus_error_free(&error); + + if (r < 0) { + if (conn == &m->system_bus) { + log_debug("Failed setting up the system bus"); + bus_done_system(m); + } else { + log_debug("Failed setting up the API bus"); + bus_done_api(m); + } + } +} + +static int manager_bus_async_register(Manager *m, DBusConnection **conn) { + DBusMessage *message = NULL; + DBusPendingCall *pending = NULL; + + message = dbus_message_new_method_call(DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "Hello"); + if (!message) + goto oom; + + if (!dbus_connection_send_with_reply(*conn, message, &pending, -1)) + goto oom; + + if (!dbus_pending_call_set_data(pending, m->conn_data_slot, conn, NULL)) + goto oom; + + if (!dbus_pending_call_set_notify(pending, bus_register_cb, m, NULL)) + goto oom; + + dbus_message_unref(message); + dbus_pending_call_unref(pending); + + return 0; +oom: + if (pending) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + } + + if (message) + dbus_message_unref(message); + + return -ENOMEM; +} + +static DBusConnection* manager_bus_connect_private(Manager *m, DBusBusType type) { + const char *address; + DBusConnection *connection; + DBusError error; + + switch (type) { + case DBUS_BUS_SYSTEM: + address = getenv("DBUS_SYSTEM_BUS_ADDRESS"); + if (!address || !address[0]) + address = DBUS_SYSTEM_BUS_DEFAULT_ADDRESS; + break; + case DBUS_BUS_SESSION: + address = getenv("DBUS_SESSION_BUS_ADDRESS"); + if (!address || !address[0]) + address = DBUS_SESSION_BUS_DEFAULT_ADDRESS; + break; + default: + assert_not_reached("Invalid bus type"); + } + + dbus_error_init(&error); + + connection = dbus_connection_open_private(address, &error); + if (!connection) { + log_warning("Failed to open private bus connection: %s", bus_error_message(&error)); + goto fail; + } + + return connection; +fail: + if (connection) + dbus_connection_close(connection); + dbus_error_free(&error); + return NULL; +} + +static int bus_init_system(Manager *m) { + int r; + + if (m->system_bus) + return 0; + + m->system_bus = manager_bus_connect_private(m, DBUS_BUS_SYSTEM); + if (!m->system_bus) { + log_debug("Failed to connect to system D-Bus, retrying later"); + r = 0; + goto fail; + } + + r = bus_setup_loop(m, m->system_bus); + if (r < 0) + goto fail; + + r = manager_bus_async_register(m, &m->system_bus); + if (r < 0) + goto fail; + + return 0; +fail: + bus_done_system(m); + + return r; +} + +static int bus_init_api(Manager *m) { + int r; + + if (m->api_bus) + return 0; + + if (m->running_as == MANAGER_SYSTEM) { + m->api_bus = m->system_bus; + /* In this mode there is no distinct connection to the API bus, + * the API is published on the system bus. + * bus_register_cb() is aware of that and will init the API + * when the system bus gets registered. + * No need to setup anything here. */ + return 0; + } + + m->api_bus = manager_bus_connect_private(m, DBUS_BUS_SESSION); + if (!m->api_bus) { + log_debug("Failed to connect to API D-Bus, retrying later"); + r = 0; + goto fail; + } + + r = bus_setup_loop(m, m->api_bus); + if (r < 0) + goto fail; + + r = manager_bus_async_register(m, &m->api_bus); + if (r < 0) + goto fail; + + return 0; +fail: + bus_done_api(m); + + return r; +} + +static int bus_init_private(Manager *m) { + DBusError error; + int r; + const char *const external_only[] = { + "EXTERNAL", + NULL + }; + + assert(m); + + dbus_error_init(&error); + + if (m->private_bus) + return 0; + + if (m->running_as == MANAGER_SYSTEM) { + + /* We want the private bus only when running as init */ + if (getpid() != 1) + return 0; + + unlink("/run/systemd/private"); + m->private_bus = dbus_server_listen("unix:path=/run/systemd/private", &error); + } else { + const char *e; + char *p; + + e = getenv("XDG_RUNTIME_DIR"); + if (!e) + return 0; + + if (asprintf(&p, "unix:path=%s/systemd/private", e) < 0) { + log_error("Not enough memory"); + r = -ENOMEM; + goto fail; + } + + mkdir_parents(p+10, 0755); + unlink(p+10); + m->private_bus = dbus_server_listen(p, &error); + free(p); + } + + if (!m->private_bus) { + log_error("Failed to create private D-Bus server: %s", bus_error_message(&error)); + r = -EIO; + goto fail; + } + + if (!dbus_server_set_auth_mechanisms(m->private_bus, (const char**) external_only) || + !dbus_server_set_watch_functions(m->private_bus, bus_add_watch, bus_remove_watch, bus_toggle_watch, m, NULL) || + !dbus_server_set_timeout_functions(m->private_bus, bus_add_timeout, bus_remove_timeout, bus_toggle_timeout, m, NULL)) { + log_error("Not enough memory"); + r = -ENOMEM; + goto fail; + } + + dbus_server_set_new_connection_function(m->private_bus, bus_new_connection, m, NULL); + + log_debug("Successfully created private D-Bus server."); + + return 0; + +fail: + bus_done_private(m); + dbus_error_free(&error); + + return r; +} + +int bus_init(Manager *m, bool try_bus_connect) { + int r; + + if (set_ensure_allocated(&m->bus_connections, trivial_hash_func, trivial_compare_func) < 0 || + set_ensure_allocated(&m->bus_connections_for_dispatch, trivial_hash_func, trivial_compare_func) < 0) + goto oom; + + if (m->name_data_slot < 0) + if (!dbus_pending_call_allocate_data_slot(&m->name_data_slot)) + goto oom; + + if (m->conn_data_slot < 0) + if (!dbus_pending_call_allocate_data_slot(&m->conn_data_slot)) + goto oom; + + if (m->subscribed_data_slot < 0) + if (!dbus_connection_allocate_data_slot(&m->subscribed_data_slot)) + goto oom; + + if (try_bus_connect) { + if ((r = bus_init_system(m)) < 0 || + (r = bus_init_api(m)) < 0) + return r; + } + + if ((r = bus_init_private(m)) < 0) + return r; + + return 0; +oom: + log_error("Not enough memory"); + return -ENOMEM; +} + +static void shutdown_connection(Manager *m, DBusConnection *c) { + Set *s; + Job *j; + Iterator i; + + HASHMAP_FOREACH(j, m->jobs, i) + if (j->bus == c) { + free(j->bus_client); + j->bus_client = NULL; + + j->bus = NULL; + } + + set_remove(m->bus_connections, c); + set_remove(m->bus_connections_for_dispatch, c); + + if ((s = BUS_CONNECTION_SUBSCRIBED(m, c))) { + char *t; + + while ((t = set_steal_first(s))) + free(t); + + set_free(s); + } + + if (m->queued_message_connection == c) { + m->queued_message_connection = NULL; + + if (m->queued_message) { + dbus_message_unref(m->queued_message); + m->queued_message = NULL; + } + } + + dbus_connection_set_dispatch_status_function(c, NULL, NULL, NULL); + /* system manager cannot afford to block on DBus */ + if (m->running_as != MANAGER_SYSTEM) + dbus_connection_flush(c); + dbus_connection_close(c); + dbus_connection_unref(c); +} + +static void bus_done_api(Manager *m) { + if (!m->api_bus) + return; + + if (m->running_as == MANAGER_USER) + shutdown_connection(m, m->api_bus); + + m->api_bus = NULL; + + if (m->queued_message) { + dbus_message_unref(m->queued_message); + m->queued_message = NULL; + } +} + +static void bus_done_system(Manager *m) { + if (!m->system_bus) + return; + + if (m->running_as == MANAGER_SYSTEM) + bus_done_api(m); + + shutdown_connection(m, m->system_bus); + m->system_bus = NULL; +} + +static void bus_done_private(Manager *m) { + if (!m->private_bus) + return; + + dbus_server_disconnect(m->private_bus); + dbus_server_unref(m->private_bus); + m->private_bus = NULL; +} + +void bus_done(Manager *m) { + DBusConnection *c; + + bus_done_api(m); + bus_done_system(m); + bus_done_private(m); + + while ((c = set_steal_first(m->bus_connections))) + shutdown_connection(m, c); + + while ((c = set_steal_first(m->bus_connections_for_dispatch))) + shutdown_connection(m, c); + + set_free(m->bus_connections); + set_free(m->bus_connections_for_dispatch); + + if (m->name_data_slot >= 0) + dbus_pending_call_free_data_slot(&m->name_data_slot); + + if (m->conn_data_slot >= 0) + dbus_pending_call_free_data_slot(&m->conn_data_slot); + + if (m->subscribed_data_slot >= 0) + dbus_connection_free_data_slot(&m->subscribed_data_slot); +} + +static void query_pid_pending_cb(DBusPendingCall *pending, void *userdata) { + Manager *m = userdata; + DBusMessage *reply; + DBusError error; + const char *name; + + dbus_error_init(&error); + + assert_se(name = BUS_PENDING_CALL_NAME(m, pending)); + assert_se(reply = dbus_pending_call_steal_reply(pending)); + + switch (dbus_message_get_type(reply)) { + + case DBUS_MESSAGE_TYPE_ERROR: + + assert_se(dbus_set_error_from_message(&error, reply)); + log_warning("GetConnectionUnixProcessID() failed: %s", bus_error_message(&error)); + break; + + case DBUS_MESSAGE_TYPE_METHOD_RETURN: { + uint32_t r; + + if (!dbus_message_get_args(reply, + &error, + DBUS_TYPE_UINT32, &r, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse GetConnectionUnixProcessID() reply: %s", bus_error_message(&error)); + break; + } + + manager_dispatch_bus_query_pid_done(m, name, (pid_t) r); + break; + } + + default: + assert_not_reached("Invalid reply message"); + } + + dbus_message_unref(reply); + dbus_error_free(&error); +} + +int bus_query_pid(Manager *m, const char *name) { + DBusMessage *message = NULL; + DBusPendingCall *pending = NULL; + char *n = NULL; + + assert(m); + assert(name); + + if (!(message = dbus_message_new_method_call( + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixProcessID"))) + goto oom; + + if (!(dbus_message_append_args( + message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID))) + goto oom; + + if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1)) + goto oom; + + if (!(n = strdup(name))) + goto oom; + + if (!dbus_pending_call_set_data(pending, m->name_data_slot, n, free)) + goto oom; + + n = NULL; + + if (!dbus_pending_call_set_notify(pending, query_pid_pending_cb, m, NULL)) + goto oom; + + dbus_message_unref(message); + dbus_pending_call_unref(pending); + + return 0; + +oom: + free(n); + + if (pending) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + } + + if (message) + dbus_message_unref(message); + + return -ENOMEM; +} + +int bus_broadcast(Manager *m, DBusMessage *message) { + bool oom = false; + Iterator i; + DBusConnection *c; + + assert(m); + assert(message); + + SET_FOREACH(c, m->bus_connections_for_dispatch, i) + if (c != m->system_bus || m->running_as == MANAGER_SYSTEM) + oom = !dbus_connection_send(c, message, NULL); + + SET_FOREACH(c, m->bus_connections, i) + if (c != m->system_bus || m->running_as == MANAGER_SYSTEM) + oom = !dbus_connection_send(c, message, NULL); + + return oom ? -ENOMEM : 0; +} + +bool bus_has_subscriber(Manager *m) { + Iterator i; + DBusConnection *c; + + assert(m); + + SET_FOREACH(c, m->bus_connections_for_dispatch, i) + if (bus_connection_has_subscriber(m, c)) + return true; + + SET_FOREACH(c, m->bus_connections, i) + if (bus_connection_has_subscriber(m, c)) + return true; + + return false; +} + +bool bus_connection_has_subscriber(Manager *m, DBusConnection *c) { + assert(m); + assert(c); + + return !set_isempty(BUS_CONNECTION_SUBSCRIBED(m, c)); +} + +int bus_fdset_add_all(Manager *m, FDSet *fds) { + Iterator i; + DBusConnection *c; + + assert(m); + assert(fds); + + /* When we are about to reexecute we add all D-Bus fds to the + * set to pass over to the newly executed systemd. They won't + * be used there however, except that they are closed at the + * very end of deserialization, those making it possible for + * clients to synchronously wait for systemd to reexec by + * simply waiting for disconnection */ + + SET_FOREACH(c, m->bus_connections_for_dispatch, i) { + int fd; + + if (dbus_connection_get_unix_fd(c, &fd)) { + fd = fdset_put_dup(fds, fd); + + if (fd < 0) + return fd; + } + } + + SET_FOREACH(c, m->bus_connections, i) { + int fd; + + if (dbus_connection_get_unix_fd(c, &fd)) { + fd = fdset_put_dup(fds, fd); + + if (fd < 0) + return fd; + } + } + + return 0; +} + +void bus_broadcast_finished( + Manager *m, + usec_t kernel_usec, + usec_t initrd_usec, + usec_t userspace_usec, + usec_t total_usec) { + + DBusMessage *message; + + assert(m); + + message = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartupFinished"); + if (!message) { + log_error("Out of memory."); + return; + } + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + if (!dbus_message_append_args(message, + DBUS_TYPE_UINT64, &kernel_usec, + DBUS_TYPE_UINT64, &initrd_usec, + DBUS_TYPE_UINT64, &userspace_usec, + DBUS_TYPE_UINT64, &total_usec, + DBUS_TYPE_INVALID)) { + log_error("Out of memory."); + goto finish; + } + + + if (bus_broadcast(m, message) < 0) { + log_error("Out of memory."); + goto finish; + } + +finish: + if (message) + dbus_message_unref(message); +} diff --git a/src/dbus.h b/src/dbus.h new file mode 100644 index 000000000..bd539d0e7 --- /dev/null +++ b/src/dbus.h @@ -0,0 +1,53 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbushfoo +#define foodbushfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "manager.h" + +int bus_init(Manager *m, bool try_bus_connect); +void bus_done(Manager *m); + +unsigned bus_dispatch(Manager *m); + +void bus_watch_event(Manager *m, Watch *w, int events); +void bus_timeout_event(Manager *m, Watch *w, int events); + +int bus_query_pid(Manager *m, const char *name); + +int bus_broadcast(Manager *m, DBusMessage *message); + +bool bus_has_subscriber(Manager *m); +bool bus_connection_has_subscriber(Manager *m, DBusConnection *c); + +int bus_fdset_add_all(Manager *m, FDSet *fds); + +void bus_broadcast_finished(Manager *m, usec_t kernel_usec, usec_t initrd_usec, usec_t userspace_usec, usec_t total_usec); + +#define BUS_CONNECTION_SUBSCRIBED(m, c) dbus_connection_get_data((c), (m)->subscribed_data_slot) +#define BUS_PENDING_CALL_NAME(m, p) dbus_pending_call_get_data((p), (m)->name_data_slot) + +extern const char * const bus_interface_table[]; + +#endif diff --git a/src/def.h b/src/def.h new file mode 100644 index 000000000..20aaa7c58 --- /dev/null +++ b/src/def.h @@ -0,0 +1,37 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodefhfoo +#define foodefhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "util.h" + +#define DEFAULT_TIMEOUT_USEC (90*USEC_PER_SEC) +#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC) + +#define DEFAULT_EXIT_USEC (5*USEC_PER_MINUTE) + +#define SYSTEMD_CGROUP_CONTROLLER "name=systemd" + +#define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT +#define SIGNALS_IGNORE SIGKILL,SIGPIPE + +#endif diff --git a/src/detect-virt.c b/src/detect-virt.c new file mode 100644 index 000000000..79cad5d8a --- /dev/null +++ b/src/detect-virt.c @@ -0,0 +1,48 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "util.h" +#include "virt.h" + +int main(int argc, char *argv[]) { + Virtualization r; + const char *id; + + /* This is mostly intended to be used for scripts which want + * to detect whether we are being run in a virtualized + * environment or not */ + + r = detect_virtualization(&id); + if (r < 0) { + log_error("Failed to check for virtualization: %s", strerror(-r)); + return EXIT_FAILURE; + } + + if (r > 0) + puts(id); + + return r > 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/device.c b/src/device.c new file mode 100644 index 000000000..0575379d8 --- /dev/null +++ b/src/device.c @@ -0,0 +1,616 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "unit.h" +#include "device.h" +#include "strv.h" +#include "log.h" +#include "unit-name.h" +#include "dbus-device.h" +#include "def.h" + +static const UnitActiveState state_translation_table[_DEVICE_STATE_MAX] = { + [DEVICE_DEAD] = UNIT_INACTIVE, + [DEVICE_PLUGGED] = UNIT_ACTIVE +}; + +static void device_unset_sysfs(Device *d) { + Device *first; + + assert(d); + + if (!d->sysfs) + return; + + /* Remove this unit from the chain of devices which share the + * same sysfs path. */ + first = hashmap_get(UNIT(d)->manager->devices_by_sysfs, d->sysfs); + LIST_REMOVE(Device, same_sysfs, first, d); + + if (first) + hashmap_remove_and_replace(UNIT(d)->manager->devices_by_sysfs, d->sysfs, first->sysfs, first); + else + hashmap_remove(UNIT(d)->manager->devices_by_sysfs, d->sysfs); + + free(d->sysfs); + d->sysfs = NULL; +} + +static void device_init(Unit *u) { + Device *d = DEVICE(u); + + assert(d); + assert(UNIT(d)->load_state == UNIT_STUB); + + /* In contrast to all other unit types we timeout jobs waiting + * for devices by default. This is because they otherwise wait + * indefinitely for plugged in devices, something which cannot + * happen for the other units since their operations time out + * anyway. */ + UNIT(d)->job_timeout = DEFAULT_TIMEOUT_USEC; + + UNIT(d)->ignore_on_isolate = true; + UNIT(d)->ignore_on_snapshot = true; +} + +static void device_done(Unit *u) { + Device *d = DEVICE(u); + + assert(d); + + device_unset_sysfs(d); +} + +static void device_set_state(Device *d, DeviceState state) { + DeviceState old_state; + assert(d); + + old_state = d->state; + d->state = state; + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(d)->id, + device_state_to_string(old_state), + device_state_to_string(state)); + + unit_notify(UNIT(d), state_translation_table[old_state], state_translation_table[state], true); +} + +static int device_coldplug(Unit *u) { + Device *d = DEVICE(u); + + assert(d); + assert(d->state == DEVICE_DEAD); + + if (d->sysfs) + device_set_state(d, DEVICE_PLUGGED); + + return 0; +} + +static void device_dump(Unit *u, FILE *f, const char *prefix) { + Device *d = DEVICE(u); + + assert(d); + + fprintf(f, + "%sDevice State: %s\n" + "%sSysfs Path: %s\n", + prefix, device_state_to_string(d->state), + prefix, strna(d->sysfs)); +} + +static UnitActiveState device_active_state(Unit *u) { + assert(u); + + return state_translation_table[DEVICE(u)->state]; +} + +static const char *device_sub_state_to_string(Unit *u) { + assert(u); + + return device_state_to_string(DEVICE(u)->state); +} + +static int device_add_escaped_name(Unit *u, const char *dn) { + char *e; + int r; + + assert(u); + assert(dn); + assert(dn[0] == '/'); + + if (!(e = unit_name_from_path(dn, ".device"))) + return -ENOMEM; + + r = unit_add_name(u, e); + free(e); + + if (r < 0 && r != -EEXIST) + return r; + + return 0; +} + +static int device_find_escape_name(Manager *m, const char *dn, Unit **_u) { + char *e; + Unit *u; + + assert(m); + assert(dn); + assert(dn[0] == '/'); + assert(_u); + + if (!(e = unit_name_from_path(dn, ".device"))) + return -ENOMEM; + + u = manager_get_unit(m, e); + free(e); + + if (u) { + *_u = u; + return 1; + } + + return 0; +} + +static int device_update_unit(Manager *m, struct udev_device *dev, const char *path, bool main) { + const char *sysfs, *model; + Unit *u = NULL; + int r; + bool delete; + + assert(m); + + if (!(sysfs = udev_device_get_syspath(dev))) + return -ENOMEM; + + if ((r = device_find_escape_name(m, path, &u)) < 0) + return r; + + if (u && DEVICE(u)->sysfs && !path_equal(DEVICE(u)->sysfs, sysfs)) + return -EEXIST; + + if (!u) { + delete = true; + + u = unit_new(m, sizeof(Device)); + if (!u) + return -ENOMEM; + + r = device_add_escaped_name(u, path); + if (r < 0) + goto fail; + + unit_add_to_load_queue(u); + } else + delete = false; + + /* If this was created via some dependency and has not + * actually been seen yet ->sysfs will not be + * initialized. Hence initialize it if necessary. */ + + if (!DEVICE(u)->sysfs) { + Device *first; + + if (!(DEVICE(u)->sysfs = strdup(sysfs))) { + r = -ENOMEM; + goto fail; + } + + if (!m->devices_by_sysfs) + if (!(m->devices_by_sysfs = hashmap_new(string_hash_func, string_compare_func))) { + r = -ENOMEM; + goto fail; + } + + first = hashmap_get(m->devices_by_sysfs, sysfs); + LIST_PREPEND(Device, same_sysfs, first, DEVICE(u)); + + if ((r = hashmap_replace(m->devices_by_sysfs, DEVICE(u)->sysfs, first)) < 0) + goto fail; + } + + if ((model = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE")) || + (model = udev_device_get_property_value(dev, "ID_MODEL"))) { + if ((r = unit_set_description(u, model)) < 0) + goto fail; + } else + if ((r = unit_set_description(u, path)) < 0) + goto fail; + + if (main) { + /* The additional systemd udev properties we only + * interpret for the main object */ + const char *wants, *alias; + + if ((alias = udev_device_get_property_value(dev, "SYSTEMD_ALIAS"))) { + if (!is_path(alias)) + log_warning("SYSTEMD_ALIAS for %s is not a path, ignoring: %s", sysfs, alias); + else { + if ((r = device_add_escaped_name(u, alias)) < 0) + goto fail; + } + } + + if ((wants = udev_device_get_property_value(dev, "SYSTEMD_WANTS"))) { + char *state, *w; + size_t l; + + FOREACH_WORD_QUOTED(w, l, wants, state) { + char *e; + + if (!(e = strndup(w, l))) { + r = -ENOMEM; + goto fail; + } + + r = unit_add_dependency_by_name(u, UNIT_WANTS, e, NULL, true); + free(e); + + if (r < 0) + goto fail; + } + } + } + + unit_add_to_dbus_queue(u); + return 0; + +fail: + log_warning("Failed to load device unit: %s", strerror(-r)); + + if (delete && u) + unit_free(u); + + return r; +} + +static int device_process_new_device(Manager *m, struct udev_device *dev, bool update_state) { + const char *sysfs, *dn; + struct udev_list_entry *item = NULL, *first = NULL; + + assert(m); + + if (!(sysfs = udev_device_get_syspath(dev))) + return -ENOMEM; + + /* Add the main unit named after the sysfs path */ + device_update_unit(m, dev, sysfs, true); + + /* Add an additional unit for the device node */ + if ((dn = udev_device_get_devnode(dev))) + device_update_unit(m, dev, dn, false); + + /* Add additional units for all symlinks */ + first = udev_device_get_devlinks_list_entry(dev); + udev_list_entry_foreach(item, first) { + const char *p; + struct stat st; + + /* Don't bother with the /dev/block links */ + p = udev_list_entry_get_name(item); + + if (path_startswith(p, "/dev/block/") || + path_startswith(p, "/dev/char/")) + continue; + + /* Verify that the symlink in the FS actually belongs + * to this device. This is useful to deal with + * conflicting devices, e.g. when two disks want the + * same /dev/disk/by-label/xxx link because they have + * the same label. We want to make sure that the same + * device that won the symlink wins in systemd, so we + * check the device node major/minor*/ + if (stat(p, &st) >= 0) + if ((!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) || + st.st_rdev != udev_device_get_devnum(dev)) + continue; + + device_update_unit(m, dev, p, false); + } + + if (update_state) { + Device *d, *l; + + manager_dispatch_load_queue(m); + + l = hashmap_get(m->devices_by_sysfs, sysfs); + LIST_FOREACH(same_sysfs, d, l) + device_set_state(d, DEVICE_PLUGGED); + } + + return 0; +} + +static int device_process_path(Manager *m, const char *path, bool update_state) { + int r; + struct udev_device *dev; + + assert(m); + assert(path); + + if (!(dev = udev_device_new_from_syspath(m->udev, path))) { + log_warning("Failed to get udev device object from udev for path %s.", path); + return -ENOMEM; + } + + r = device_process_new_device(m, dev, update_state); + udev_device_unref(dev); + return r; +} + +static int device_process_removed_device(Manager *m, struct udev_device *dev) { + const char *sysfs; + Device *d; + + assert(m); + assert(dev); + + if (!(sysfs = udev_device_get_syspath(dev))) + return -ENOMEM; + + /* Remove all units of this sysfs path */ + while ((d = hashmap_get(m->devices_by_sysfs, sysfs))) { + device_unset_sysfs(d); + device_set_state(d, DEVICE_DEAD); + } + + return 0; +} + +static Unit *device_following(Unit *u) { + Device *d = DEVICE(u); + Device *other, *first = NULL; + + assert(d); + + if (startswith(u->id, "sys-")) + return NULL; + + /* Make everybody follow the unit that's named after the sysfs path */ + for (other = d->same_sysfs_next; other; other = other->same_sysfs_next) + if (startswith(UNIT(other)->id, "sys-")) + return UNIT(other); + + for (other = d->same_sysfs_prev; other; other = other->same_sysfs_prev) { + if (startswith(UNIT(other)->id, "sys-")) + return UNIT(other); + + first = other; + } + + return UNIT(first); +} + +static int device_following_set(Unit *u, Set **_s) { + Device *d = DEVICE(u); + Device *other; + Set *s; + int r; + + assert(d); + assert(_s); + + if (!d->same_sysfs_prev && !d->same_sysfs_next) { + *_s = NULL; + return 0; + } + + if (!(s = set_new(NULL, NULL))) + return -ENOMEM; + + for (other = d->same_sysfs_next; other; other = other->same_sysfs_next) + if ((r = set_put(s, other)) < 0) + goto fail; + + for (other = d->same_sysfs_prev; other; other = other->same_sysfs_prev) + if ((r = set_put(s, other)) < 0) + goto fail; + + *_s = s; + return 1; + +fail: + set_free(s); + return r; +} + +static void device_shutdown(Manager *m) { + assert(m); + + if (m->udev_monitor) { + udev_monitor_unref(m->udev_monitor); + m->udev_monitor = NULL; + } + + if (m->udev) { + udev_unref(m->udev); + m->udev = NULL; + } + + hashmap_free(m->devices_by_sysfs); + m->devices_by_sysfs = NULL; +} + +static int device_enumerate(Manager *m) { + struct epoll_event ev; + int r; + struct udev_enumerate *e = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + + assert(m); + + if (!m->udev) { + if (!(m->udev = udev_new())) + return -ENOMEM; + + if (!(m->udev_monitor = udev_monitor_new_from_netlink(m->udev, "udev"))) { + r = -ENOMEM; + goto fail; + } + + /* This will fail if we are unprivileged, but that + * should not matter much, as user instances won't run + * during boot. */ + udev_monitor_set_receive_buffer_size(m->udev_monitor, 128*1024*1024); + + if (udev_monitor_filter_add_match_tag(m->udev_monitor, "systemd") < 0) { + r = -ENOMEM; + goto fail; + } + + if (udev_monitor_enable_receiving(m->udev_monitor) < 0) { + r = -EIO; + goto fail; + } + + m->udev_watch.type = WATCH_UDEV; + m->udev_watch.fd = udev_monitor_get_fd(m->udev_monitor); + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = &m->udev_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->udev_watch.fd, &ev) < 0) + return -errno; + } + + if (!(e = udev_enumerate_new(m->udev))) { + r = -ENOMEM; + goto fail; + } + if (udev_enumerate_add_match_tag(e, "systemd") < 0) { + r = -EIO; + goto fail; + } + + if (udev_enumerate_scan_devices(e) < 0) { + r = -EIO; + goto fail; + } + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) + device_process_path(m, udev_list_entry_get_name(item), false); + + udev_enumerate_unref(e); + return 0; + +fail: + if (e) + udev_enumerate_unref(e); + + device_shutdown(m); + return r; +} + +void device_fd_event(Manager *m, int events) { + struct udev_device *dev; + int r; + const char *action, *ready; + + assert(m); + + if (events != EPOLLIN) { + static RATELIMIT_DEFINE(limit, 10*USEC_PER_SEC, 5); + + if (!ratelimit_test(&limit)) + log_error("Failed to get udev event: %m"); + if (!(events & EPOLLIN)) + return; + } + + if (!(dev = udev_monitor_receive_device(m->udev_monitor))) { + /* + * libudev might filter-out devices which pass the bloom filter, + * so getting NULL here is not necessarily an error + */ + return; + } + + if (!(action = udev_device_get_action(dev))) { + log_error("Failed to get udev action string."); + goto fail; + } + + ready = udev_device_get_property_value(dev, "SYSTEMD_READY"); + + if (streq(action, "remove") || (ready && parse_boolean(ready) == 0)) { + if ((r = device_process_removed_device(m, dev)) < 0) { + log_error("Failed to process udev device event: %s", strerror(-r)); + goto fail; + } + } else { + if ((r = device_process_new_device(m, dev, true)) < 0) { + log_error("Failed to process udev device event: %s", strerror(-r)); + goto fail; + } + } + +fail: + udev_device_unref(dev); +} + +static const char* const device_state_table[_DEVICE_STATE_MAX] = { + [DEVICE_DEAD] = "dead", + [DEVICE_PLUGGED] = "plugged" +}; + +DEFINE_STRING_TABLE_LOOKUP(device_state, DeviceState); + +const UnitVTable device_vtable = { + .suffix = ".device", + .object_size = sizeof(Device), + .sections = + "Unit\0" + "Device\0" + "Install\0", + + .no_instances = true, + + .init = device_init, + + .load = unit_load_fragment_and_dropin_optional, + .done = device_done, + .coldplug = device_coldplug, + + .dump = device_dump, + + .active_state = device_active_state, + .sub_state_to_string = device_sub_state_to_string, + + .bus_interface = "org.freedesktop.systemd1.Device", + .bus_message_handler = bus_device_message_handler, + .bus_invalidating_properties = bus_device_invalidating_properties, + + .following = device_following, + .following_set = device_following_set, + + .enumerate = device_enumerate, + .shutdown = device_shutdown +}; diff --git a/src/device.h b/src/device.h new file mode 100644 index 000000000..a05c3d37b --- /dev/null +++ b/src/device.h @@ -0,0 +1,59 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodevicehfoo +#define foodevicehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Device Device; + +#include "unit.h" + +/* We simply watch devices, we cannot plug/unplug them. That + * simplifies the state engine greatly */ +typedef enum DeviceState { + DEVICE_DEAD, + DEVICE_PLUGGED, + _DEVICE_STATE_MAX, + _DEVICE_STATE_INVALID = -1 +} DeviceState; + +struct Device { + Unit meta; + + char *sysfs; + + /* In order to be able to distinguish dependencies on + different device nodes we might end up creating multiple + devices for the same sysfs path. We chain them up here. */ + + LIST_FIELDS(struct Device, same_sysfs); + + DeviceState state; +}; + +extern const UnitVTable device_vtable; + +void device_fd_event(Manager *m, int events); + +const char* device_state_to_string(DeviceState i); +DeviceState device_state_from_string(const char *s); + +#endif diff --git a/src/execute.c b/src/execute.c new file mode 100644 index 000000000..dab485682 --- /dev/null +++ b/src/execute.c @@ -0,0 +1,2112 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_PAM +#include +#endif + +#include "execute.h" +#include "strv.h" +#include "macro.h" +#include "util.h" +#include "log.h" +#include "ioprio.h" +#include "securebits.h" +#include "cgroup.h" +#include "namespace.h" +#include "tcpwrap.h" +#include "exit-status.h" +#include "missing.h" +#include "utmp-wtmp.h" +#include "def.h" +#include "loopback-setup.h" + +/* This assumes there is a 'tty' group */ +#define TTY_MODE 0620 + +static int shift_fds(int fds[], unsigned n_fds) { + int start, restart_from; + + if (n_fds <= 0) + return 0; + + /* Modifies the fds array! (sorts it) */ + + assert(fds); + + start = 0; + for (;;) { + int i; + + restart_from = -1; + + for (i = start; i < (int) n_fds; i++) { + int nfd; + + /* Already at right index? */ + if (fds[i] == i+3) + continue; + + if ((nfd = fcntl(fds[i], F_DUPFD, i+3)) < 0) + return -errno; + + close_nointr_nofail(fds[i]); + fds[i] = nfd; + + /* Hmm, the fd we wanted isn't free? Then + * let's remember that and try again from here*/ + if (nfd != i+3 && restart_from < 0) + restart_from = i; + } + + if (restart_from < 0) + break; + + start = restart_from; + } + + return 0; +} + +static int flags_fds(const int fds[], unsigned n_fds, bool nonblock) { + unsigned i; + int r; + + if (n_fds <= 0) + return 0; + + assert(fds); + + /* Drops/Sets O_NONBLOCK and FD_CLOEXEC from the file flags */ + + for (i = 0; i < n_fds; i++) { + + if ((r = fd_nonblock(fds[i], nonblock)) < 0) + return r; + + /* We unconditionally drop FD_CLOEXEC from the fds, + * since after all we want to pass these fds to our + * children */ + + if ((r = fd_cloexec(fds[i], false)) < 0) + return r; + } + + return 0; +} + +static const char *tty_path(const ExecContext *context) { + assert(context); + + if (context->tty_path) + return context->tty_path; + + return "/dev/console"; +} + +void exec_context_tty_reset(const ExecContext *context) { + assert(context); + + if (context->tty_vhangup) + terminal_vhangup(tty_path(context)); + + if (context->tty_reset) + reset_terminal(tty_path(context)); + + if (context->tty_vt_disallocate && context->tty_path) + vt_disallocate(context->tty_path); +} + +static int open_null_as(int flags, int nfd) { + int fd, r; + + assert(nfd >= 0); + + if ((fd = open("/dev/null", flags|O_NOCTTY)) < 0) + return -errno; + + if (fd != nfd) { + r = dup2(fd, nfd) < 0 ? -errno : nfd; + close_nointr_nofail(fd); + } else + r = nfd; + + return r; +} + +static int connect_logger_as(const ExecContext *context, ExecOutput output, const char *ident, int nfd) { + int fd, r; + union sockaddr_union sa; + + assert(context); + assert(output < _EXEC_OUTPUT_MAX); + assert(ident); + assert(nfd >= 0); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return -errno; + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, "/run/systemd/journal/stdout", sizeof(sa.un.sun_path)); + + r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)); + if (r < 0) { + close_nointr_nofail(fd); + return -errno; + } + + if (shutdown(fd, SHUT_RD) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + dprintf(fd, + "%s\n" + "%i\n" + "%i\n" + "%i\n" + "%i\n" + "%i\n", + context->syslog_identifier ? context->syslog_identifier : ident, + context->syslog_priority, + !!context->syslog_level_prefix, + output == EXEC_OUTPUT_SYSLOG || output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE, + output == EXEC_OUTPUT_KMSG || output == EXEC_OUTPUT_KMSG_AND_CONSOLE, + output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || output == EXEC_OUTPUT_KMSG_AND_CONSOLE || output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE); + + if (fd != nfd) { + r = dup2(fd, nfd) < 0 ? -errno : nfd; + close_nointr_nofail(fd); + } else + r = nfd; + + return r; +} +static int open_terminal_as(const char *path, mode_t mode, int nfd) { + int fd, r; + + assert(path); + assert(nfd >= 0); + + if ((fd = open_terminal(path, mode | O_NOCTTY)) < 0) + return fd; + + if (fd != nfd) { + r = dup2(fd, nfd) < 0 ? -errno : nfd; + close_nointr_nofail(fd); + } else + r = 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) + return EXEC_INPUT_NULL; + + if (std_input == EXEC_INPUT_SOCKET && socket_fd < 0) + return EXEC_INPUT_NULL; + + return std_input; +} + +static int fixup_output(ExecOutput std_output, int socket_fd) { + + if (std_output == EXEC_OUTPUT_SOCKET && socket_fd < 0) + return EXEC_OUTPUT_INHERIT; + + return std_output; +} + +static int setup_input(const ExecContext *context, int socket_fd, bool apply_tty_stdin) { + ExecInput i; + + assert(context); + + i = fixup_input(context->std_input, socket_fd, apply_tty_stdin); + + switch (i) { + + case EXEC_INPUT_NULL: + return open_null_as(O_RDONLY, STDIN_FILENO); + + case EXEC_INPUT_TTY: + case EXEC_INPUT_TTY_FORCE: + case EXEC_INPUT_TTY_FAIL: { + int fd, r; + + if ((fd = acquire_terminal( + tty_path(context), + i == EXEC_INPUT_TTY_FAIL, + i == EXEC_INPUT_TTY_FORCE, + false)) < 0) + return fd; + + if (fd != STDIN_FILENO) { + r = dup2(fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; + close_nointr_nofail(fd); + } else + r = STDIN_FILENO; + + return r; + } + + case EXEC_INPUT_SOCKET: + return dup2(socket_fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; + + default: + assert_not_reached("Unknown input type"); + } +} + +static int setup_output(const ExecContext *context, int socket_fd, const char *ident, bool apply_tty_stdin) { + ExecOutput o; + ExecInput i; + + assert(context); + assert(ident); + + i = fixup_input(context->std_input, socket_fd, apply_tty_stdin); + o = fixup_output(context->std_output, socket_fd); + + /* This expects the input is already set up */ + + switch (o) { + + case EXEC_OUTPUT_INHERIT: + + /* If input got downgraded, inherit the original value */ + if (i == EXEC_INPUT_NULL && is_terminal_input(context->std_input)) + return open_terminal_as(tty_path(context), O_WRONLY, STDOUT_FILENO); + + /* If the input is connected to anything that's not a /dev/null, inherit that... */ + if (i != EXEC_INPUT_NULL) + return dup2(STDIN_FILENO, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO; + + /* If we are not started from PID 1 we just inherit STDOUT from our parent process. */ + if (getppid() != 1) + return STDOUT_FILENO; + + /* We need to open /dev/null here anew, to get the + * right access mode. So we fall through */ + + case EXEC_OUTPUT_NULL: + return open_null_as(O_WRONLY, STDOUT_FILENO); + + case EXEC_OUTPUT_TTY: + if (is_terminal_input(i)) + return dup2(STDIN_FILENO, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO; + + /* We don't reset the terminal if this is just about output */ + return open_terminal_as(tty_path(context), O_WRONLY, STDOUT_FILENO); + + case EXEC_OUTPUT_SYSLOG: + case EXEC_OUTPUT_SYSLOG_AND_CONSOLE: + case EXEC_OUTPUT_KMSG: + case EXEC_OUTPUT_KMSG_AND_CONSOLE: + case EXEC_OUTPUT_JOURNAL: + case EXEC_OUTPUT_JOURNAL_AND_CONSOLE: + return connect_logger_as(context, o, ident, STDOUT_FILENO); + + case EXEC_OUTPUT_SOCKET: + assert(socket_fd >= 0); + return dup2(socket_fd, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO; + + default: + assert_not_reached("Unknown output type"); + } +} + +static int setup_error(const ExecContext *context, int socket_fd, const char *ident, bool apply_tty_stdin) { + ExecOutput o, e; + ExecInput i; + + assert(context); + assert(ident); + + i = fixup_input(context->std_input, socket_fd, apply_tty_stdin); + o = fixup_output(context->std_output, socket_fd); + e = fixup_output(context->std_error, socket_fd); + + /* This expects the input and output are already set up */ + + /* Don't change the stderr file descriptor if we inherit all + * the way and are not on a tty */ + if (e == EXEC_OUTPUT_INHERIT && + o == EXEC_OUTPUT_INHERIT && + i == EXEC_INPUT_NULL && + !is_terminal_input(context->std_input) && + getppid () != 1) + return STDERR_FILENO; + + /* Duplicate from stdout if possible */ + if (e == o || e == EXEC_OUTPUT_INHERIT) + return dup2(STDOUT_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO; + + switch (e) { + + case EXEC_OUTPUT_NULL: + return open_null_as(O_WRONLY, STDERR_FILENO); + + case EXEC_OUTPUT_TTY: + if (is_terminal_input(i)) + return dup2(STDIN_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO; + + /* We don't reset the terminal if this is just about output */ + return open_terminal_as(tty_path(context), O_WRONLY, STDERR_FILENO); + + case EXEC_OUTPUT_SYSLOG: + case EXEC_OUTPUT_SYSLOG_AND_CONSOLE: + case EXEC_OUTPUT_KMSG: + case EXEC_OUTPUT_KMSG_AND_CONSOLE: + case EXEC_OUTPUT_JOURNAL: + case EXEC_OUTPUT_JOURNAL_AND_CONSOLE: + return connect_logger_as(context, e, ident, STDERR_FILENO); + + case EXEC_OUTPUT_SOCKET: + assert(socket_fd >= 0); + return dup2(socket_fd, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO; + + default: + assert_not_reached("Unknown error type"); + } +} + +static int chown_terminal(int fd, uid_t uid) { + struct stat st; + + assert(fd >= 0); + + /* This might fail. What matters are the results. */ + (void) fchown(fd, uid, -1); + (void) fchmod(fd, TTY_MODE); + + if (fstat(fd, &st) < 0) + return -errno; + + if (st.st_uid != uid || (st.st_mode & 0777) != TTY_MODE) + return -EPERM; + + return 0; +} + +static int setup_confirm_stdio(const ExecContext *context, + int *_saved_stdin, + int *_saved_stdout) { + int fd = -1, saved_stdin, saved_stdout = -1, r; + + assert(context); + assert(_saved_stdin); + assert(_saved_stdout); + + /* This returns positive EXIT_xxx return values instead of + * negative errno style values! */ + + if ((saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3)) < 0) + return EXIT_STDIN; + + if ((saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3)) < 0) { + r = EXIT_STDOUT; + goto fail; + } + + if ((fd = acquire_terminal( + tty_path(context), + context->std_input == EXEC_INPUT_TTY_FAIL, + context->std_input == EXEC_INPUT_TTY_FORCE, + false)) < 0) { + r = EXIT_STDIN; + goto fail; + } + + if (chown_terminal(fd, getuid()) < 0) { + r = EXIT_STDIN; + goto fail; + } + + if (dup2(fd, STDIN_FILENO) < 0) { + r = EXIT_STDIN; + goto fail; + } + + if (dup2(fd, STDOUT_FILENO) < 0) { + r = EXIT_STDOUT; + goto fail; + } + + if (fd >= 2) + close_nointr_nofail(fd); + + *_saved_stdin = saved_stdin; + *_saved_stdout = saved_stdout; + + return 0; + +fail: + if (saved_stdout >= 0) + close_nointr_nofail(saved_stdout); + + if (saved_stdin >= 0) + close_nointr_nofail(saved_stdin); + + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +static int restore_confirm_stdio(const ExecContext *context, + int *saved_stdin, + int *saved_stdout, + bool *keep_stdin, + bool *keep_stdout) { + + assert(context); + assert(saved_stdin); + assert(*saved_stdin >= 0); + assert(saved_stdout); + assert(*saved_stdout >= 0); + + /* This returns positive EXIT_xxx return values instead of + * negative errno style values! */ + + if (is_terminal_input(context->std_input)) { + + /* The service wants terminal input. */ + + *keep_stdin = true; + *keep_stdout = + context->std_output == EXEC_OUTPUT_INHERIT || + context->std_output == EXEC_OUTPUT_TTY; + + } else { + /* If the service doesn't want a controlling terminal, + * then we need to get rid entirely of what we have + * already. */ + + if (release_terminal() < 0) + return EXIT_STDIN; + + if (dup2(*saved_stdin, STDIN_FILENO) < 0) + return EXIT_STDIN; + + if (dup2(*saved_stdout, STDOUT_FILENO) < 0) + return EXIT_STDOUT; + + *keep_stdout = *keep_stdin = false; + } + + return 0; +} + +static int enforce_groups(const ExecContext *context, const char *username, gid_t gid) { + bool keep_groups = false; + int r; + + assert(context); + + /* Lookup and set GID and supplementary group list. Here too + * we avoid NSS lookups for gid=0. */ + + if (context->group || username) { + + if (context->group) { + const char *g = context->group; + + if ((r = get_group_creds(&g, &gid)) < 0) + return r; + } + + /* First step, initialize groups from /etc/groups */ + if (username && gid != 0) { + if (initgroups(username, gid) < 0) + return -errno; + + keep_groups = true; + } + + /* Second step, set our gids */ + if (setresgid(gid, gid, gid) < 0) + return -errno; + } + + if (context->supplementary_groups) { + int ngroups_max, k; + gid_t *gids; + char **i; + + /* Final step, initialize any manually set supplementary groups */ + assert_se((ngroups_max = (int) sysconf(_SC_NGROUPS_MAX)) > 0); + + if (!(gids = new(gid_t, ngroups_max))) + return -ENOMEM; + + if (keep_groups) { + if ((k = getgroups(ngroups_max, gids)) < 0) { + free(gids); + return -errno; + } + } else + k = 0; + + STRV_FOREACH(i, context->supplementary_groups) { + const char *g; + + if (k >= ngroups_max) { + free(gids); + return -E2BIG; + } + + g = *i; + r = get_group_creds(&g, gids+k); + if (r < 0) { + free(gids); + return r; + } + + k++; + } + + if (setgroups(k, gids) < 0) { + free(gids); + return -errno; + } + + free(gids); + } + + return 0; +} + +static int enforce_user(const ExecContext *context, uid_t uid) { + int r; + assert(context); + + /* Sets (but doesn't lookup) the uid and make sure we keep the + * capabilities while doing so. */ + + if (context->capabilities) { + cap_t d; + static const cap_value_t bits[] = { + CAP_SETUID, /* Necessary so that we can run setresuid() below */ + CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */ + }; + + /* First step: If we need to keep capabilities but + * drop privileges we need to make sure we keep our + * caps, whiel we drop privileges. */ + if (uid != 0) { + int sb = context->secure_bits|SECURE_KEEP_CAPS; + + if (prctl(PR_GET_SECUREBITS) != sb) + if (prctl(PR_SET_SECUREBITS, sb) < 0) + return -errno; + } + + /* Second step: set the capabilities. This will reduce + * the capabilities to the minimum we need. */ + + if (!(d = cap_dup(context->capabilities))) + return -errno; + + if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 || + cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) { + r = -errno; + cap_free(d); + return r; + } + + if (cap_set_proc(d) < 0) { + r = -errno; + cap_free(d); + return r; + } + + cap_free(d); + } + + /* Third step: actually set the uids */ + if (setresuid(uid, uid, uid) < 0) + return -errno; + + /* At this point we should have all necessary capabilities but + are otherwise a normal user. However, the caps might got + corrupted due to the setresuid() so we need clean them up + later. This is done outside of this call. */ + + return 0; +} + +#ifdef HAVE_PAM + +static int null_conv( + int num_msg, + const struct pam_message **msg, + struct pam_response **resp, + void *appdata_ptr) { + + /* We don't support conversations */ + + return PAM_CONV_ERR; +} + +static int setup_pam( + const char *name, + const char *user, + const char *tty, + char ***pam_env, + int fds[], unsigned n_fds) { + + static const struct pam_conv conv = { + .conv = null_conv, + .appdata_ptr = NULL + }; + + pam_handle_t *handle = NULL; + sigset_t ss, old_ss; + int pam_code = PAM_SUCCESS; + int err; + char **e = NULL; + bool close_session = false; + pid_t pam_pid = 0, parent_pid; + + assert(name); + assert(user); + assert(pam_env); + + /* We set up PAM in the parent process, then fork. The child + * will then stay around until killed via PR_GET_PDEATHSIG or + * systemd via the cgroup logic. It will then remove the PAM + * session again. The parent process will exec() the actual + * daemon. We do things this way to ensure that the main PID + * of the daemon is the one we initially fork()ed. */ + + if ((pam_code = pam_start(name, user, &conv, &handle)) != PAM_SUCCESS) { + handle = NULL; + goto fail; + } + + if (tty) + if ((pam_code = pam_set_item(handle, PAM_TTY, tty)) != PAM_SUCCESS) + goto fail; + + if ((pam_code = pam_acct_mgmt(handle, PAM_SILENT)) != PAM_SUCCESS) + goto fail; + + if ((pam_code = pam_open_session(handle, PAM_SILENT)) != PAM_SUCCESS) + goto fail; + + close_session = true; + + if ((!(e = pam_getenvlist(handle)))) { + pam_code = PAM_BUF_ERR; + goto fail; + } + + /* Block SIGTERM, so that we know that it won't get lost in + * the child */ + if (sigemptyset(&ss) < 0 || + sigaddset(&ss, SIGTERM) < 0 || + sigprocmask(SIG_BLOCK, &ss, &old_ss) < 0) + goto fail; + + parent_pid = getpid(); + + if ((pam_pid = fork()) < 0) + goto fail; + + if (pam_pid == 0) { + int sig; + int r = EXIT_PAM; + + /* The child's job is to reset the PAM session on + * termination */ + + /* This string must fit in 10 chars (i.e. the length + * of "/sbin/init"), to look pretty in /bin/ps */ + rename_process("(sd-pam)"); + + /* Make sure we don't keep open the passed fds in this + child. We assume that otherwise only those fds are + open here that have been opened by PAM. */ + close_many(fds, n_fds); + + /* Wait until our parent died. This will most likely + * not work since the kernel does not allow + * unprivileged parents kill their privileged children + * this way. We rely on the control groups kill logic + * to do the rest for us. */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + goto child_finish; + + /* Check if our parent process might already have + * died? */ + if (getppid() == parent_pid) { + for (;;) { + if (sigwait(&ss, &sig) < 0) { + if (errno == EINTR) + continue; + + goto child_finish; + } + + assert(sig == SIGTERM); + break; + } + } + + /* If our parent died we'll end the session */ + if (getppid() != parent_pid) + if ((pam_code = pam_close_session(handle, PAM_DATA_SILENT)) != PAM_SUCCESS) + goto child_finish; + + r = 0; + + child_finish: + pam_end(handle, pam_code | PAM_DATA_SILENT); + _exit(r); + } + + /* If the child was forked off successfully it will do all the + * cleanups, so forget about the handle here. */ + handle = NULL; + + /* Unblock SIGTERM again in the parent */ + if (sigprocmask(SIG_SETMASK, &old_ss, NULL) < 0) + goto fail; + + /* We close the log explicitly here, since the PAM modules + * might have opened it, but we don't want this fd around. */ + closelog(); + + *pam_env = e; + e = NULL; + + return 0; + +fail: + if (pam_code != PAM_SUCCESS) + err = -EPERM; /* PAM errors do not map to errno */ + else + err = -errno; + + if (handle) { + if (close_session) + pam_code = pam_close_session(handle, PAM_DATA_SILENT); + + pam_end(handle, pam_code | PAM_DATA_SILENT); + } + + strv_free(e); + + closelog(); + + if (pam_pid > 1) { + kill(pam_pid, SIGTERM); + kill(pam_pid, SIGCONT); + } + + return err; +} +#endif + +static int do_capability_bounding_set_drop(uint64_t drop) { + unsigned long i; + cap_t old_cap = NULL, new_cap = NULL; + cap_flag_value_t fv; + int r; + + /* If we are run as PID 1 we will lack CAP_SETPCAP by default + * in the effective set (yes, the kernel drops that when + * executing init!), so get it back temporarily so that we can + * call PR_CAPBSET_DROP. */ + + old_cap = cap_get_proc(); + if (!old_cap) + return -errno; + + if (cap_get_flag(old_cap, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) { + r = -errno; + goto finish; + } + + if (fv != CAP_SET) { + static const cap_value_t v = CAP_SETPCAP; + + new_cap = cap_dup(old_cap); + if (!new_cap) { + r = -errno; + goto finish; + } + + if (cap_set_flag(new_cap, CAP_EFFECTIVE, 1, &v, CAP_SET) < 0) { + r = -errno; + goto finish; + } + + if (cap_set_proc(new_cap) < 0) { + r = -errno; + goto finish; + } + } + + for (i = 0; i <= cap_last_cap(); i++) + if (drop & ((uint64_t) 1ULL << (uint64_t) i)) { + if (prctl(PR_CAPBSET_DROP, i) < 0) { + r = -errno; + goto finish; + } + } + + r = 0; + +finish: + if (new_cap) + cap_free(new_cap); + + if (old_cap) { + cap_set_proc(old_cap); + cap_free(old_cap); + } + + return r; +} + +static void rename_process_from_path(const char *path) { + char process_name[11]; + const char *p; + size_t l; + + /* This resulting string must fit in 10 chars (i.e. the length + * of "/sbin/init") to look pretty in /bin/ps */ + + p = file_name_from_path(path); + if (isempty(p)) { + rename_process("(...)"); + return; + } + + l = strlen(p); + if (l > 8) { + /* The end of the process name is usually more + * interesting, since the first bit might just be + * "systemd-" */ + p = p + l - 8; + l = 8; + } + + process_name[0] = '('; + memcpy(process_name+1, p, l); + process_name[1+l] = ')'; + process_name[1+l+1] = 0; + + rename_process(process_name); +} + +int exec_spawn(ExecCommand *command, + char **argv, + const ExecContext *context, + int fds[], unsigned n_fds, + char **environment, + bool apply_permissions, + bool apply_chroot, + bool apply_tty_stdin, + bool confirm_spawn, + CGroupBonding *cgroup_bondings, + CGroupAttribute *cgroup_attributes, + pid_t *ret) { + + pid_t pid; + int r; + char *line; + int socket_fd; + char **files_env = NULL; + + assert(command); + assert(context); + assert(ret); + assert(fds || n_fds <= 0); + + if (context->std_input == EXEC_INPUT_SOCKET || + context->std_output == EXEC_OUTPUT_SOCKET || + context->std_error == EXEC_OUTPUT_SOCKET) { + + if (n_fds != 1) + return -EINVAL; + + socket_fd = fds[0]; + + fds = NULL; + n_fds = 0; + } else + socket_fd = -1; + + if ((r = exec_context_load_environment(context, &files_env)) < 0) { + log_error("Failed to load environment files: %s", strerror(-r)); + return r; + } + + if (!argv) + argv = command->argv; + + if (!(line = exec_command_line(argv))) { + r = -ENOMEM; + goto fail_parent; + } + + log_debug("About to execute: %s", line); + free(line); + + r = cgroup_bonding_realize_list(cgroup_bondings); + if (r < 0) + goto fail_parent; + + cgroup_attribute_apply_list(cgroup_attributes, cgroup_bondings); + + if ((pid = fork()) < 0) { + r = -errno; + goto fail_parent; + } + + if (pid == 0) { + int i, err; + sigset_t ss; + const char *username = NULL, *home = NULL; + uid_t uid = (uid_t) -1; + gid_t gid = (gid_t) -1; + char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL; + unsigned n_env = 0; + int saved_stdout = -1, saved_stdin = -1; + bool keep_stdout = false, keep_stdin = false, set_access = false; + + /* child */ + + rename_process_from_path(command->path); + + /* We reset exactly these signals, since they are the + * only ones we set to SIG_IGN in the main daemon. All + * others we leave untouched because we set them to + * SIG_DFL or a valid handler initially, both of which + * will be demoted to SIG_DFL. */ + default_signals(SIGNALS_CRASH_HANDLER, + SIGNALS_IGNORE, -1); + + if (context->ignore_sigpipe) + ignore_signals(SIGPIPE, -1); + + assert_se(sigemptyset(&ss) == 0); + if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0) { + err = -errno; + r = EXIT_SIGNAL_MASK; + goto fail_child; + } + + /* Close sockets very early to make sure we don't + * block init reexecution because it cannot bind its + * sockets */ + log_forget_fds(); + err = close_all_fds(socket_fd >= 0 ? &socket_fd : fds, + socket_fd >= 0 ? 1 : n_fds); + if (err < 0) { + r = EXIT_FDS; + goto fail_child; + } + + if (!context->same_pgrp) + if (setsid() < 0) { + err = -errno; + r = EXIT_SETSID; + goto fail_child; + } + + if (context->tcpwrap_name) { + if (socket_fd >= 0) + if (!socket_tcpwrap(socket_fd, context->tcpwrap_name)) { + err = -EACCES; + r = EXIT_TCPWRAP; + goto fail_child; + } + + for (i = 0; i < (int) n_fds; i++) { + if (!socket_tcpwrap(fds[i], context->tcpwrap_name)) { + err = -EACCES; + r = EXIT_TCPWRAP; + goto fail_child; + } + } + } + + exec_context_tty_reset(context); + + /* We skip the confirmation step if we shall not apply the TTY */ + if (confirm_spawn && + (!is_terminal_input(context->std_input) || apply_tty_stdin)) { + char response; + + /* Set up terminal for the question */ + if ((r = setup_confirm_stdio(context, + &saved_stdin, &saved_stdout))) { + err = -errno; + goto fail_child; + } + + /* Now ask the question. */ + if (!(line = exec_command_line(argv))) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + r = ask(&response, "yns", "Execute %s? [Yes, No, Skip] ", line); + free(line); + + if (r < 0 || response == 'n') { + err = -ECANCELED; + r = EXIT_CONFIRM; + goto fail_child; + } else if (response == 's') { + err = r = 0; + goto fail_child; + } + + /* Release terminal for the question */ + if ((r = restore_confirm_stdio(context, + &saved_stdin, &saved_stdout, + &keep_stdin, &keep_stdout))) { + err = -errno; + goto fail_child; + } + } + + /* If a socket is connected to STDIN/STDOUT/STDERR, we + * must sure to drop O_NONBLOCK */ + if (socket_fd >= 0) + fd_nonblock(socket_fd, false); + + if (!keep_stdin) { + err = setup_input(context, socket_fd, apply_tty_stdin); + if (err < 0) { + r = EXIT_STDIN; + goto fail_child; + } + } + + if (!keep_stdout) { + err = setup_output(context, socket_fd, file_name_from_path(command->path), apply_tty_stdin); + if (err < 0) { + r = EXIT_STDOUT; + goto fail_child; + } + } + + err = setup_error(context, socket_fd, file_name_from_path(command->path), apply_tty_stdin); + if (err < 0) { + r = EXIT_STDERR; + goto fail_child; + } + + if (cgroup_bondings) { + err = cgroup_bonding_install_list(cgroup_bondings, 0); + if (err < 0) { + r = EXIT_CGROUP; + goto fail_child; + } + } + + if (context->oom_score_adjust_set) { + char t[16]; + + snprintf(t, sizeof(t), "%i", context->oom_score_adjust); + char_array_0(t); + + if (write_one_line_file("/proc/self/oom_score_adj", t) < 0) { + /* Compatibility with Linux <= 2.6.35 */ + + int adj; + + adj = (context->oom_score_adjust * -OOM_DISABLE) / OOM_SCORE_ADJ_MAX; + adj = CLAMP(adj, OOM_DISABLE, OOM_ADJUST_MAX); + + snprintf(t, sizeof(t), "%i", adj); + char_array_0(t); + + if (write_one_line_file("/proc/self/oom_adj", t) < 0 + && errno != EACCES) { + err = -errno; + r = EXIT_OOM_ADJUST; + goto fail_child; + } + } + } + + if (context->nice_set) + if (setpriority(PRIO_PROCESS, 0, context->nice) < 0) { + err = -errno; + r = EXIT_NICE; + goto fail_child; + } + + if (context->cpu_sched_set) { + struct sched_param param; + + zero(param); + param.sched_priority = context->cpu_sched_priority; + + if (sched_setscheduler(0, context->cpu_sched_policy | + (context->cpu_sched_reset_on_fork ? SCHED_RESET_ON_FORK : 0), ¶m) < 0) { + err = -errno; + r = EXIT_SETSCHEDULER; + goto fail_child; + } + } + + if (context->cpuset) + if (sched_setaffinity(0, CPU_ALLOC_SIZE(context->cpuset_ncpus), context->cpuset) < 0) { + err = -errno; + r = EXIT_CPUAFFINITY; + goto fail_child; + } + + if (context->ioprio_set) + if (ioprio_set(IOPRIO_WHO_PROCESS, 0, context->ioprio) < 0) { + err = -errno; + r = EXIT_IOPRIO; + goto fail_child; + } + + if (context->timer_slack_nsec_set) + if (prctl(PR_SET_TIMERSLACK, context->timer_slack_nsec) < 0) { + err = -errno; + r = EXIT_TIMERSLACK; + goto fail_child; + } + + if (context->utmp_id) + utmp_put_init_process(context->utmp_id, getpid(), getsid(0), context->tty_path); + + if (context->user) { + username = context->user; + err = get_user_creds(&username, &uid, &gid, &home); + if (err < 0) { + r = EXIT_USER; + goto fail_child; + } + + if (is_terminal_input(context->std_input)) { + err = chown_terminal(STDIN_FILENO, uid); + if (err < 0) { + r = EXIT_STDIN; + goto fail_child; + } + } + + if (cgroup_bondings && context->control_group_modify) { + err = cgroup_bonding_set_group_access_list(cgroup_bondings, 0755, uid, gid); + if (err >= 0) + err = cgroup_bonding_set_task_access_list(cgroup_bondings, 0644, uid, gid, context->control_group_persistent); + if (err < 0) { + r = EXIT_CGROUP; + goto fail_child; + } + + set_access = true; + } + } + + if (cgroup_bondings && !set_access && context->control_group_persistent >= 0) { + err = cgroup_bonding_set_task_access_list(cgroup_bondings, (mode_t) -1, (uid_t) -1, (uid_t) -1, context->control_group_persistent); + if (err < 0) { + r = EXIT_CGROUP; + goto fail_child; + } + } + + if (apply_permissions) { + err = enforce_groups(context, username, gid); + if (err < 0) { + r = EXIT_GROUP; + goto fail_child; + } + } + + umask(context->umask); + +#ifdef HAVE_PAM + if (context->pam_name && username) { + err = setup_pam(context->pam_name, username, context->tty_path, &pam_env, fds, n_fds); + if (err < 0) { + r = EXIT_PAM; + goto fail_child; + } + } +#endif + if (context->private_network) { + if (unshare(CLONE_NEWNET) < 0) { + err = -errno; + r = EXIT_NETWORK; + goto fail_child; + } + + loopback_setup(); + } + + if (strv_length(context->read_write_dirs) > 0 || + strv_length(context->read_only_dirs) > 0 || + strv_length(context->inaccessible_dirs) > 0 || + context->mount_flags != MS_SHARED || + context->private_tmp) { + err = setup_namespace(context->read_write_dirs, + context->read_only_dirs, + context->inaccessible_dirs, + context->private_tmp, + context->mount_flags); + if (err < 0) { + r = EXIT_NAMESPACE; + goto fail_child; + } + } + + if (apply_chroot) { + if (context->root_directory) + if (chroot(context->root_directory) < 0) { + err = -errno; + r = EXIT_CHROOT; + goto fail_child; + } + + if (chdir(context->working_directory ? context->working_directory : "/") < 0) { + err = -errno; + r = EXIT_CHDIR; + goto fail_child; + } + } else { + + char *d; + + if (asprintf(&d, "%s/%s", + context->root_directory ? context->root_directory : "", + context->working_directory ? context->working_directory : "") < 0) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + if (chdir(d) < 0) { + err = -errno; + free(d); + r = EXIT_CHDIR; + goto fail_child; + } + + free(d); + } + + /* We repeat the fd closing here, to make sure that + * nothing is leaked from the PAM modules */ + err = close_all_fds(fds, n_fds); + if (err >= 0) + err = shift_fds(fds, n_fds); + if (err >= 0) + err = flags_fds(fds, n_fds, context->non_blocking); + if (err < 0) { + r = EXIT_FDS; + goto fail_child; + } + + if (apply_permissions) { + + for (i = 0; i < RLIMIT_NLIMITS; i++) { + if (!context->rlimit[i]) + continue; + + if (setrlimit(i, context->rlimit[i]) < 0) { + err = -errno; + r = EXIT_LIMITS; + goto fail_child; + } + } + + if (context->capability_bounding_set_drop) { + err = do_capability_bounding_set_drop(context->capability_bounding_set_drop); + if (err < 0) { + r = EXIT_CAPABILITIES; + goto fail_child; + } + } + + if (context->user) { + err = enforce_user(context, uid); + if (err < 0) { + r = EXIT_USER; + goto fail_child; + } + } + + /* PR_GET_SECUREBITS is not privileged, while + * PR_SET_SECUREBITS is. So to suppress + * potential EPERMs we'll try not to call + * PR_SET_SECUREBITS unless necessary. */ + if (prctl(PR_GET_SECUREBITS) != context->secure_bits) + if (prctl(PR_SET_SECUREBITS, context->secure_bits) < 0) { + err = -errno; + r = EXIT_SECUREBITS; + goto fail_child; + } + + if (context->capabilities) + if (cap_set_proc(context->capabilities) < 0) { + err = -errno; + r = EXIT_CAPABILITIES; + goto fail_child; + } + } + + if (!(our_env = new0(char*, 7))) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + if (n_fds > 0) + if (asprintf(our_env + n_env++, "LISTEN_PID=%lu", (unsigned long) getpid()) < 0 || + asprintf(our_env + n_env++, "LISTEN_FDS=%u", n_fds) < 0) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + if (home) + if (asprintf(our_env + n_env++, "HOME=%s", home) < 0) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + if (username) + if (asprintf(our_env + n_env++, "LOGNAME=%s", username) < 0 || + asprintf(our_env + n_env++, "USER=%s", username) < 0) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + if (is_terminal_input(context->std_input) || + context->std_output == EXEC_OUTPUT_TTY || + context->std_error == EXEC_OUTPUT_TTY) + if (!(our_env[n_env++] = strdup(default_term_for_tty(tty_path(context))))) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + assert(n_env <= 7); + + if (!(final_env = strv_env_merge( + 5, + environment, + our_env, + context->environment, + files_env, + pam_env, + NULL))) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + if (!(final_argv = replace_env_argv(argv, final_env))) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + final_env = strv_env_clean(final_env); + + execve(command->path, final_argv, final_env); + err = -errno; + r = EXIT_EXEC; + + fail_child: + if (r != 0) { + log_open(); + log_warning("Failed at step %s spawning %s: %s", + exit_status_to_string(r, EXIT_STATUS_SYSTEMD), + command->path, strerror(-err)); + } + + strv_free(our_env); + strv_free(final_env); + strv_free(pam_env); + strv_free(files_env); + strv_free(final_argv); + + if (saved_stdin >= 0) + close_nointr_nofail(saved_stdin); + + if (saved_stdout >= 0) + close_nointr_nofail(saved_stdout); + + _exit(r); + } + + strv_free(files_env); + + /* We add the new process to the cgroup both in the child (so + * that we can be sure that no user code is ever executed + * outside of the cgroup) and in the parent (so that we can be + * sure that when we kill the cgroup the process will be + * killed too). */ + if (cgroup_bondings) + cgroup_bonding_install_list(cgroup_bondings, pid); + + log_debug("Forked %s as %lu", command->path, (unsigned long) pid); + + exec_status_start(&command->exec_status, pid); + + *ret = pid; + return 0; + +fail_parent: + strv_free(files_env); + + return r; +} + +void exec_context_init(ExecContext *c) { + assert(c); + + c->umask = 0022; + c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 0); + c->cpu_sched_policy = SCHED_OTHER; + c->syslog_priority = LOG_DAEMON|LOG_INFO; + c->syslog_level_prefix = true; + c->mount_flags = MS_SHARED; + c->kill_signal = SIGTERM; + c->send_sigkill = true; + c->control_group_persistent = -1; + c->ignore_sigpipe = true; +} + +void exec_context_done(ExecContext *c) { + unsigned l; + + assert(c); + + strv_free(c->environment); + c->environment = NULL; + + strv_free(c->environment_files); + c->environment_files = NULL; + + for (l = 0; l < ELEMENTSOF(c->rlimit); l++) { + free(c->rlimit[l]); + c->rlimit[l] = NULL; + } + + free(c->working_directory); + c->working_directory = NULL; + free(c->root_directory); + c->root_directory = NULL; + + free(c->tty_path); + c->tty_path = NULL; + + free(c->tcpwrap_name); + c->tcpwrap_name = NULL; + + free(c->syslog_identifier); + c->syslog_identifier = NULL; + + free(c->user); + c->user = NULL; + + free(c->group); + c->group = NULL; + + strv_free(c->supplementary_groups); + c->supplementary_groups = NULL; + + free(c->pam_name); + c->pam_name = NULL; + + if (c->capabilities) { + cap_free(c->capabilities); + c->capabilities = NULL; + } + + strv_free(c->read_only_dirs); + c->read_only_dirs = NULL; + + strv_free(c->read_write_dirs); + c->read_write_dirs = NULL; + + strv_free(c->inaccessible_dirs); + c->inaccessible_dirs = NULL; + + if (c->cpuset) + CPU_FREE(c->cpuset); + + free(c->utmp_id); + c->utmp_id = NULL; +} + +void exec_command_done(ExecCommand *c) { + assert(c); + + free(c->path); + c->path = NULL; + + strv_free(c->argv); + c->argv = NULL; +} + +void exec_command_done_array(ExecCommand *c, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) + exec_command_done(c+i); +} + +void exec_command_free_list(ExecCommand *c) { + ExecCommand *i; + + while ((i = c)) { + LIST_REMOVE(ExecCommand, command, c, i); + exec_command_done(i); + free(i); + } +} + +void exec_command_free_array(ExecCommand **c, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) { + exec_command_free_list(c[i]); + c[i] = NULL; + } +} + +int exec_context_load_environment(const ExecContext *c, char ***l) { + char **i, **r = NULL; + + assert(c); + assert(l); + + STRV_FOREACH(i, c->environment_files) { + char *fn; + int k; + bool ignore = false; + char **p; + + fn = *i; + + if (fn[0] == '-') { + ignore = true; + fn ++; + } + + if (!path_is_absolute(fn)) { + + if (ignore) + continue; + + strv_free(r); + return -EINVAL; + } + + if ((k = load_env_file(fn, &p)) < 0) { + + if (ignore) + continue; + + strv_free(r); + return k; + } + + if (r == NULL) + r = p; + else { + char **m; + + m = strv_env_merge(2, r, p); + strv_free(r); + strv_free(p); + + if (!m) + return -ENOMEM; + + r = m; + } + } + + *l = r; + + return 0; +} + +static void strv_fprintf(FILE *f, char **l) { + char **g; + + assert(f); + + STRV_FOREACH(g, l) + fprintf(f, " %s", *g); +} + +void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { + char ** e; + unsigned i; + + assert(c); + assert(f); + + if (!prefix) + prefix = ""; + + fprintf(f, + "%sUMask: %04o\n" + "%sWorkingDirectory: %s\n" + "%sRootDirectory: %s\n" + "%sNonBlocking: %s\n" + "%sPrivateTmp: %s\n" + "%sControlGroupModify: %s\n" + "%sControlGroupPersistent: %s\n" + "%sPrivateNetwork: %s\n", + prefix, c->umask, + prefix, c->working_directory ? c->working_directory : "/", + prefix, c->root_directory ? c->root_directory : "/", + prefix, yes_no(c->non_blocking), + prefix, yes_no(c->private_tmp), + prefix, yes_no(c->control_group_modify), + prefix, yes_no(c->control_group_persistent), + prefix, yes_no(c->private_network)); + + STRV_FOREACH(e, c->environment) + fprintf(f, "%sEnvironment: %s\n", prefix, *e); + + STRV_FOREACH(e, c->environment_files) + fprintf(f, "%sEnvironmentFile: %s\n", prefix, *e); + + if (c->tcpwrap_name) + fprintf(f, + "%sTCPWrapName: %s\n", + prefix, c->tcpwrap_name); + + if (c->nice_set) + fprintf(f, + "%sNice: %i\n", + prefix, c->nice); + + if (c->oom_score_adjust_set) + fprintf(f, + "%sOOMScoreAdjust: %i\n", + prefix, c->oom_score_adjust); + + for (i = 0; i < RLIM_NLIMITS; i++) + if (c->rlimit[i]) + fprintf(f, "%s%s: %llu\n", prefix, rlimit_to_string(i), (unsigned long long) c->rlimit[i]->rlim_max); + + if (c->ioprio_set) + fprintf(f, + "%sIOSchedulingClass: %s\n" + "%sIOPriority: %i\n", + prefix, ioprio_class_to_string(IOPRIO_PRIO_CLASS(c->ioprio)), + prefix, (int) IOPRIO_PRIO_DATA(c->ioprio)); + + if (c->cpu_sched_set) + fprintf(f, + "%sCPUSchedulingPolicy: %s\n" + "%sCPUSchedulingPriority: %i\n" + "%sCPUSchedulingResetOnFork: %s\n", + prefix, sched_policy_to_string(c->cpu_sched_policy), + prefix, c->cpu_sched_priority, + prefix, yes_no(c->cpu_sched_reset_on_fork)); + + if (c->cpuset) { + fprintf(f, "%sCPUAffinity:", prefix); + for (i = 0; i < c->cpuset_ncpus; i++) + if (CPU_ISSET_S(i, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset)) + fprintf(f, " %i", i); + fputs("\n", f); + } + + if (c->timer_slack_nsec_set) + fprintf(f, "%sTimerSlackNSec: %lu\n", prefix, c->timer_slack_nsec); + + fprintf(f, + "%sStandardInput: %s\n" + "%sStandardOutput: %s\n" + "%sStandardError: %s\n", + prefix, exec_input_to_string(c->std_input), + prefix, exec_output_to_string(c->std_output), + prefix, exec_output_to_string(c->std_error)); + + if (c->tty_path) + fprintf(f, + "%sTTYPath: %s\n" + "%sTTYReset: %s\n" + "%sTTYVHangup: %s\n" + "%sTTYVTDisallocate: %s\n", + prefix, c->tty_path, + prefix, yes_no(c->tty_reset), + prefix, yes_no(c->tty_vhangup), + prefix, yes_no(c->tty_vt_disallocate)); + + if (c->std_output == EXEC_OUTPUT_SYSLOG || c->std_output == EXEC_OUTPUT_KMSG || c->std_output == EXEC_OUTPUT_JOURNAL || + c->std_output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || c->std_output == EXEC_OUTPUT_KMSG_AND_CONSOLE || c->std_output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE || + c->std_error == EXEC_OUTPUT_SYSLOG || c->std_error == EXEC_OUTPUT_KMSG || c->std_error == EXEC_OUTPUT_JOURNAL || + c->std_error == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || c->std_error == EXEC_OUTPUT_KMSG_AND_CONSOLE || c->std_error == EXEC_OUTPUT_JOURNAL_AND_CONSOLE) + fprintf(f, + "%sSyslogFacility: %s\n" + "%sSyslogLevel: %s\n", + prefix, log_facility_unshifted_to_string(c->syslog_priority >> 3), + prefix, log_level_to_string(LOG_PRI(c->syslog_priority))); + + if (c->capabilities) { + char *t; + if ((t = cap_to_text(c->capabilities, NULL))) { + fprintf(f, "%sCapabilities: %s\n", + prefix, t); + cap_free(t); + } + } + + if (c->secure_bits) + fprintf(f, "%sSecure Bits:%s%s%s%s%s%s\n", + prefix, + (c->secure_bits & SECURE_KEEP_CAPS) ? " keep-caps" : "", + (c->secure_bits & SECURE_KEEP_CAPS_LOCKED) ? " keep-caps-locked" : "", + (c->secure_bits & SECURE_NO_SETUID_FIXUP) ? " no-setuid-fixup" : "", + (c->secure_bits & SECURE_NO_SETUID_FIXUP_LOCKED) ? " no-setuid-fixup-locked" : "", + (c->secure_bits & SECURE_NOROOT) ? " noroot" : "", + (c->secure_bits & SECURE_NOROOT_LOCKED) ? "noroot-locked" : ""); + + if (c->capability_bounding_set_drop) { + unsigned long l; + fprintf(f, "%sCapabilityBoundingSet:", prefix); + + for (l = 0; l <= cap_last_cap(); l++) + if (!(c->capability_bounding_set_drop & ((uint64_t) 1ULL << (uint64_t) l))) { + char *t; + + if ((t = cap_to_name(l))) { + fprintf(f, " %s", t); + cap_free(t); + } + } + + fputs("\n", f); + } + + if (c->user) + fprintf(f, "%sUser: %s\n", prefix, c->user); + if (c->group) + fprintf(f, "%sGroup: %s\n", prefix, c->group); + + if (strv_length(c->supplementary_groups) > 0) { + fprintf(f, "%sSupplementaryGroups:", prefix); + strv_fprintf(f, c->supplementary_groups); + fputs("\n", f); + } + + if (c->pam_name) + fprintf(f, "%sPAMName: %s\n", prefix, c->pam_name); + + if (strv_length(c->read_write_dirs) > 0) { + fprintf(f, "%sReadWriteDirs:", prefix); + strv_fprintf(f, c->read_write_dirs); + fputs("\n", f); + } + + if (strv_length(c->read_only_dirs) > 0) { + fprintf(f, "%sReadOnlyDirs:", prefix); + strv_fprintf(f, c->read_only_dirs); + fputs("\n", f); + } + + if (strv_length(c->inaccessible_dirs) > 0) { + fprintf(f, "%sInaccessibleDirs:", prefix); + strv_fprintf(f, c->inaccessible_dirs); + fputs("\n", f); + } + + fprintf(f, + "%sKillMode: %s\n" + "%sKillSignal: SIG%s\n" + "%sSendSIGKILL: %s\n" + "%sIgnoreSIGPIPE: %s\n", + prefix, kill_mode_to_string(c->kill_mode), + prefix, signal_to_string(c->kill_signal), + prefix, yes_no(c->send_sigkill), + prefix, yes_no(c->ignore_sigpipe)); + + if (c->utmp_id) + fprintf(f, + "%sUtmpIdentifier: %s\n", + prefix, c->utmp_id); +} + +void exec_status_start(ExecStatus *s, pid_t pid) { + assert(s); + + zero(*s); + s->pid = pid; + dual_timestamp_get(&s->start_timestamp); +} + +void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status) { + assert(s); + + if (s->pid && s->pid != pid) + zero(*s); + + s->pid = pid; + dual_timestamp_get(&s->exit_timestamp); + + s->code = code; + s->status = status; + + if (context) { + if (context->utmp_id) + utmp_put_dead_process(context->utmp_id, pid, code, status); + + exec_context_tty_reset(context); + } +} + +void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) { + char buf[FORMAT_TIMESTAMP_MAX]; + + assert(s); + assert(f); + + if (!prefix) + prefix = ""; + + if (s->pid <= 0) + return; + + fprintf(f, + "%sPID: %lu\n", + prefix, (unsigned long) s->pid); + + if (s->start_timestamp.realtime > 0) + fprintf(f, + "%sStart Timestamp: %s\n", + prefix, format_timestamp(buf, sizeof(buf), s->start_timestamp.realtime)); + + if (s->exit_timestamp.realtime > 0) + fprintf(f, + "%sExit Timestamp: %s\n" + "%sExit Code: %s\n" + "%sExit Status: %i\n", + prefix, format_timestamp(buf, sizeof(buf), s->exit_timestamp.realtime), + prefix, sigchld_code_to_string(s->code), + prefix, s->status); +} + +char *exec_command_line(char **argv) { + size_t k; + char *n, *p, **a; + bool first = true; + + assert(argv); + + k = 1; + STRV_FOREACH(a, argv) + k += strlen(*a)+3; + + if (!(n = new(char, k))) + return NULL; + + p = n; + STRV_FOREACH(a, argv) { + + if (!first) + *(p++) = ' '; + else + first = false; + + if (strpbrk(*a, WHITESPACE)) { + *(p++) = '\''; + p = stpcpy(p, *a); + *(p++) = '\''; + } else + p = stpcpy(p, *a); + + } + + *p = 0; + + /* FIXME: this doesn't really handle arguments that have + * spaces and ticks in them */ + + return n; +} + +void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix) { + char *p2; + const char *prefix2; + + char *cmd; + + assert(c); + assert(f); + + if (!prefix) + prefix = ""; + p2 = strappend(prefix, "\t"); + prefix2 = p2 ? p2 : prefix; + + cmd = exec_command_line(c->argv); + + fprintf(f, + "%sCommand Line: %s\n", + prefix, cmd ? cmd : strerror(ENOMEM)); + + free(cmd); + + exec_status_dump(&c->exec_status, f, prefix2); + + free(p2); +} + +void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix) { + assert(f); + + if (!prefix) + prefix = ""; + + LIST_FOREACH(command, c, c) + exec_command_dump(c, f, prefix); +} + +void exec_command_append_list(ExecCommand **l, ExecCommand *e) { + ExecCommand *end; + + assert(l); + assert(e); + + if (*l) { + /* It's kind of important, that we keep the order here */ + LIST_FIND_TAIL(ExecCommand, command, *l, end); + LIST_INSERT_AFTER(ExecCommand, command, *l, end, e); + } else + *l = e; +} + +int exec_command_set(ExecCommand *c, const char *path, ...) { + va_list ap; + char **l, *p; + + assert(c); + assert(path); + + va_start(ap, path); + l = strv_new_ap(path, ap); + va_end(ap); + + if (!l) + return -ENOMEM; + + if (!(p = strdup(path))) { + strv_free(l); + return -ENOMEM; + } + + free(c->path); + c->path = p; + + strv_free(c->argv); + c->argv = l; + + return 0; +} + +static const char* const exec_input_table[_EXEC_INPUT_MAX] = { + [EXEC_INPUT_NULL] = "null", + [EXEC_INPUT_TTY] = "tty", + [EXEC_INPUT_TTY_FORCE] = "tty-force", + [EXEC_INPUT_TTY_FAIL] = "tty-fail", + [EXEC_INPUT_SOCKET] = "socket" +}; + +DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput); + +static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = { + [EXEC_OUTPUT_INHERIT] = "inherit", + [EXEC_OUTPUT_NULL] = "null", + [EXEC_OUTPUT_TTY] = "tty", + [EXEC_OUTPUT_SYSLOG] = "syslog", + [EXEC_OUTPUT_SYSLOG_AND_CONSOLE] = "syslog+console", + [EXEC_OUTPUT_KMSG] = "kmsg", + [EXEC_OUTPUT_KMSG_AND_CONSOLE] = "kmsg+console", + [EXEC_OUTPUT_JOURNAL] = "journal", + [EXEC_OUTPUT_JOURNAL_AND_CONSOLE] = "journal+console", + [EXEC_OUTPUT_SOCKET] = "socket" +}; + +DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput); + +static const char* const kill_mode_table[_KILL_MODE_MAX] = { + [KILL_CONTROL_GROUP] = "control-group", + [KILL_PROCESS] = "process", + [KILL_NONE] = "none" +}; + +DEFINE_STRING_TABLE_LOOKUP(kill_mode, KillMode); + +static const char* const kill_who_table[_KILL_WHO_MAX] = { + [KILL_MAIN] = "main", + [KILL_CONTROL] = "control", + [KILL_ALL] = "all" +}; + +DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/execute.h b/src/execute.h new file mode 100644 index 000000000..0d7e7dd65 --- /dev/null +++ b/src/execute.h @@ -0,0 +1,233 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooexecutehfoo +#define fooexecutehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct ExecStatus ExecStatus; +typedef struct ExecCommand ExecCommand; +typedef struct ExecContext ExecContext; + +#include +#include +#include +#include +#include +#include +#include + +struct CGroupBonding; +struct CGroupAttribute; + +#include "list.h" +#include "util.h" + +typedef enum KillMode { + KILL_CONTROL_GROUP = 0, + KILL_PROCESS, + KILL_NONE, + _KILL_MODE_MAX, + _KILL_MODE_INVALID = -1 +} KillMode; + +typedef enum KillWho { + KILL_MAIN, + KILL_CONTROL, + KILL_ALL, + _KILL_WHO_MAX, + _KILL_WHO_INVALID = -1 +} KillWho; + +typedef enum ExecInput { + EXEC_INPUT_NULL, + EXEC_INPUT_TTY, + EXEC_INPUT_TTY_FORCE, + EXEC_INPUT_TTY_FAIL, + EXEC_INPUT_SOCKET, + _EXEC_INPUT_MAX, + _EXEC_INPUT_INVALID = -1 +} ExecInput; + +typedef enum ExecOutput { + EXEC_OUTPUT_INHERIT, + EXEC_OUTPUT_NULL, + EXEC_OUTPUT_TTY, + EXEC_OUTPUT_SYSLOG, + EXEC_OUTPUT_SYSLOG_AND_CONSOLE, + EXEC_OUTPUT_KMSG, + EXEC_OUTPUT_KMSG_AND_CONSOLE, + EXEC_OUTPUT_JOURNAL, + EXEC_OUTPUT_JOURNAL_AND_CONSOLE, + EXEC_OUTPUT_SOCKET, + _EXEC_OUTPUT_MAX, + _EXEC_OUTPUT_INVALID = -1 +} ExecOutput; + +struct ExecStatus { + dual_timestamp start_timestamp; + dual_timestamp exit_timestamp; + pid_t pid; + int code; /* as in siginfo_t::si_code */ + int status; /* as in sigingo_t::si_status */ +}; + +struct ExecCommand { + char *path; + char **argv; + ExecStatus exec_status; + LIST_FIELDS(ExecCommand, command); /* useful for chaining commands */ + bool ignore; +}; + +struct ExecContext { + char **environment; + char **environment_files; + + struct rlimit *rlimit[RLIMIT_NLIMITS]; + char *working_directory, *root_directory; + + mode_t umask; + int oom_score_adjust; + int nice; + int ioprio; + int cpu_sched_policy; + int cpu_sched_priority; + + cpu_set_t *cpuset; + unsigned cpuset_ncpus; + + ExecInput std_input; + ExecOutput std_output; + ExecOutput std_error; + + unsigned long timer_slack_nsec; + + char *tcpwrap_name; + + char *tty_path; + + bool tty_reset; + bool tty_vhangup; + bool tty_vt_disallocate; + + bool ignore_sigpipe; + + /* Since resolving these names might might involve socket + * connections and we don't want to deadlock ourselves these + * names are resolved on execution only and in the child + * process. */ + char *user; + char *group; + char **supplementary_groups; + + char *pam_name; + + char *utmp_id; + + char **read_write_dirs, **read_only_dirs, **inaccessible_dirs; + unsigned long mount_flags; + + uint64_t capability_bounding_set_drop; + + /* Not relevant for spawning processes, just for killing */ + KillMode kill_mode; + int kill_signal; + bool send_sigkill; + + cap_t capabilities; + int secure_bits; + + int syslog_priority; + char *syslog_identifier; + bool syslog_level_prefix; + + bool cpu_sched_reset_on_fork; + bool non_blocking; + bool private_tmp; + bool private_network; + + bool control_group_modify; + int control_group_persistent; + + /* This is not exposed to the user but available + * internally. We need it to make sure that whenever we spawn + * /bin/mount it is run in the same process group as us so + * that the autofs logic detects that it belongs to us and we + * don't enter a trigger loop. */ + bool same_pgrp; + + bool oom_score_adjust_set:1; + bool nice_set:1; + bool ioprio_set:1; + bool cpu_sched_set:1; + bool timer_slack_nsec_set:1; +}; + +int exec_spawn(ExecCommand *command, + char **argv, + const ExecContext *context, + int fds[], unsigned n_fds, + char **environment, + bool apply_permissions, + bool apply_chroot, + bool apply_tty_stdin, + bool confirm_spawn, + struct CGroupBonding *cgroup_bondings, + struct CGroupAttribute *cgroup_attributes, + pid_t *ret); + +void exec_command_done(ExecCommand *c); +void exec_command_done_array(ExecCommand *c, unsigned n); + +void exec_command_free_list(ExecCommand *c); +void exec_command_free_array(ExecCommand **c, unsigned n); + +char *exec_command_line(char **argv); + +void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix); +void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix); +void exec_command_append_list(ExecCommand **l, ExecCommand *e); +int exec_command_set(ExecCommand *c, const char *path, ...); + +void exec_context_init(ExecContext *c); +void exec_context_done(ExecContext *c); +void exec_context_dump(ExecContext *c, FILE* f, const char *prefix); +void exec_context_tty_reset(const ExecContext *context); + +int exec_context_load_environment(const ExecContext *c, char ***l); + +void exec_status_start(ExecStatus *s, pid_t pid); +void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status); +void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix); + +const char* exec_output_to_string(ExecOutput i); +ExecOutput exec_output_from_string(const char *s); + +const char* exec_input_to_string(ExecInput i); +ExecInput exec_input_from_string(const char *s); + +const char *kill_mode_to_string(KillMode k); +KillMode kill_mode_from_string(const char *s); + +const char *kill_who_to_string(KillWho k); +KillWho kill_who_from_string(const char *s); + +#endif diff --git a/src/exit-status.c b/src/exit-status.c new file mode 100644 index 000000000..ab8907d32 --- /dev/null +++ b/src/exit-status.c @@ -0,0 +1,180 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "exit-status.h" + +const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { + + /* We cast to int here, so that -Wenum doesn't complain that + * EXIT_SUCCESS/EXIT_FAILURE aren't in the enum */ + + switch ((int) status) { + + case EXIT_SUCCESS: + return "SUCCESS"; + + case EXIT_FAILURE: + return "FAILURE"; + } + + + if (level == EXIT_STATUS_SYSTEMD || level == EXIT_STATUS_LSB) { + switch ((int) status) { + + case EXIT_CHDIR: + return "CHDIR"; + + case EXIT_NICE: + return "NICE"; + + case EXIT_FDS: + return "FDS"; + + case EXIT_EXEC: + return "EXEC"; + + case EXIT_MEMORY: + return "MEMORY"; + + case EXIT_LIMITS: + return "LIMITS"; + + case EXIT_OOM_ADJUST: + return "OOM_ADJUST"; + + case EXIT_SIGNAL_MASK: + return "SIGNAL_MASK"; + + case EXIT_STDIN: + return "STDIN"; + + case EXIT_STDOUT: + return "STDOUT"; + + case EXIT_CHROOT: + return "CHROOT"; + + case EXIT_IOPRIO: + return "IOPRIO"; + + case EXIT_TIMERSLACK: + return "TIMERSLACK"; + + case EXIT_SECUREBITS: + return "SECUREBITS"; + + case EXIT_SETSCHEDULER: + return "SETSCHEDULER"; + + case EXIT_CPUAFFINITY: + return "CPUAFFINITY"; + + case EXIT_GROUP: + return "GROUP"; + + case EXIT_USER: + return "USER"; + + case EXIT_CAPABILITIES: + return "CAPABILITIES"; + + case EXIT_CGROUP: + return "CGROUP"; + + case EXIT_SETSID: + return "SETSID"; + + case EXIT_CONFIRM: + return "CONFIRM"; + + case EXIT_STDERR: + return "STDERR"; + + case EXIT_TCPWRAP: + return "TCPWRAP"; + + case EXIT_PAM: + return "PAM"; + + case EXIT_NETWORK: + return "NETWORK"; + + case EXIT_NAMESPACE: + return "NAMESPACE"; + } + } + + if (level == EXIT_STATUS_LSB) { + switch ((int) status) { + + case EXIT_INVALIDARGUMENT: + return "INVALIDARGUMENT"; + + case EXIT_NOTIMPLEMENTED: + return "NOTIMPLEMENTED"; + + case EXIT_NOPERMISSION: + return "NOPERMISSION"; + + case EXIT_NOTINSTALLED: + return "NOTINSSTALLED"; + + case EXIT_NOTCONFIGURED: + return "NOTCONFIGURED"; + + case EXIT_NOTRUNNING: + return "NOTRUNNING"; + } + } + + return NULL; +} + + +bool is_clean_exit(int code, int status) { + + if (code == CLD_EXITED) + return status == 0; + + /* If a daemon does not implement handlers for some of the + * signals that's not considered an unclean shutdown */ + if (code == CLD_KILLED) + return + status == SIGHUP || + status == SIGINT || + status == SIGTERM || + status == SIGPIPE; + + return false; +} + +bool is_clean_exit_lsb(int code, int status) { + + if (is_clean_exit(code, status)) + return true; + + return + code == CLD_EXITED && + (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED); +} diff --git a/src/exit-status.h b/src/exit-status.h new file mode 100644 index 000000000..44ef87956 --- /dev/null +++ b/src/exit-status.h @@ -0,0 +1,85 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooexitstatushfoo +#define fooexitstatushfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +typedef enum ExitStatus { + /* EXIT_SUCCESS defined by libc */ + /* EXIT_FAILURE defined by libc */ + EXIT_INVALIDARGUMENT = 2, + EXIT_NOTIMPLEMENTED = 3, + EXIT_NOPERMISSION = 4, + EXIT_NOTINSTALLED = 5, + EXIT_NOTCONFIGURED = 6, + EXIT_NOTRUNNING = 7, + + /* The LSB suggests that error codes >= 200 are "reserved". We + * use them here under the assumption that they hence are + * unused by init scripts. + * + * http://refspecs.freestandards.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */ + + EXIT_CHDIR = 200, + EXIT_NICE, + EXIT_FDS, + EXIT_EXEC, + EXIT_MEMORY, + EXIT_LIMITS, + EXIT_OOM_ADJUST, + EXIT_SIGNAL_MASK, + EXIT_STDIN, + EXIT_STDOUT, + EXIT_CHROOT, /* 210 */ + EXIT_IOPRIO, + EXIT_TIMERSLACK, + EXIT_SECUREBITS, + EXIT_SETSCHEDULER, + EXIT_CPUAFFINITY, + EXIT_GROUP, + EXIT_USER, + EXIT_CAPABILITIES, + EXIT_CGROUP, + EXIT_SETSID, /* 220 */ + EXIT_CONFIRM, + EXIT_STDERR, + EXIT_TCPWRAP, + EXIT_PAM, + EXIT_NETWORK, + EXIT_NAMESPACE + +} ExitStatus; + +typedef enum ExitStatusLevel { + EXIT_STATUS_MINIMAL, + EXIT_STATUS_SYSTEMD, + EXIT_STATUS_LSB, + EXIT_STATUS_FULL = EXIT_STATUS_LSB +} ExitStatusLevel; + +const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level); + +bool is_clean_exit(int code, int status); +bool is_clean_exit_lsb(int code, int status); + +#endif diff --git a/src/fdset.c b/src/fdset.c new file mode 100644 index 000000000..e67fe6fab --- /dev/null +++ b/src/fdset.c @@ -0,0 +1,167 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "set.h" +#include "util.h" +#include "macro.h" +#include "fdset.h" + +#define MAKE_SET(s) ((Set*) s) +#define MAKE_FDSET(s) ((FDSet*) s) + +/* Make sure we can distuingish fd 0 and NULL */ +#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1) +#define PTR_TO_FD(p) (PTR_TO_INT(p)-1) + +FDSet *fdset_new(void) { + return MAKE_FDSET(set_new(trivial_hash_func, trivial_compare_func)); +} + +void fdset_free(FDSet *s) { + void *p; + + while ((p = set_steal_first(MAKE_SET(s)))) { + /* Valgrind's fd might have ended up in this set here, + * due to fdset_new_fill(). We'll ignore all failures + * here, so that the EBADFD that valgrind will return + * us on close() doesn't influence us */ + + /* When reloading duplicates of the private bus + * connection fds and suchlike are closed here, which + * has no effect at all, since they are only + * duplicates. So don't be surprised about these log + * messages. */ + + log_debug("Closing left-over fd %i", PTR_TO_FD(p)); + close_nointr(PTR_TO_FD(p)); + } + + set_free(MAKE_SET(s)); +} + +int fdset_put(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return set_put(MAKE_SET(s), FD_TO_PTR(fd)); +} + +int fdset_put_dup(FDSet *s, int fd) { + int copy, r; + + assert(s); + assert(fd >= 0); + + if ((copy = fcntl(fd, F_DUPFD_CLOEXEC, 3)) < 0) + return -errno; + + if ((r = fdset_put(s, copy)) < 0) { + close_nointr_nofail(copy); + return r; + } + + return copy; +} + +bool fdset_contains(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return !!set_get(MAKE_SET(s), FD_TO_PTR(fd)); +} + +int fdset_remove(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT; +} + +int fdset_new_fill(FDSet **_s) { + DIR *d; + struct dirent *de; + int r = 0; + FDSet *s; + + assert(_s); + + /* Creates an fdsets and fills in all currently open file + * descriptors. */ + + if (!(d = opendir("/proc/self/fd"))) + return -errno; + + if (!(s = fdset_new())) { + r = -ENOMEM; + goto finish; + } + + while ((de = readdir(d))) { + int fd = -1; + + if (ignore_file(de->d_name)) + continue; + + if ((r = safe_atoi(de->d_name, &fd)) < 0) + goto finish; + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + if ((r = fdset_put(s, fd)) < 0) + goto finish; + } + + r = 0; + *_s = s; + s = NULL; + +finish: + closedir(d); + + /* We won't close the fds here! */ + if (s) + set_free(MAKE_SET(s)); + + return r; +} + +int fdset_cloexec(FDSet *fds, bool b) { + Iterator i; + void *p; + int r; + + assert(fds); + + SET_FOREACH(p, MAKE_SET(fds), i) + if ((r = fd_cloexec(PTR_TO_FD(p), b)) < 0) + return r; + + return 0; +} diff --git a/src/fdset.h b/src/fdset.h new file mode 100644 index 000000000..044a9e6d1 --- /dev/null +++ b/src/fdset.h @@ -0,0 +1,40 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foofdsethfoo +#define foofdsethfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct FDSet FDSet; + +FDSet* fdset_new(void); +void fdset_free(FDSet *s); + +int fdset_put(FDSet *s, int fd); +int fdset_put_dup(FDSet *s, int fd); + +bool fdset_contains(FDSet *s, int fd); +int fdset_remove(FDSet *s, int fd); + +int fdset_new_fill(FDSet **_s); + +int fdset_cloexec(FDSet *fds, bool b); + +#endif diff --git a/src/fsck.c b/src/fsck.c new file mode 100644 index 000000000..d3ac83c25 --- /dev/null +++ b/src/fsck.c @@ -0,0 +1,406 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "util.h" +#include "dbus-common.h" +#include "special.h" +#include "bus-errors.h" +#include "virt.h" + +static bool arg_skip = false; +static bool arg_force = false; +static bool arg_show_progress = false; + +static void start_target(const char *target, bool isolate) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + const char *mode, *basic_target = "basic.target"; + DBusConnection *bus = NULL; + + assert(target); + + dbus_error_init(&error); + + if (bus_connect(DBUS_BUS_SYSTEM, &bus, NULL, &error) < 0) { + log_error("Failed to get D-Bus connection: %s", bus_error_message(&error)); + goto finish; + } + + if (isolate) + mode = "isolate"; + else + mode = "replace"; + + log_info("Running request %s/start/%s", target, mode); + + if (!(m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartUnitReplace"))) { + log_error("Could not allocate message."); + goto finish; + } + + /* Start these units only if we can replace base.target with it */ + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &basic_target, + DBUS_TYPE_STRING, &target, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID)) { + log_error("Could not attach target and flag information to message."); + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + + /* Don't print a warning if we aren't called during + * startup */ + if (!dbus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB)) + log_error("Failed to start unit: %s", bus_error_message(&error)); + + goto finish; + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + if (bus) { + dbus_connection_flush(bus); + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + dbus_error_free(&error); +} + +static int parse_proc_cmdline(void) { + char *line, *w, *state; + int r; + size_t l; + + if (detect_container(NULL) > 0) + return 0; + + if ((r = read_one_line_file("/proc/cmdline", &line)) < 0) { + log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r)); + return 0; + } + + FOREACH_WORD_QUOTED(w, l, line, state) { + + if (strneq(w, "fsck.mode=auto", l)) + arg_force = arg_skip = false; + else if (strneq(w, "fsck.mode=force", l)) + arg_force = true; + else if (strneq(w, "fsck.mode=skip", l)) + arg_skip = true; + else if (startswith(w, "fsck.mode")) + log_warning("Invalid fsck.mode= parameter. Ignoring."); +#if defined(TARGET_FEDORA) || defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA) + else if (strneq(w, "fastboot", l)) + arg_skip = true; + else if (strneq(w, "forcefsck", l)) + arg_force = true; +#endif + } + + free(line); + return 0; +} + +static void test_files(void) { + if (access("/fastboot", F_OK) >= 0) + arg_skip = true; + + if (access("/forcefsck", F_OK) >= 0) + arg_force = true; + + if (access("/run/systemd/show-status", F_OK) >= 0 || plymouth_running()) + arg_show_progress = true; +} + +static double percent(int pass, unsigned long cur, unsigned long max) { + /* Values stolen from e2fsck */ + + static const int pass_table[] = { + 0, 70, 90, 92, 95, 100 + }; + + if (pass <= 0) + return 0.0; + + if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0) + return 100.0; + + return (double) pass_table[pass-1] + + ((double) pass_table[pass] - (double) pass_table[pass-1]) * + (double) cur / (double) max; +} + +static int process_progress(int fd) { + FILE *f, *console; + usec_t last = 0; + bool locked = false; + int clear = 0; + + f = fdopen(fd, "r"); + if (!f) { + close_nointr_nofail(fd); + return -errno; + } + + console = fopen("/dev/console", "w"); + if (!console) { + fclose(f); + return -ENOMEM; + } + + while (!feof(f)) { + int pass, m; + unsigned long cur, max; + char *device; + double p; + usec_t t; + + if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4) + break; + + /* Only show one progress counter at max */ + if (!locked) { + if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0) { + free(device); + continue; + } + + locked = true; + } + + /* Only update once every 50ms */ + t = now(CLOCK_MONOTONIC); + if (last + 50 * USEC_PER_MSEC > t) { + free(device); + continue; + } + + last = t; + + p = percent(pass, cur, max); + fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", device, p, &m); + fflush(console); + + free(device); + + if (m > clear) + clear = m; + } + + if (clear > 0) { + unsigned j; + + fputc('\r', console); + for (j = 0; j < (unsigned) clear; j++) + fputc(' ', console); + fputc('\r', console); + fflush(console); + } + + fclose(f); + fclose(console); + return 0; +} + +int main(int argc, char *argv[]) { + const char *cmdline[9]; + int i = 0, r = EXIT_FAILURE, q; + pid_t pid; + siginfo_t status; + struct udev *udev = NULL; + struct udev_device *udev_device = NULL; + const char *device; + bool root_directory; + int progress_pipe[2] = { -1, -1 }; + char dash_c[2+10+1]; + + if (argc > 2) { + log_error("This program expects one or no arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + parse_proc_cmdline(); + test_files(); + + if (!arg_force && arg_skip) + return 0; + + if (argc > 1) { + device = argv[1]; + root_directory = false; + } else { + struct stat st; + struct timespec times[2]; + + /* Find root device */ + + if (stat("/", &st) < 0) { + log_error("Failed to stat() the root directory: %m"); + goto finish; + } + + /* Virtual root devices don't need an fsck */ + if (major(st.st_dev) == 0) + return 0; + + /* check if we are already writable */ + times[0] = st.st_atim; + times[1] = st.st_mtim; + if (utimensat(AT_FDCWD, "/", times, 0) == 0) { + log_info("Root directory is writable, skipping check."); + return 0; + } + + if (!(udev = udev_new())) { + log_error("Out of memory"); + goto finish; + } + + if (!(udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev))) { + log_error("Failed to detect root device."); + goto finish; + } + + if (!(device = udev_device_get_devnode(udev_device))) { + log_error("Failed to detect device node of root directory."); + goto finish; + } + + root_directory = true; + } + + if (arg_show_progress) + if (pipe(progress_pipe) < 0) { + log_error("pipe(): %m"); + goto finish; + } + + cmdline[i++] = "/sbin/fsck"; + cmdline[i++] = "-a"; + cmdline[i++] = "-T"; + cmdline[i++] = "-l"; + + if (!root_directory) + cmdline[i++] = "-M"; + + if (arg_force) + cmdline[i++] = "-f"; + + if (progress_pipe[1] >= 0) { + snprintf(dash_c, sizeof(dash_c), "-C%i", progress_pipe[1]); + char_array_0(dash_c); + cmdline[i++] = dash_c; + } + + cmdline[i++] = device; + cmdline[i++] = NULL; + + pid = fork(); + if (pid < 0) { + log_error("fork(): %m"); + goto finish; + } else if (pid == 0) { + /* Child */ + if (progress_pipe[0] >= 0) + close_nointr_nofail(progress_pipe[0]); + execv(cmdline[0], (char**) cmdline); + _exit(8); /* Operational error */ + } + + if (progress_pipe[1] >= 0) { + close_nointr_nofail(progress_pipe[1]); + progress_pipe[1] = -1; + } + + if (progress_pipe[0] >= 0) { + process_progress(progress_pipe[0]); + progress_pipe[0] = -1; + } + + q = wait_for_terminate(pid, &status); + if (q < 0) { + log_error("waitid(): %s", strerror(-q)); + goto finish; + } + + if (status.si_code != CLD_EXITED || (status.si_status & ~1)) { + + if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED) + log_error("fsck terminated by signal %s.", signal_to_string(status.si_status)); + else if (status.si_code == CLD_EXITED) + log_error("fsck failed with error code %i.", status.si_status); + else + log_error("fsck failed due to unknown reason."); + + if (status.si_code == CLD_EXITED && (status.si_status & 2) && root_directory) + /* System should be rebooted. */ + start_target(SPECIAL_REBOOT_TARGET, false); + else if (status.si_code == CLD_EXITED && (status.si_status & 6)) + /* Some other problem */ + start_target(SPECIAL_EMERGENCY_TARGET, true); + else { + r = EXIT_SUCCESS; + log_warning("Ignoring error."); + } + + } else + r = EXIT_SUCCESS; + + if (status.si_code == CLD_EXITED && (status.si_status & 1)) + touch("/run/systemd/quotacheck"); + +finish: + if (udev_device) + udev_device_unref(udev_device); + + if (udev) + udev_unref(udev); + + close_pipe(progress_pipe); + + return r; +} diff --git a/src/getty-generator.c b/src/getty-generator.c new file mode 100644 index 000000000..7fac43a0b --- /dev/null +++ b/src/getty-generator.c @@ -0,0 +1,182 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "log.h" +#include "util.h" +#include "unit-name.h" +#include "virt.h" + +static const char *arg_dest = "/tmp"; + +static int add_symlink(const char *fservice, const char *tservice) { + char *from = NULL, *to = NULL; + int r; + + assert(fservice); + assert(tservice); + + asprintf(&from, SYSTEM_DATA_UNIT_PATH "/%s", fservice); + asprintf(&to, "%s/getty.target.wants/%s", arg_dest, tservice); + + if (!from || !to) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + mkdir_parents(to, 0755); + + r = symlink(from, to); + if (r < 0) { + if (errno == EEXIST) + /* In case console=hvc0 is passed this will very likely result in EEXIST */ + r = 0; + else { + log_error("Failed to create symlink from %s to %s: %m", from, to); + r = -errno; + } + } + +finish: + + free(from); + free(to); + + return r; +} + +static int add_serial_getty(const char *tty) { + char *n; + int r; + + assert(tty); + + log_debug("Automatically adding serial getty for /dev/%s.", tty); + + n = unit_name_replace_instance("serial-getty@.service", tty); + if (!n) { + log_error("Out of memory"); + return -ENOMEM; + } + + r = add_symlink("serial-getty@.service", n); + free(n); + + return r; +} + +int main(int argc, char *argv[]) { + + static const char virtualization_consoles[] = + "hvc0\0" + "xvc0\0" + "hvsi0\0"; + + int r = EXIT_SUCCESS; + char *active; + const char *j; + + if (argc > 2) { + log_error("This program takes one or no arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc > 1) + arg_dest = argv[1]; + + if (detect_container(NULL) > 0) { + log_debug("Automatically adding console shell."); + + if (add_symlink("console-shell.service", "console-shell.service") < 0) + r = EXIT_FAILURE; + + /* Don't add any further magic if we are in a container */ + goto finish; + } + + if (read_one_line_file("/sys/class/tty/console/active", &active) >= 0) { + const char *tty; + + tty = strrchr(active, ' '); + if (tty) + tty ++; + else + tty = active; + + /* Automatically add in a serial getty on the kernel + * console */ + if (tty_is_vc(tty)) + free(active); + else { + int k; + + /* We assume that gettys on virtual terminals are + * started via manual configuration and do this magic + * only for non-VC terminals. */ + + k = add_serial_getty(tty); + free(active); + + if (k < 0) { + r = EXIT_FAILURE; + goto finish; + } + } + } + + /* Automatically add in a serial getty on the first + * virtualizer console */ + NULSTR_FOREACH(j, virtualization_consoles) { + char *p; + int k; + + if (asprintf(&p, "/sys/class/tty/%s", j) < 0) { + log_error("Out of memory"); + r = EXIT_FAILURE; + goto finish; + } + + k = access(p, F_OK); + free(p); + + if (k < 0) + continue; + + k = add_serial_getty(j); + if (k < 0) { + r = EXIT_FAILURE; + goto finish; + } + } + +finish: + return r; +} diff --git a/src/hashmap.c b/src/hashmap.c new file mode 100644 index 000000000..692811861 --- /dev/null +++ b/src/hashmap.c @@ -0,0 +1,731 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "util.h" +#include "hashmap.h" +#include "macro.h" + +#define NBUCKETS 127 + +struct hashmap_entry { + const void *key; + void *value; + struct hashmap_entry *bucket_next, *bucket_previous; + struct hashmap_entry *iterate_next, *iterate_previous; +}; + +struct Hashmap { + hash_func_t hash_func; + compare_func_t compare_func; + + struct hashmap_entry *iterate_list_head, *iterate_list_tail; + unsigned n_entries; + + bool from_pool; +}; + +#define BY_HASH(h) ((struct hashmap_entry**) ((uint8_t*) (h) + ALIGN(sizeof(Hashmap)))) + +struct pool { + struct pool *next; + unsigned n_tiles; + unsigned n_used; +}; + +static struct pool *first_hashmap_pool = NULL; +static void *first_hashmap_tile = NULL; + +static struct pool *first_entry_pool = NULL; +static void *first_entry_tile = NULL; + +static void* allocate_tile(struct pool **first_pool, void **first_tile, size_t tile_size) { + unsigned i; + + if (*first_tile) { + void *r; + + r = *first_tile; + *first_tile = * (void**) (*first_tile); + return r; + } + + if (_unlikely_(!*first_pool) || _unlikely_((*first_pool)->n_used >= (*first_pool)->n_tiles)) { + unsigned n; + size_t size; + struct pool *p; + + n = *first_pool ? (*first_pool)->n_tiles : 0; + n = MAX(512U, n * 2); + size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*tile_size); + n = (size - ALIGN(sizeof(struct pool))) / tile_size; + + p = malloc(size); + if (!p) + return NULL; + + p->next = *first_pool; + p->n_tiles = n; + p->n_used = 0; + + *first_pool = p; + } + + i = (*first_pool)->n_used++; + + return ((uint8_t*) (*first_pool)) + ALIGN(sizeof(struct pool)) + i*tile_size; +} + +static void deallocate_tile(void **first_tile, void *p) { + * (void**) p = *first_tile; + *first_tile = p; +} + +#ifndef __OPTIMIZE__ + +static void drop_pool(struct pool *p) { + while (p) { + struct pool *n; + n = p->next; + free(p); + p = n; + } +} + +__attribute__((destructor)) static void cleanup_pool(void) { + /* Be nice to valgrind */ + + drop_pool(first_hashmap_pool); + drop_pool(first_entry_pool); +} + +#endif + +unsigned string_hash_func(const void *p) { + unsigned hash = 5381; + const signed char *c; + + /* DJB's hash function */ + + for (c = p; *c; c++) + hash = (hash << 5) + hash + (unsigned) *c; + + return hash; +} + +int string_compare_func(const void *a, const void *b) { + return strcmp(a, b); +} + +unsigned trivial_hash_func(const void *p) { + return PTR_TO_UINT(p); +} + +int trivial_compare_func(const void *a, const void *b) { + return a < b ? -1 : (a > b ? 1 : 0); +} + +Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func) { + bool b; + Hashmap *h; + size_t size; + + b = is_main_thread(); + + size = ALIGN(sizeof(Hashmap)) + NBUCKETS * sizeof(struct hashmap_entry*); + + if (b) { + h = allocate_tile(&first_hashmap_pool, &first_hashmap_tile, size); + if (!h) + return NULL; + + memset(h, 0, size); + } else { + h = malloc0(size); + + if (!h) + return NULL; + } + + h->hash_func = hash_func ? hash_func : trivial_hash_func; + h->compare_func = compare_func ? compare_func : trivial_compare_func; + + h->n_entries = 0; + h->iterate_list_head = h->iterate_list_tail = NULL; + + h->from_pool = b; + + return h; +} + +int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func) { + assert(h); + + if (*h) + return 0; + + if (!(*h = hashmap_new(hash_func, compare_func))) + return -ENOMEM; + + return 0; +} + +static void link_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) { + assert(h); + assert(e); + + /* Insert into hash table */ + e->bucket_next = BY_HASH(h)[hash]; + e->bucket_previous = NULL; + if (BY_HASH(h)[hash]) + BY_HASH(h)[hash]->bucket_previous = e; + BY_HASH(h)[hash] = e; + + /* Insert into iteration list */ + e->iterate_previous = h->iterate_list_tail; + e->iterate_next = NULL; + if (h->iterate_list_tail) { + assert(h->iterate_list_head); + h->iterate_list_tail->iterate_next = e; + } else { + assert(!h->iterate_list_head); + h->iterate_list_head = e; + } + h->iterate_list_tail = e; + + h->n_entries++; + assert(h->n_entries >= 1); +} + +static void unlink_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) { + assert(h); + assert(e); + + /* Remove from iteration list */ + if (e->iterate_next) + e->iterate_next->iterate_previous = e->iterate_previous; + else + h->iterate_list_tail = e->iterate_previous; + + if (e->iterate_previous) + e->iterate_previous->iterate_next = e->iterate_next; + else + h->iterate_list_head = e->iterate_next; + + /* Remove from hash table bucket list */ + if (e->bucket_next) + e->bucket_next->bucket_previous = e->bucket_previous; + + if (e->bucket_previous) + e->bucket_previous->bucket_next = e->bucket_next; + else + BY_HASH(h)[hash] = e->bucket_next; + + assert(h->n_entries >= 1); + h->n_entries--; +} + +static void remove_entry(Hashmap *h, struct hashmap_entry *e) { + unsigned hash; + + assert(h); + assert(e); + + hash = h->hash_func(e->key) % NBUCKETS; + + unlink_entry(h, e, hash); + + if (h->from_pool) + deallocate_tile(&first_entry_tile, e); + else + free(e); +} + +void hashmap_free(Hashmap*h) { + + if (!h) + return; + + hashmap_clear(h); + + if (h->from_pool) + deallocate_tile(&first_hashmap_tile, h); + else + free(h); +} + +void hashmap_free_free(Hashmap *h) { + void *p; + + while ((p = hashmap_steal_first(h))) + free(p); + + hashmap_free(h); +} + +void hashmap_clear(Hashmap *h) { + if (!h) + return; + + while (h->iterate_list_head) + remove_entry(h, h->iterate_list_head); +} + +static struct hashmap_entry *hash_scan(Hashmap *h, unsigned hash, const void *key) { + struct hashmap_entry *e; + assert(h); + assert(hash < NBUCKETS); + + for (e = BY_HASH(h)[hash]; e; e = e->bucket_next) + if (h->compare_func(e->key, key) == 0) + return e; + + return NULL; +} + +int hashmap_put(Hashmap *h, const void *key, void *value) { + struct hashmap_entry *e; + unsigned hash; + + assert(h); + + hash = h->hash_func(key) % NBUCKETS; + + if ((e = hash_scan(h, hash, key))) { + + if (e->value == value) + return 0; + + return -EEXIST; + } + + if (h->from_pool) + e = allocate_tile(&first_entry_pool, &first_entry_tile, sizeof(struct hashmap_entry)); + else + e = new(struct hashmap_entry, 1); + + if (!e) + return -ENOMEM; + + e->key = key; + e->value = value; + + link_entry(h, e, hash); + + return 1; +} + +int hashmap_replace(Hashmap *h, const void *key, void *value) { + struct hashmap_entry *e; + unsigned hash; + + assert(h); + + hash = h->hash_func(key) % NBUCKETS; + + if ((e = hash_scan(h, hash, key))) { + e->key = key; + e->value = value; + return 0; + } + + return hashmap_put(h, key, value); +} + +void* hashmap_get(Hashmap *h, const void *key) { + unsigned hash; + struct hashmap_entry *e; + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + + if (!(e = hash_scan(h, hash, key))) + return NULL; + + return e->value; +} + +void* hashmap_remove(Hashmap *h, const void *key) { + struct hashmap_entry *e; + unsigned hash; + void *data; + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + + if (!(e = hash_scan(h, hash, key))) + return NULL; + + data = e->value; + remove_entry(h, e); + + return data; +} + +int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) { + struct hashmap_entry *e; + unsigned old_hash, new_hash; + + if (!h) + return -ENOENT; + + old_hash = h->hash_func(old_key) % NBUCKETS; + if (!(e = hash_scan(h, old_hash, old_key))) + return -ENOENT; + + new_hash = h->hash_func(new_key) % NBUCKETS; + if (hash_scan(h, new_hash, new_key)) + return -EEXIST; + + unlink_entry(h, e, old_hash); + + e->key = new_key; + e->value = value; + + link_entry(h, e, new_hash); + + return 0; +} + +int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) { + struct hashmap_entry *e, *k; + unsigned old_hash, new_hash; + + if (!h) + return -ENOENT; + + old_hash = h->hash_func(old_key) % NBUCKETS; + if (!(e = hash_scan(h, old_hash, old_key))) + return -ENOENT; + + new_hash = h->hash_func(new_key) % NBUCKETS; + + if ((k = hash_scan(h, new_hash, new_key))) + if (e != k) + remove_entry(h, k); + + unlink_entry(h, e, old_hash); + + e->key = new_key; + e->value = value; + + link_entry(h, e, new_hash); + + return 0; +} + +void* hashmap_remove_value(Hashmap *h, const void *key, void *value) { + struct hashmap_entry *e; + unsigned hash; + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + + if (!(e = hash_scan(h, hash, key))) + return NULL; + + if (e->value != value) + return NULL; + + remove_entry(h, e); + + return value; +} + +void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key) { + struct hashmap_entry *e; + + assert(i); + + if (!h) + goto at_end; + + if (*i == ITERATOR_LAST) + goto at_end; + + if (*i == ITERATOR_FIRST && !h->iterate_list_head) + goto at_end; + + e = *i == ITERATOR_FIRST ? h->iterate_list_head : (struct hashmap_entry*) *i; + + if (e->iterate_next) + *i = (Iterator) e->iterate_next; + else + *i = ITERATOR_LAST; + + if (key) + *key = e->key; + + return e->value; + +at_end: + *i = ITERATOR_LAST; + + if (key) + *key = NULL; + + return NULL; +} + +void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key) { + struct hashmap_entry *e; + + assert(i); + + if (!h) + goto at_beginning; + + if (*i == ITERATOR_FIRST) + goto at_beginning; + + if (*i == ITERATOR_LAST && !h->iterate_list_tail) + goto at_beginning; + + e = *i == ITERATOR_LAST ? h->iterate_list_tail : (struct hashmap_entry*) *i; + + if (e->iterate_previous) + *i = (Iterator) e->iterate_previous; + else + *i = ITERATOR_FIRST; + + if (key) + *key = e->key; + + return e->value; + +at_beginning: + *i = ITERATOR_FIRST; + + if (key) + *key = NULL; + + return NULL; +} + +void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i) { + unsigned hash; + struct hashmap_entry *e; + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + + if (!(e = hash_scan(h, hash, key))) + return NULL; + + *i = (Iterator) e; + + return e->value; +} + +void* hashmap_first(Hashmap *h) { + + if (!h) + return NULL; + + if (!h->iterate_list_head) + return NULL; + + return h->iterate_list_head->value; +} + +void* hashmap_first_key(Hashmap *h) { + + if (!h) + return NULL; + + if (!h->iterate_list_head) + return NULL; + + return (void*) h->iterate_list_head->key; +} + +void* hashmap_last(Hashmap *h) { + + if (!h) + return NULL; + + if (!h->iterate_list_tail) + return NULL; + + return h->iterate_list_tail->value; +} + +void* hashmap_steal_first(Hashmap *h) { + void *data; + + if (!h) + return NULL; + + if (!h->iterate_list_head) + return NULL; + + data = h->iterate_list_head->value; + remove_entry(h, h->iterate_list_head); + + return data; +} + +void* hashmap_steal_first_key(Hashmap *h) { + void *key; + + if (!h) + return NULL; + + if (!h->iterate_list_head) + return NULL; + + key = (void*) h->iterate_list_head->key; + remove_entry(h, h->iterate_list_head); + + return key; +} + +unsigned hashmap_size(Hashmap *h) { + + if (!h) + return 0; + + return h->n_entries; +} + +bool hashmap_isempty(Hashmap *h) { + + if (!h) + return true; + + return h->n_entries == 0; +} + +int hashmap_merge(Hashmap *h, Hashmap *other) { + struct hashmap_entry *e; + + assert(h); + + if (!other) + return 0; + + for (e = other->iterate_list_head; e; e = e->iterate_next) { + int r; + + if ((r = hashmap_put(h, e->key, e->value)) < 0) + if (r != -EEXIST) + return r; + } + + return 0; +} + +void hashmap_move(Hashmap *h, Hashmap *other) { + struct hashmap_entry *e, *n; + + assert(h); + + /* The same as hashmap_merge(), but every new item from other + * is moved to h. This function is guaranteed to succeed. */ + + if (!other) + return; + + for (e = other->iterate_list_head; e; e = n) { + unsigned h_hash, other_hash; + + n = e->iterate_next; + + h_hash = h->hash_func(e->key) % NBUCKETS; + + if (hash_scan(h, h_hash, e->key)) + continue; + + other_hash = other->hash_func(e->key) % NBUCKETS; + + unlink_entry(other, e, other_hash); + link_entry(h, e, h_hash); + } +} + +int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) { + unsigned h_hash, other_hash; + struct hashmap_entry *e; + + if (!other) + return 0; + + assert(h); + + h_hash = h->hash_func(key) % NBUCKETS; + if (hash_scan(h, h_hash, key)) + return -EEXIST; + + other_hash = other->hash_func(key) % NBUCKETS; + if (!(e = hash_scan(other, other_hash, key))) + return -ENOENT; + + unlink_entry(other, e, other_hash); + link_entry(h, e, h_hash); + + return 0; +} + +Hashmap *hashmap_copy(Hashmap *h) { + Hashmap *copy; + + assert(h); + + if (!(copy = hashmap_new(h->hash_func, h->compare_func))) + return NULL; + + if (hashmap_merge(copy, h) < 0) { + hashmap_free(copy); + return NULL; + } + + return copy; +} + +char **hashmap_get_strv(Hashmap *h) { + char **sv; + Iterator it; + char *item; + int n; + + sv = new(char*, h->n_entries+1); + if (!sv) + return NULL; + + n = 0; + HASHMAP_FOREACH(item, h, it) + sv[n++] = item; + sv[n] = NULL; + + return sv; +} diff --git a/src/hashmap.h b/src/hashmap.h new file mode 100644 index 000000000..ab4363a7a --- /dev/null +++ b/src/hashmap.h @@ -0,0 +1,91 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foohashmaphfoo +#define foohashmaphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +/* Pretty straightforward hash table implementation. As a minor + * optimization a NULL hashmap object will be treated as empty hashmap + * for all read operations. That way it is not necessary to + * instantiate an object for each Hashmap use. */ + +typedef struct Hashmap Hashmap; +typedef struct _IteratorStruct _IteratorStruct; +typedef _IteratorStruct* Iterator; + +#define ITERATOR_FIRST ((Iterator) 0) +#define ITERATOR_LAST ((Iterator) -1) + +typedef unsigned (*hash_func_t)(const void *p); +typedef int (*compare_func_t)(const void *a, const void *b); + +unsigned string_hash_func(const void *p); +int string_compare_func(const void *a, const void *b); + +unsigned trivial_hash_func(const void *p); +int trivial_compare_func(const void *a, const void *b); + +Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func); +void hashmap_free(Hashmap *h); +void hashmap_free_free(Hashmap *h); +Hashmap *hashmap_copy(Hashmap *h); +int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func); + +int hashmap_put(Hashmap *h, const void *key, void *value); +int hashmap_replace(Hashmap *h, const void *key, void *value); +void* hashmap_get(Hashmap *h, const void *key); +void* hashmap_remove(Hashmap *h, const void *key); +void* hashmap_remove_value(Hashmap *h, const void *key, void *value); +int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value); +int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value); + +int hashmap_merge(Hashmap *h, Hashmap *other); +void hashmap_move(Hashmap *h, Hashmap *other); +int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key); + +unsigned hashmap_size(Hashmap *h); +bool hashmap_isempty(Hashmap *h); + +void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key); +void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key); +void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i); + +void hashmap_clear(Hashmap *h); +void *hashmap_steal_first(Hashmap *h); +void *hashmap_steal_first_key(Hashmap *h); +void* hashmap_first(Hashmap *h); +void* hashmap_first_key(Hashmap *h); +void* hashmap_last(Hashmap *h); + +char **hashmap_get_strv(Hashmap *h); + +#define HASHMAP_FOREACH(e, h, i) \ + for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), NULL); (e); (e) = hashmap_iterate((h), &(i), NULL)) + +#define HASHMAP_FOREACH_KEY(e, k, h, i) \ + for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), (const void**) &(k)); (e); (e) = hashmap_iterate((h), &(i), (const void**) &(k))) + +#define HASHMAP_FOREACH_BACKWARDS(e, h, i) \ + for ((i) = ITERATOR_LAST, (e) = hashmap_iterate_backwards((h), &(i), NULL); (e); (e) = hashmap_iterate_backwards((h), &(i), NULL)) + +#endif diff --git a/src/hostname-setup.c b/src/hostname-setup.c new file mode 100644 index 000000000..2c2f10cfd --- /dev/null +++ b/src/hostname-setup.c @@ -0,0 +1,187 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "hostname-setup.h" +#include "macro.h" +#include "util.h" +#include "log.h" + +#if defined(TARGET_FEDORA) || defined(TARGET_ALTLINUX) || defined(TARGET_MANDRIVA) || defined(TARGET_MEEGO) || defined(TARGET_MAGEIA) +#define FILENAME "/etc/sysconfig/network" +#elif defined(TARGET_SUSE) || defined(TARGET_SLACKWARE) +#define FILENAME "/etc/HOSTNAME" +#elif defined(TARGET_ARCH) +#define FILENAME "/etc/rc.conf" +#elif defined(TARGET_GENTOO) +#define FILENAME "/etc/conf.d/hostname" +#endif + +static int read_and_strip_hostname(const char *path, char **hn) { + char *s; + int r; + + assert(path); + assert(hn); + + if ((r = read_one_line_file(path, &s)) < 0) + return r; + + hostname_cleanup(s); + + if (isempty(s)) { + free(s); + return -ENOENT; + } + + *hn = s; + + return 0; +} + +static int read_distro_hostname(char **hn) { + +#if defined(TARGET_FEDORA) || defined(TARGET_ARCH) || defined(TARGET_GENTOO) || defined(TARGET_ALTLINUX) || defined(TARGET_MANDRIVA) || defined(TARGET_MEEGO) || defined(TARGET_MAGEIA) + int r; + FILE *f; + + assert(hn); + + if (!(f = fopen(FILENAME, "re"))) + return -errno; + + for (;;) { + char line[LINE_MAX]; + char *s, *k; + + if (!fgets(line, sizeof(line), f)) { + if (feof(f)) + break; + + r = -errno; + goto finish; + } + + s = strstrip(line); + + if (!startswith_no_case(s, "HOSTNAME=")) + continue; + + if (!(k = strdup(s+9))) { + r = -ENOMEM; + goto finish; + } + + hostname_cleanup(k); + + if (isempty(k)) { + free(k); + r = -ENOENT; + goto finish; + } + + *hn = k; + r = 0; + goto finish; + } + + r = -ENOENT; + +finish: + fclose(f); + return r; + +#elif defined(TARGET_SUSE) || defined(TARGET_SLACKWARE) + return read_and_strip_hostname(FILENAME, hn); +#else + return -ENOENT; +#endif +} + +static int read_hostname(char **hn) { + int r; + + assert(hn); + + /* First, try to load the generic hostname configuration file, + * that we support on all distributions */ + + if ((r = read_and_strip_hostname("/etc/hostname", hn)) < 0) { + + if (r == -ENOENT) + return read_distro_hostname(hn); + + return r; + } + + return 0; +} + +int hostname_setup(void) { + int r; + char *b = NULL; + const char *hn = NULL; + + if ((r = read_hostname(&b)) < 0) { + if (r == -ENOENT) + log_info("No hostname configured."); + else + log_warning("Failed to read configured hostname: %s", strerror(-r)); + + hn = NULL; + } else + hn = b; + + if (!hn) { + /* Don't override the hostname if it is unset and not + * explicitly configured */ + + char *old_hostname = NULL; + + if ((old_hostname = gethostname_malloc())) { + bool already_set; + + already_set = old_hostname[0] != 0; + free(old_hostname); + + if (already_set) + goto finish; + } + + hn = "localhost"; + } + + if (sethostname(hn, strlen(hn)) < 0) { + log_warning("Failed to set hostname to <%s>: %m", hn); + r = -errno; + } else + log_info("Set hostname to <%s>.", hn); + +finish: + free(b); + + return r; +} diff --git a/src/hostname-setup.h b/src/hostname-setup.h new file mode 100644 index 000000000..ff11df945 --- /dev/null +++ b/src/hostname-setup.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foohostnamesetuphfoo +#define foohostnamesetuphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +int hostname_setup(void); + +#endif diff --git a/src/hostname/.gitignore b/src/hostname/.gitignore new file mode 100644 index 000000000..1ff281b23 --- /dev/null +++ b/src/hostname/.gitignore @@ -0,0 +1 @@ +org.freedesktop.hostname1.policy diff --git a/src/hostname/Makefile b/src/hostname/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/hostname/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c new file mode 100644 index 000000000..ad7244084 --- /dev/null +++ b/src/hostname/hostnamed.c @@ -0,0 +1,629 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include +#include +#include +#include + +#include "util.h" +#include "strv.h" +#include "dbus-common.h" +#include "polkit.h" +#include "def.h" +#include "virt.h" + +#define INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + BUS_PEER_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.hostname1\0" + +const char hostname_interface[] _introspect_("hostname1") = INTERFACE; + +enum { + PROP_HOSTNAME, + PROP_STATIC_HOSTNAME, + PROP_PRETTY_HOSTNAME, + PROP_ICON_NAME, + _PROP_MAX +}; + +static char *data[_PROP_MAX] = { + NULL, + NULL, + NULL, + NULL +}; + +static usec_t remain_until = 0; + +static void free_data(void) { + int p; + + for (p = 0; p < _PROP_MAX; p++) { + free(data[p]); + data[p] = NULL; + } +} + +static int read_data(void) { + int r; + + free_data(); + + data[PROP_HOSTNAME] = gethostname_malloc(); + if (!data[PROP_HOSTNAME]) + return -ENOMEM; + + r = read_one_line_file("/etc/hostname", &data[PROP_STATIC_HOSTNAME]); + if (r < 0 && r != -ENOENT) + return r; + + r = parse_env_file("/etc/machine-info", NEWLINE, + "PRETTY_HOSTNAME", &data[PROP_PRETTY_HOSTNAME], + "ICON_NAME", &data[PROP_ICON_NAME], + NULL); + if (r < 0 && r != -ENOENT) + return r; + + return 0; +} + +static bool check_nss(void) { + + void *dl; + + if ((dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY))) { + dlclose(dl); + return true; + } + + return false; +} + +static const char* fallback_icon_name(void) { + +#if defined(__i386__) || defined(__x86_64__) + int r; + char *type; + unsigned t; +#endif + + if (detect_virtualization(NULL) > 0) + return "computer-vm"; + +#if defined(__i386__) || defined(__x86_64__) + r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type); + if (r < 0) + return NULL; + + r = safe_atou(type, &t); + free(type); + + if (r < 0) + return NULL; + + /* We only list the really obvious cases here. The DMI data is + unreliable enough, so let's not do any additional guesswork + on top of that. + + See the SMBIOS Specification 2.7.1 section 7.4.1 for + details about the values listed here: + + http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf + */ + + switch (t) { + + case 0x3: + case 0x4: + case 0x6: + case 0x7: + return "computer-desktop"; + + case 0x9: + case 0xA: + case 0xE: + return "computer-laptop"; + + case 0x11: + case 0x1C: + return "computer-server"; + } + +#endif + return NULL; +} + +static int write_data_hostname(void) { + const char *hn; + + if (isempty(data[PROP_HOSTNAME])) + hn = "localhost"; + else + hn = data[PROP_HOSTNAME]; + + if (sethostname(hn, strlen(hn)) < 0) + return -errno; + + return 0; +} + +static int write_data_static_hostname(void) { + + if (isempty(data[PROP_STATIC_HOSTNAME])) { + + if (unlink("/etc/hostname") < 0) + return errno == ENOENT ? 0 : -errno; + + return 0; + } + + return write_one_line_file_atomic("/etc/hostname", data[PROP_STATIC_HOSTNAME]); +} + +static int write_data_other(void) { + + static const char * const name[_PROP_MAX] = { + [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME", + [PROP_ICON_NAME] = "ICON_NAME" + }; + + char **l = NULL; + int r, p; + + r = load_env_file("/etc/machine-info", &l); + if (r < 0 && r != -ENOENT) + return r; + + for (p = 2; p < _PROP_MAX; p++) { + char *t, **u; + + assert(name[p]); + + if (isempty(data[p])) { + strv_env_unset(l, name[p]); + continue; + } + + if (asprintf(&t, "%s=%s", name[p], strempty(data[p])) < 0) { + strv_free(l); + return -ENOMEM; + } + + u = strv_env_set(l, t); + free(t); + strv_free(l); + + if (!u) + return -ENOMEM; + l = u; + } + + if (strv_isempty(l)) { + + if (unlink("/etc/machine-info") < 0) + return errno == ENOENT ? 0 : -errno; + + return 0; + } + + r = write_env_file("/etc/machine-info", l); + strv_free(l); + + return r; +} + +static int bus_hostname_append_icon_name(DBusMessageIter *i, const char *property, void *userdata) { + const char *name; + + assert(i); + assert(property); + + if (isempty(data[PROP_ICON_NAME])) + name = fallback_icon_name(); + else + name = data[PROP_ICON_NAME]; + + return bus_property_append_string(i, property, (void*) name); +} + +static const BusProperty bus_hostname_properties[] = { + { "Hostname", bus_property_append_string, "s", sizeof(data[0])*PROP_HOSTNAME, true }, + { "StaticHostname", bus_property_append_string, "s", sizeof(data[0])*PROP_STATIC_HOSTNAME, true }, + { "PrettyHostname", bus_property_append_string, "s", sizeof(data[0])*PROP_PRETTY_HOSTNAME, true }, + { "IconName", bus_hostname_append_icon_name, "s", sizeof(data[0])*PROP_ICON_NAME, true }, + { NULL, } +}; + +static const BusBoundProperties bps[] = { + { "org.freedesktop.hostname1", bus_hostname_properties, data }, + { NULL, } +}; + +static DBusHandlerResult hostname_message_handler( + DBusConnection *connection, + DBusMessage *message, + void *userdata) { + + + DBusMessage *reply = NULL, *changed = NULL; + DBusError error; + int r; + + assert(connection); + assert(message); + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetHostname")) { + const char *name; + dbus_bool_t interactive; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(name)) + name = data[PROP_STATIC_HOSTNAME]; + + if (isempty(name)) + name = "localhost"; + + if (!hostname_is_valid(name)) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + + if (!streq_ptr(name, data[PROP_HOSTNAME])) { + char *h; + + r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-hostname", interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + h = strdup(name); + if (!h) + goto oom; + + free(data[PROP_HOSTNAME]); + data[PROP_HOSTNAME] = h; + + r = write_data_hostname(); + if (r < 0) { + log_error("Failed to set host name: %s", strerror(-r)); + return bus_send_error_reply(connection, message, NULL, r); + } + + log_info("Changed host name to '%s'", strempty(data[PROP_HOSTNAME])); + + changed = bus_properties_changed_new( + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + "Hostname\0"); + if (!changed) + goto oom; + } + + } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetStaticHostname")) { + const char *name; + dbus_bool_t interactive; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(name)) + name = NULL; + + if (!streq_ptr(name, data[PROP_STATIC_HOSTNAME])) { + + r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-static-hostname", interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (isempty(name)) { + free(data[PROP_STATIC_HOSTNAME]); + data[PROP_STATIC_HOSTNAME] = NULL; + } else { + char *h; + + if (!hostname_is_valid(name)) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + + h = strdup(name); + if (!h) + goto oom; + + free(data[PROP_STATIC_HOSTNAME]); + data[PROP_STATIC_HOSTNAME] = h; + } + + r = write_data_static_hostname(); + if (r < 0) { + log_error("Failed to write static host name: %s", strerror(-r)); + return bus_send_error_reply(connection, message, NULL, r); + } + + log_info("Changed static host name to '%s'", strempty(data[PROP_STATIC_HOSTNAME])); + + changed = bus_properties_changed_new( + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + "StaticHostname\0"); + if (!changed) + goto oom; + } + + } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetPrettyHostname") || + dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetIconName")) { + + const char *name; + dbus_bool_t interactive; + int k; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(name)) + name = NULL; + + k = streq(dbus_message_get_member(message), "SetPrettyHostname") ? PROP_PRETTY_HOSTNAME : PROP_ICON_NAME; + + if (!streq_ptr(name, data[k])) { + + /* Since the pretty hostname should always be + * changed at the same time as the static one, + * use the same policy action for both... */ + + r = verify_polkit(connection, message, k == PROP_PRETTY_HOSTNAME ? + "org.freedesktop.hostname1.set-static-hostname" : + "org.freedesktop.hostname1.set-machine-info", interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (isempty(name)) { + free(data[k]); + data[k] = NULL; + } else { + char *h; + + h = strdup(name); + if (!h) + goto oom; + + free(data[k]); + data[k] = h; + } + + r = write_data_other(); + if (r < 0) { + log_error("Failed to write machine info: %s", strerror(-r)); + return bus_send_error_reply(connection, message, NULL, r); + } + + log_info("Changed %s to '%s'", k == PROP_PRETTY_HOSTNAME ? "pretty host name" : "icon name", strempty(data[k])); + + changed = bus_properties_changed_new( + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + k == PROP_PRETTY_HOSTNAME ? "PrettyHostname\0" : "IconName\0"); + if (!changed) + goto oom; + } + + } else + return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + reply = NULL; + + if (changed) { + + if (!dbus_connection_send(connection, changed, NULL)) + goto oom; + + dbus_message_unref(changed); + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + if (changed) + dbus_message_unref(changed); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static int connect_bus(DBusConnection **_bus) { + static const DBusObjectPathVTable hostname_vtable = { + .message_function = hostname_message_handler + }; + DBusError error; + DBusConnection *bus = NULL; + int r; + + assert(_bus); + + dbus_error_init(&error); + + bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + if (!bus) { + log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error)); + r = -ECONNREFUSED; + goto fail; + } + + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + if (!dbus_connection_register_object_path(bus, "/org/freedesktop/hostname1", &hostname_vtable, NULL) || + !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) { + log_error("Not enough memory"); + r = -ENOMEM; + goto fail; + } + + r = dbus_bus_request_name(bus, "org.freedesktop.hostname1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to register name on bus: %s", bus_error_message(&error)); + r = -EEXIST; + goto fail; + } + + if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + log_error("Failed to acquire name."); + r = -EEXIST; + goto fail; + } + + if (_bus) + *_bus = bus; + + return 0; + +fail: + dbus_connection_close(bus); + dbus_connection_unref(bus); + + dbus_error_free(&error); + + return r; +} + +int main(int argc, char *argv[]) { + int r; + DBusConnection *bus = NULL; + bool exiting = false; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc == 2 && streq(argv[1], "--introspect")) { + fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "\n", stdout); + fputs(hostname_interface, stdout); + fputs("\n", stdout); + return 0; + } + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + if (!check_nss()) + log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!"); + + r = read_data(); + if (r < 0) { + log_error("Failed to read hostname data: %s", strerror(-r)); + goto finish; + } + + r = connect_bus(&bus); + if (r < 0) + goto finish; + + remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC; + for (;;) { + + if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC))) + break; + + if (!exiting && remain_until < now(CLOCK_MONOTONIC)) { + exiting = true; + bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1"); + } + } + + r = 0; + +finish: + free_data(); + + if (bus) { + dbus_connection_flush(bus); + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/hostname/org.freedesktop.hostname1.conf b/src/hostname/org.freedesktop.hostname1.conf new file mode 100644 index 000000000..eb241c022 --- /dev/null +++ b/src/hostname/org.freedesktop.hostname1.conf @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/hostname/org.freedesktop.hostname1.policy.in b/src/hostname/org.freedesktop.hostname1.policy.in new file mode 100644 index 000000000..7d56b22c2 --- /dev/null +++ b/src/hostname/org.freedesktop.hostname1.policy.in @@ -0,0 +1,49 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Set host name + <_message>Authentication is required to set the local host name. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Set static host name + <_message>Authentication is required to set the statically configured local host name, as well as the pretty host name. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Set machine information + <_message>Authentication is required to set local machine information. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + diff --git a/src/hostname/org.freedesktop.hostname1.service b/src/hostname/org.freedesktop.hostname1.service new file mode 100644 index 000000000..42e4adb2c --- /dev/null +++ b/src/hostname/org.freedesktop.hostname1.service @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[D-BUS Service] +Name=org.freedesktop.hostname1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.hostname1.service diff --git a/src/ima-setup.c b/src/ima-setup.c new file mode 100644 index 000000000..03e43dcf1 --- /dev/null +++ b/src/ima-setup.c @@ -0,0 +1,115 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy + TORSEC group -- http://security.polito.it + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ima-setup.h" +#include "mount-setup.h" +#include "macro.h" +#include "util.h" +#include "log.h" +#include "label.h" + +#define IMA_SECFS_DIR "/sys/kernel/security/ima" +#define IMA_SECFS_POLICY IMA_SECFS_DIR "/policy" +#define IMA_POLICY_PATH "/etc/ima/ima-policy" + +int ima_setup(void) { + +#ifdef HAVE_IMA + struct stat st; + ssize_t policy_size = 0, written = 0; + char *policy; + int policyfd = -1, imafd = -1; + int result = 0; + +#ifndef HAVE_SELINUX + /* Mount the securityfs filesystem */ + mount_setup_early(); +#endif + + if (stat(IMA_POLICY_PATH, &st) < 0) + return 0; + + policy_size = st.st_size; + if (stat(IMA_SECFS_DIR, &st) < 0) { + log_debug("IMA support is disabled in the kernel, ignoring."); + return 0; + } + + if (stat(IMA_SECFS_POLICY, &st) < 0) { + log_error("Another IMA custom policy has already been loaded, " + "ignoring."); + return 0; + } + + policyfd = open(IMA_POLICY_PATH, O_RDONLY|O_CLOEXEC); + if (policyfd < 0) { + log_error("Failed to open the IMA custom policy file %s (%m), " + "ignoring.", IMA_POLICY_PATH); + return 0; + } + + imafd = open(IMA_SECFS_POLICY, O_WRONLY|O_CLOEXEC); + if (imafd < 0) { + log_error("Failed to open the IMA kernel interface %s (%m), " + "ignoring.", IMA_SECFS_POLICY); + goto out; + } + + policy = mmap(NULL, policy_size, PROT_READ, MAP_PRIVATE, policyfd, 0); + if (policy == MAP_FAILED) { + log_error("mmap() failed (%m), freezing"); + result = -errno; + goto out; + } + + written = loop_write(imafd, policy, (size_t)policy_size, false); + if (written != policy_size) { + log_error("Failed to load the IMA custom policy file %s (%m), " + "ignoring.", IMA_POLICY_PATH); + goto out_mmap; + } + + log_info("Successfully loaded the IMA custom policy %s.", + IMA_POLICY_PATH); +out_mmap: + munmap(policy, policy_size); +out: + if (policyfd >= 0) + close_nointr_nofail(policyfd); + if (imafd >= 0) + close_nointr_nofail(imafd); + if (result) + return result; +#endif /* HAVE_IMA */ + + return 0; +} diff --git a/src/ima-setup.h b/src/ima-setup.h new file mode 100644 index 000000000..7d677cf85 --- /dev/null +++ b/src/ima-setup.h @@ -0,0 +1,29 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooimasetuphfoo +#define fooimasetuphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy + TORSEC group -- http://security.polito.it + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +int ima_setup(void); + +#endif diff --git a/src/initctl.c b/src/initctl.c new file mode 100644 index 000000000..53d03a9e1 --- /dev/null +++ b/src/initctl.c @@ -0,0 +1,451 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "util.h" +#include "log.h" +#include "list.h" +#include "initreq.h" +#include "special.h" +#include "dbus-common.h" +#include "def.h" + +#define SERVER_FD_MAX 16 +#define TIMEOUT_MSEC ((int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)) + +typedef struct Fifo Fifo; + +typedef struct Server { + int epoll_fd; + + LIST_HEAD(Fifo, fifos); + unsigned n_fifos; + + DBusConnection *bus; + + bool quit; +} Server; + +struct Fifo { + Server *server; + + int fd; + + struct init_request buffer; + size_t bytes_read; + + LIST_FIELDS(Fifo, fifo); +}; + +static const char *translate_runlevel(int runlevel, bool *isolate) { + static const struct { + const int runlevel; + const char *special; + bool isolate; + } table[] = { + { '0', SPECIAL_POWEROFF_TARGET, false }, + { '1', SPECIAL_RESCUE_TARGET, true }, + { 's', SPECIAL_RESCUE_TARGET, true }, + { 'S', SPECIAL_RESCUE_TARGET, true }, + { '2', SPECIAL_RUNLEVEL2_TARGET, true }, + { '3', SPECIAL_RUNLEVEL3_TARGET, true }, + { '4', SPECIAL_RUNLEVEL4_TARGET, true }, + { '5', SPECIAL_RUNLEVEL5_TARGET, true }, + { '6', SPECIAL_REBOOT_TARGET, false }, + }; + + unsigned i; + + assert(isolate); + + for (i = 0; i < ELEMENTSOF(table); i++) + if (table[i].runlevel == runlevel) { + *isolate = table[i].isolate; + if (runlevel == '6' && kexec_loaded()) + return SPECIAL_KEXEC_TARGET; + return table[i].special; + } + + return NULL; +} + +static void change_runlevel(Server *s, int runlevel) { + const char *target; + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + const char *mode; + bool isolate = false; + + assert(s); + + dbus_error_init(&error); + + if (!(target = translate_runlevel(runlevel, &isolate))) { + log_warning("Got request for unknown runlevel %c, ignoring.", runlevel); + goto finish; + } + + if (isolate) + mode = "isolate"; + else + mode = "replace"; + + log_debug("Running request %s/start/%s", target, mode); + + if (!(m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartUnit"))) { + log_error("Could not allocate message."); + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &target, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID)) { + log_error("Could not attach target and flag information to message."); + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(s->bus, m, -1, &error))) { + log_error("Failed to start unit: %s", bus_error_message(&error)); + goto finish; + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); +} + +static void request_process(Server *s, const struct init_request *req) { + assert(s); + assert(req); + + if (req->magic != INIT_MAGIC) { + log_error("Got initctl request with invalid magic. Ignoring."); + return; + } + + switch (req->cmd) { + + case INIT_CMD_RUNLVL: + if (!isprint(req->runlevel)) + log_error("Got invalid runlevel. Ignoring."); + else + switch (req->runlevel) { + + /* we are async anyway, so just use kill for reexec/reload */ + case 'u': + case 'U': + if (kill(1, SIGTERM) < 0) + log_error("kill() failed: %m"); + + /* The bus connection will be + * terminated if PID 1 is reexecuted, + * hence let's just exit here, and + * rely on that we'll be restarted on + * the next request */ + s->quit = true; + break; + + case 'q': + case 'Q': + if (kill(1, SIGHUP) < 0) + log_error("kill() failed: %m"); + break; + + default: + change_runlevel(s, req->runlevel); + } + return; + + case INIT_CMD_POWERFAIL: + case INIT_CMD_POWERFAILNOW: + case INIT_CMD_POWEROK: + log_warning("Received UPS/power initctl request. This is not implemented in systemd. Upgrade your UPS daemon!"); + return; + + case INIT_CMD_CHANGECONS: + log_warning("Received console change initctl request. This is not implemented in systemd."); + return; + + case INIT_CMD_SETENV: + case INIT_CMD_UNSETENV: + log_warning("Received environment initctl request. This is not implemented in systemd."); + return; + + default: + log_warning("Received unknown initctl request. Ignoring."); + return; + } +} + +static int fifo_process(Fifo *f) { + ssize_t l; + + assert(f); + + errno = EIO; + if ((l = read(f->fd, ((uint8_t*) &f->buffer) + f->bytes_read, sizeof(f->buffer) - f->bytes_read)) <= 0) { + + if (errno == EAGAIN) + return 0; + + log_warning("Failed to read from fifo: %s", strerror(errno)); + return -1; + } + + f->bytes_read += l; + assert(f->bytes_read <= sizeof(f->buffer)); + + if (f->bytes_read == sizeof(f->buffer)) { + request_process(f->server, &f->buffer); + f->bytes_read = 0; + } + + return 0; +} + +static void fifo_free(Fifo *f) { + assert(f); + + if (f->server) { + assert(f->server->n_fifos > 0); + f->server->n_fifos--; + LIST_REMOVE(Fifo, fifo, f->server->fifos, f); + } + + if (f->fd >= 0) { + if (f->server) + epoll_ctl(f->server->epoll_fd, EPOLL_CTL_DEL, f->fd, NULL); + + close_nointr_nofail(f->fd); + } + + free(f); +} + +static void server_done(Server *s) { + assert(s); + + while (s->fifos) + fifo_free(s->fifos); + + if (s->epoll_fd >= 0) + close_nointr_nofail(s->epoll_fd); + + if (s->bus) { + dbus_connection_flush(s->bus); + dbus_connection_close(s->bus); + dbus_connection_unref(s->bus); + } +} + +static int server_init(Server *s, unsigned n_sockets) { + int r; + unsigned i; + DBusError error; + + assert(s); + assert(n_sockets > 0); + + dbus_error_init(&error); + + zero(*s); + + if ((s->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) { + r = -errno; + log_error("Failed to create epoll object: %s", strerror(errno)); + goto fail; + } + + for (i = 0; i < n_sockets; i++) { + struct epoll_event ev; + Fifo *f; + int fd; + + fd = SD_LISTEN_FDS_START+i; + + if ((r = sd_is_fifo(fd, NULL)) < 0) { + log_error("Failed to determine file descriptor type: %s", strerror(-r)); + goto fail; + } + + if (!r) { + log_error("Wrong file descriptor type."); + r = -EINVAL; + goto fail; + } + + if (!(f = new0(Fifo, 1))) { + r = -ENOMEM; + log_error("Failed to create fifo object: %s", strerror(errno)); + goto fail; + } + + f->fd = -1; + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = f; + if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) { + r = -errno; + fifo_free(f); + log_error("Failed to add fifo fd to epoll object: %s", strerror(errno)); + goto fail; + } + + f->fd = fd; + LIST_PREPEND(Fifo, fifo, s->fifos, f); + f->server = s; + s->n_fifos ++; + } + + if (bus_connect(DBUS_BUS_SYSTEM, &s->bus, NULL, &error) < 0) { + log_error("Failed to get D-Bus connection: %s", bus_error_message(&error)); + goto fail; + } + + return 0; + +fail: + server_done(s); + + dbus_error_free(&error); + return r; +} + +static int process_event(Server *s, struct epoll_event *ev) { + int r; + Fifo *f; + + assert(s); + + if (!(ev->events & EPOLLIN)) { + log_info("Got invalid event from epoll. (3)"); + return -EIO; + } + + f = (Fifo*) ev->data.ptr; + + if ((r = fifo_process(f)) < 0) { + log_info("Got error on fifo: %s", strerror(-r)); + fifo_free(f); + return r; + } + + return 0; +} + +int main(int argc, char *argv[]) { + Server server; + int r = EXIT_FAILURE, n; + + if (getppid() != 1) { + log_error("This program should be invoked by init only."); + return EXIT_FAILURE; + } + + if (argc > 1) { + log_error("This program does not take arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if ((n = sd_listen_fds(true)) < 0) { + log_error("Failed to read listening file descriptors from environment: %s", strerror(-r)); + return EXIT_FAILURE; + } + + if (n <= 0 || n > SERVER_FD_MAX) { + log_error("No or too many file descriptors passed."); + return EXIT_FAILURE; + } + + if (server_init(&server, (unsigned) n) < 0) + return EXIT_FAILURE; + + log_debug("systemd-initctl running as pid %lu", (unsigned long) getpid()); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + while (!server.quit) { + struct epoll_event event; + int k; + + if ((k = epoll_wait(server.epoll_fd, + &event, 1, + TIMEOUT_MSEC)) < 0) { + + if (errno == EINTR) + continue; + + log_error("epoll_wait() failed: %s", strerror(errno)); + goto fail; + } + + if (k <= 0) + break; + + if (process_event(&server, &event) < 0) + goto fail; + } + + r = EXIT_SUCCESS; + + log_debug("systemd-initctl stopped as pid %lu", (unsigned long) getpid()); + +fail: + sd_notify(false, + "STATUS=Shutting down..."); + + server_done(&server); + + dbus_shutdown(); + + return r; +} diff --git a/src/initreq.h b/src/initreq.h new file mode 100644 index 000000000..859042ce4 --- /dev/null +++ b/src/initreq.h @@ -0,0 +1,77 @@ +/* + * initreq.h Interface to talk to init through /dev/initctl. + * + * Copyright (C) 1995-2004 Miquel van Smoorenburg + * + * This library 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 of the License, or (at your option) any later version. + * + * Version: @(#)initreq.h 1.28 31-Mar-2004 MvS + * + */ +#ifndef _INITREQ_H +#define _INITREQ_H + +#include + +#if defined(__FreeBSD_kernel__) +# define INIT_FIFO "/etc/.initctl" +#else +# define INIT_FIFO "/dev/initctl" +#endif + +#define INIT_MAGIC 0x03091969 +#define INIT_CMD_START 0 +#define INIT_CMD_RUNLVL 1 +#define INIT_CMD_POWERFAIL 2 +#define INIT_CMD_POWERFAILNOW 3 +#define INIT_CMD_POWEROK 4 +#define INIT_CMD_BSD 5 +#define INIT_CMD_SETENV 6 +#define INIT_CMD_UNSETENV 7 + +#define INIT_CMD_CHANGECONS 12345 + +#ifdef MAXHOSTNAMELEN +# define INITRQ_HLEN MAXHOSTNAMELEN +#else +# define INITRQ_HLEN 64 +#endif + +/* + * This is what BSD 4.4 uses when talking to init. + * Linux doesn't use this right now. + */ +struct init_request_bsd { + char gen_id[8]; /* Beats me.. telnetd uses "fe" */ + char tty_id[16]; /* Tty name minus /dev/tty */ + char host[INITRQ_HLEN]; /* Hostname */ + char term_type[16]; /* Terminal type */ + int signal; /* Signal to send */ + int pid; /* Process to send to */ + char exec_name[128]; /* Program to execute */ + char reserved[128]; /* For future expansion. */ +}; + + +/* + * Because of legacy interfaces, "runlevel" and "sleeptime" + * aren't in a separate struct in the union. + * + * The weird sizes are because init expects the whole + * struct to be 384 bytes. + */ +struct init_request { + int magic; /* Magic number */ + int cmd; /* What kind of request */ + int runlevel; /* Runlevel to change to */ + int sleeptime; /* Time between TERM and KILL */ + union { + struct init_request_bsd bsd; + char data[368]; + } i; +}; + +#endif diff --git a/src/install.c b/src/install.c new file mode 100644 index 000000000..925611680 --- /dev/null +++ b/src/install.c @@ -0,0 +1,1953 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 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 . +***/ + +#include +#include +#include +#include +#include + +#include "util.h" +#include "hashmap.h" +#include "set.h" +#include "path-lookup.h" +#include "strv.h" +#include "unit-name.h" +#include "install.h" +#include "conf-parser.h" + +typedef struct { + char *name; + char *path; + + char **aliases; + char **wanted_by; +} InstallInfo; + +typedef struct { + Hashmap *will_install; + Hashmap *have_installed; +} InstallContext; + +static int lookup_paths_init_from_scope(LookupPaths *paths, UnitFileScope scope) { + assert(paths); + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + zero(*paths); + + return lookup_paths_init(paths, + scope == UNIT_FILE_SYSTEM ? MANAGER_SYSTEM : MANAGER_USER, + scope == UNIT_FILE_USER); +} + +static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) { + char *p = NULL; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(ret); + + switch (scope) { + + case UNIT_FILE_SYSTEM: + + if (root_dir && runtime) + asprintf(&p, "%s/run/systemd/system", root_dir); + else if (runtime) + p = strdup("/run/systemd/system"); + else if (root_dir) + asprintf(&p, "%s/%s", root_dir, SYSTEM_CONFIG_UNIT_PATH); + else + p = strdup(SYSTEM_CONFIG_UNIT_PATH); + + break; + + case UNIT_FILE_GLOBAL: + + if (root_dir) + return -EINVAL; + + if (runtime) + p = strdup("/run/systemd/user"); + else + p = strdup(USER_CONFIG_UNIT_PATH); + break; + + case UNIT_FILE_USER: + + if (root_dir || runtime) + return -EINVAL; + + r = user_config_home(&p); + if (r <= 0) + return r < 0 ? r : -ENOENT; + + break; + + default: + assert_not_reached("Bad scope"); + } + + if (!p) + return -ENOMEM; + + *ret = p; + return 0; +} + +static int add_file_change( + UnitFileChange **changes, + unsigned *n_changes, + UnitFileChangeType type, + const char *path, + const char *source) { + + UnitFileChange *c; + unsigned i; + + assert(path); + assert(!changes == !n_changes); + + if (!changes) + return 0; + + c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange)); + if (!c) + return -ENOMEM; + + *changes = c; + i = *n_changes; + + c[i].type = type; + c[i].path = strdup(path); + if (!c[i].path) + return -ENOMEM; + + if (source) { + c[i].source = strdup(source); + if (!c[i].source) { + free(c[i].path); + return -ENOMEM; + } + } else + c[i].source = NULL; + + *n_changes = i+1; + return 0; +} + +static int mark_symlink_for_removal( + Set **remove_symlinks_to, + const char *p) { + + char *n; + int r; + + assert(p); + + r = set_ensure_allocated(remove_symlinks_to, string_hash_func, string_compare_func); + if (r < 0) + return r; + + n = strdup(p); + if (!n) + return -ENOMEM; + + path_kill_slashes(n); + + r = set_put(*remove_symlinks_to, n); + if (r < 0) { + free(n); + return r == -EEXIST ? 0 : r; + } + + return 0; +} + +static int remove_marked_symlinks_fd( + Set *remove_symlinks_to, + int fd, + const char *path, + const char *config_path, + bool *deleted, + UnitFileChange **changes, + unsigned *n_changes) { + + int r = 0; + DIR *d; + struct dirent buffer, *de; + + assert(remove_symlinks_to); + assert(fd >= 0); + assert(path); + assert(config_path); + assert(deleted); + + d = fdopendir(fd); + if (!d) { + close_nointr_nofail(fd); + return -errno; + } + + rewinddir(d); + + for (;;) { + int k; + + k = readdir_r(d, &buffer, &de); + if (k != 0) { + r = -errno; + break; + } + + if (!de) + break; + + if (ignore_file(de->d_name)) + continue; + + dirent_ensure_type(d, de); + + if (de->d_type == DT_DIR) { + int nfd, q; + char *p; + + nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (nfd < 0) { + if (errno == ENOENT) + continue; + + if (r == 0) + r = -errno; + continue; + } + + p = path_make_absolute(de->d_name, path); + if (!p) { + close_nointr_nofail(nfd); + r = -ENOMEM; + break; + } + + /* This will close nfd, regardless whether it succeeds or not */ + q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, deleted, changes, n_changes); + free(p); + + if (r == 0) + r = q; + + } else if (de->d_type == DT_LNK) { + char *p, *dest; + int q; + bool found; + + p = path_make_absolute(de->d_name, path); + if (!p) { + r = -ENOMEM; + break; + } + + q = readlink_and_canonicalize(p, &dest); + if (q < 0) { + free(p); + + if (q == -ENOENT) + continue; + + if (r == 0) + r = q; + continue; + } + + found = + set_get(remove_symlinks_to, dest) || + set_get(remove_symlinks_to, file_name_from_path(dest)); + + if (found) { + + if (unlink(p) < 0 && errno != ENOENT) { + + if (r == 0) + r = -errno; + } else { + rmdir_parents(p, config_path); + path_kill_slashes(p); + + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, p, NULL); + + if (!set_get(remove_symlinks_to, p)) { + + q = mark_symlink_for_removal(&remove_symlinks_to, p); + if (q < 0) { + if (r == 0) + r = q; + } else + *deleted = true; + } + } + } + + free(p); + free(dest); + } + } + + closedir(d); + + return r; +} + +static int remove_marked_symlinks( + Set *remove_symlinks_to, + const char *config_path, + UnitFileChange **changes, + unsigned *n_changes) { + + int fd, r = 0; + bool deleted; + + assert(config_path); + + if (set_size(remove_symlinks_to) <= 0) + return 0; + + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) + return -errno; + + do { + int q, cfd; + deleted = false; + + cfd = dup(fd); + if (cfd < 0) { + r = -errno; + break; + } + + /* This takes possession of cfd and closes it */ + q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &deleted, changes, n_changes); + if (r == 0) + r = q; + } while (deleted); + + close_nointr_nofail(fd); + + return r; +} + +static int find_symlinks_fd( + const char *name, + int fd, + const char *path, + const char *config_path, + bool *same_name_link) { + + int r = 0; + DIR *d; + struct dirent buffer, *de; + + assert(name); + assert(fd >= 0); + assert(path); + assert(config_path); + assert(same_name_link); + + d = fdopendir(fd); + if (!d) { + close_nointr_nofail(fd); + return -errno; + } + + for (;;) { + int k; + + k = readdir_r(d, &buffer, &de); + if (k != 0) { + r = -errno; + break; + } + + if (!de) + break; + + if (ignore_file(de->d_name)) + continue; + + dirent_ensure_type(d, de); + + if (de->d_type == DT_DIR) { + int nfd, q; + char *p; + + nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (nfd < 0) { + if (errno == ENOENT) + continue; + + if (r == 0) + r = -errno; + continue; + } + + p = path_make_absolute(de->d_name, path); + if (!p) { + close_nointr_nofail(nfd); + r = -ENOMEM; + break; + } + + /* This will close nfd, regardless whether it succeeds or not */ + q = find_symlinks_fd(name, nfd, p, config_path, same_name_link); + free(p); + + if (q > 0) { + r = 1; + break; + } + + if (r == 0) + r = q; + + } else if (de->d_type == DT_LNK) { + char *p, *dest; + bool found_path, found_dest, b = false; + int q; + + /* Acquire symlink name */ + p = path_make_absolute(de->d_name, path); + if (!p) { + r = -ENOMEM; + break; + } + + /* Acquire symlink destination */ + q = readlink_and_canonicalize(p, &dest); + if (q < 0) { + free(p); + + if (q == -ENOENT) + continue; + + if (r == 0) + r = q; + continue; + } + + /* Check if the symlink itself matches what we + * are looking for */ + if (path_is_absolute(name)) + found_path = path_equal(p, name); + else + found_path = streq(de->d_name, name); + + /* Check if what the symlink points to + * matches what we are looking for */ + if (path_is_absolute(name)) + found_dest = path_equal(dest, name); + else + found_dest = streq(file_name_from_path(dest), name); + + free(dest); + + if (found_path && found_dest) { + char *t; + + /* Filter out same name links in the main + * config path */ + t = path_make_absolute(name, config_path); + if (!t) { + free(p); + r = -ENOMEM; + break; + } + + b = path_equal(t, p); + free(t); + } + + free(p); + + if (b) + *same_name_link = true; + else if (found_path || found_dest) { + r = 1; + break; + } + } + } + + closedir(d); + + return r; +} + +static int find_symlinks( + const char *name, + const char *config_path, + bool *same_name_link) { + + int fd; + + assert(name); + assert(config_path); + assert(same_name_link); + + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) + return -errno; + + /* This takes possession of fd and closes it */ + return find_symlinks_fd(name, fd, config_path, config_path, same_name_link); +} + +static int find_symlinks_in_scope( + UnitFileScope scope, + const char *root_dir, + const char *name, + UnitFileState *state) { + + int r; + char *path; + bool same_name_link_runtime = false, same_name_link = false; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + if (scope == UNIT_FILE_SYSTEM || scope == UNIT_FILE_GLOBAL) { + + /* First look in runtime config path */ + r = get_config_path(scope, true, root_dir, &path); + if (r < 0) + return r; + + r = find_symlinks(name, path, &same_name_link_runtime); + free(path); + + if (r < 0) + return r; + else if (r > 0) { + *state = UNIT_FILE_ENABLED_RUNTIME; + return r; + } + } + + /* Then look in the normal config path */ + r = get_config_path(scope, false, root_dir, &path); + if (r < 0) + return r; + + r = find_symlinks(name, path, &same_name_link); + free(path); + + if (r < 0) + return r; + else if (r > 0) { + *state = UNIT_FILE_ENABLED; + return r; + } + + /* Hmm, we didn't find it, but maybe we found the same name + * link? */ + if (same_name_link_runtime) { + *state = UNIT_FILE_LINKED_RUNTIME; + return 1; + } else if (same_name_link) { + *state = UNIT_FILE_LINKED; + return 1; + } + + return 0; +} + +int unit_file_mask( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char **i, *prefix; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = get_config_path(scope, runtime, root_dir, &prefix); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + char *path; + + if (!unit_name_is_valid_no_type(*i, true)) { + if (r == 0) + r = -EINVAL; + continue; + } + + path = path_make_absolute(*i, prefix); + if (!path) { + r = -ENOMEM; + break; + } + + if (symlink("/dev/null", path) >= 0) { + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null"); + + free(path); + continue; + } + + if (errno == EEXIST) { + + if (null_or_empty_path(path) > 0) { + free(path); + continue; + } + + if (force) { + unlink(path); + + if (symlink("/dev/null", path) >= 0) { + + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null"); + + free(path); + continue; + } + } + + if (r == 0) + r = -EEXIST; + } else { + if (r == 0) + r = -errno; + } + + free(path); + } + + free(prefix); + + return r; +} + +int unit_file_unmask( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + UnitFileChange **changes, + unsigned *n_changes) { + + char **i, *config_path = NULL; + int r, q; + Set *remove_symlinks_to = NULL; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + goto finish; + + STRV_FOREACH(i, files) { + char *path; + + if (!unit_name_is_valid_no_type(*i, true)) { + if (r == 0) + r = -EINVAL; + continue; + } + + path = path_make_absolute(*i, config_path); + if (!path) { + r = -ENOMEM; + break; + } + + q = null_or_empty_path(path); + if (q > 0) { + if (unlink(path) >= 0) { + mark_symlink_for_removal(&remove_symlinks_to, path); + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); + + free(path); + continue; + } + + q = -errno; + } + + if (q != -ENOENT && r == 0) + r = q; + + free(path); + } + + +finish: + q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); + if (r == 0) + r = q; + + set_free_free(remove_symlinks_to); + free(config_path); + + return r; +} + +int unit_file_link( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + LookupPaths paths; + char **i, *config_path = NULL; + int r, q; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + zero(paths); + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + goto finish; + + STRV_FOREACH(i, files) { + char *path, *fn; + struct stat st; + + fn = file_name_from_path(*i); + + if (!path_is_absolute(*i) || + !unit_name_is_valid_no_type(fn, true)) { + if (r == 0) + r = -EINVAL; + continue; + } + + if (lstat(*i, &st) < 0) { + if (r == 0) + r = -errno; + continue; + } + + if (!S_ISREG(st.st_mode)) { + r = -ENOENT; + continue; + } + + q = in_search_path(*i, paths.unit_path); + if (q < 0) { + r = q; + break; + } + + if (q > 0) + continue; + + path = path_make_absolute(fn, config_path); + if (!path) { + r = -ENOMEM; + break; + } + + if (symlink(*i, path) >= 0) { + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i); + + free(path); + continue; + } + + if (errno == EEXIST) { + char *dest = NULL; + + q = readlink_and_make_absolute(path, &dest); + + if (q < 0 && errno != ENOENT) { + free(path); + + if (r == 0) + r = q; + + continue; + } + + if (q >= 0 && path_equal(dest, *i)) { + free(dest); + free(path); + continue; + } + + free(dest); + + if (force) { + unlink(path); + + if (symlink(*i, path) >= 0) { + + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i); + + free(path); + continue; + } + } + + if (r == 0) + r = -EEXIST; + } else { + if (r == 0) + r = -errno; + } + + free(path); + } + + finish: + lookup_paths_free(&paths); + free(config_path); + + return r; +} + +void unit_file_list_free(Hashmap *h) { + UnitFileList *i; + + while ((i = hashmap_steal_first(h))) { + free(i->path); + free(i); + } + + hashmap_free(h); +} + +void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) { + unsigned i; + + assert(changes || n_changes == 0); + + if (!changes) + return; + + for (i = 0; i < n_changes; i++) { + free(changes[i].path); + free(changes[i].source); + } + + free(changes); +} + +static void install_info_free(InstallInfo *i) { + assert(i); + + free(i->name); + free(i->path); + strv_free(i->aliases); + strv_free(i->wanted_by); + free(i); +} + +static void install_info_hashmap_free(Hashmap *m) { + InstallInfo *i; + + if (!m) + return; + + while ((i = hashmap_steal_first(m))) + install_info_free(i); + + hashmap_free(m); +} + +static void install_context_done(InstallContext *c) { + assert(c); + + install_info_hashmap_free(c->will_install); + install_info_hashmap_free(c->have_installed); + + c->will_install = c->have_installed = NULL; +} + +static int install_info_add( + InstallContext *c, + const char *name, + const char *path) { + InstallInfo *i = NULL; + int r; + + assert(c); + assert(name || path); + + if (!name) + name = file_name_from_path(path); + + if (!unit_name_is_valid_no_type(name, true)) + return -EINVAL; + + if (hashmap_get(c->have_installed, name) || + hashmap_get(c->will_install, name)) + return 0; + + r = hashmap_ensure_allocated(&c->will_install, string_hash_func, string_compare_func); + if (r < 0) + return r; + + i = new0(InstallInfo, 1); + if (!i) + return -ENOMEM; + + i->name = strdup(name); + if (!i->name) { + r = -ENOMEM; + goto fail; + } + + if (path) { + i->path = strdup(path); + if (!i->path) { + r = -ENOMEM; + goto fail; + } + } + + r = hashmap_put(c->will_install, i->name, i); + if (r < 0) + goto fail; + + return 0; + +fail: + if (i) + install_info_free(i); + + return r; +} + +static int install_info_add_auto( + InstallContext *c, + const char *name_or_path) { + + assert(c); + assert(name_or_path); + + if (path_is_absolute(name_or_path)) + return install_info_add(c, NULL, name_or_path); + else + return install_info_add(c, name_or_path, NULL); +} + +static int config_parse_also( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char *w; + size_t l; + char *state; + InstallContext *c = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *n; + int r; + + n = strndup(w, l); + if (!n) + return -ENOMEM; + + r = install_info_add(c, n, NULL); + if (r < 0) { + free(n); + return r; + } + + free(n); + } + + return 0; +} + +static int unit_file_load( + InstallContext *c, + InstallInfo *info, + const char *path, + bool allow_symlink) { + + const ConfigTableItem items[] = { + { "Install", "Alias", config_parse_strv, 0, &info->aliases }, + { "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by }, + { "Install", "Also", config_parse_also, 0, c }, + { NULL, NULL, NULL, 0, NULL } + }; + + int fd; + FILE *f; + int r; + + assert(c); + assert(info); + assert(path); + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|(allow_symlink ? 0 : O_NOFOLLOW)); + if (fd < 0) + return -errno; + + f = fdopen(fd, "re"); + if (!f) { + close_nointr_nofail(fd); + return -ENOMEM; + } + + r = config_parse(path, f, NULL, config_item_table_lookup, (void*) items, true, info); + fclose(f); + if (r < 0) + return r; + + return strv_length(info->aliases) + strv_length(info->wanted_by); +} + +static int unit_file_search( + InstallContext *c, + InstallInfo *info, + LookupPaths *paths, + const char *root_dir, + bool allow_symlink) { + + char **p; + int r; + + assert(c); + assert(info); + assert(paths); + + if (info->path) + return unit_file_load(c, info, info->path, allow_symlink); + + assert(info->name); + + STRV_FOREACH(p, paths->unit_path) { + char *path = NULL; + + if (isempty(root_dir)) + asprintf(&path, "%s/%s", *p, info->name); + else + asprintf(&path, "%s/%s/%s", root_dir, *p, info->name); + + if (!path) + return -ENOMEM; + + r = unit_file_load(c, info, path, allow_symlink); + + if (r >= 0) + info->path = path; + else + free(path); + + if (r != -ENOENT && r != -ELOOP) + return r; + } + + return -ENOENT; +} + +static int unit_file_can_install( + LookupPaths *paths, + const char *root_dir, + const char *name, + bool allow_symlink) { + + InstallContext c; + InstallInfo *i; + int r; + + assert(paths); + assert(name); + + zero(c); + + r = install_info_add_auto(&c, name); + if (r < 0) + return r; + + assert_se(i = hashmap_first(c.will_install)); + + r = unit_file_search(&c, i, paths, root_dir, allow_symlink); + + if (r >= 0) + r = strv_length(i->aliases) + strv_length(i->wanted_by); + + install_context_done(&c); + + return r; +} + +static int create_symlink( + const char *old_path, + const char *new_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char *dest; + int r; + + assert(old_path); + assert(new_path); + + mkdir_parents(new_path, 0755); + + if (symlink(old_path, new_path) >= 0) { + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); + return 0; + } + + if (errno != EEXIST) + return -errno; + + r = readlink_and_make_absolute(new_path, &dest); + if (r < 0) + return r; + + if (path_equal(dest, old_path)) { + free(dest); + return 0; + } + + free(dest); + + if (force) + return -EEXIST; + + unlink(new_path); + + if (symlink(old_path, new_path) >= 0) { + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL); + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); + return 0; + } + + return -errno; +} + +static int install_info_symlink_alias( + InstallInfo *i, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char **s; + int r = 0, q; + + assert(i); + assert(config_path); + + STRV_FOREACH(s, i->aliases) { + char *alias_path; + + alias_path = path_make_absolute(*s, config_path); + + if (!alias_path) + return -ENOMEM; + + q = create_symlink(i->path, alias_path, force, changes, n_changes); + free(alias_path); + + if (r == 0) + r = q; + } + + return r; +} + +static int install_info_symlink_wants( + InstallInfo *i, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char **s; + int r = 0, q; + + assert(i); + assert(config_path); + + STRV_FOREACH(s, i->wanted_by) { + char *path; + + if (!unit_name_is_valid_no_type(*s, true)) { + r = -EINVAL; + continue; + } + + if (asprintf(&path, "%s/%s.wants/%s", config_path, *s, i->name) < 0) + return -ENOMEM; + + q = create_symlink(i->path, path, force, changes, n_changes); + free(path); + + if (r == 0) + r = q; + } + + return r; +} + +static int install_info_symlink_link( + InstallInfo *i, + LookupPaths *paths, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + int r; + char *path; + + assert(i); + assert(paths); + assert(config_path); + assert(i->path); + + r = in_search_path(i->path, paths->unit_path); + if (r != 0) + return r; + + if (asprintf(&path, "%s/%s", config_path, i->name) < 0) + return -ENOMEM; + + r = create_symlink(i->path, path, force, changes, n_changes); + free(path); + + return r; +} + +static int install_info_apply( + InstallInfo *i, + LookupPaths *paths, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + int r, q; + + assert(i); + assert(paths); + assert(config_path); + + r = install_info_symlink_alias(i, config_path, force, changes, n_changes); + + q = install_info_symlink_wants(i, config_path, force, changes, n_changes); + if (r == 0) + r = q; + + q = install_info_symlink_link(i, paths, config_path, force, changes, n_changes); + if (r == 0) + r = q; + + return r; +} + +static int install_context_apply( + InstallContext *c, + LookupPaths *paths, + const char *config_path, + const char *root_dir, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + InstallInfo *i; + int r = 0, q; + + assert(c); + assert(paths); + assert(config_path); + + while ((i = hashmap_first(c->will_install))) { + + q = hashmap_ensure_allocated(&c->have_installed, string_hash_func, string_compare_func); + if (q < 0) + return q; + + assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0); + + q = unit_file_search(c, i, paths, root_dir, false); + if (q < 0) { + if (r >= 0) + r = q; + + return r; + } else if (r >= 0) + r += q; + + q = install_info_apply(i, paths, config_path, force, changes, n_changes); + if (r >= 0 && q < 0) + r = q; + } + + return r; +} + +static int install_context_mark_for_removal( + InstallContext *c, + LookupPaths *paths, + Set **remove_symlinks_to, + const char *config_path, + const char *root_dir) { + + InstallInfo *i; + int r = 0, q; + + assert(c); + assert(paths); + assert(config_path); + + /* Marks all items for removal */ + + while ((i = hashmap_first(c->will_install))) { + + q = hashmap_ensure_allocated(&c->have_installed, string_hash_func, string_compare_func); + if (q < 0) + return q; + + assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0); + + q = unit_file_search(c, i, paths, root_dir, false); + if (q < 0) { + if (r >= 0) + r = q; + + return r; + } else if (r >= 0) + r += q; + + q = mark_symlink_for_removal(remove_symlinks_to, i->name); + if (r >= 0 && q < 0) + r = q; + } + + return r; +} + +int unit_file_enable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + LookupPaths paths; + InstallContext c; + char **i, *config_path = NULL; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + zero(paths); + zero(c); + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + goto finish; + + STRV_FOREACH(i, files) { + r = install_info_add_auto(&c, *i); + if (r < 0) + goto finish; + } + + /* This will return the number of symlink rules that were + supposed to be created, not the ones actually created. This is + useful to determine whether the passed files hat any + installation data at all. */ + r = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes); + +finish: + install_context_done(&c); + lookup_paths_free(&paths); + free(config_path); + + return r; +} + +int unit_file_disable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + UnitFileChange **changes, + unsigned *n_changes) { + + LookupPaths paths; + InstallContext c; + char **i, *config_path = NULL; + Set *remove_symlinks_to = NULL; + int r, q; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + zero(paths); + zero(c); + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + goto finish; + + STRV_FOREACH(i, files) { + r = install_info_add_auto(&c, *i); + if (r < 0) + goto finish; + } + + r = install_context_mark_for_removal(&c, &paths, &remove_symlinks_to, config_path, root_dir); + + q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); + if (r == 0) + r = q; + +finish: + install_context_done(&c); + lookup_paths_free(&paths); + set_free_free(remove_symlinks_to); + free(config_path); + + return r; +} + +int unit_file_reenable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + LookupPaths paths; + InstallContext c; + char **i, *config_path = NULL; + Set *remove_symlinks_to = NULL; + int r, q; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + zero(paths); + zero(c); + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + goto finish; + + STRV_FOREACH(i, files) { + r = mark_symlink_for_removal(&remove_symlinks_to, *i); + if (r < 0) + goto finish; + + r = install_info_add_auto(&c, *i); + if (r < 0) + goto finish; + } + + r = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); + + /* Returns number of symlinks that where supposed to be installed. */ + q = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes); + if (r == 0) + r = q; + +finish: + lookup_paths_free(&paths); + install_context_done(&c); + set_free_free(remove_symlinks_to); + free(config_path); + + return r; +} + +UnitFileState unit_file_get_state( + UnitFileScope scope, + const char *root_dir, + const char *name) { + + LookupPaths paths; + UnitFileState state = _UNIT_FILE_STATE_INVALID; + char **i, *path = NULL; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + zero(paths); + + if (root_dir && scope != UNIT_FILE_SYSTEM) + return -EINVAL; + + if (!unit_name_is_valid_no_type(name, true)) + return -EINVAL; + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + STRV_FOREACH(i, paths.unit_path) { + struct stat st; + + free(path); + path = NULL; + + if (root_dir) + asprintf(&path, "%s/%s/%s", root_dir, *i, name); + else + asprintf(&path, "%s/%s", *i, name); + + if (!path) { + r = -ENOMEM; + goto finish; + } + + if (lstat(path, &st) < 0) { + r = -errno; + if (errno == ENOENT) + continue; + + goto finish; + } + + if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) { + r = -ENOENT; + goto finish; + } + + r = null_or_empty_path(path); + if (r < 0 && r != -ENOENT) + goto finish; + else if (r > 0) { + state = path_startswith(*i, "/run") ? + UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; + r = 0; + goto finish; + } + + r = find_symlinks_in_scope(scope, root_dir, name, &state); + if (r < 0) { + goto finish; + } else if (r > 0) { + r = 0; + goto finish; + } + + r = unit_file_can_install(&paths, root_dir, path, true); + if (r < 0 && errno != -ENOENT) + goto finish; + else if (r > 0) { + state = UNIT_FILE_DISABLED; + r = 0; + goto finish; + } else if (r == 0) { + state = UNIT_FILE_STATIC; + r = 0; + goto finish; + } + } + +finish: + lookup_paths_free(&paths); + free(path); + + return r < 0 ? r : state; +} + +int unit_file_query_preset(UnitFileScope scope, const char *name) { + char **files, **i; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + if (scope == UNIT_FILE_SYSTEM) + r = conf_files_list(&files, ".preset", + "/etc/systemd/system.preset", + "/usr/local/lib/systemd/system.preset", + "/usr/lib/systemd/system.preset", + "/lib/systemd/system.preset", + NULL); + else if (scope == UNIT_FILE_GLOBAL) + r = conf_files_list(&files, ".preset", + "/etc/systemd/user.preset", + "/usr/local/lib/systemd/user.preset", + "/usr/lib/systemd/user.preset", + NULL); + else + return 1; + + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + FILE *f; + + f = fopen(*i, "re"); + if (!f) { + if (errno == ENOENT) + continue; + + r = -errno; + goto finish; + } + + for (;;) { + char line[LINE_MAX], *l; + + if (!fgets(line, sizeof(line), f)) + break; + + l = strstrip(line); + if (!*l) + continue; + + if (strchr(COMMENTS, *l)) + continue; + + if (first_word(l, "enable")) { + l += 6; + l += strspn(l, WHITESPACE); + + if (fnmatch(l, name, FNM_NOESCAPE) == 0) { + r = 1; + fclose(f); + goto finish; + } + } else if (first_word(l, "disable")) { + l += 7; + l += strspn(l, WHITESPACE); + + if (fnmatch(l, name, FNM_NOESCAPE) == 0) { + r = 0; + fclose(f); + goto finish; + } + } else + log_debug("Couldn't parse line '%s'", l); + } + + fclose(f); + } + + /* Default is "enable" */ + r = 1; + +finish: + strv_free(files); + + return r; +} + +int unit_file_preset( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + LookupPaths paths; + InstallContext plus, minus; + char **i, *config_path = NULL; + Set *remove_symlinks_to = NULL; + int r, q; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + zero(paths); + zero(plus); + zero(minus); + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + goto finish; + + STRV_FOREACH(i, files) { + + if (!unit_name_is_valid_no_type(*i, true)) { + r = -EINVAL; + goto finish; + } + + r = unit_file_query_preset(scope, *i); + if (r < 0) + goto finish; + + if (r) + r = install_info_add_auto(&plus, *i); + else + r = install_info_add_auto(&minus, *i); + + if (r < 0) + goto finish; + } + + r = install_context_mark_for_removal(&minus, &paths, &remove_symlinks_to, config_path, root_dir); + + q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); + if (r == 0) + r = q; + + /* Returns number of symlinks that where supposed to be installed. */ + q = install_context_apply(&plus, &paths, config_path, root_dir, force, changes, n_changes); + if (r == 0) + r = q; + +finish: + lookup_paths_free(&paths); + install_context_done(&plus); + install_context_done(&minus); + set_free_free(remove_symlinks_to); + free(config_path); + + return r; +} + +int unit_file_get_list( + UnitFileScope scope, + const char *root_dir, + Hashmap *h) { + + LookupPaths paths; + char **i, *buf = NULL; + DIR *d = NULL; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(h); + + zero(paths); + + if (root_dir && scope != UNIT_FILE_SYSTEM) + return -EINVAL; + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + STRV_FOREACH(i, paths.unit_path) { + struct dirent buffer, *de; + const char *units_dir; + + free(buf); + buf = NULL; + + if (root_dir) { + if (asprintf(&buf, "%s/%s", root_dir, *i) < 0) { + r = -ENOMEM; + goto finish; + } + units_dir = buf; + } else + units_dir = *i; + + if (d) + closedir(d); + + d = opendir(units_dir); + if (!d) { + if (errno == ENOENT) + continue; + + r = -errno; + goto finish; + } + + for (;;) { + UnitFileList *f; + + r = readdir_r(d, &buffer, &de); + if (r != 0) { + r = -r; + goto finish; + } + + if (!de) + break; + + if (ignore_file(de->d_name)) + continue; + + if (!unit_name_is_valid_no_type(de->d_name, true)) + continue; + + if (hashmap_get(h, de->d_name)) + continue; + + r = dirent_ensure_type(d, de); + if (r < 0) { + if (r == -ENOENT) + continue; + + goto finish; + } + + if (de->d_type != DT_LNK && de->d_type != DT_REG) + continue; + + f = new0(UnitFileList, 1); + if (!f) { + r = -ENOMEM; + goto finish; + } + + f->path = path_make_absolute(de->d_name, units_dir); + if (!f->path) { + free(f); + r = -ENOMEM; + goto finish; + } + + r = null_or_empty_path(f->path); + if (r < 0 && r != -ENOENT) { + free(f->path); + free(f); + goto finish; + } else if (r > 0) { + f->state = + path_startswith(*i, "/run") ? + UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; + goto found; + } + + r = find_symlinks_in_scope(scope, root_dir, de->d_name, &f->state); + if (r < 0) { + free(f->path); + free(f); + goto finish; + } else if (r > 0) + goto found; + + r = unit_file_can_install(&paths, root_dir, f->path, true); + if (r < 0) { + free(f->path); + free(f); + goto finish; + } else if (r > 0) { + f->state = UNIT_FILE_DISABLED; + goto found; + } else { + f->state = UNIT_FILE_STATIC; + goto found; + } + + free(f->path); + free(f); + continue; + + found: + r = hashmap_put(h, file_name_from_path(f->path), f); + if (r < 0) { + free(f->path); + free(f); + goto finish; + } + } + } + +finish: + lookup_paths_free(&paths); + free(buf); + + if (d) + closedir(d); + + return r; +} + +static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = { + [UNIT_FILE_ENABLED] = "enabled", + [UNIT_FILE_ENABLED_RUNTIME] = "enabled-runtie", + [UNIT_FILE_LINKED] = "linked", + [UNIT_FILE_LINKED_RUNTIME] = "linked-runtime", + [UNIT_FILE_MASKED] = "masked", + [UNIT_FILE_MASKED_RUNTIME] = "masked-runtime", + [UNIT_FILE_STATIC] = "static", + [UNIT_FILE_DISABLED] = "disabled" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState); + +static const char* const unit_file_change_type_table[_UNIT_FILE_CHANGE_TYPE_MAX] = { + [UNIT_FILE_SYMLINK] = "symlink", + [UNIT_FILE_UNLINK] = "unlink", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType); diff --git a/src/install.h b/src/install.h new file mode 100644 index 000000000..0505a82f0 --- /dev/null +++ b/src/install.h @@ -0,0 +1,89 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooinstallhfoo +#define fooinstallhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "hashmap.h" + +typedef enum UnitFileScope { + UNIT_FILE_SYSTEM, + UNIT_FILE_GLOBAL, + UNIT_FILE_USER, + _UNIT_FILE_SCOPE_MAX, + _UNIT_FILE_SCOPE_INVALID = -1 +} UnitFileScope; + +typedef enum UnitFileState { + UNIT_FILE_ENABLED, + UNIT_FILE_ENABLED_RUNTIME, + UNIT_FILE_LINKED, + UNIT_FILE_LINKED_RUNTIME, + UNIT_FILE_MASKED, + UNIT_FILE_MASKED_RUNTIME, + UNIT_FILE_STATIC, + UNIT_FILE_DISABLED, + _UNIT_FILE_STATE_MAX, + _UNIT_FILE_STATE_INVALID = -1 +} UnitFileState; + +typedef enum UnitFileChangeType { + UNIT_FILE_SYMLINK, + UNIT_FILE_UNLINK, + _UNIT_FILE_CHANGE_TYPE_MAX, + _UNIT_FILE_CHANGE_TYPE_INVALID = -1 +} UnitFileChangeType; + +typedef struct UnitFileChange { + UnitFileChangeType type; + char *path; + char *source; +} UnitFileChange; + +typedef struct UnitFileList { + char *path; + UnitFileState state; +} UnitFileList; + +int unit_file_enable(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_disable(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes); +int unit_file_reenable(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_link(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_preset(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_mask(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_unmask(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes); + +UnitFileState unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename); + +int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h); + +void unit_file_list_free(Hashmap *h); +void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes); + +int unit_file_query_preset(UnitFileScope scope, const char *name); + +const char *unit_file_state_to_string(UnitFileState s); +UnitFileState unit_file_state_from_string(const char *s); + +const char *unit_file_change_type_to_string(UnitFileChangeType s); +UnitFileChangeType unit_file_change_type_from_string(const char *s); + +#endif diff --git a/src/ioprio.h b/src/ioprio.h new file mode 100644 index 000000000..9800fc255 --- /dev/null +++ b/src/ioprio.h @@ -0,0 +1,57 @@ +#ifndef IOPRIO_H +#define IOPRIO_H + +/* This is minimal version of Linux' linux/ioprio.h header file, which + * is licensed GPL2 */ + +#include +#include + +/* + * Gives us 8 prio classes with 13-bits of data for each class + */ +#define IOPRIO_BITS (16) +#define IOPRIO_CLASS_SHIFT (13) +#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) + +#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) +#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) +#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) + +#define ioprio_valid(mask) (IOPRIO_PRIO_CLASS((mask)) != IOPRIO_CLASS_NONE) + +/* + * These are the io priority groups as implemented by CFQ. RT is the realtime + * class, it always gets premium service. BE is the best-effort scheduling + * class, the default for any process. IDLE is the idle scheduling class, it + * is only served when no one else is using the disk. + */ +enum { + IOPRIO_CLASS_NONE, + IOPRIO_CLASS_RT, + IOPRIO_CLASS_BE, + IOPRIO_CLASS_IDLE, +}; + +/* + * 8 best effort priority levels are supported + */ +#define IOPRIO_BE_NR (8) + +enum { + IOPRIO_WHO_PROCESS = 1, + IOPRIO_WHO_PGRP, + IOPRIO_WHO_USER, +}; + +static inline int ioprio_set(int which, int who, int ioprio) +{ + return syscall(__NR_ioprio_set, which, who, ioprio); +} + +static inline int ioprio_get(int which, int who) +{ + return syscall(__NR_ioprio_get, which, who); +} + +#endif diff --git a/src/job.c b/src/job.c new file mode 100644 index 000000000..8e944b35d --- /dev/null +++ b/src/job.c @@ -0,0 +1,754 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "set.h" +#include "unit.h" +#include "macro.h" +#include "strv.h" +#include "load-fragment.h" +#include "load-dropin.h" +#include "log.h" +#include "dbus-job.h" + +Job* job_new(Manager *m, JobType type, Unit *unit) { + Job *j; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(unit); + + if (!(j = new0(Job, 1))) + return NULL; + + j->manager = m; + j->id = m->current_job_id++; + j->type = type; + j->unit = unit; + + j->timer_watch.type = WATCH_INVALID; + + /* We don't link it here, that's what job_dependency() is for */ + + return j; +} + +void job_free(Job *j) { + assert(j); + + /* Detach from next 'bigger' objects */ + if (j->installed) { + bus_job_send_removed_signal(j); + + if (j->unit->job == j) { + j->unit->job = NULL; + unit_add_to_gc_queue(j->unit); + } + + hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id)); + j->installed = false; + } + + /* Detach from next 'smaller' objects */ + manager_transaction_unlink_job(j->manager, j, true); + + if (j->in_run_queue) + LIST_REMOVE(Job, run_queue, j->manager->run_queue, j); + + if (j->in_dbus_queue) + LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j); + + if (j->timer_watch.type != WATCH_INVALID) { + assert(j->timer_watch.type == WATCH_JOB_TIMER); + assert(j->timer_watch.data.job == j); + assert(j->timer_watch.fd >= 0); + + assert_se(epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_DEL, j->timer_watch.fd, NULL) >= 0); + close_nointr_nofail(j->timer_watch.fd); + } + + free(j->bus_client); + free(j); +} + +JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) { + JobDependency *l; + + assert(object); + + /* Adds a new job link, which encodes that the 'subject' job + * needs the 'object' job in some way. If 'subject' is NULL + * this means the 'anchor' job (i.e. the one the user + * explicitly asked for) is the requester. */ + + if (!(l = new0(JobDependency, 1))) + return NULL; + + l->subject = subject; + l->object = object; + l->matters = matters; + l->conflicts = conflicts; + + if (subject) + LIST_PREPEND(JobDependency, subject, subject->subject_list, l); + else + LIST_PREPEND(JobDependency, subject, object->manager->transaction_anchor, l); + + LIST_PREPEND(JobDependency, object, object->object_list, l); + + return l; +} + +void job_dependency_free(JobDependency *l) { + assert(l); + + if (l->subject) + LIST_REMOVE(JobDependency, subject, l->subject->subject_list, l); + else + LIST_REMOVE(JobDependency, subject, l->object->manager->transaction_anchor, l); + + LIST_REMOVE(JobDependency, object, l->object->object_list, l); + + free(l); +} + +void job_dump(Job *j, FILE*f, const char *prefix) { + assert(j); + assert(f); + + if (!prefix) + prefix = ""; + + fprintf(f, + "%s-> Job %u:\n" + "%s\tAction: %s -> %s\n" + "%s\tState: %s\n" + "%s\tForced: %s\n", + prefix, j->id, + prefix, j->unit->id, job_type_to_string(j->type), + prefix, job_state_to_string(j->state), + prefix, yes_no(j->override)); +} + +bool job_is_anchor(Job *j) { + JobDependency *l; + + assert(j); + + LIST_FOREACH(object, l, j->object_list) + if (!l->subject) + return true; + + return false; +} + +static bool types_match(JobType a, JobType b, JobType c, JobType d) { + return + (a == c && b == d) || + (a == d && b == c); +} + +int job_type_merge(JobType *a, JobType b) { + if (*a == b) + return 0; + + /* Merging is associative! a merged with b merged with c is + * the same as a merged with c merged with b. */ + + /* Mergeability is transitive! if a can be merged with b and b + * with c then a also with c */ + + /* Also, if a merged with b cannot be merged with c, then + * either a or b cannot be merged with c either */ + + if (types_match(*a, b, JOB_START, JOB_VERIFY_ACTIVE)) + *a = JOB_START; + else if (types_match(*a, b, JOB_START, JOB_RELOAD) || + types_match(*a, b, JOB_START, JOB_RELOAD_OR_START) || + types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RELOAD_OR_START) || + types_match(*a, b, JOB_RELOAD, JOB_RELOAD_OR_START)) + *a = JOB_RELOAD_OR_START; + else if (types_match(*a, b, JOB_START, JOB_RESTART) || + types_match(*a, b, JOB_START, JOB_TRY_RESTART) || + types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RESTART) || + types_match(*a, b, JOB_RELOAD, JOB_RESTART) || + types_match(*a, b, JOB_RELOAD_OR_START, JOB_RESTART) || + types_match(*a, b, JOB_RELOAD_OR_START, JOB_TRY_RESTART) || + types_match(*a, b, JOB_RESTART, JOB_TRY_RESTART)) + *a = JOB_RESTART; + else if (types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RELOAD)) + *a = JOB_RELOAD; + else if (types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_TRY_RESTART) || + types_match(*a, b, JOB_RELOAD, JOB_TRY_RESTART)) + *a = JOB_TRY_RESTART; + else + return -EEXIST; + + return 0; +} + +bool job_type_is_mergeable(JobType a, JobType b) { + return job_type_merge(&a, b) >= 0; +} + +bool job_type_is_superset(JobType a, JobType b) { + + /* Checks whether operation a is a "superset" of b in its + * actions */ + + if (a == b) + return true; + + switch (a) { + case JOB_START: + return b == JOB_VERIFY_ACTIVE; + + case JOB_RELOAD: + return + b == JOB_VERIFY_ACTIVE; + + case JOB_RELOAD_OR_START: + return + b == JOB_RELOAD || + b == JOB_START || + b == JOB_VERIFY_ACTIVE; + + case JOB_RESTART: + return + b == JOB_START || + b == JOB_VERIFY_ACTIVE || + b == JOB_RELOAD || + b == JOB_RELOAD_OR_START || + b == JOB_TRY_RESTART; + + case JOB_TRY_RESTART: + return + b == JOB_VERIFY_ACTIVE || + b == JOB_RELOAD; + default: + return false; + + } +} + +bool job_type_is_conflicting(JobType a, JobType b) { + assert(a >= 0 && a < _JOB_TYPE_MAX); + assert(b >= 0 && b < _JOB_TYPE_MAX); + + return (a == JOB_STOP) != (b == JOB_STOP); +} + +bool job_type_is_redundant(JobType a, UnitActiveState b) { + switch (a) { + + case JOB_START: + return + b == UNIT_ACTIVE || + b == UNIT_RELOADING; + + case JOB_STOP: + return + b == UNIT_INACTIVE || + b == UNIT_FAILED; + + case JOB_VERIFY_ACTIVE: + return + b == UNIT_ACTIVE || + b == UNIT_RELOADING; + + case JOB_RELOAD: + return + b == UNIT_RELOADING; + + case JOB_RELOAD_OR_START: + return + b == UNIT_ACTIVATING || + b == UNIT_RELOADING; + + case JOB_RESTART: + return + b == UNIT_ACTIVATING; + + case JOB_TRY_RESTART: + return + b == UNIT_ACTIVATING; + + default: + assert_not_reached("Invalid job type"); + } +} + +bool job_is_runnable(Job *j) { + Iterator i; + Unit *other; + + assert(j); + assert(j->installed); + + /* Checks whether there is any job running for the units this + * job needs to be running after (in the case of a 'positive' + * job type) or before (in the case of a 'negative' job + * type. */ + + /* First check if there is an override */ + if (j->ignore_order) + return true; + + if (j->type == JOB_START || + j->type == JOB_VERIFY_ACTIVE || + j->type == JOB_RELOAD || + j->type == JOB_RELOAD_OR_START) { + + /* Immediate result is that the job is or might be + * started. In this case lets wait for the + * dependencies, regardless whether they are + * starting or stopping something. */ + + SET_FOREACH(other, j->unit->dependencies[UNIT_AFTER], i) + if (other->job) + return false; + } + + /* Also, if something else is being stopped and we should + * change state after it, then lets wait. */ + + SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i) + if (other->job && + (other->job->type == JOB_STOP || + other->job->type == JOB_RESTART || + other->job->type == JOB_TRY_RESTART)) + return false; + + /* This means that for a service a and a service b where b + * shall be started after a: + * + * start a + start b → 1st step start a, 2nd step start b + * start a + stop b → 1st step stop b, 2nd step start a + * stop a + start b → 1st step stop a, 2nd step start b + * stop a + stop b → 1st step stop b, 2nd step stop a + * + * This has the side effect that restarts are properly + * synchronized too. */ + + return true; +} + +static void job_change_type(Job *j, JobType newtype) { + log_debug("Converting job %s/%s -> %s/%s", + j->unit->id, job_type_to_string(j->type), + j->unit->id, job_type_to_string(newtype)); + + j->type = newtype; +} + +int job_run_and_invalidate(Job *j) { + int r; + uint32_t id; + Manager *m; + + assert(j); + assert(j->installed); + + if (j->in_run_queue) { + LIST_REMOVE(Job, run_queue, j->manager->run_queue, j); + j->in_run_queue = false; + } + + if (j->state != JOB_WAITING) + return 0; + + if (!job_is_runnable(j)) + return -EAGAIN; + + j->state = JOB_RUNNING; + job_add_to_dbus_queue(j); + + /* While we execute this operation the job might go away (for + * example: because it is replaced by a new, conflicting + * job.) To make sure we don't access a freed job later on we + * store the id here, so that we can verify the job is still + * valid. */ + id = j->id; + m = j->manager; + + switch (j->type) { + + case JOB_RELOAD_OR_START: + if (unit_active_state(j->unit) == UNIT_ACTIVE) { + job_change_type(j, JOB_RELOAD); + r = unit_reload(j->unit); + break; + } + job_change_type(j, JOB_START); + /* fall through */ + + case JOB_START: + r = unit_start(j->unit); + + /* If this unit cannot be started, then simply wait */ + if (r == -EBADR) + r = 0; + break; + + case JOB_VERIFY_ACTIVE: { + UnitActiveState t = unit_active_state(j->unit); + if (UNIT_IS_ACTIVE_OR_RELOADING(t)) + r = -EALREADY; + else if (t == UNIT_ACTIVATING) + r = -EAGAIN; + else + r = -ENOEXEC; + break; + } + + case JOB_TRY_RESTART: + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(j->unit))) { + r = -ENOEXEC; + break; + } + job_change_type(j, JOB_RESTART); + /* fall through */ + + case JOB_STOP: + case JOB_RESTART: + r = unit_stop(j->unit); + + /* If this unit cannot stopped, then simply wait. */ + if (r == -EBADR) + r = 0; + break; + + case JOB_RELOAD: + r = unit_reload(j->unit); + break; + + default: + assert_not_reached("Unknown job type"); + } + + if ((j = manager_get_job(m, id))) { + if (r == -EALREADY) + r = job_finish_and_invalidate(j, JOB_DONE); + else if (r == -ENOEXEC) + r = job_finish_and_invalidate(j, JOB_SKIPPED); + else if (r == -EAGAIN) + j->state = JOB_WAITING; + else if (r < 0) + r = job_finish_and_invalidate(j, JOB_FAILED); + } + + return r; +} + +static void job_print_status_message(Unit *u, JobType t, JobResult result) { + assert(u); + + if (t == JOB_START) { + + switch (result) { + + case JOB_DONE: + unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, "Started %s", unit_description(u)); + break; + + case JOB_FAILED: + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON "FAILED" ANSI_HIGHLIGHT_OFF, "Failed to start %s", unit_description(u)); + unit_status_printf(u, NULL, "See 'systemctl status %s' for details.", u->id); + break; + + case JOB_DEPENDENCY: + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " ABORT" ANSI_HIGHLIGHT_OFF, "Dependency failed. Aborted start of %s", unit_description(u)); + break; + + case JOB_TIMEOUT: + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, "Timed out starting %s", unit_description(u)); + break; + + default: + ; + } + + } else if (t == JOB_STOP) { + + switch (result) { + + case JOB_TIMEOUT: + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, "Timed out stopping %s", unit_description(u)); + break; + + case JOB_DONE: + case JOB_FAILED: + unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, "Stopped %s", unit_description(u)); + break; + + default: + ; + } + } +} + +int job_finish_and_invalidate(Job *j, JobResult result) { + Unit *u; + Unit *other; + JobType t; + Iterator i; + bool recursed = false; + + assert(j); + assert(j->installed); + + job_add_to_dbus_queue(j); + + /* Patch restart jobs so that they become normal start jobs */ + if (result == JOB_DONE && j->type == JOB_RESTART) { + + job_change_type(j, JOB_START); + j->state = JOB_WAITING; + + job_add_to_run_queue(j); + + u = j->unit; + goto finish; + } + + j->result = result; + + log_debug("Job %s/%s finished, result=%s", j->unit->id, job_type_to_string(j->type), job_result_to_string(result)); + + if (result == JOB_FAILED) + j->manager->n_failed_jobs ++; + + u = j->unit; + t = j->type; + job_free(j); + + job_print_status_message(u, t, result); + + /* Fail depending jobs on failure */ + if (result != JOB_DONE) { + + if (t == JOB_START || + t == JOB_VERIFY_ACTIVE || + t == JOB_RELOAD_OR_START) { + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i) + if (other->job && + (other->job->type == JOB_START || + other->job->type == JOB_VERIFY_ACTIVE || + other->job->type == JOB_RELOAD_OR_START)) { + job_finish_and_invalidate(other->job, JOB_DEPENDENCY); + recursed = true; + } + + SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) + if (other->job && + (other->job->type == JOB_START || + other->job->type == JOB_VERIFY_ACTIVE || + other->job->type == JOB_RELOAD_OR_START)) { + job_finish_and_invalidate(other->job, JOB_DEPENDENCY); + recursed = true; + } + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i) + if (other->job && + !other->job->override && + (other->job->type == JOB_START || + other->job->type == JOB_VERIFY_ACTIVE || + other->job->type == JOB_RELOAD_OR_START)) { + job_finish_and_invalidate(other->job, JOB_DEPENDENCY); + recursed = true; + } + + } else if (t == JOB_STOP) { + + SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i) + if (other->job && + (other->job->type == JOB_START || + other->job->type == JOB_VERIFY_ACTIVE || + other->job->type == JOB_RELOAD_OR_START)) { + job_finish_and_invalidate(other->job, JOB_DEPENDENCY); + recursed = true; + } + } + } + + /* Trigger OnFailure dependencies that are not generated by + * the unit itself. We don't tread JOB_CANCELED as failure in + * this context. And JOB_FAILURE is already handled by the + * unit itself. */ + if (result == JOB_TIMEOUT || result == JOB_DEPENDENCY) { + log_notice("Job %s/%s failed with result '%s'.", + u->id, + job_type_to_string(t), + job_result_to_string(result)); + + unit_trigger_on_failure(u); + } + +finish: + /* Try to start the next jobs that can be started */ + SET_FOREACH(other, u->dependencies[UNIT_AFTER], i) + if (other->job) + job_add_to_run_queue(other->job); + SET_FOREACH(other, u->dependencies[UNIT_BEFORE], i) + if (other->job) + job_add_to_run_queue(other->job); + + manager_check_finished(u->manager); + + return recursed; +} + +int job_start_timer(Job *j) { + struct itimerspec its; + struct epoll_event ev; + int fd, r; + assert(j); + + if (j->unit->job_timeout <= 0 || + j->timer_watch.type == WATCH_JOB_TIMER) + return 0; + + assert(j->timer_watch.type == WATCH_INVALID); + + if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) { + r = -errno; + goto fail; + } + + zero(its); + timespec_store(&its.it_value, j->unit->job_timeout); + + if (timerfd_settime(fd, 0, &its, NULL) < 0) { + r = -errno; + goto fail; + } + + zero(ev); + ev.data.ptr = &j->timer_watch; + ev.events = EPOLLIN; + + if (epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) { + r = -errno; + goto fail; + } + + j->timer_watch.type = WATCH_JOB_TIMER; + j->timer_watch.fd = fd; + j->timer_watch.data.job = j; + + return 0; + +fail: + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +void job_add_to_run_queue(Job *j) { + assert(j); + assert(j->installed); + + if (j->in_run_queue) + return; + + LIST_PREPEND(Job, run_queue, j->manager->run_queue, j); + j->in_run_queue = true; +} + +void job_add_to_dbus_queue(Job *j) { + assert(j); + assert(j->installed); + + if (j->in_dbus_queue) + return; + + /* We don't check if anybody is subscribed here, since this + * job might just have been created and not yet assigned to a + * connection/client. */ + + LIST_PREPEND(Job, dbus_queue, j->manager->dbus_job_queue, j); + j->in_dbus_queue = true; +} + +char *job_dbus_path(Job *j) { + char *p; + + assert(j); + + if (asprintf(&p, "/org/freedesktop/systemd1/job/%lu", (unsigned long) j->id) < 0) + return NULL; + + return p; +} + +void job_timer_event(Job *j, uint64_t n_elapsed, Watch *w) { + assert(j); + assert(w == &j->timer_watch); + + log_warning("Job %s/%s timed out.", j->unit->id, job_type_to_string(j->type)); + job_finish_and_invalidate(j, JOB_TIMEOUT); +} + +static const char* const job_state_table[_JOB_STATE_MAX] = { + [JOB_WAITING] = "waiting", + [JOB_RUNNING] = "running" +}; + +DEFINE_STRING_TABLE_LOOKUP(job_state, JobState); + +static const char* const job_type_table[_JOB_TYPE_MAX] = { + [JOB_START] = "start", + [JOB_VERIFY_ACTIVE] = "verify-active", + [JOB_STOP] = "stop", + [JOB_RELOAD] = "reload", + [JOB_RELOAD_OR_START] = "reload-or-start", + [JOB_RESTART] = "restart", + [JOB_TRY_RESTART] = "try-restart", +}; + +DEFINE_STRING_TABLE_LOOKUP(job_type, JobType); + +static const char* const job_mode_table[_JOB_MODE_MAX] = { + [JOB_FAIL] = "fail", + [JOB_REPLACE] = "replace", + [JOB_ISOLATE] = "isolate", + [JOB_IGNORE_DEPENDENCIES] = "ignore-dependencies", + [JOB_IGNORE_REQUIREMENTS] = "ignore-requirements" +}; + +DEFINE_STRING_TABLE_LOOKUP(job_mode, JobMode); + +static const char* const job_result_table[_JOB_RESULT_MAX] = { + [JOB_DONE] = "done", + [JOB_CANCELED] = "canceled", + [JOB_TIMEOUT] = "timeout", + [JOB_FAILED] = "failed", + [JOB_DEPENDENCY] = "dependency", + [JOB_SKIPPED] = "skipped" +}; + +DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult); diff --git a/src/job.h b/src/job.h new file mode 100644 index 000000000..2121426b3 --- /dev/null +++ b/src/job.h @@ -0,0 +1,179 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foojobhfoo +#define foojobhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +typedef struct Job Job; +typedef struct JobDependency JobDependency; +typedef enum JobType JobType; +typedef enum JobState JobState; +typedef enum JobMode JobMode; +typedef enum JobResult JobResult; + +#include "manager.h" +#include "unit.h" +#include "hashmap.h" +#include "list.h" + +enum JobType { + JOB_START, /* if a unit does not support being started, we'll just wait until it becomes active */ + JOB_VERIFY_ACTIVE, + + JOB_STOP, + + JOB_RELOAD, /* if running reload */ + JOB_RELOAD_OR_START, /* if running reload, if not running start */ + + /* Note that restarts are first treated like JOB_STOP, but + * then instead of finishing are patched to become + * JOB_START. */ + JOB_RESTART, /* if running stop, then start unconditionally */ + JOB_TRY_RESTART, /* if running stop and then start */ + + _JOB_TYPE_MAX, + _JOB_TYPE_INVALID = -1 +}; + +enum JobState { + JOB_WAITING, + JOB_RUNNING, + _JOB_STATE_MAX, + _JOB_STATE_INVALID = -1 +}; + +enum JobMode { + JOB_FAIL, /* Fail if a conflicting job is already queued */ + JOB_REPLACE, /* Replace an existing conflicting job */ + JOB_ISOLATE, /* Start a unit, and stop all others */ + JOB_IGNORE_DEPENDENCIES, /* Ignore both requirement and ordering dependencies */ + JOB_IGNORE_REQUIREMENTS, /* Ignore requirement dependencies */ + _JOB_MODE_MAX, + _JOB_MODE_INVALID = -1 +}; + +enum JobResult { + JOB_DONE, + JOB_CANCELED, + JOB_TIMEOUT, + JOB_FAILED, + JOB_DEPENDENCY, + JOB_SKIPPED, + _JOB_RESULT_MAX, + _JOB_RESULT_INVALID = -1 +}; + +struct JobDependency { + /* Encodes that the 'subject' job needs the 'object' job in + * some way. This structure is used only while building a transaction. */ + Job *subject; + Job *object; + + LIST_FIELDS(JobDependency, subject); + LIST_FIELDS(JobDependency, object); + + bool matters; + bool conflicts; +}; + +struct Job { + Manager *manager; + Unit *unit; + + LIST_FIELDS(Job, transaction); + LIST_FIELDS(Job, run_queue); + LIST_FIELDS(Job, dbus_queue); + + LIST_HEAD(JobDependency, subject_list); + LIST_HEAD(JobDependency, object_list); + + /* Used for graph algs as a "I have been here" marker */ + Job* marker; + unsigned generation; + + uint32_t id; + + JobType type; + JobState state; + + Watch timer_watch; + + /* Note that this bus object is not ref counted here. */ + DBusConnection *bus; + char *bus_client; + + JobResult result; + + bool installed:1; + bool in_run_queue:1; + bool matters_to_anchor:1; + bool override:1; + bool in_dbus_queue:1; + bool sent_dbus_new_signal:1; + bool ignore_order:1; +}; + +Job* job_new(Manager *m, JobType type, Unit *unit); +void job_free(Job *job); +void job_dump(Job *j, FILE*f, const char *prefix); + +JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts); +void job_dependency_free(JobDependency *l); + +bool job_is_anchor(Job *j); + +int job_merge(Job *j, Job *other); + +int job_type_merge(JobType *a, JobType b); +bool job_type_is_mergeable(JobType a, JobType b); +bool job_type_is_superset(JobType a, JobType b); +bool job_type_is_conflicting(JobType a, JobType b); +bool job_type_is_redundant(JobType a, UnitActiveState b); + +bool job_is_runnable(Job *j); + +void job_add_to_run_queue(Job *j); +void job_add_to_dbus_queue(Job *j); + +int job_start_timer(Job *j); +void job_timer_event(Job *j, uint64_t n_elapsed, Watch *w); + +int job_run_and_invalidate(Job *j); +int job_finish_and_invalidate(Job *j, JobResult result); + +char *job_dbus_path(Job *j); + +const char* job_type_to_string(JobType t); +JobType job_type_from_string(const char *s); + +const char* job_state_to_string(JobState t); +JobState job_state_from_string(const char *s); + +const char* job_mode_to_string(JobMode t); +JobMode job_mode_from_string(const char *s); + +const char* job_result_to_string(JobResult t); +JobResult job_result_from_string(const char *s); + +#endif diff --git a/src/journal/.gitignore b/src/journal/.gitignore new file mode 100644 index 000000000..d6a79460c --- /dev/null +++ b/src/journal/.gitignore @@ -0,0 +1,2 @@ +/journald-gperf.c +/libsystemd-journal.pc diff --git a/src/journal/Makefile b/src/journal/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/journal/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/journal/cat.c b/src/journal/cat.c new file mode 100644 index 000000000..f0a666642 --- /dev/null +++ b/src/journal/cat.c @@ -0,0 +1,182 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "util.h" +#include "build.h" + +static char *arg_identifier = NULL; +static int arg_priority = LOG_INFO; +static bool arg_level_prefix = true; + +static int help(void) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Execute process with stdout/stderr connected to the journal.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -t --identifier=STRING Set syslog identifier\n" + " -p --priority=PRIORITY Set priority value (0..7)\n" + " --level-prefix=BOOL Control whether level prefix shall be parsed\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_LEVEL_PREFIX + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "identifier", required_argument, NULL, 't' }, + { "priority", required_argument, NULL, 'p' }, + { "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(DISTRIBUTION); + puts(SYSTEMD_FEATURES); + return 0; + + case 't': + free(arg_identifier); + if (isempty(optarg)) + arg_identifier = NULL; + else { + arg_identifier = strdup(optarg); + if (!arg_identifier) { + log_error("Out of memory."); + return -ENOMEM; + } + } + break; + + case 'p': + arg_priority = log_level_from_string(optarg); + if (arg_priority < 0) { + log_error("Failed to parse priority value."); + return arg_priority; + } + break; + + case ARG_LEVEL_PREFIX: { + int k; + + k = parse_boolean(optarg); + if (k < 0) { + log_error("Failed to parse level prefix value."); + return k; + } + arg_level_prefix = k; + break; + } + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r, fd = -1, saved_stderr = -1; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + fd = sd_journal_stream_fd(arg_identifier, arg_priority, arg_level_prefix); + if (fd < 0) { + log_error("Failed to create stream fd: %s", strerror(-fd)); + r = fd; + goto finish; + } + + saved_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3); + + if (dup3(fd, STDOUT_FILENO, 0) < 0 || + dup3(fd, STDERR_FILENO, 0) < 0) { + log_error("Failed to duplicate fd: %m"); + r = -errno; + goto finish; + } + + if (fd >= 3) + close_nointr_nofail(fd); + + fd = -1; + + if (argc <= optind) + execl("/bin/cat", "/bin/cat", NULL); + else + execvp(argv[optind], argv + optind); + + r = -errno; + + /* Let's try to restore a working stderr, so we can print the error message */ + if (saved_stderr >= 0) + dup3(saved_stderr, STDERR_FILENO, 0); + + log_error("Failed to execute process: %s", strerror(-r)); + +finish: + if (fd >= 0) + close_nointr_nofail(fd); + + if (saved_stderr >= 0) + close_nointr_nofail(saved_stderr); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/journal/compress.c b/src/journal/compress.c new file mode 100644 index 000000000..ff906581f --- /dev/null +++ b/src/journal/compress.c @@ -0,0 +1,208 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "compress.h" + +bool compress_blob(const void *src, uint64_t src_size, void *dst, uint64_t *dst_size) { + lzma_stream s = LZMA_STREAM_INIT; + lzma_ret ret; + bool b = false; + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_size); + + /* Returns false if we couldn't compress the data or the + * compressed result is longer than the original */ + + ret = lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_NONE); + if (ret != LZMA_OK) + return false; + + s.next_in = src; + s.avail_in = src_size; + s.next_out = dst; + s.avail_out = src_size; + + /* Does it fit? */ + if (lzma_code(&s, LZMA_FINISH) != LZMA_STREAM_END) + goto fail; + + /* Is it actually shorter? */ + if (s.avail_out == 0) + goto fail; + + *dst_size = src_size - s.avail_out; + b = true; + +fail: + lzma_end(&s); + + return b; +} + +bool uncompress_blob(const void *src, uint64_t src_size, + void **dst, uint64_t *dst_alloc_size, uint64_t* dst_size) { + + lzma_stream s = LZMA_STREAM_INIT; + lzma_ret ret; + bool b = false; + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_alloc_size); + assert(dst_size); + assert(*dst_alloc_size == 0 || *dst); + + ret = lzma_stream_decoder(&s, UINT64_MAX, 0); + if (ret != LZMA_OK) + return false; + + if (*dst_alloc_size <= src_size) { + void *p; + + p = realloc(*dst, src_size*2); + if (!p) + return false; + + *dst = p; + *dst_alloc_size = src_size*2; + } + + s.next_in = src; + s.avail_in = src_size; + + s.next_out = *dst; + s.avail_out = *dst_alloc_size; + + for (;;) { + void *p; + + ret = lzma_code(&s, LZMA_FINISH); + + if (ret == LZMA_STREAM_END) + break; + + if (ret != LZMA_OK) + goto fail; + + p = realloc(*dst, *dst_alloc_size*2); + if (!p) + goto fail; + + s.next_out = (uint8_t*) p + ((uint8_t*) s.next_out - (uint8_t*) *dst); + s.avail_out += *dst_alloc_size; + + *dst = p; + *dst_alloc_size *= 2; + } + + *dst_size = *dst_alloc_size - s.avail_out; + b = true; + +fail: + lzma_end(&s); + + return b; +} + +bool uncompress_startswith(const void *src, uint64_t src_size, + void **buffer, uint64_t *buffer_size, + const void *prefix, uint64_t prefix_len, + uint8_t extra) { + + lzma_stream s = LZMA_STREAM_INIT; + lzma_ret ret; + bool b = false; + + /* Checks whether the uncompressed blob starts with the + * mentioned prefix. The byte extra needs to follow the + * prefix */ + + assert(src); + assert(src_size > 0); + assert(buffer); + assert(buffer_size); + assert(prefix); + assert(*buffer_size == 0 || *buffer); + + ret = lzma_stream_decoder(&s, UINT64_MAX, 0); + if (ret != LZMA_OK) + return false; + + if (*buffer_size <= prefix_len) { + void *p; + + p = realloc(*buffer, prefix_len*2); + if (!p) + return false; + + *buffer = p; + *buffer_size = prefix_len*2; + } + + s.next_in = src; + s.avail_in = src_size; + + s.next_out = *buffer; + s.avail_out = *buffer_size; + + for (;;) { + void *p; + + ret = lzma_code(&s, LZMA_FINISH); + + if (ret != LZMA_STREAM_END && ret != LZMA_OK) + goto fail; + + if ((*buffer_size - s.avail_out > prefix_len) && + memcmp(*buffer, prefix, prefix_len) == 0 && + ((const uint8_t*) *buffer)[prefix_len] == extra) + break; + + if (ret == LZMA_STREAM_END) + goto fail; + + p = realloc(*buffer, *buffer_size*2); + if (!p) + goto fail; + + s.next_out = (uint8_t*) p + ((uint8_t*) s.next_out - (uint8_t*) *buffer); + s.avail_out += *buffer_size; + + *buffer = p; + *buffer_size *= 2; + } + + b = true; + +fail: + lzma_end(&s); + + return b; +} diff --git a/src/journal/compress.h b/src/journal/compress.h new file mode 100644 index 000000000..f187a6e00 --- /dev/null +++ b/src/journal/compress.h @@ -0,0 +1,38 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foocompresshfoo +#define foocompresshfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +bool compress_blob(const void *src, uint64_t src_size, void *dst, uint64_t *dst_size); + +bool uncompress_blob(const void *src, uint64_t src_size, + void **dst, uint64_t *dst_alloc_size, uint64_t* dst_size); + +bool uncompress_startswith(const void *src, uint64_t src_size, + void **buffer, uint64_t *buffer_size, + const void *prefix, uint64_t prefix_len, + uint8_t extra); + +#endif diff --git a/src/journal/coredump.c b/src/journal/coredump.c new file mode 100644 index 000000000..7dea66e6f --- /dev/null +++ b/src/journal/coredump.c @@ -0,0 +1,271 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include +#include + +#include "log.h" +#include "util.h" +#include "special.h" + +#define COREDUMP_MAX (24*1024*1024) + +enum { + ARG_PID = 1, + ARG_UID, + ARG_GID, + ARG_SIGNAL, + ARG_TIMESTAMP, + ARG_COMM, + _ARG_MAX +}; + +static int divert_coredump(void) { + FILE *f; + int r; + + log_info("Detected coredump of the journal daemon itself, diverting coredump to /var/lib/systemd/coredump/."); + + mkdir_p("/var/lib/systemd/coredump", 0755); + + f = fopen("/var/lib/systemd/coredump/core.systemd-journald", "we"); + if (!f) { + log_error("Failed to create coredump file: %m"); + return -errno; + } + + for (;;) { + uint8_t buffer[4096]; + size_t l, q; + + l = fread(buffer, 1, sizeof(buffer), stdin); + if (l <= 0) { + if (ferror(f)) { + log_error("Failed to read coredump: %m"); + r = -errno; + goto finish; + } + + r = 0; + break; + } + + q = fwrite(buffer, 1, l, f); + if (q != l) { + log_error("Failed to write coredump: %m"); + r = -errno; + goto finish; + } + } + + fflush(f); + + if (ferror(f)) { + log_error("Failed to write coredump: %m"); + r = -errno; + } + +finish: + fclose(f); + return r; +} + +int main(int argc, char* argv[]) { + int r, j = 0; + char *p = NULL; + ssize_t n; + pid_t pid; + uid_t uid; + gid_t gid; + struct iovec iovec[14]; + char *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL, + *core_timestamp = NULL, *core_comm = NULL, *core_exe = NULL, *core_unit = NULL, + *core_session = NULL, *core_message = NULL, *core_cmdline = NULL, *t; + + prctl(PR_SET_DUMPABLE, 0); + + if (argc != _ARG_MAX) { + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + log_open(); + + log_error("Invalid number of arguments passed from kernel."); + r = -EINVAL; + goto finish; + } + + r = parse_pid(argv[ARG_PID], &pid); + if (r < 0) { + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + log_open(); + + log_error("Failed to parse PID."); + goto finish; + } + + if (sd_pid_get_unit(pid, &t) >= 0) { + + if (streq(t, SPECIAL_JOURNALD_SERVICE)) { + /* Make sure we don't make use of the journal, + * if it's the journal which is crashing */ + log_set_target(LOG_TARGET_KMSG); + log_open(); + + r = divert_coredump(); + goto finish; + } + + core_unit = strappend("COREDUMP_UNIT=", t); + free(t); + + if (core_unit) + IOVEC_SET_STRING(iovec[j++], core_unit); + } + + /* OK, now we know it's not the journal, hence make use of + * it */ + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + log_open(); + + r = parse_uid(argv[ARG_UID], &uid); + if (r < 0) { + log_error("Failed to parse UID."); + goto finish; + } + + r = parse_gid(argv[ARG_GID], &gid); + if (r < 0) { + log_error("Failed to parse GID."); + goto finish; + } + + core_pid = strappend("COREDUMP_PID=", argv[ARG_PID]); + if (core_pid) + IOVEC_SET_STRING(iovec[j++], core_pid); + + core_uid = strappend("COREDUMP_UID=", argv[ARG_UID]); + if (core_uid) + IOVEC_SET_STRING(iovec[j++], core_uid); + + core_gid = strappend("COREDUMP_GID=", argv[ARG_GID]); + if (core_gid) + IOVEC_SET_STRING(iovec[j++], core_gid); + + core_signal = strappend("COREDUMP_SIGNAL=", argv[ARG_SIGNAL]); + if (core_signal) + IOVEC_SET_STRING(iovec[j++], core_signal); + + core_comm = strappend("COREDUMP_COMM=", argv[ARG_COMM]); + if (core_comm) + IOVEC_SET_STRING(iovec[j++], core_comm); + + if (sd_pid_get_session(pid, &t) >= 0) { + core_session = strappend("COREDUMP_SESSION=", t); + free(t); + + if (core_session) + IOVEC_SET_STRING(iovec[j++], core_session); + } + + if (get_process_exe(pid, &t) >= 0) { + core_exe = strappend("COREDUMP_EXE=", t); + free(t); + + if (core_exe) + IOVEC_SET_STRING(iovec[j++], core_exe); + } + + if (get_process_cmdline(pid, LINE_MAX, false, &t) >= 0) { + core_cmdline = strappend("COREDUMP_CMDLINE=", t); + free(t); + + if (core_cmdline) + IOVEC_SET_STRING(iovec[j++], core_cmdline); + } + + core_timestamp = join("COREDUMP_TIMESTAMP=", argv[ARG_TIMESTAMP], "000000", NULL); + if (core_timestamp) + IOVEC_SET_STRING(iovec[j++], core_timestamp); + + IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1"); + IOVEC_SET_STRING(iovec[j++], "PRIORITY=2"); + + core_message = join("MESSAGE=Process ", argv[ARG_PID], " (", argv[ARG_COMM], ") dumped core.", NULL); + if (core_message) + IOVEC_SET_STRING(iovec[j++], core_message); + + /* Now, let's drop privileges to become the user who owns the + * segfaulted process and allocate the coredump memory under + * his uid. This also ensures that the credentials journald + * will see are the ones of the coredumping user, thus making + * sure the user himself gets access to the core dump. */ + + if (setresgid(gid, gid, gid) < 0 || + setresuid(uid, uid, uid) < 0) { + log_error("Failed to drop privileges: %m"); + r = -errno; + goto finish; + } + + p = malloc(9 + COREDUMP_MAX); + if (!p) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + memcpy(p, "COREDUMP=", 9); + + n = loop_read(STDIN_FILENO, p + 9, COREDUMP_MAX, false); + if (n < 0) { + log_error("Failed to read core dump data: %s", strerror(-n)); + r = (int) n; + goto finish; + } + + iovec[j].iov_base = p; + iovec[j].iov_len = 9 + n; + j++; + + r = sd_journal_sendv(iovec, j); + if (r < 0) + log_error("Failed to send coredump: %s", strerror(-r)); + +finish: + free(p); + free(core_pid); + free(core_uid); + free(core_gid); + free(core_signal); + free(core_timestamp); + free(core_comm); + free(core_exe); + free(core_cmdline); + free(core_unit); + free(core_session); + free(core_message); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/journal/journal-def.h b/src/journal/journal-def.h new file mode 100644 index 000000000..9cb805108 --- /dev/null +++ b/src/journal/journal-def.h @@ -0,0 +1,165 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foojournaldefhfoo +#define foojournaldefhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "sparse-endian.h" + +#include + +#include "macro.h" + +typedef struct Header Header; +typedef struct ObjectHeader ObjectHeader; +typedef union Object Object; +typedef struct DataObject DataObject; +typedef struct FieldObject FieldObject; +typedef struct EntryObject EntryObject; +typedef struct HashTableObject HashTableObject; +typedef struct EntryArrayObject EntryArrayObject; +typedef struct EntryItem EntryItem; +typedef struct HashItem HashItem; + +/* Object types */ +enum { + OBJECT_UNUSED, + OBJECT_DATA, + OBJECT_FIELD, + OBJECT_ENTRY, + OBJECT_DATA_HASH_TABLE, + OBJECT_FIELD_HASH_TABLE, + OBJECT_ENTRY_ARRAY, + _OBJECT_TYPE_MAX +}; + +/* Object flags */ +enum { + OBJECT_COMPRESSED = 1 +}; + +_packed_ struct ObjectHeader { + uint8_t type; + uint8_t flags; + uint8_t reserved[6]; + le64_t size; + uint8_t payload[]; +}; + +_packed_ struct DataObject { + ObjectHeader object; + le64_t hash; + le64_t next_hash_offset; + le64_t next_field_offset; + le64_t entry_offset; /* the first array entry we store inline */ + le64_t entry_array_offset; + le64_t n_entries; + uint8_t payload[]; +}; + +_packed_ struct FieldObject { + ObjectHeader object; + le64_t hash; + le64_t next_hash_offset; + le64_t head_data_offset; + le64_t tail_data_offset; + uint8_t payload[]; +}; + +_packed_ struct EntryItem { + le64_t object_offset; + le64_t hash; +}; + +_packed_ struct EntryObject { + ObjectHeader object; + le64_t seqnum; + le64_t realtime; + le64_t monotonic; + sd_id128_t boot_id; + le64_t xor_hash; + EntryItem items[]; +}; + +_packed_ struct HashItem { + le64_t head_hash_offset; + le64_t tail_hash_offset; +}; + +_packed_ struct HashTableObject { + ObjectHeader object; + HashItem items[]; +}; + +_packed_ struct EntryArrayObject { + ObjectHeader object; + le64_t next_entry_array_offset; + le64_t items[]; +}; + +union Object { + ObjectHeader object; + DataObject data; + FieldObject field; + EntryObject entry; + HashTableObject hash_table; + EntryArrayObject entry_array; +}; + +enum { + STATE_OFFLINE, + STATE_ONLINE, + STATE_ARCHIVED +}; + +/* Header flags */ +enum { + HEADER_INCOMPATIBLE_COMPRESSED = 1 +}; + +_packed_ struct Header { + uint8_t signature[8]; /* "LPKSHHRH" */ + uint32_t compatible_flags; + uint32_t incompatible_flags; + uint8_t state; + uint8_t reserved[7]; + sd_id128_t file_id; + sd_id128_t machine_id; + sd_id128_t boot_id; + sd_id128_t seqnum_id; + le64_t arena_offset; + le64_t arena_size; + le64_t data_hash_table_offset; /* for looking up data objects */ + le64_t data_hash_table_size; + le64_t field_hash_table_offset; /* for looking up field objects */ + le64_t field_hash_table_size; + le64_t tail_object_offset; + le64_t n_objects; + le64_t n_entries; + le64_t seqnum; + le64_t first_seqnum; + le64_t entry_array_offset; + le64_t head_entry_realtime; + le64_t tail_entry_realtime; + le64_t tail_entry_monotonic; +}; + +#endif diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c new file mode 100644 index 000000000..be92c9044 --- /dev/null +++ b/src/journal/journal-file.c @@ -0,0 +1,2274 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "journal-def.h" +#include "journal-file.h" +#include "lookup3.h" +#include "compress.h" + +#define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*16ULL) +#define DEFAULT_FIELD_HASH_TABLE_SIZE (2047ULL*16ULL) + +#define DEFAULT_WINDOW_SIZE (8ULL*1024ULL*1024ULL) + +#define COMPRESSION_SIZE_THRESHOLD (512ULL) + +/* This is the minimum journal file size */ +#define JOURNAL_FILE_SIZE_MIN (64ULL*1024ULL) /* 64 KiB */ + +/* These are the lower and upper bounds if we deduce the max_use value + * from the file system size */ +#define DEFAULT_MAX_USE_LOWER (1ULL*1024ULL*1024ULL) /* 1 MiB */ +#define DEFAULT_MAX_USE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ + +/* This is the upper bound if we deduce max_size from max_use */ +#define DEFAULT_MAX_SIZE_UPPER (128ULL*1024ULL*1024ULL) /* 128 MiB */ + +/* This is the upper bound if we deduce the keep_free value from the + * file system size */ +#define DEFAULT_KEEP_FREE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */ + +/* This is the keep_free value when we can't determine the system + * size */ +#define DEFAULT_KEEP_FREE (1024ULL*1024ULL) /* 1 MB */ + +static const char signature[] = { 'L', 'P', 'K', 'S', 'H', 'H', 'R', 'H' }; + +#define ALIGN64(x) (((x) + 7ULL) & ~7ULL) + +void journal_file_close(JournalFile *f) { + int t; + + assert(f); + + if (f->header && f->writable) + f->header->state = STATE_OFFLINE; + + + for (t = 0; t < _WINDOW_MAX; t++) + if (f->windows[t].ptr) + munmap(f->windows[t].ptr, f->windows[t].size); + + if (f->fd >= 0) + close_nointr_nofail(f->fd); + + free(f->path); + +#ifdef HAVE_XZ + free(f->compress_buffer); +#endif + + free(f); +} + +static int journal_file_init_header(JournalFile *f, JournalFile *template) { + Header h; + ssize_t k; + int r; + + assert(f); + + zero(h); + memcpy(h.signature, signature, 8); + h.arena_offset = htole64(ALIGN64(sizeof(h))); + + r = sd_id128_randomize(&h.file_id); + if (r < 0) + return r; + + if (template) { + h.seqnum_id = template->header->seqnum_id; + h.seqnum = template->header->seqnum; + } else + h.seqnum_id = h.file_id; + + k = pwrite(f->fd, &h, sizeof(h), 0); + if (k < 0) + return -errno; + + if (k != sizeof(h)) + return -EIO; + + return 0; +} + +static int journal_file_refresh_header(JournalFile *f) { + int r; + sd_id128_t boot_id; + + assert(f); + + r = sd_id128_get_machine(&f->header->machine_id); + if (r < 0) + return r; + + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return r; + + if (sd_id128_equal(boot_id, f->header->boot_id)) + f->tail_entry_monotonic_valid = true; + + f->header->boot_id = boot_id; + + f->header->state = STATE_ONLINE; + + __sync_synchronize(); + + return 0; +} + +static int journal_file_verify_header(JournalFile *f) { + assert(f); + + if (memcmp(f->header, signature, 8)) + return -EBADMSG; + +#ifdef HAVE_XZ + if ((le64toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_COMPRESSED) != 0) + return -EPROTONOSUPPORT; +#else + if (f->header->incompatible_flags != 0) + return -EPROTONOSUPPORT; +#endif + + if ((uint64_t) f->last_stat.st_size < (le64toh(f->header->arena_offset) + le64toh(f->header->arena_size))) + return -ENODATA; + + if (f->writable) { + uint8_t state; + sd_id128_t machine_id; + int r; + + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return r; + + if (!sd_id128_equal(machine_id, f->header->machine_id)) + return -EHOSTDOWN; + + state = f->header->state; + + if (state == STATE_ONLINE) + log_debug("Journal file %s is already online. Assuming unclean closing. Ignoring.", f->path); + else if (state == STATE_ARCHIVED) + return -ESHUTDOWN; + else if (state != STATE_OFFLINE) + log_debug("Journal file %s has unknown state %u. Ignoring.", f->path, state); + } + + return 0; +} + +static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) { + uint64_t old_size, new_size; + + assert(f); + + /* We assume that this file is not sparse, and we know that + * for sure, since we always call posix_fallocate() + * ourselves */ + + old_size = + le64toh(f->header->arena_offset) + + le64toh(f->header->arena_size); + + new_size = PAGE_ALIGN(offset + size); + if (new_size < le64toh(f->header->arena_offset)) + new_size = le64toh(f->header->arena_offset); + + if (new_size <= old_size) + return 0; + + if (f->metrics.max_size > 0 && + new_size > f->metrics.max_size) + return -E2BIG; + + if (new_size > f->metrics.min_size && + f->metrics.keep_free > 0) { + struct statvfs svfs; + + if (fstatvfs(f->fd, &svfs) >= 0) { + uint64_t available; + + available = svfs.f_bfree * svfs.f_bsize; + + if (available >= f->metrics.keep_free) + available -= f->metrics.keep_free; + else + available = 0; + + if (new_size - old_size > available) + return -E2BIG; + } + } + + /* Note that the glibc fallocate() fallback is very + inefficient, hence we try to minimize the allocation area + as we can. */ + if (posix_fallocate(f->fd, old_size, new_size - old_size) < 0) + return -errno; + + if (fstat(f->fd, &f->last_stat) < 0) + return -errno; + + f->header->arena_size = htole64(new_size - le64toh(f->header->arena_offset)); + + return 0; +} + +static int journal_file_map( + JournalFile *f, + uint64_t offset, + uint64_t size, + void **_window, + uint64_t *_woffset, + uint64_t *_wsize, + void **ret) { + + uint64_t woffset, wsize; + void *window; + + assert(f); + assert(size > 0); + assert(ret); + + woffset = offset & ~((uint64_t) page_size() - 1ULL); + wsize = size + (offset - woffset); + wsize = PAGE_ALIGN(wsize); + + /* Avoid SIGBUS on invalid accesses */ + if (woffset + wsize > (uint64_t) PAGE_ALIGN(f->last_stat.st_size)) + return -EADDRNOTAVAIL; + + window = mmap(NULL, wsize, f->prot, MAP_SHARED, f->fd, woffset); + if (window == MAP_FAILED) + return -errno; + + if (_window) + *_window = window; + + if (_woffset) + *_woffset = woffset; + + if (_wsize) + *_wsize = wsize; + + *ret = (uint8_t*) window + (offset - woffset); + + return 0; +} + +static int journal_file_move_to(JournalFile *f, int wt, uint64_t offset, uint64_t size, void **ret) { + void *p = NULL; + uint64_t delta; + int r; + Window *w; + + assert(f); + assert(ret); + assert(wt >= 0); + assert(wt < _WINDOW_MAX); + + if (offset + size > (uint64_t) f->last_stat.st_size) { + /* Hmm, out of range? Let's refresh the fstat() data + * first, before we trust that check. */ + + if (fstat(f->fd, &f->last_stat) < 0 || + offset + size > (uint64_t) f->last_stat.st_size) + return -EADDRNOTAVAIL; + } + + w = f->windows + wt; + + if (_likely_(w->ptr && + w->offset <= offset && + w->offset + w->size >= offset + size)) { + + *ret = (uint8_t*) w->ptr + (offset - w->offset); + return 0; + } + + if (w->ptr) { + if (munmap(w->ptr, w->size) < 0) + return -errno; + + w->ptr = NULL; + w->size = w->offset = 0; + } + + if (size < DEFAULT_WINDOW_SIZE) { + /* If the default window size is larger then what was + * asked for extend the mapping a bit in the hope to + * minimize needed remappings later on. We add half + * the window space before and half behind the + * requested mapping */ + + delta = (DEFAULT_WINDOW_SIZE - size) / 2; + + if (delta > offset) + delta = offset; + + offset -= delta; + size = DEFAULT_WINDOW_SIZE; + } else + delta = 0; + + if (offset + size > (uint64_t) f->last_stat.st_size) + size = (uint64_t) f->last_stat.st_size - offset; + + if (size <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_map(f, + offset, size, + &w->ptr, &w->offset, &w->size, + &p); + + if (r < 0) + return r; + + *ret = (uint8_t*) p + delta; + return 0; +} + +static bool verify_hash(Object *o) { + uint64_t h1, h2; + + assert(o); + + if (o->object.type == OBJECT_DATA && !(o->object.flags & OBJECT_COMPRESSED)) { + h1 = le64toh(o->data.hash); + h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload)); + } else if (o->object.type == OBJECT_FIELD) { + h1 = le64toh(o->field.hash); + h2 = hash64(o->field.payload, le64toh(o->object.size) - offsetof(Object, field.payload)); + } else + return true; + + return h1 == h2; +} + +int journal_file_move_to_object(JournalFile *f, int type, uint64_t offset, Object **ret) { + int r; + void *t; + Object *o; + uint64_t s; + + assert(f); + assert(ret); + assert(type < _OBJECT_TYPE_MAX); + + r = journal_file_move_to(f, type >= 0 ? type : WINDOW_UNKNOWN, offset, sizeof(ObjectHeader), &t); + if (r < 0) + return r; + + o = (Object*) t; + s = le64toh(o->object.size); + + if (s < sizeof(ObjectHeader)) + return -EBADMSG; + + if (type >= 0 && o->object.type != type) + return -EBADMSG; + + if (s > sizeof(ObjectHeader)) { + r = journal_file_move_to(f, o->object.type, offset, s, &t); + if (r < 0) + return r; + + o = (Object*) t; + } + + if (!verify_hash(o)) + return -EBADMSG; + + *ret = o; + return 0; +} + +static uint64_t journal_file_seqnum(JournalFile *f, uint64_t *seqnum) { + uint64_t r; + + assert(f); + + r = le64toh(f->header->seqnum) + 1; + + if (seqnum) { + /* If an external seqnum counter was passed, we update + * both the local and the external one, and set it to + * the maximum of both */ + + if (*seqnum + 1 > r) + r = *seqnum + 1; + + *seqnum = r; + } + + f->header->seqnum = htole64(r); + + if (f->header->first_seqnum == 0) + f->header->first_seqnum = htole64(r); + + return r; +} + +static int journal_file_append_object(JournalFile *f, int type, uint64_t size, Object **ret, uint64_t *offset) { + int r; + uint64_t p; + Object *tail, *o; + void *t; + + assert(f); + assert(size >= sizeof(ObjectHeader)); + assert(offset); + assert(ret); + + p = le64toh(f->header->tail_object_offset); + if (p == 0) + p = le64toh(f->header->arena_offset); + else { + r = journal_file_move_to_object(f, -1, p, &tail); + if (r < 0) + return r; + + p += ALIGN64(le64toh(tail->object.size)); + } + + r = journal_file_allocate(f, p, size); + if (r < 0) + return r; + + r = journal_file_move_to(f, type, p, size, &t); + if (r < 0) + return r; + + o = (Object*) t; + + zero(o->object); + o->object.type = type; + o->object.size = htole64(size); + + f->header->tail_object_offset = htole64(p); + f->header->n_objects = htole64(le64toh(f->header->n_objects) + 1); + + *ret = o; + *offset = p; + + return 0; +} + +static int journal_file_setup_data_hash_table(JournalFile *f) { + uint64_t s, p; + Object *o; + int r; + + assert(f); + + s = DEFAULT_DATA_HASH_TABLE_SIZE; + r = journal_file_append_object(f, + OBJECT_DATA_HASH_TABLE, + offsetof(Object, hash_table.items) + s, + &o, &p); + if (r < 0) + return r; + + memset(o->hash_table.items, 0, s); + + f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items)); + f->header->data_hash_table_size = htole64(s); + + return 0; +} + +static int journal_file_setup_field_hash_table(JournalFile *f) { + uint64_t s, p; + Object *o; + int r; + + assert(f); + + s = DEFAULT_FIELD_HASH_TABLE_SIZE; + r = journal_file_append_object(f, + OBJECT_FIELD_HASH_TABLE, + offsetof(Object, hash_table.items) + s, + &o, &p); + if (r < 0) + return r; + + memset(o->hash_table.items, 0, s); + + f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items)); + f->header->field_hash_table_size = htole64(s); + + return 0; +} + +static int journal_file_map_data_hash_table(JournalFile *f) { + uint64_t s, p; + void *t; + int r; + + assert(f); + + p = le64toh(f->header->data_hash_table_offset); + s = le64toh(f->header->data_hash_table_size); + + r = journal_file_move_to(f, + WINDOW_DATA_HASH_TABLE, + p, s, + &t); + if (r < 0) + return r; + + f->data_hash_table = t; + return 0; +} + +static int journal_file_map_field_hash_table(JournalFile *f) { + uint64_t s, p; + void *t; + int r; + + assert(f); + + p = le64toh(f->header->field_hash_table_offset); + s = le64toh(f->header->field_hash_table_size); + + r = journal_file_move_to(f, + WINDOW_FIELD_HASH_TABLE, + p, s, + &t); + if (r < 0) + return r; + + f->field_hash_table = t; + return 0; +} + +static int journal_file_link_data(JournalFile *f, Object *o, uint64_t offset, uint64_t hash) { + uint64_t p, h; + int r; + + assert(f); + assert(o); + assert(offset > 0); + assert(o->object.type == OBJECT_DATA); + + /* This might alter the window we are looking at */ + + o->data.next_hash_offset = o->data.next_field_offset = 0; + o->data.entry_offset = o->data.entry_array_offset = 0; + o->data.n_entries = 0; + + h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)); + p = le64toh(f->data_hash_table[h].head_hash_offset); + if (p == 0) { + /* Only entry in the hash table is easy */ + f->data_hash_table[h].head_hash_offset = htole64(offset); + } else { + /* Move back to the previous data object, to patch in + * pointer */ + + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + o->data.next_hash_offset = htole64(offset); + } + + f->data_hash_table[h].tail_hash_offset = htole64(offset); + + return 0; +} + +int journal_file_find_data_object_with_hash( + JournalFile *f, + const void *data, uint64_t size, uint64_t hash, + Object **ret, uint64_t *offset) { + + uint64_t p, osize, h; + int r; + + assert(f); + assert(data || size == 0); + + osize = offsetof(Object, data.payload) + size; + + if (f->header->data_hash_table_size == 0) + return -EBADMSG; + + h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)); + p = le64toh(f->data_hash_table[h].head_hash_offset); + + while (p > 0) { + Object *o; + + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + if (le64toh(o->data.hash) != hash) + goto next; + + if (o->object.flags & OBJECT_COMPRESSED) { +#ifdef HAVE_XZ + uint64_t l, rsize; + + l = le64toh(o->object.size); + if (l <= offsetof(Object, data.payload)) + return -EBADMSG; + + l -= offsetof(Object, data.payload); + + if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize)) + return -EBADMSG; + + if (rsize == size && + memcmp(f->compress_buffer, data, size) == 0) { + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 1; + } +#else + return -EPROTONOSUPPORT; +#endif + + } else if (le64toh(o->object.size) == osize && + memcmp(o->data.payload, data, size) == 0) { + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 1; + } + + next: + p = le64toh(o->data.next_hash_offset); + } + + return 0; +} + +int journal_file_find_data_object( + JournalFile *f, + const void *data, uint64_t size, + Object **ret, uint64_t *offset) { + + uint64_t hash; + + assert(f); + assert(data || size == 0); + + hash = hash64(data, size); + + return journal_file_find_data_object_with_hash(f, + data, size, hash, + ret, offset); +} + +static int journal_file_append_data( + JournalFile *f, + const void *data, uint64_t size, + Object **ret, uint64_t *offset) { + + uint64_t hash, p; + uint64_t osize; + Object *o; + int r; + bool compressed = false; + + assert(f); + assert(data || size == 0); + + hash = hash64(data, size); + + r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p); + if (r < 0) + return r; + else if (r > 0) { + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 0; + } + + osize = offsetof(Object, data.payload) + size; + r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p); + if (r < 0) + return r; + + o->data.hash = htole64(hash); + +#ifdef HAVE_XZ + if (f->compress && + size >= COMPRESSION_SIZE_THRESHOLD) { + uint64_t rsize; + + compressed = compress_blob(data, size, o->data.payload, &rsize); + + if (compressed) { + o->object.size = htole64(offsetof(Object, data.payload) + rsize); + o->object.flags |= OBJECT_COMPRESSED; + + f->header->incompatible_flags = htole32(le32toh(f->header->incompatible_flags) | HEADER_INCOMPATIBLE_COMPRESSED); + + log_debug("Compressed data object %lu -> %lu", (unsigned long) size, (unsigned long) rsize); + } + } +#endif + + if (!compressed) + memcpy(o->data.payload, data, size); + + r = journal_file_link_data(f, o, p, hash); + if (r < 0) + return r; + + /* The linking might have altered the window, so let's + * refresh our pointer */ + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 0; +} + +uint64_t journal_file_entry_n_items(Object *o) { + assert(o); + assert(o->object.type == OBJECT_ENTRY); + + return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem); +} + +static uint64_t journal_file_entry_array_n_items(Object *o) { + assert(o); + assert(o->object.type == OBJECT_ENTRY_ARRAY); + + return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t); +} + +static int link_entry_into_array(JournalFile *f, + le64_t *first, + le64_t *idx, + uint64_t p) { + int r; + uint64_t n = 0, ap = 0, q, i, a, hidx; + Object *o; + + assert(f); + assert(first); + assert(idx); + assert(p > 0); + + a = le64toh(*first); + i = hidx = le64toh(*idx); + while (a > 0) { + + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); + if (r < 0) + return r; + + n = journal_file_entry_array_n_items(o); + if (i < n) { + o->entry_array.items[i] = htole64(p); + *idx = htole64(hidx + 1); + return 0; + } + + i -= n; + ap = a; + a = le64toh(o->entry_array.next_entry_array_offset); + } + + if (hidx > n) + n = (hidx+1) * 2; + else + n = n * 2; + + if (n < 4) + n = 4; + + r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY, + offsetof(Object, entry_array.items) + n * sizeof(uint64_t), + &o, &q); + if (r < 0) + return r; + + o->entry_array.items[i] = htole64(p); + + if (ap == 0) + *first = htole64(q); + else { + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o); + if (r < 0) + return r; + + o->entry_array.next_entry_array_offset = htole64(q); + } + + *idx = htole64(hidx + 1); + + return 0; +} + +static int link_entry_into_array_plus_one(JournalFile *f, + le64_t *extra, + le64_t *first, + le64_t *idx, + uint64_t p) { + + int r; + + assert(f); + assert(extra); + assert(first); + assert(idx); + assert(p > 0); + + if (*idx == 0) + *extra = htole64(p); + else { + le64_t i; + + i = htole64(le64toh(*idx) - 1); + r = link_entry_into_array(f, first, &i, p); + if (r < 0) + return r; + } + + *idx = htole64(le64toh(*idx) + 1); + return 0; +} + +static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) { + uint64_t p; + int r; + assert(f); + assert(o); + assert(offset > 0); + + p = le64toh(o->entry.items[i].object_offset); + if (p == 0) + return -EINVAL; + + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + return link_entry_into_array_plus_one(f, + &o->data.entry_offset, + &o->data.entry_array_offset, + &o->data.n_entries, + offset); +} + +static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) { + uint64_t n, i; + int r; + + assert(f); + assert(o); + assert(offset > 0); + assert(o->object.type == OBJECT_ENTRY); + + __sync_synchronize(); + + /* Link up the entry itself */ + r = link_entry_into_array(f, + &f->header->entry_array_offset, + &f->header->n_entries, + offset); + if (r < 0) + return r; + + /* log_debug("=> %s seqnr=%lu n_entries=%lu", f->path, (unsigned long) o->entry.seqnum, (unsigned long) f->header->n_entries); */ + + if (f->header->head_entry_realtime == 0) + f->header->head_entry_realtime = o->entry.realtime; + + f->header->tail_entry_realtime = o->entry.realtime; + f->header->tail_entry_monotonic = o->entry.monotonic; + + f->tail_entry_monotonic_valid = true; + + /* Link up the items */ + n = journal_file_entry_n_items(o); + for (i = 0; i < n; i++) { + r = journal_file_link_entry_item(f, o, offset, i); + if (r < 0) + return r; + } + + return 0; +} + +static int journal_file_append_entry_internal( + JournalFile *f, + const dual_timestamp *ts, + uint64_t xor_hash, + const EntryItem items[], unsigned n_items, + uint64_t *seqnum, + Object **ret, uint64_t *offset) { + uint64_t np; + uint64_t osize; + Object *o; + int r; + + assert(f); + assert(items || n_items == 0); + assert(ts); + + osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem)); + + r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np); + if (r < 0) + return r; + + o->entry.seqnum = htole64(journal_file_seqnum(f, seqnum)); + memcpy(o->entry.items, items, n_items * sizeof(EntryItem)); + o->entry.realtime = htole64(ts->realtime); + o->entry.monotonic = htole64(ts->monotonic); + o->entry.xor_hash = htole64(xor_hash); + o->entry.boot_id = f->header->boot_id; + + r = journal_file_link_entry(f, o, np); + if (r < 0) + return r; + + if (ret) + *ret = o; + + if (offset) + *offset = np; + + return 0; +} + +void journal_file_post_change(JournalFile *f) { + assert(f); + + /* inotify() does not receive IN_MODIFY events from file + * accesses done via mmap(). After each access we hence + * trigger IN_MODIFY by truncating the journal file to its + * current size which triggers IN_MODIFY. */ + + __sync_synchronize(); + + if (ftruncate(f->fd, f->last_stat.st_size) < 0) + log_error("Failed to to truncate file to its own size: %m"); +} + +int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqnum, Object **ret, uint64_t *offset) { + unsigned i; + EntryItem *items; + int r; + uint64_t xor_hash = 0; + struct dual_timestamp _ts; + + assert(f); + assert(iovec || n_iovec == 0); + + if (!f->writable) + return -EPERM; + + if (!ts) { + dual_timestamp_get(&_ts); + ts = &_ts; + } + + if (f->tail_entry_monotonic_valid && + ts->monotonic < le64toh(f->header->tail_entry_monotonic)) + return -EINVAL; + + items = alloca(sizeof(EntryItem) * n_iovec); + + for (i = 0; i < n_iovec; i++) { + uint64_t p; + Object *o; + + r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p); + if (r < 0) + return r; + + xor_hash ^= le64toh(o->data.hash); + items[i].object_offset = htole64(p); + items[i].hash = o->data.hash; + } + + r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset); + + journal_file_post_change(f); + + return r; +} + +static int generic_array_get(JournalFile *f, + uint64_t first, + uint64_t i, + Object **ret, uint64_t *offset) { + + Object *o; + uint64_t p = 0, a; + int r; + + assert(f); + + a = first; + while (a > 0) { + uint64_t n; + + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); + if (r < 0) + return r; + + n = journal_file_entry_array_n_items(o); + if (i < n) { + p = le64toh(o->entry_array.items[i]); + break; + } + + i -= n; + a = le64toh(o->entry_array.next_entry_array_offset); + } + + if (a <= 0 || p <= 0) + return 0; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 1; +} + +static int generic_array_get_plus_one(JournalFile *f, + uint64_t extra, + uint64_t first, + uint64_t i, + Object **ret, uint64_t *offset) { + + Object *o; + + assert(f); + + if (i == 0) { + int r; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o); + if (r < 0) + return r; + + if (ret) + *ret = o; + + if (offset) + *offset = extra; + + return 1; + } + + return generic_array_get(f, first, i-1, ret, offset); +} + +enum { + TEST_FOUND, + TEST_LEFT, + TEST_RIGHT +}; + +static int generic_array_bisect(JournalFile *f, + uint64_t first, + uint64_t n, + uint64_t needle, + int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle), + direction_t direction, + Object **ret, + uint64_t *offset, + uint64_t *idx) { + + uint64_t a, p, t = 0, i = 0, last_p = 0; + bool subtract_one = false; + Object *o, *array = NULL; + int r; + + assert(f); + assert(test_object); + + a = first; + while (a > 0) { + uint64_t left, right, k, lp; + + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array); + if (r < 0) + return r; + + k = journal_file_entry_array_n_items(array); + right = MIN(k, n); + if (right <= 0) + return 0; + + i = right - 1; + lp = p = le64toh(array->entry_array.items[i]); + if (p <= 0) + return -EBADMSG; + + r = test_object(f, p, needle); + if (r < 0) + return r; + + if (r == TEST_FOUND) + r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT; + + if (r == TEST_RIGHT) { + left = 0; + right -= 1; + for (;;) { + if (left == right) { + if (direction == DIRECTION_UP) + subtract_one = true; + + i = left; + goto found; + } + + assert(left < right); + + i = (left + right) / 2; + p = le64toh(array->entry_array.items[i]); + if (p <= 0) + return -EBADMSG; + + r = test_object(f, p, needle); + if (r < 0) + return r; + + if (r == TEST_FOUND) + r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT; + + if (r == TEST_RIGHT) + right = i; + else + left = i + 1; + } + } + + if (k > n) + return 0; + + last_p = lp; + + n -= k; + t += k; + a = le64toh(array->entry_array.next_entry_array_offset); + } + + return 0; + +found: + if (subtract_one && t == 0 && i == 0) + return 0; + + if (subtract_one && i == 0) + p = last_p; + else if (subtract_one) + p = le64toh(array->entry_array.items[i-1]); + else + p = le64toh(array->entry_array.items[i]); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + if (idx) + *idx = t + i - (subtract_one ? 1 : 0); + + return 1; +} + +static int generic_array_bisect_plus_one(JournalFile *f, + uint64_t extra, + uint64_t first, + uint64_t n, + uint64_t needle, + int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle), + direction_t direction, + Object **ret, + uint64_t *offset, + uint64_t *idx) { + + int r; + + assert(f); + assert(test_object); + + if (n <= 0) + return 0; + + /* This bisects the array in object 'first', but first checks + * an extra */ + r = test_object(f, extra, needle); + if (r < 0) + return r; + else if (r == TEST_FOUND) { + Object *o; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o); + if (r < 0) + return r; + + if (ret) + *ret = o; + + if (offset) + *offset = extra; + + if (idx) + *idx = 0; + + return 1; + } else if (r == TEST_RIGHT) + return 0; + + r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx); + + if (r > 0) + (*idx) ++; + + return r; +} + +static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) { + Object *o; + int r; + + assert(f); + assert(p > 0); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + + if (le64toh(o->entry.seqnum) == needle) + return TEST_FOUND; + else if (le64toh(o->entry.seqnum) < needle) + return TEST_LEFT; + else + return TEST_RIGHT; +} + +int journal_file_move_to_entry_by_seqnum( + JournalFile *f, + uint64_t seqnum, + direction_t direction, + Object **ret, + uint64_t *offset) { + + return generic_array_bisect(f, + le64toh(f->header->entry_array_offset), + le64toh(f->header->n_entries), + seqnum, + test_object_seqnum, + direction, + ret, offset, NULL); +} + +static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) { + Object *o; + int r; + + assert(f); + assert(p > 0); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + + if (le64toh(o->entry.realtime) == needle) + return TEST_FOUND; + else if (le64toh(o->entry.realtime) < needle) + return TEST_LEFT; + else + return TEST_RIGHT; +} + +int journal_file_move_to_entry_by_realtime( + JournalFile *f, + uint64_t realtime, + direction_t direction, + Object **ret, + uint64_t *offset) { + + return generic_array_bisect(f, + le64toh(f->header->entry_array_offset), + le64toh(f->header->n_entries), + realtime, + test_object_realtime, + direction, + ret, offset, NULL); +} + +static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) { + Object *o; + int r; + + assert(f); + assert(p > 0); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + + if (le64toh(o->entry.monotonic) == needle) + return TEST_FOUND; + else if (le64toh(o->entry.monotonic) < needle) + return TEST_LEFT; + else + return TEST_RIGHT; +} + +int journal_file_move_to_entry_by_monotonic( + JournalFile *f, + sd_id128_t boot_id, + uint64_t monotonic, + direction_t direction, + Object **ret, + uint64_t *offset) { + + char t[8+32+1] = "_BOOT_ID="; + Object *o; + int r; + + sd_id128_to_string(boot_id, t + 8); + + r = journal_file_find_data_object(f, t, strlen(t), &o, NULL); + if (r < 0) + return r; + else if (r == 0) + return -ENOENT; + + return generic_array_bisect_plus_one(f, + le64toh(o->data.entry_offset), + le64toh(o->data.entry_array_offset), + le64toh(o->data.n_entries), + monotonic, + test_object_monotonic, + direction, + ret, offset, NULL); +} + +static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) { + assert(f); + assert(p > 0); + + if (p == needle) + return TEST_FOUND; + else if (p < needle) + return TEST_LEFT; + else + return TEST_RIGHT; +} + +int journal_file_next_entry( + JournalFile *f, + Object *o, uint64_t p, + direction_t direction, + Object **ret, uint64_t *offset) { + + uint64_t i, n; + int r; + + assert(f); + assert(p > 0 || !o); + + n = le64toh(f->header->n_entries); + if (n <= 0) + return 0; + + if (!o) + i = direction == DIRECTION_DOWN ? 0 : n - 1; + else { + if (o->object.type != OBJECT_ENTRY) + return -EINVAL; + + r = generic_array_bisect(f, + le64toh(f->header->entry_array_offset), + le64toh(f->header->n_entries), + p, + test_object_offset, + DIRECTION_DOWN, + NULL, NULL, + &i); + if (r <= 0) + return r; + + if (direction == DIRECTION_DOWN) { + if (i >= n - 1) + return 0; + + i++; + } else { + if (i <= 0) + return 0; + + i--; + } + } + + /* And jump to it */ + return generic_array_get(f, + le64toh(f->header->entry_array_offset), + i, + ret, offset); +} + +int journal_file_skip_entry( + JournalFile *f, + Object *o, uint64_t p, + int64_t skip, + Object **ret, uint64_t *offset) { + + uint64_t i, n; + int r; + + assert(f); + assert(o); + assert(p > 0); + + if (o->object.type != OBJECT_ENTRY) + return -EINVAL; + + r = generic_array_bisect(f, + le64toh(f->header->entry_array_offset), + le64toh(f->header->n_entries), + p, + test_object_offset, + DIRECTION_DOWN, + NULL, NULL, + &i); + if (r <= 0) + return r; + + /* Calculate new index */ + if (skip < 0) { + if ((uint64_t) -skip >= i) + i = 0; + else + i = i - (uint64_t) -skip; + } else + i += (uint64_t) skip; + + n = le64toh(f->header->n_entries); + if (n <= 0) + return -EBADMSG; + + if (i >= n) + i = n-1; + + return generic_array_get(f, + le64toh(f->header->entry_array_offset), + i, + ret, offset); +} + +int journal_file_next_entry_for_data( + JournalFile *f, + Object *o, uint64_t p, + uint64_t data_offset, + direction_t direction, + Object **ret, uint64_t *offset) { + + uint64_t n, i; + int r; + Object *d; + + assert(f); + assert(p > 0 || !o); + + r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); + if (r < 0) + return r; + + n = le64toh(d->data.n_entries); + if (n <= 0) + return n; + + if (!o) + i = direction == DIRECTION_DOWN ? 0 : n - 1; + else { + if (o->object.type != OBJECT_ENTRY) + return -EINVAL; + + r = generic_array_bisect_plus_one(f, + le64toh(d->data.entry_offset), + le64toh(d->data.entry_array_offset), + le64toh(d->data.n_entries), + p, + test_object_offset, + DIRECTION_DOWN, + NULL, NULL, + &i); + + if (r <= 0) + return r; + + if (direction == DIRECTION_DOWN) { + if (i >= n - 1) + return 0; + + i++; + } else { + if (i <= 0) + return 0; + + i--; + } + + } + + return generic_array_get_plus_one(f, + le64toh(d->data.entry_offset), + le64toh(d->data.entry_array_offset), + i, + ret, offset); +} + +int journal_file_move_to_entry_by_seqnum_for_data( + JournalFile *f, + uint64_t data_offset, + uint64_t seqnum, + direction_t direction, + Object **ret, uint64_t *offset) { + + Object *d; + int r; + + r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); + if (r <= 0) + return r; + + return generic_array_bisect_plus_one(f, + le64toh(d->data.entry_offset), + le64toh(d->data.entry_array_offset), + le64toh(d->data.n_entries), + seqnum, + test_object_seqnum, + direction, + ret, offset, NULL); +} + +int journal_file_move_to_entry_by_realtime_for_data( + JournalFile *f, + uint64_t data_offset, + uint64_t realtime, + direction_t direction, + Object **ret, uint64_t *offset) { + + Object *d; + int r; + + r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d); + if (r <= 0) + return r; + + return generic_array_bisect_plus_one(f, + le64toh(d->data.entry_offset), + le64toh(d->data.entry_array_offset), + le64toh(d->data.n_entries), + realtime, + test_object_realtime, + direction, + ret, offset, NULL); +} + +void journal_file_dump(JournalFile *f) { + char a[33], b[33], c[33]; + Object *o; + int r; + uint64_t p; + + assert(f); + + printf("File Path: %s\n" + "File ID: %s\n" + "Machine ID: %s\n" + "Boot ID: %s\n" + "Arena size: %llu\n" + "Objects: %lu\n" + "Entries: %lu\n", + f->path, + sd_id128_to_string(f->header->file_id, a), + sd_id128_to_string(f->header->machine_id, b), + sd_id128_to_string(f->header->boot_id, c), + (unsigned long long) le64toh(f->header->arena_size), + (unsigned long) le64toh(f->header->n_objects), + (unsigned long) le64toh(f->header->n_entries)); + + p = le64toh(f->header->arena_offset); + while (p != 0) { + r = journal_file_move_to_object(f, -1, p, &o); + if (r < 0) + goto fail; + + switch (o->object.type) { + + case OBJECT_UNUSED: + printf("Type: OBJECT_UNUSED\n"); + break; + + case OBJECT_DATA: + printf("Type: OBJECT_DATA\n"); + break; + + case OBJECT_ENTRY: + printf("Type: OBJECT_ENTRY %llu %llu %llu\n", + (unsigned long long) le64toh(o->entry.seqnum), + (unsigned long long) le64toh(o->entry.monotonic), + (unsigned long long) le64toh(o->entry.realtime)); + break; + + case OBJECT_FIELD_HASH_TABLE: + printf("Type: OBJECT_FIELD_HASH_TABLE\n"); + break; + + case OBJECT_DATA_HASH_TABLE: + printf("Type: OBJECT_DATA_HASH_TABLE\n"); + break; + + case OBJECT_ENTRY_ARRAY: + printf("Type: OBJECT_ENTRY_ARRAY\n"); + break; + } + + if (o->object.flags & OBJECT_COMPRESSED) + printf("Flags: COMPRESSED\n"); + + if (p == le64toh(f->header->tail_object_offset)) + p = 0; + else + p = p + ALIGN64(le64toh(o->object.size)); + } + + return; +fail: + log_error("File corrupt"); +} + +int journal_file_open( + const char *fname, + int flags, + mode_t mode, + JournalFile *template, + JournalFile **ret) { + + JournalFile *f; + int r; + bool newly_created = false; + + assert(fname); + + if ((flags & O_ACCMODE) != O_RDONLY && + (flags & O_ACCMODE) != O_RDWR) + return -EINVAL; + + if (!endswith(fname, ".journal")) + return -EINVAL; + + f = new0(JournalFile, 1); + if (!f) + return -ENOMEM; + + f->fd = -1; + f->flags = flags; + f->mode = mode; + f->writable = (flags & O_ACCMODE) != O_RDONLY; + f->prot = prot_from_flags(flags); + + if (template) { + f->metrics = template->metrics; + f->compress = template->compress; + } + + f->path = strdup(fname); + if (!f->path) { + r = -ENOMEM; + goto fail; + } + + f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode); + if (f->fd < 0) { + r = -errno; + goto fail; + } + + if (fstat(f->fd, &f->last_stat) < 0) { + r = -errno; + goto fail; + } + + if (f->last_stat.st_size == 0 && f->writable) { + newly_created = true; + + r = journal_file_init_header(f, template); + if (r < 0) + goto fail; + + if (fstat(f->fd, &f->last_stat) < 0) { + r = -errno; + goto fail; + } + } + + if (f->last_stat.st_size < (off_t) sizeof(Header)) { + r = -EIO; + goto fail; + } + + f->header = mmap(NULL, PAGE_ALIGN(sizeof(Header)), prot_from_flags(flags), MAP_SHARED, f->fd, 0); + if (f->header == MAP_FAILED) { + f->header = NULL; + r = -errno; + goto fail; + } + + if (!newly_created) { + r = journal_file_verify_header(f); + if (r < 0) + goto fail; + } + + if (f->writable) { + r = journal_file_refresh_header(f); + if (r < 0) + goto fail; + } + + if (newly_created) { + + r = journal_file_setup_field_hash_table(f); + if (r < 0) + goto fail; + + r = journal_file_setup_data_hash_table(f); + if (r < 0) + goto fail; + } + + r = journal_file_map_field_hash_table(f); + if (r < 0) + goto fail; + + r = journal_file_map_data_hash_table(f); + if (r < 0) + goto fail; + + if (ret) + *ret = f; + + return 0; + +fail: + journal_file_close(f); + + return r; +} + +int journal_file_rotate(JournalFile **f) { + char *p; + size_t l; + JournalFile *old_file, *new_file = NULL; + int r; + + assert(f); + assert(*f); + + old_file = *f; + + if (!old_file->writable) + return -EINVAL; + + if (!endswith(old_file->path, ".journal")) + return -EINVAL; + + l = strlen(old_file->path); + + p = new(char, l + 1 + 32 + 1 + 16 + 1 + 16 + 1); + if (!p) + return -ENOMEM; + + memcpy(p, old_file->path, l - 8); + p[l-8] = '@'; + sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1); + snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1, + "-%016llx-%016llx.journal", + (unsigned long long) le64toh((*f)->header->seqnum), + (unsigned long long) le64toh((*f)->header->tail_entry_realtime)); + + r = rename(old_file->path, p); + free(p); + + if (r < 0) + return -errno; + + old_file->header->state = STATE_ARCHIVED; + + r = journal_file_open(old_file->path, old_file->flags, old_file->mode, old_file, &new_file); + journal_file_close(old_file); + + *f = new_file; + return r; +} + +int journal_file_open_reliably( + const char *fname, + int flags, + mode_t mode, + JournalFile *template, + JournalFile **ret) { + + int r; + size_t l; + char *p; + + r = journal_file_open(fname, flags, mode, template, ret); + if (r != -EBADMSG && /* corrupted */ + r != -ENODATA && /* truncated */ + r != -EHOSTDOWN && /* other machine */ + r != -EPROTONOSUPPORT) /* incompatible feature */ + return r; + + if ((flags & O_ACCMODE) == O_RDONLY) + return r; + + if (!(flags & O_CREAT)) + return r; + + /* The file is corrupted. Rotate it away and try it again (but only once) */ + + l = strlen(fname); + if (asprintf(&p, "%.*s@%016llx-%016llx.journal~", + (int) (l-8), fname, + (unsigned long long) now(CLOCK_REALTIME), + random_ull()) < 0) + return -ENOMEM; + + r = rename(fname, p); + free(p); + if (r < 0) + return -errno; + + log_warning("File %s corrupted, renaming and replacing.", fname); + + return journal_file_open(fname, flags, mode, template, ret); +} + +struct vacuum_info { + off_t usage; + char *filename; + + uint64_t realtime; + sd_id128_t seqnum_id; + uint64_t seqnum; + + bool have_seqnum; +}; + +static int vacuum_compare(const void *_a, const void *_b) { + const struct vacuum_info *a, *b; + + a = _a; + b = _b; + + if (a->have_seqnum && b->have_seqnum && + sd_id128_equal(a->seqnum_id, b->seqnum_id)) { + if (a->seqnum < b->seqnum) + return -1; + else if (a->seqnum > b->seqnum) + return 1; + else + return 0; + } + + if (a->realtime < b->realtime) + return -1; + else if (a->realtime > b->realtime) + return 1; + else if (a->have_seqnum && b->have_seqnum) + return memcmp(&a->seqnum_id, &b->seqnum_id, 16); + else + return strcmp(a->filename, b->filename); +} + +int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t min_free) { + DIR *d; + int r = 0; + struct vacuum_info *list = NULL; + unsigned n_list = 0, n_allocated = 0, i; + uint64_t sum = 0; + + assert(directory); + + if (max_use <= 0) + return 0; + + d = opendir(directory); + if (!d) + return -errno; + + for (;;) { + int k; + struct dirent buf, *de; + size_t q; + struct stat st; + char *p; + unsigned long long seqnum, realtime; + sd_id128_t seqnum_id; + bool have_seqnum; + + k = readdir_r(d, &buf, &de); + if (k != 0) { + r = -k; + goto finish; + } + + if (!de) + break; + + if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) + continue; + + if (!S_ISREG(st.st_mode)) + continue; + + q = strlen(de->d_name); + + if (endswith(de->d_name, ".journal")) { + + /* Vacuum archived files */ + + if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) + continue; + + if (de->d_name[q-8-16-1] != '-' || + de->d_name[q-8-16-1-16-1] != '-' || + de->d_name[q-8-16-1-16-1-32-1] != '@') + continue; + + p = strdup(de->d_name); + if (!p) { + r = -ENOMEM; + goto finish; + } + + de->d_name[q-8-16-1-16-1] = 0; + if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) { + free(p); + continue; + } + + if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) { + free(p); + continue; + } + + have_seqnum = true; + + } else if (endswith(de->d_name, ".journal~")) { + unsigned long long tmp; + + /* Vacuum corrupted files */ + + if (q < 1 + 16 + 1 + 16 + 8 + 1) + continue; + + if (de->d_name[q-1-8-16-1] != '-' || + de->d_name[q-1-8-16-1-16-1] != '@') + continue; + + p = strdup(de->d_name); + if (!p) { + r = -ENOMEM; + goto finish; + } + + if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) { + free(p); + continue; + } + + have_seqnum = false; + } else + continue; + + if (n_list >= n_allocated) { + struct vacuum_info *j; + + n_allocated = MAX(n_allocated * 2U, 8U); + j = realloc(list, n_allocated * sizeof(struct vacuum_info)); + if (!j) { + free(p); + r = -ENOMEM; + goto finish; + } + + list = j; + } + + list[n_list].filename = p; + list[n_list].usage = 512UL * (uint64_t) st.st_blocks; + list[n_list].seqnum = seqnum; + list[n_list].realtime = realtime; + list[n_list].seqnum_id = seqnum_id; + list[n_list].have_seqnum = have_seqnum; + + sum += list[n_list].usage; + + n_list ++; + } + + qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare); + + for(i = 0; i < n_list; i++) { + struct statvfs ss; + + if (fstatvfs(dirfd(d), &ss) < 0) { + r = -errno; + goto finish; + } + + if (sum <= max_use && + (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free) + break; + + if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) { + log_info("Deleted archived journal %s/%s.", directory, list[i].filename); + sum -= list[i].usage; + } else if (errno != ENOENT) + log_warning("Failed to delete %s/%s: %m", directory, list[i].filename); + } + +finish: + for (i = 0; i < n_list; i++) + free(list[i].filename); + + free(list); + + if (d) + closedir(d); + + return r; +} + +int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) { + uint64_t i, n; + uint64_t q, xor_hash = 0; + int r; + EntryItem *items; + dual_timestamp ts; + + assert(from); + assert(to); + assert(o); + assert(p); + + if (!to->writable) + return -EPERM; + + ts.monotonic = le64toh(o->entry.monotonic); + ts.realtime = le64toh(o->entry.realtime); + + if (to->tail_entry_monotonic_valid && + ts.monotonic < le64toh(to->header->tail_entry_monotonic)) + return -EINVAL; + + if (ts.realtime < le64toh(to->header->tail_entry_realtime)) + return -EINVAL; + + n = journal_file_entry_n_items(o); + items = alloca(sizeof(EntryItem) * n); + + for (i = 0; i < n; i++) { + uint64_t l, h; + le64_t le_hash; + size_t t; + void *data; + Object *u; + + q = le64toh(o->entry.items[i].object_offset); + le_hash = o->entry.items[i].hash; + + r = journal_file_move_to_object(from, OBJECT_DATA, q, &o); + if (r < 0) + return r; + + if (le_hash != o->data.hash) + return -EBADMSG; + + l = le64toh(o->object.size) - offsetof(Object, data.payload); + t = (size_t) l; + + /* We hit the limit on 32bit machines */ + if ((uint64_t) t != l) + return -E2BIG; + + if (o->object.flags & OBJECT_COMPRESSED) { +#ifdef HAVE_XZ + uint64_t rsize; + + if (!uncompress_blob(o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize)) + return -EBADMSG; + + data = from->compress_buffer; + l = rsize; +#else + return -EPROTONOSUPPORT; +#endif + } else + data = o->data.payload; + + r = journal_file_append_data(to, data, l, &u, &h); + if (r < 0) + return r; + + xor_hash ^= le64toh(u->data.hash); + items[i].object_offset = htole64(h); + items[i].hash = u->data.hash; + + r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o); + if (r < 0) + return r; + } + + return journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset); +} + +void journal_default_metrics(JournalMetrics *m, int fd) { + uint64_t fs_size = 0; + struct statvfs ss; + char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX]; + + assert(m); + assert(fd >= 0); + + if (fstatvfs(fd, &ss) >= 0) + fs_size = ss.f_frsize * ss.f_blocks; + + if (m->max_use == (uint64_t) -1) { + + if (fs_size > 0) { + m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */ + + if (m->max_use > DEFAULT_MAX_USE_UPPER) + m->max_use = DEFAULT_MAX_USE_UPPER; + + if (m->max_use < DEFAULT_MAX_USE_LOWER) + m->max_use = DEFAULT_MAX_USE_LOWER; + } else + m->max_use = DEFAULT_MAX_USE_LOWER; + } else { + m->max_use = PAGE_ALIGN(m->max_use); + + if (m->max_use < JOURNAL_FILE_SIZE_MIN*2) + m->max_use = JOURNAL_FILE_SIZE_MIN*2; + } + + if (m->max_size == (uint64_t) -1) { + m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */ + + if (m->max_size > DEFAULT_MAX_SIZE_UPPER) + m->max_size = DEFAULT_MAX_SIZE_UPPER; + } else + m->max_size = PAGE_ALIGN(m->max_size); + + if (m->max_size < JOURNAL_FILE_SIZE_MIN) + m->max_size = JOURNAL_FILE_SIZE_MIN; + + if (m->max_size*2 > m->max_use) + m->max_use = m->max_size*2; + + if (m->min_size == (uint64_t) -1) + m->min_size = JOURNAL_FILE_SIZE_MIN; + else { + m->min_size = PAGE_ALIGN(m->min_size); + + if (m->min_size < JOURNAL_FILE_SIZE_MIN) + m->min_size = JOURNAL_FILE_SIZE_MIN; + + if (m->min_size > m->max_size) + m->max_size = m->min_size; + } + + if (m->keep_free == (uint64_t) -1) { + + if (fs_size > 0) { + m->keep_free = PAGE_ALIGN(fs_size / 20); /* 5% of file system size */ + + if (m->keep_free > DEFAULT_KEEP_FREE_UPPER) + m->keep_free = DEFAULT_KEEP_FREE_UPPER; + + } else + m->keep_free = DEFAULT_KEEP_FREE; + } + + log_info("Fixed max_use=%s max_size=%s min_size=%s keep_free=%s", + format_bytes(a, sizeof(a), m->max_use), + format_bytes(b, sizeof(b), m->max_size), + format_bytes(c, sizeof(c), m->min_size), + format_bytes(d, sizeof(d), m->keep_free)); +} diff --git a/src/journal/journal-file.h b/src/journal/journal-file.h new file mode 100644 index 000000000..57d66cafc --- /dev/null +++ b/src/journal/journal-file.h @@ -0,0 +1,128 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foojournalfilehfoo +#define foojournalfilehfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include + +#include "sparse-endian.h" +#include "journal-def.h" +#include "util.h" + +typedef struct Window { + void *ptr; + uint64_t offset; + uint64_t size; +} Window; + +enum { + WINDOW_UNKNOWN = OBJECT_UNUSED, + WINDOW_DATA = OBJECT_DATA, + WINDOW_ENTRY = OBJECT_ENTRY, + WINDOW_DATA_HASH_TABLE = OBJECT_DATA_HASH_TABLE, + WINDOW_FIELD_HASH_TABLE = OBJECT_FIELD_HASH_TABLE, + WINDOW_ENTRY_ARRAY = OBJECT_ENTRY_ARRAY, + WINDOW_HEADER, + _WINDOW_MAX +}; + +typedef struct JournalMetrics { + uint64_t max_use; + uint64_t max_size; + uint64_t min_size; + uint64_t keep_free; +} JournalMetrics; + +typedef struct JournalFile { + int fd; + char *path; + struct stat last_stat; + mode_t mode; + int flags; + int prot; + bool writable; + bool tail_entry_monotonic_valid; + + Header *header; + HashItem *data_hash_table; + HashItem *field_hash_table; + + Window windows[_WINDOW_MAX]; + + uint64_t current_offset; + + JournalMetrics metrics; + + bool compress; + +#ifdef HAVE_XZ + void *compress_buffer; + uint64_t compress_buffer_size; +#endif +} JournalFile; + +typedef enum direction { + DIRECTION_UP, + DIRECTION_DOWN +} direction_t; + +int journal_file_open(const char *fname, int flags, mode_t mode, JournalFile *template, JournalFile **ret); +void journal_file_close(JournalFile *j); + +int journal_file_open_reliably(const char *fname, int flags, mode_t mode, JournalFile *template, JournalFile **ret); + +int journal_file_move_to_object(JournalFile *f, int type, uint64_t offset, Object **ret); + +uint64_t journal_file_entry_n_items(Object *o); + +int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqno, Object **ret, uint64_t *offset); + +int journal_file_find_data_object(JournalFile *f, const void *data, uint64_t size, Object **ret, uint64_t *offset); +int journal_file_find_data_object_with_hash(JournalFile *f, const void *data, uint64_t size, uint64_t hash, Object **ret, uint64_t *offset); + +int journal_file_next_entry(JournalFile *f, Object *o, uint64_t p, direction_t direction, Object **ret, uint64_t *offset); +int journal_file_skip_entry(JournalFile *f, Object *o, uint64_t p, int64_t skip, Object **ret, uint64_t *offset); + +int journal_file_next_entry_for_data(JournalFile *f, Object *o, uint64_t p, uint64_t data_offset, direction_t direction, Object **ret, uint64_t *offset); + +int journal_file_move_to_entry_by_seqnum(JournalFile *f, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset); +int journal_file_move_to_entry_by_realtime(JournalFile *f, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset); +int journal_file_move_to_entry_by_monotonic(JournalFile *f, sd_id128_t boot_id, uint64_t monotonic, direction_t direction, Object **ret, uint64_t *offset); + +int journal_file_move_to_entry_by_seqnum_for_data(JournalFile *f, uint64_t data_offset, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset); +int journal_file_move_to_entry_by_realtime_for_data(JournalFile *f, uint64_t data_offset, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset); + +int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset); + +void journal_file_dump(JournalFile *f); + +int journal_file_rotate(JournalFile **f); + +int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t min_free); + +void journal_file_post_change(JournalFile *f); + +void journal_default_metrics(JournalMetrics *m, int fd); + +#endif diff --git a/src/journal/journal-internal.h b/src/journal/journal-internal.h new file mode 100644 index 000000000..17f1d317c --- /dev/null +++ b/src/journal/journal-internal.h @@ -0,0 +1,84 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foojournalinternalhfoo +#define foojournalinternalhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include + +#include "list.h" + +typedef struct Match Match; + +struct Match { + char *data; + size_t size; + le64_t le_hash; + + LIST_FIELDS(Match, matches); +}; + +typedef enum location_type { + LOCATION_HEAD, + LOCATION_TAIL, + LOCATION_DISCRETE +} location_type_t; + +typedef struct Location { + location_type_t type; + + uint64_t seqnum; + sd_id128_t seqnum_id; + bool seqnum_set; + + uint64_t realtime; + bool realtime_set; + + uint64_t monotonic; + sd_id128_t boot_id; + bool monotonic_set; + + uint64_t xor_hash; + bool xor_hash_set; +} Location; + +struct sd_journal { + int flags; + + Hashmap *files; + + Location current_location; + JournalFile *current_file; + uint64_t current_field; + + int inotify_fd; + Hashmap *inotify_wd_dirs; + Hashmap *inotify_wd_roots; + + LIST_HEAD(Match, matches); + unsigned n_matches; +}; + +#endif diff --git a/src/journal/journal-rate-limit.c b/src/journal/journal-rate-limit.c new file mode 100644 index 000000000..243ff2a37 --- /dev/null +++ b/src/journal/journal-rate-limit.c @@ -0,0 +1,275 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "journal-rate-limit.h" +#include "list.h" +#include "util.h" +#include "hashmap.h" + +#define POOLS_MAX 5 +#define BUCKETS_MAX 127 +#define GROUPS_MAX 2047 + +static const int priority_map[] = { + [LOG_EMERG] = 0, + [LOG_ALERT] = 0, + [LOG_CRIT] = 0, + [LOG_ERR] = 1, + [LOG_WARNING] = 2, + [LOG_NOTICE] = 3, + [LOG_INFO] = 3, + [LOG_DEBUG] = 4 +}; + +typedef struct JournalRateLimitPool JournalRateLimitPool; +typedef struct JournalRateLimitGroup JournalRateLimitGroup; + +struct JournalRateLimitPool { + usec_t begin; + unsigned num; + unsigned suppressed; +}; + +struct JournalRateLimitGroup { + JournalRateLimit *parent; + + char *id; + JournalRateLimitPool pools[POOLS_MAX]; + unsigned hash; + + LIST_FIELDS(JournalRateLimitGroup, bucket); + LIST_FIELDS(JournalRateLimitGroup, lru); +}; + +struct JournalRateLimit { + usec_t interval; + unsigned burst; + + JournalRateLimitGroup* buckets[BUCKETS_MAX]; + JournalRateLimitGroup *lru, *lru_tail; + + unsigned n_groups; +}; + +JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst) { + JournalRateLimit *r; + + assert(interval > 0 || burst == 0); + + r = new0(JournalRateLimit, 1); + if (!r) + return NULL; + + r->interval = interval; + r->burst = burst; + + return r; +} + +static void journal_rate_limit_group_free(JournalRateLimitGroup *g) { + assert(g); + + if (g->parent) { + assert(g->parent->n_groups > 0); + + if (g->parent->lru_tail == g) + g->parent->lru_tail = g->lru_prev; + + LIST_REMOVE(JournalRateLimitGroup, lru, g->parent->lru, g); + LIST_REMOVE(JournalRateLimitGroup, bucket, g->parent->buckets[g->hash % BUCKETS_MAX], g); + + g->parent->n_groups --; + } + + free(g->id); + free(g); +} + +void journal_rate_limit_free(JournalRateLimit *r) { + assert(r); + + while (r->lru) + journal_rate_limit_group_free(r->lru); + + free(r); +} + +static bool journal_rate_limit_group_expired(JournalRateLimitGroup *g, usec_t ts) { + unsigned i; + + assert(g); + + for (i = 0; i < POOLS_MAX; i++) + if (g->pools[i].begin + g->parent->interval >= ts) + return false; + + return true; +} + +static void journal_rate_limit_vacuum(JournalRateLimit *r, usec_t ts) { + assert(r); + + /* Makes room for at least one new item, but drop all + * expored items too. */ + + while (r->n_groups >= GROUPS_MAX || + (r->lru_tail && journal_rate_limit_group_expired(r->lru_tail, ts))) + journal_rate_limit_group_free(r->lru_tail); +} + +static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t ts) { + JournalRateLimitGroup *g; + + assert(r); + assert(id); + + g = new0(JournalRateLimitGroup, 1); + if (!g) + return NULL; + + g->id = strdup(id); + if (!g->id) + goto fail; + + g->hash = string_hash_func(g->id); + + journal_rate_limit_vacuum(r, ts); + + LIST_PREPEND(JournalRateLimitGroup, bucket, r->buckets[g->hash % BUCKETS_MAX], g); + LIST_PREPEND(JournalRateLimitGroup, lru, r->lru, g); + if (!g->lru_next) + r->lru_tail = g; + r->n_groups ++; + + g->parent = r; + return g; + +fail: + journal_rate_limit_group_free(g); + return NULL; +} + +static uint64_t u64log2(uint64_t n) { + unsigned r; + + if (n <= 1) + return 0; + + r = 0; + for (;;) { + n = n >> 1; + if (!n) + return r; + r++; + } +} + +static unsigned burst_modulate(unsigned burst, uint64_t available) { + unsigned k; + + /* Modulates the burst rate a bit with the amount of available + * disk space */ + + k = u64log2(available); + + /* 1MB */ + if (k <= 20) + return burst; + + burst = (burst * (k-20)) / 4; + + /* + * Example: + * + * <= 1MB = rate * 1 + * 16MB = rate * 2 + * 256MB = rate * 3 + * 4GB = rate * 4 + * 64GB = rate * 5 + * 1TB = rate * 6 + */ + + return burst; +} + +int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available) { + unsigned h; + JournalRateLimitGroup *g; + JournalRateLimitPool *p; + unsigned burst; + usec_t ts; + + assert(id); + + if (!r) + return 1; + + if (r->interval == 0 || r->burst == 0) + return 1; + + burst = burst_modulate(r->burst, available); + + ts = now(CLOCK_MONOTONIC); + + h = string_hash_func(id); + g = r->buckets[h % BUCKETS_MAX]; + + LIST_FOREACH(bucket, g, g) + if (streq(g->id, id)) + break; + + if (!g) { + g = journal_rate_limit_group_new(r, id, ts); + if (!g) + return -ENOMEM; + } + + p = &g->pools[priority_map[priority]]; + + if (p->begin <= 0) { + p->suppressed = 0; + p->num = 1; + p->begin = ts; + return 1; + } + + if (p->begin + r->interval < ts) { + unsigned s; + + s = p->suppressed; + p->suppressed = 0; + p->num = 1; + p->begin = ts; + + return 1 + s; + } + + if (p->num <= burst) { + p->num++; + return 1; + } + + p->suppressed++; + return 0; +} diff --git a/src/journal/journal-rate-limit.h b/src/journal/journal-rate-limit.h new file mode 100644 index 000000000..2bbdd5f9f --- /dev/null +++ b/src/journal/journal-rate-limit.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foojournalratelimithfoo +#define foojournalratelimithfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "macro.h" +#include "util.h" + +typedef struct JournalRateLimit JournalRateLimit; + +JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst); +void journal_rate_limit_free(JournalRateLimit *r); +int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available); + +#endif diff --git a/src/journal/journal-send.c b/src/journal/journal-send.c new file mode 100644 index 000000000..32e94af91 --- /dev/null +++ b/src/journal/journal-send.c @@ -0,0 +1,516 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include + +#define SD_JOURNAL_SUPPRESS_LOCATION + +#include "sd-journal.h" +#include "util.h" +#include "socket-util.h" + +#define SNDBUF_SIZE (8*1024*1024) + +/* We open a single fd, and we'll share it with the current process, + * all its threads, and all its subprocesses. This means we need to + * initialize it atomically, and need to operate on it atomically + * never assuming we are the only user */ + +static int journal_fd(void) { + int fd; + static int fd_plus_one = 0; + +retry: + if (fd_plus_one > 0) + return fd_plus_one - 1; + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (fd < 0) + return -errno; + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + if (!__sync_bool_compare_and_swap(&fd_plus_one, 0, fd+1)) { + close_nointr_nofail(fd); + goto retry; + } + + return fd; +} + +_public_ int sd_journal_print(int priority, const char *format, ...) { + int r; + va_list ap; + + va_start(ap, format); + r = sd_journal_printv(priority, format, ap); + va_end(ap); + + return r; +} + +_public_ int sd_journal_printv(int priority, const char *format, va_list ap) { + char buffer[8 + LINE_MAX], p[11]; + struct iovec iov[2]; + + if (priority < 0 || priority > 7) + return -EINVAL; + + if (!format) + return -EINVAL; + + snprintf(p, sizeof(p), "PRIORITY=%i", priority & LOG_PRIMASK); + char_array_0(p); + + memcpy(buffer, "MESSAGE=", 8); + vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap); + char_array_0(buffer); + + zero(iov); + IOVEC_SET_STRING(iov[0], buffer); + IOVEC_SET_STRING(iov[1], p); + + return sd_journal_sendv(iov, 2); +} + +static int fill_iovec_sprintf(const char *format, va_list ap, int extra, struct iovec **_iov) { + int r, n = 0, i, j; + struct iovec *iov = NULL; + int saved_errno; + + assert(_iov); + saved_errno = errno; + + if (extra > 0) { + n = MAX(extra * 2, extra + 4); + iov = malloc0(n * sizeof(struct iovec)); + if (!iov) { + r = -ENOMEM; + goto fail; + } + + i = extra; + } else + i = 0; + + while (format) { + struct iovec *c; + char *buffer; + + if (i >= n) { + n = MAX(i*2, 4); + c = realloc(iov, n * sizeof(struct iovec)); + if (!c) { + r = -ENOMEM; + goto fail; + } + + iov = c; + } + + if (vasprintf(&buffer, format, ap) < 0) { + r = -ENOMEM; + goto fail; + } + + IOVEC_SET_STRING(iov[i++], buffer); + + format = va_arg(ap, char *); + } + + *_iov = iov; + + errno = saved_errno; + return i; + +fail: + for (j = 0; j < i; j++) + free(iov[j].iov_base); + + free(iov); + + errno = saved_errno; + return r; +} + +_public_ int sd_journal_send(const char *format, ...) { + int r, i, j; + va_list ap; + struct iovec *iov = NULL; + + va_start(ap, format); + i = fill_iovec_sprintf(format, ap, 0, &iov); + va_end(ap); + + if (_unlikely_(i < 0)) { + r = i; + goto finish; + } + + r = sd_journal_sendv(iov, i); + +finish: + for (j = 0; j < i; j++) + free(iov[j].iov_base); + + free(iov); + + return r; +} + +_public_ int sd_journal_sendv(const struct iovec *iov, int n) { + int fd, buffer_fd; + struct iovec *w; + uint64_t *l; + int r, i, j = 0; + struct msghdr mh; + struct sockaddr_un sa; + ssize_t k; + int saved_errno; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control; + struct cmsghdr *cmsg; + /* We use /dev/shm instead of /tmp here, since we want this to + * be a tmpfs, and one that is available from early boot on + * and where unprivileged users can create files. */ + char path[] = "/dev/shm/journal.XXXXXX"; + + if (_unlikely_(!iov)) + return -EINVAL; + + if (_unlikely_(n <= 0)) + return -EINVAL; + + saved_errno = errno; + + w = alloca(sizeof(struct iovec) * n * 5); + l = alloca(sizeof(uint64_t) * n); + + for (i = 0; i < n; i++) { + char *c, *nl; + + if (_unlikely_(!iov[i].iov_base || iov[i].iov_len <= 1)) { + r = -EINVAL; + goto finish; + } + + c = memchr(iov[i].iov_base, '=', iov[i].iov_len); + if (_unlikely_(!c || c == iov[i].iov_base)) { + r = -EINVAL; + goto finish; + } + + nl = memchr(iov[i].iov_base, '\n', iov[i].iov_len); + if (nl) { + if (_unlikely_(nl < c)) { + r = -EINVAL; + goto finish; + } + + /* Already includes a newline? Bummer, then + * let's write the variable name, then a + * newline, then the size (64bit LE), followed + * by the data and a final newline */ + + w[j].iov_base = iov[i].iov_base; + w[j].iov_len = c - (char*) iov[i].iov_base; + j++; + + IOVEC_SET_STRING(w[j++], "\n"); + + l[i] = htole64(iov[i].iov_len - (c - (char*) iov[i].iov_base) - 1); + w[j].iov_base = &l[i]; + w[j].iov_len = sizeof(uint64_t); + j++; + + w[j].iov_base = c + 1; + w[j].iov_len = iov[i].iov_len - (c - (char*) iov[i].iov_base) - 1; + j++; + + } else + /* Nothing special? Then just add the line and + * append a newline */ + w[j++] = iov[i]; + + IOVEC_SET_STRING(w[j++], "\n"); + } + + fd = journal_fd(); + if (_unlikely_(fd < 0)) { + r = fd; + goto finish; + } + + zero(sa); + sa.sun_family = AF_UNIX; + strncpy(sa.sun_path, "/run/systemd/journal/socket", sizeof(sa.sun_path)); + + zero(mh); + mh.msg_name = &sa; + mh.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(sa.sun_path); + mh.msg_iov = w; + mh.msg_iovlen = j; + + k = sendmsg(fd, &mh, MSG_NOSIGNAL); + if (k >= 0) { + r = 0; + goto finish; + } + + if (errno != EMSGSIZE && errno != ENOBUFS) { + r = -errno; + goto finish; + } + + /* Message doesn't fit... Let's dump the data in a temporary + * file and just pass a file descriptor of it to the other + * side */ + + buffer_fd = mkostemp(path, O_CLOEXEC|O_RDWR); + if (buffer_fd < 0) { + r = -errno; + goto finish; + } + + if (unlink(path) < 0) { + close_nointr_nofail(buffer_fd); + r = -errno; + goto finish; + } + + n = writev(buffer_fd, w, j); + if (n < 0) { + close_nointr_nofail(buffer_fd); + r = -errno; + goto finish; + } + + mh.msg_iov = NULL; + mh.msg_iovlen = 0; + + zero(control); + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + + 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), &buffer_fd, sizeof(int)); + + mh.msg_controllen = cmsg->cmsg_len; + + k = sendmsg(fd, &mh, MSG_NOSIGNAL); + close_nointr_nofail(buffer_fd); + + if (k < 0) { + r = -errno; + goto finish; + } + + r = 0; + +finish: + errno = saved_errno; + + return r; +} + +_public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) { + union sockaddr_union sa; + int fd; + char *header; + size_t l; + ssize_t r; + + if (priority < 0 || priority > 7) + return -EINVAL; + + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (fd < 0) + return -errno; + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, "/run/systemd/journal/stdout", sizeof(sa.un.sun_path)); + + r = connect(fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path)); + if (r < 0) { + close_nointr_nofail(fd); + return -errno; + } + + if (shutdown(fd, SHUT_RD) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + if (!identifier) + identifier = ""; + + l = strlen(identifier); + header = alloca(l + 1 + 2 + 2 + 2 + 2 + 2); + + memcpy(header, identifier, l); + header[l++] = '\n'; + header[l++] = '0' + priority; + header[l++] = '\n'; + header[l++] = '0' + !!level_prefix; + header[l++] = '\n'; + header[l++] = '0'; + header[l++] = '\n'; + header[l++] = '0'; + header[l++] = '\n'; + header[l++] = '0'; + header[l++] = '\n'; + + r = loop_write(fd, header, l, false); + if (r < 0) { + close_nointr_nofail(fd); + return (int) r; + } + + if ((size_t) r != l) { + close_nointr_nofail(fd); + return -errno; + } + + return fd; +} + +_public_ int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) { + int r; + va_list ap; + + va_start(ap, format); + r = sd_journal_printv_with_location(priority, file, line, func, format, ap); + va_end(ap); + + return r; +} + +_public_ int sd_journal_printv_with_location(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) { + char buffer[8 + LINE_MAX], p[11]; + struct iovec iov[5]; + char *f; + size_t fl; + + if (priority < 0 || priority > 7) + return -EINVAL; + + if (_unlikely_(!format)) + return -EINVAL; + + snprintf(p, sizeof(p), "PRIORITY=%i", priority & LOG_PRIMASK); + char_array_0(p); + + memcpy(buffer, "MESSAGE=", 8); + vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap); + char_array_0(buffer); + + /* func is initialized from __func__ which is not a macro, but + * a static const char[], hence cannot easily be prefixed with + * CODE_FUNC=, hence let's do it manually here. */ + fl = strlen(func); + f = alloca(fl + 10); + memcpy(f, "CODE_FUNC=", 10); + memcpy(f + 10, func, fl + 1); + + zero(iov); + IOVEC_SET_STRING(iov[0], buffer); + IOVEC_SET_STRING(iov[1], p); + IOVEC_SET_STRING(iov[2], file); + IOVEC_SET_STRING(iov[3], line); + IOVEC_SET_STRING(iov[4], f); + + return sd_journal_sendv(iov, ELEMENTSOF(iov)); +} + +_public_ int sd_journal_send_with_location(const char *file, const char *line, const char *func, const char *format, ...) { + int r, i, j; + va_list ap; + struct iovec *iov = NULL; + char *f; + size_t fl; + + va_start(ap, format); + i = fill_iovec_sprintf(format, ap, 3, &iov); + va_end(ap); + + if (_unlikely_(i < 0)) { + r = i; + goto finish; + } + + fl = strlen(func); + f = alloca(fl + 10); + memcpy(f, "CODE_FUNC=", 10); + memcpy(f + 10, func, fl + 1); + + IOVEC_SET_STRING(iov[0], file); + IOVEC_SET_STRING(iov[1], line); + IOVEC_SET_STRING(iov[2], f); + + r = sd_journal_sendv(iov, i); + +finish: + for (j = 3; j < i; j++) + free(iov[j].iov_base); + + free(iov); + + return r; +} + +_public_ int sd_journal_sendv_with_location(const char *file, const char *line, const char *func, const struct iovec *iov, int n) { + struct iovec *niov; + char *f; + size_t fl; + + if (_unlikely_(!iov)) + return -EINVAL; + + if (_unlikely_(n <= 0)) + return -EINVAL; + + niov = alloca(sizeof(struct iovec) * (n + 3)); + memcpy(niov, iov, sizeof(struct iovec) * n); + + fl = strlen(func); + f = alloca(fl + 10); + memcpy(f, "CODE_FUNC=", 10); + memcpy(f + 10, func, fl + 1); + + IOVEC_SET_STRING(niov[n++], file); + IOVEC_SET_STRING(niov[n++], line); + IOVEC_SET_STRING(niov[n++], f); + + return sd_journal_sendv(niov, n); +} diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c new file mode 100644 index 000000000..01dceca23 --- /dev/null +++ b/src/journal/journalctl.c @@ -0,0 +1,327 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "log.h" +#include "util.h" +#include "build.h" +#include "pager.h" +#include "logs-show.h" + +static OutputMode arg_output = OUTPUT_SHORT; +static bool arg_follow = false; +static bool arg_show_all = false; +static bool arg_no_pager = false; +static int arg_lines = -1; +static bool arg_no_tail = false; +static bool arg_new_id128 = false; +static bool arg_quiet = false; +static bool arg_local = false; + +static int help(void) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Send control commands to or query the journal.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " -a --all Show all fields, including long and unprintable\n" + " -f --follow Follow journal\n" + " -n --lines=INTEGER Journal entries to show\n" + " --no-tail Show all lines, even in follow mode\n" + " -o --output=STRING Change journal output mode (short, short-monotonic,\n" + " verbose, export, json, cat)\n" + " -q --quiet Don't show privilege warning\n" + " --new-id128 Generate a new 128 Bit id\n" + " -l --local Only local entries\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_TAIL, + ARG_NEW_ID128 + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version" , no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "follow", no_argument, NULL, 'f' }, + { "output", required_argument, NULL, 'o' }, + { "all", no_argument, NULL, 'a' }, + { "lines", required_argument, NULL, 'n' }, + { "no-tail", no_argument, NULL, ARG_NO_TAIL }, + { "new-id128", no_argument, NULL, ARG_NEW_ID128 }, + { "quiet", no_argument, NULL, 'q' }, + { "local", no_argument, NULL, 'l' }, + { NULL, 0, NULL, 0 } + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hfo:an:ql", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(DISTRIBUTION); + puts(SYSTEMD_FEATURES); + return 0; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 'f': + arg_follow = true; + break; + + case 'o': + arg_output = output_mode_from_string(optarg); + if (arg_output < 0) { + log_error("Unknown output '%s'.", optarg); + return -EINVAL; + } + + break; + + case 'a': + arg_show_all = true; + break; + + case 'n': + r = safe_atoi(optarg, &arg_lines); + if (r < 0 || arg_lines < 0) { + log_error("Failed to parse lines '%s'", optarg); + return -EINVAL; + } + break; + + case ARG_NO_TAIL: + arg_no_tail = true; + break; + + case ARG_NEW_ID128: + arg_new_id128 = true; + break; + + case 'q': + arg_quiet = true; + break; + + case 'l': + arg_local = true; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (arg_follow && !arg_no_tail && arg_lines < 0) + arg_lines = 10; + + return 1; +} + +static int generate_new_id128(void) { + sd_id128_t id; + int r; + unsigned i; + + r = sd_id128_randomize(&id); + if (r < 0) { + log_error("Failed to generate ID: %s", strerror(-r)); + return r; + } + + printf("As string:\n" + SD_ID128_FORMAT_STR "\n\n" + "As UUID:\n" + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n" + "As macro:\n" + "#define MESSAGE_XYZ SD_ID128_MAKE(", + SD_ID128_FORMAT_VAL(id), + SD_ID128_FORMAT_VAL(id)); + + for (i = 0; i < 16; i++) + printf("%02x%s", id.bytes[i], i != 15 ? "," : ""); + + fputs(")\n", stdout); + + return 0; +} + +int main(int argc, char *argv[]) { + int r, i, fd; + sd_journal *j = NULL; + unsigned line = 0; + bool need_seek = false; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_new_id128) { + r = generate_new_id128(); + goto finish; + } + +#ifdef HAVE_ACL + if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0) + log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off."); +#endif + + r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0); + if (r < 0) { + log_error("Failed to open journal: %s", strerror(-r)); + goto finish; + } + + for (i = optind; i < argc; i++) { + r = sd_journal_add_match(j, argv[i], strlen(argv[i])); + if (r < 0) { + log_error("Failed to add match: %s", strerror(-r)); + goto finish; + } + } + + fd = sd_journal_get_fd(j); + if (fd < 0) { + log_error("Failed to get wakeup fd: %s", strerror(-fd)); + goto finish; + } + + if (arg_lines >= 0) { + r = sd_journal_seek_tail(j); + if (r < 0) { + log_error("Failed to seek to tail: %s", strerror(-r)); + goto finish; + } + + r = sd_journal_previous_skip(j, arg_lines); + } else { + r = sd_journal_seek_head(j); + if (r < 0) { + log_error("Failed to seek to head: %s", strerror(-r)); + goto finish; + } + + r = sd_journal_next(j); + } + + if (r < 0) { + log_error("Failed to iterate through journal: %s", strerror(-r)); + goto finish; + } + + if (!arg_no_pager && !arg_follow) { + columns(); + pager_open(); + } + + if (arg_output == OUTPUT_JSON) { + fputc('[', stdout); + fflush(stdout); + } + + for (;;) { + for (;;) { + if (need_seek) { + r = sd_journal_next(j); + if (r < 0) { + log_error("Failed to iterate through journal: %s", strerror(-r)); + goto finish; + } + } + + if (r == 0) + break; + + line ++; + + r = output_journal(j, arg_output, line, 0, arg_show_all); + if (r < 0) + goto finish; + + need_seek = true; + } + + if (!arg_follow) + break; + + r = fd_wait_for_event(fd, POLLIN, (usec_t) -1); + if (r < 0) { + log_error("Couldn't wait for event: %s", strerror(-r)); + goto finish; + } + + r = sd_journal_process(j); + if (r < 0) { + log_error("Failed to process: %s", strerror(-r)); + goto finish; + } + } + + if (arg_output == OUTPUT_JSON) + fputs("\n]\n", stdout); + +finish: + if (j) + sd_journal_close(j); + + pager_close(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/journal/journald-gperf.gperf b/src/journal/journald-gperf.gperf new file mode 100644 index 000000000..a56f6d966 --- /dev/null +++ b/src/journal/journald-gperf.gperf @@ -0,0 +1,31 @@ +%{ +#include +#include "conf-parser.h" +#include "journald.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name journald_gperf_hash +%define lookup-function-name journald_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Journal.RateLimitInterval, config_parse_usec, 0, offsetof(Server, rate_limit_interval) +Journal.RateLimitBurst, config_parse_unsigned, 0, offsetof(Server, rate_limit_burst) +Journal.Compress, config_parse_bool, 0, offsetof(Server, compress) +Journal.SystemMaxUse, config_parse_bytes_off, 0, offsetof(Server, system_metrics.max_use) +Journal.SystemMaxFileSize, config_parse_bytes_off, 0, offsetof(Server, system_metrics.max_size) +Journal.SystemMinFileSize, config_parse_bytes_off, 0, offsetof(Server, system_metrics.min_size) +Journal.SystemKeepFree, config_parse_bytes_off, 0, offsetof(Server, system_metrics.keep_free) +Journal.RuntimeMaxUse, config_parse_bytes_off, 0, offsetof(Server, runtime_metrics.max_use) +Journal.RuntimeMaxFileSize, config_parse_bytes_off, 0, offsetof(Server, runtime_metrics.max_size) +Journal.RuntimeMinFileSize, config_parse_bytes_off, 0, offsetof(Server, runtime_metrics.min_size) +Journal.RuntimeKeepFree, config_parse_bytes_off, 0, offsetof(Server, runtime_metrics.keep_free) +Journal.ForwardToSyslog, config_parse_bool, 0, offsetof(Server, forward_to_syslog) +Journal.ForwardToKMsg, config_parse_bool, 0, offsetof(Server, forward_to_kmsg) +Journal.ForwardToConsole, config_parse_bool, 0, offsetof(Server, forward_to_console) +Journal.ImportKernel, config_parse_bool, 0, offsetof(Server, import_proc_kmsg) diff --git a/src/journal/journald.c b/src/journal/journald.c new file mode 100644 index 000000000..555d74f04 --- /dev/null +++ b/src/journal/journald.c @@ -0,0 +1,2821 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "hashmap.h" +#include "journal-file.h" +#include "socket-util.h" +#include "cgroup-util.h" +#include "list.h" +#include "journal-rate-limit.h" +#include "journal-internal.h" +#include "conf-parser.h" +#include "journald.h" +#include "virt.h" +#include "missing.h" + +#ifdef HAVE_ACL +#include +#include +#include "acl-util.h" +#endif + +#ifdef HAVE_SELINUX +#include +#endif + +#define USER_JOURNALS_MAX 1024 +#define STDOUT_STREAMS_MAX 4096 + +#define DEFAULT_RATE_LIMIT_INTERVAL (10*USEC_PER_SEC) +#define DEFAULT_RATE_LIMIT_BURST 200 + +#define RECHECK_AVAILABLE_SPACE_USEC (30*USEC_PER_SEC) + +#define RECHECK_VAR_AVAILABLE_USEC (30*USEC_PER_SEC) + +#define N_IOVEC_META_FIELDS 17 + +#define ENTRY_SIZE_MAX (1024*1024*32) + +typedef enum StdoutStreamState { + STDOUT_STREAM_IDENTIFIER, + STDOUT_STREAM_PRIORITY, + STDOUT_STREAM_LEVEL_PREFIX, + STDOUT_STREAM_FORWARD_TO_SYSLOG, + STDOUT_STREAM_FORWARD_TO_KMSG, + STDOUT_STREAM_FORWARD_TO_CONSOLE, + STDOUT_STREAM_RUNNING +} StdoutStreamState; + +struct StdoutStream { + Server *server; + StdoutStreamState state; + + int fd; + + struct ucred ucred; +#ifdef HAVE_SELINUX + security_context_t security_context; +#endif + + char *identifier; + int priority; + bool level_prefix:1; + bool forward_to_syslog:1; + bool forward_to_kmsg:1; + bool forward_to_console:1; + + char buffer[LINE_MAX+1]; + size_t length; + + LIST_FIELDS(StdoutStream, stdout_stream); +}; + +static int server_flush_to_var(Server *s); + +static uint64_t available_space(Server *s) { + char ids[33], *p; + const char *f; + sd_id128_t machine; + struct statvfs ss; + uint64_t sum = 0, avail = 0, ss_avail = 0; + int r; + DIR *d; + usec_t ts; + JournalMetrics *m; + + ts = now(CLOCK_MONOTONIC); + + if (s->cached_available_space_timestamp + RECHECK_AVAILABLE_SPACE_USEC > ts) + return s->cached_available_space; + + r = sd_id128_get_machine(&machine); + if (r < 0) + return 0; + + if (s->system_journal) { + f = "/var/log/journal/"; + m = &s->system_metrics; + } else { + f = "/run/log/journal/"; + m = &s->runtime_metrics; + } + + assert(m); + + p = strappend(f, sd_id128_to_string(machine, ids)); + if (!p) + return 0; + + d = opendir(p); + free(p); + + if (!d) + return 0; + + if (fstatvfs(dirfd(d), &ss) < 0) + goto finish; + + for (;;) { + struct stat st; + struct dirent buf, *de; + + r = readdir_r(d, &buf, &de); + if (r != 0) + break; + + if (!de) + break; + + if (!endswith(de->d_name, ".journal") && + !endswith(de->d_name, ".journal~")) + continue; + + if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) + continue; + + if (!S_ISREG(st.st_mode)) + continue; + + sum += (uint64_t) st.st_blocks * 512UL; + } + + avail = sum >= m->max_use ? 0 : m->max_use - sum; + + ss_avail = ss.f_bsize * ss.f_bavail; + + ss_avail = ss_avail < m->keep_free ? 0 : ss_avail - m->keep_free; + + if (ss_avail < avail) + avail = ss_avail; + + s->cached_available_space = avail; + s->cached_available_space_timestamp = ts; + +finish: + closedir(d); + + return avail; +} + +static void server_read_file_gid(Server *s) { + const char *adm = "adm"; + int r; + + assert(s); + + if (s->file_gid_valid) + return; + + r = get_group_creds(&adm, &s->file_gid); + if (r < 0) + log_warning("Failed to resolve 'adm' group: %s", strerror(-r)); + + /* if we couldn't read the gid, then it will be 0, but that's + * fine and we shouldn't try to resolve the group again, so + * let's just pretend it worked right-away. */ + s->file_gid_valid = true; +} + +static void server_fix_perms(Server *s, JournalFile *f, uid_t uid) { + int r; +#ifdef HAVE_ACL + acl_t acl; + acl_entry_t entry; + acl_permset_t permset; +#endif + + assert(f); + + server_read_file_gid(s); + + r = fchmod_and_fchown(f->fd, 0640, 0, s->file_gid); + if (r < 0) + log_warning("Failed to fix access mode/rights on %s, ignoring: %s", f->path, strerror(-r)); + +#ifdef HAVE_ACL + if (uid <= 0) + return; + + acl = acl_get_fd(f->fd); + if (!acl) { + log_warning("Failed to read ACL on %s, ignoring: %m", f->path); + return; + } + + r = acl_find_uid(acl, uid, &entry); + if (r <= 0) { + + if (acl_create_entry(&acl, &entry) < 0 || + acl_set_tag_type(entry, ACL_USER) < 0 || + acl_set_qualifier(entry, &uid) < 0) { + log_warning("Failed to patch ACL on %s, ignoring: %m", f->path); + goto finish; + } + } + + if (acl_get_permset(entry, &permset) < 0 || + acl_add_perm(permset, ACL_READ) < 0 || + acl_calc_mask(&acl) < 0) { + log_warning("Failed to patch ACL on %s, ignoring: %m", f->path); + goto finish; + } + + if (acl_set_fd(f->fd, acl) < 0) + log_warning("Failed to set ACL on %s, ignoring: %m", f->path); + +finish: + acl_free(acl); +#endif +} + +static JournalFile* find_journal(Server *s, uid_t uid) { + char *p; + int r; + JournalFile *f; + char ids[33]; + sd_id128_t machine; + + assert(s); + + /* We split up user logs only on /var, not on /run. If the + * runtime file is open, we write to it exclusively, in order + * to guarantee proper order as soon as we flush /run to + * /var and close the runtime file. */ + + if (s->runtime_journal) + return s->runtime_journal; + + if (uid <= 0) + return s->system_journal; + + r = sd_id128_get_machine(&machine); + if (r < 0) + return s->system_journal; + + f = hashmap_get(s->user_journals, UINT32_TO_PTR(uid)); + if (f) + return f; + + if (asprintf(&p, "/var/log/journal/%s/user-%lu.journal", sd_id128_to_string(machine, ids), (unsigned long) uid) < 0) + return s->system_journal; + + while (hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) { + /* Too many open? Then let's close one */ + f = hashmap_steal_first(s->user_journals); + assert(f); + journal_file_close(f); + } + + r = journal_file_open_reliably(p, O_RDWR|O_CREAT, 0640, s->system_journal, &f); + free(p); + + if (r < 0) + return s->system_journal; + + server_fix_perms(s, f, uid); + + r = hashmap_put(s->user_journals, UINT32_TO_PTR(uid), f); + if (r < 0) { + journal_file_close(f); + return s->system_journal; + } + + return f; +} + +static void server_rotate(Server *s) { + JournalFile *f; + void *k; + Iterator i; + int r; + + log_info("Rotating..."); + + if (s->runtime_journal) { + r = journal_file_rotate(&s->runtime_journal); + if (r < 0) + log_error("Failed to rotate %s: %s", s->runtime_journal->path, strerror(-r)); + else + server_fix_perms(s, s->runtime_journal, 0); + } + + if (s->system_journal) { + r = journal_file_rotate(&s->system_journal); + if (r < 0) + log_error("Failed to rotate %s: %s", s->system_journal->path, strerror(-r)); + else + server_fix_perms(s, s->system_journal, 0); + } + + HASHMAP_FOREACH_KEY(f, k, s->user_journals, i) { + r = journal_file_rotate(&f); + if (r < 0) + log_error("Failed to rotate %s: %s", f->path, strerror(-r)); + else { + hashmap_replace(s->user_journals, k, f); + server_fix_perms(s, s->system_journal, PTR_TO_UINT32(k)); + } + } +} + +static void server_vacuum(Server *s) { + char *p; + char ids[33]; + sd_id128_t machine; + int r; + + log_info("Vacuuming..."); + + r = sd_id128_get_machine(&machine); + if (r < 0) { + log_error("Failed to get machine ID: %s", strerror(-r)); + return; + } + + sd_id128_to_string(machine, ids); + + if (s->system_journal) { + if (asprintf(&p, "/var/log/journal/%s", ids) < 0) { + log_error("Out of memory."); + return; + } + + r = journal_directory_vacuum(p, s->system_metrics.max_use, s->system_metrics.keep_free); + if (r < 0 && r != -ENOENT) + log_error("Failed to vacuum %s: %s", p, strerror(-r)); + free(p); + } + + + if (s->runtime_journal) { + if (asprintf(&p, "/run/log/journal/%s", ids) < 0) { + log_error("Out of memory."); + return; + } + + r = journal_directory_vacuum(p, s->runtime_metrics.max_use, s->runtime_metrics.keep_free); + if (r < 0 && r != -ENOENT) + log_error("Failed to vacuum %s: %s", p, strerror(-r)); + free(p); + } + + s->cached_available_space_timestamp = 0; +} + +static char *shortened_cgroup_path(pid_t pid) { + int r; + char *process_path, *init_path, *path; + + assert(pid > 0); + + r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, pid, &process_path); + if (r < 0) + return NULL; + + r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 1, &init_path); + if (r < 0) { + free(process_path); + return NULL; + } + + if (endswith(init_path, "/system")) + init_path[strlen(init_path) - 7] = 0; + else if (streq(init_path, "/")) + init_path[0] = 0; + + if (startswith(process_path, init_path)) { + char *p; + + p = strdup(process_path + strlen(init_path)); + if (!p) { + free(process_path); + free(init_path); + return NULL; + } + path = p; + } else { + path = process_path; + process_path = NULL; + } + + free(process_path); + free(init_path); + + return path; +} + +static void dispatch_message_real( + Server *s, + struct iovec *iovec, unsigned n, unsigned m, + struct ucred *ucred, + struct timeval *tv, + const char *label, size_t label_len) { + + char *pid = NULL, *uid = NULL, *gid = NULL, + *source_time = NULL, *boot_id = NULL, *machine_id = NULL, + *comm = NULL, *cmdline = NULL, *hostname = NULL, + *audit_session = NULL, *audit_loginuid = NULL, + *exe = NULL, *cgroup = NULL, *session = NULL, + *owner_uid = NULL, *unit = NULL, *selinux_context = NULL; + + char idbuf[33]; + sd_id128_t id; + int r; + char *t; + uid_t loginuid = 0, realuid = 0; + JournalFile *f; + bool vacuumed = false; + + assert(s); + assert(iovec); + assert(n > 0); + assert(n + N_IOVEC_META_FIELDS <= m); + + if (ucred) { + uint32_t audit; + uid_t owner; + + realuid = ucred->uid; + + if (asprintf(&pid, "_PID=%lu", (unsigned long) ucred->pid) >= 0) + IOVEC_SET_STRING(iovec[n++], pid); + + if (asprintf(&uid, "_UID=%lu", (unsigned long) ucred->uid) >= 0) + IOVEC_SET_STRING(iovec[n++], uid); + + if (asprintf(&gid, "_GID=%lu", (unsigned long) ucred->gid) >= 0) + IOVEC_SET_STRING(iovec[n++], gid); + + r = get_process_comm(ucred->pid, &t); + if (r >= 0) { + comm = strappend("_COMM=", t); + free(t); + + if (comm) + IOVEC_SET_STRING(iovec[n++], comm); + } + + r = get_process_exe(ucred->pid, &t); + if (r >= 0) { + exe = strappend("_EXE=", t); + free(t); + + if (exe) + IOVEC_SET_STRING(iovec[n++], exe); + } + + r = get_process_cmdline(ucred->pid, LINE_MAX, false, &t); + if (r >= 0) { + cmdline = strappend("_CMDLINE=", t); + free(t); + + if (cmdline) + IOVEC_SET_STRING(iovec[n++], cmdline); + } + + r = audit_session_from_pid(ucred->pid, &audit); + if (r >= 0) + if (asprintf(&audit_session, "_AUDIT_SESSION=%lu", (unsigned long) audit) >= 0) + IOVEC_SET_STRING(iovec[n++], audit_session); + + r = audit_loginuid_from_pid(ucred->pid, &loginuid); + if (r >= 0) + if (asprintf(&audit_loginuid, "_AUDIT_LOGINUID=%lu", (unsigned long) loginuid) >= 0) + IOVEC_SET_STRING(iovec[n++], audit_loginuid); + + t = shortened_cgroup_path(ucred->pid); + if (t) { + cgroup = strappend("_SYSTEMD_CGROUP=", t); + free(t); + + if (cgroup) + IOVEC_SET_STRING(iovec[n++], cgroup); + } + + if (sd_pid_get_session(ucred->pid, &t) >= 0) { + session = strappend("_SYSTEMD_SESSION=", t); + free(t); + + if (session) + IOVEC_SET_STRING(iovec[n++], session); + } + + if (sd_pid_get_unit(ucred->pid, &t) >= 0) { + unit = strappend("_SYSTEMD_UNIT=", t); + free(t); + + if (unit) + IOVEC_SET_STRING(iovec[n++], unit); + } + + if (sd_pid_get_owner_uid(ucred->uid, &owner) >= 0) + if (asprintf(&owner_uid, "_SYSTEMD_OWNER_UID=%lu", (unsigned long) owner) >= 0) + IOVEC_SET_STRING(iovec[n++], owner_uid); + +#ifdef HAVE_SELINUX + if (label) { + selinux_context = malloc(sizeof("_SELINUX_CONTEXT=") + label_len); + if (selinux_context) { + memcpy(selinux_context, "_SELINUX_CONTEXT=", sizeof("_SELINUX_CONTEXT=")-1); + memcpy(selinux_context+sizeof("_SELINUX_CONTEXT=")-1, label, label_len); + selinux_context[sizeof("_SELINUX_CONTEXT=")-1+label_len] = 0; + IOVEC_SET_STRING(iovec[n++], selinux_context); + } + } else { + security_context_t con; + + if (getpidcon(ucred->pid, &con) >= 0) { + selinux_context = strappend("_SELINUX_CONTEXT=", con); + if (selinux_context) + IOVEC_SET_STRING(iovec[n++], selinux_context); + + freecon(con); + } + } +#endif + } + + if (tv) { + if (asprintf(&source_time, "_SOURCE_REALTIME_TIMESTAMP=%llu", + (unsigned long long) timeval_load(tv)) >= 0) + IOVEC_SET_STRING(iovec[n++], source_time); + } + + /* Note that strictly speaking storing the boot id here is + * redundant since the entry includes this in-line + * anyway. However, we need this indexed, too. */ + r = sd_id128_get_boot(&id); + if (r >= 0) + if (asprintf(&boot_id, "_BOOT_ID=%s", sd_id128_to_string(id, idbuf)) >= 0) + IOVEC_SET_STRING(iovec[n++], boot_id); + + r = sd_id128_get_machine(&id); + if (r >= 0) + if (asprintf(&machine_id, "_MACHINE_ID=%s", sd_id128_to_string(id, idbuf)) >= 0) + IOVEC_SET_STRING(iovec[n++], machine_id); + + t = gethostname_malloc(); + if (t) { + hostname = strappend("_HOSTNAME=", t); + free(t); + if (hostname) + IOVEC_SET_STRING(iovec[n++], hostname); + } + + assert(n <= m); + + server_flush_to_var(s); + +retry: + f = find_journal(s, realuid == 0 ? 0 : loginuid); + if (!f) + log_warning("Dropping message, as we can't find a place to store the data."); + else { + r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL); + + if ((r == -E2BIG || /* hit limit */ + r == -EFBIG || /* hit fs limit */ + r == -EDQUOT || /* quota hit */ + r == -ENOSPC || /* disk full */ + r == -EBADMSG || /* corrupted */ + r == -ENODATA || /* truncated */ + r == -EHOSTDOWN || /* other machine */ + r == -EPROTONOSUPPORT) && /* unsupported feature */ + !vacuumed) { + + if (r == -E2BIG) + log_info("Allocation limit reached, rotating."); + else + log_warning("Journal file corrupted, rotating."); + + server_rotate(s); + server_vacuum(s); + vacuumed = true; + + log_info("Retrying write."); + goto retry; + } + + if (r < 0) + log_error("Failed to write entry, ignoring: %s", strerror(-r)); + } + + free(pid); + free(uid); + free(gid); + free(comm); + free(exe); + free(cmdline); + free(source_time); + free(boot_id); + free(machine_id); + free(hostname); + free(audit_session); + free(audit_loginuid); + free(cgroup); + free(session); + free(owner_uid); + free(unit); + free(selinux_context); +} + +static void driver_message(Server *s, sd_id128_t message_id, const char *format, ...) { + char mid[11 + 32 + 1]; + char buffer[16 + LINE_MAX + 1]; + struct iovec iovec[N_IOVEC_META_FIELDS + 4]; + int n = 0; + va_list ap; + struct ucred ucred; + + assert(s); + assert(format); + + IOVEC_SET_STRING(iovec[n++], "PRIORITY=5"); + IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=driver"); + + memcpy(buffer, "MESSAGE=", 8); + va_start(ap, format); + vsnprintf(buffer + 8, sizeof(buffer) - 8, format, ap); + va_end(ap); + char_array_0(buffer); + IOVEC_SET_STRING(iovec[n++], buffer); + + snprintf(mid, sizeof(mid), "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(message_id)); + char_array_0(mid); + IOVEC_SET_STRING(iovec[n++], mid); + + zero(ucred); + ucred.pid = getpid(); + ucred.uid = getuid(); + ucred.gid = getgid(); + + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0); +} + +static void dispatch_message(Server *s, + struct iovec *iovec, unsigned n, unsigned m, + struct ucred *ucred, + struct timeval *tv, + const char *label, size_t label_len, + int priority) { + int rl; + char *path = NULL, *c; + + assert(s); + assert(iovec || n == 0); + + if (n == 0) + return; + + if (!ucred) + goto finish; + + path = shortened_cgroup_path(ucred->pid); + if (!path) + goto finish; + + /* example: /user/lennart/3/foobar + * /system/dbus.service/foobar + * + * So let's cut of everything past the third /, since that is + * wher user directories start */ + + c = strchr(path, '/'); + if (c) { + c = strchr(c+1, '/'); + if (c) { + c = strchr(c+1, '/'); + if (c) + *c = 0; + } + } + + rl = journal_rate_limit_test(s->rate_limit, path, priority & LOG_PRIMASK, available_space(s)); + + if (rl == 0) { + free(path); + return; + } + + /* Write a suppression message if we suppressed something */ + if (rl > 1) + driver_message(s, SD_MESSAGE_JOURNAL_DROPPED, "Suppressed %u messages from %s", rl - 1, path); + + free(path); + +finish: + dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len); +} + +static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned n_iovec, struct ucred *ucred, struct timeval *tv) { + struct msghdr msghdr; + struct cmsghdr *cmsg; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; + } control; + union sockaddr_union sa; + + assert(s); + assert(iovec); + assert(n_iovec > 0); + + zero(msghdr); + msghdr.msg_iov = (struct iovec*) iovec; + msghdr.msg_iovlen = n_iovec; + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, "/run/systemd/journal/syslog", sizeof(sa.un.sun_path)); + msghdr.msg_name = &sa; + msghdr.msg_namelen = offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path); + + if (ucred) { + zero(control); + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + cmsg = CMSG_FIRSTHDR(&msghdr); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + memcpy(CMSG_DATA(cmsg), ucred, sizeof(struct ucred)); + msghdr.msg_controllen = cmsg->cmsg_len; + } + + /* Forward the syslog message we received via /dev/log to + * /run/systemd/syslog. Unfortunately we currently can't set + * the SO_TIMESTAMP auxiliary data, and hence we don't. */ + + if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0) + return; + + /* The socket is full? I guess the syslog implementation is + * too slow, and we shouldn't wait for that... */ + if (errno == EAGAIN) + return; + + if (ucred && errno == ESRCH) { + struct ucred u; + + /* Hmm, presumably the sender process vanished + * by now, so let's fix it as good as we + * can, and retry */ + + u = *ucred; + u.pid = getpid(); + memcpy(CMSG_DATA(cmsg), &u, sizeof(struct ucred)); + + if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0) + return; + + if (errno == EAGAIN) + return; + } + + log_debug("Failed to forward syslog message: %m"); +} + +static void forward_syslog_raw(Server *s, const char *buffer, struct ucred *ucred, struct timeval *tv) { + struct iovec iovec; + + assert(s); + assert(buffer); + + IOVEC_SET_STRING(iovec, buffer); + forward_syslog_iovec(s, &iovec, 1, ucred, tv); +} + +static void forward_syslog(Server *s, int priority, const char *identifier, const char *message, struct ucred *ucred, struct timeval *tv) { + struct iovec iovec[5]; + char header_priority[6], header_time[64], header_pid[16]; + int n = 0; + time_t t; + struct tm *tm; + char *ident_buf = NULL; + + assert(s); + assert(priority >= 0); + assert(priority <= 999); + assert(message); + + /* First: priority field */ + snprintf(header_priority, sizeof(header_priority), "<%i>", priority); + char_array_0(header_priority); + IOVEC_SET_STRING(iovec[n++], header_priority); + + /* Second: timestamp */ + t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC)); + tm = localtime(&t); + if (!tm) + return; + if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0) + return; + IOVEC_SET_STRING(iovec[n++], header_time); + + /* Third: identifier and PID */ + if (ucred) { + if (!identifier) { + get_process_comm(ucred->pid, &ident_buf); + identifier = ident_buf; + } + + snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) ucred->pid); + char_array_0(header_pid); + + if (identifier) + IOVEC_SET_STRING(iovec[n++], identifier); + + IOVEC_SET_STRING(iovec[n++], header_pid); + } else if (identifier) { + IOVEC_SET_STRING(iovec[n++], identifier); + IOVEC_SET_STRING(iovec[n++], ": "); + } + + /* Fourth: message */ + IOVEC_SET_STRING(iovec[n++], message); + + forward_syslog_iovec(s, iovec, n, ucred, tv); + + free(ident_buf); +} + +static int fixup_priority(int priority) { + + if ((priority & LOG_FACMASK) == 0) + return (priority & LOG_PRIMASK) | LOG_USER; + + return priority; +} + +static void forward_kmsg(Server *s, int priority, const char *identifier, const char *message, struct ucred *ucred) { + struct iovec iovec[5]; + char header_priority[6], header_pid[16]; + int n = 0; + char *ident_buf = NULL; + int fd; + + assert(s); + assert(priority >= 0); + assert(priority <= 999); + assert(message); + + /* Never allow messages with kernel facility to be written to + * kmsg, regardless where the data comes from. */ + priority = fixup_priority(priority); + + /* First: priority field */ + snprintf(header_priority, sizeof(header_priority), "<%i>", priority); + char_array_0(header_priority); + IOVEC_SET_STRING(iovec[n++], header_priority); + + /* Second: identifier and PID */ + if (ucred) { + if (!identifier) { + get_process_comm(ucred->pid, &ident_buf); + identifier = ident_buf; + } + + snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) ucred->pid); + char_array_0(header_pid); + + if (identifier) + IOVEC_SET_STRING(iovec[n++], identifier); + + IOVEC_SET_STRING(iovec[n++], header_pid); + } else if (identifier) { + IOVEC_SET_STRING(iovec[n++], identifier); + IOVEC_SET_STRING(iovec[n++], ": "); + } + + /* Fourth: message */ + IOVEC_SET_STRING(iovec[n++], message); + IOVEC_SET_STRING(iovec[n++], "\n"); + + fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) { + log_debug("Failed to open /dev/kmsg for logging: %s", strerror(errno)); + goto finish; + } + + if (writev(fd, iovec, n) < 0) + log_debug("Failed to write to /dev/kmsg for logging: %s", strerror(errno)); + + close_nointr_nofail(fd); + +finish: + free(ident_buf); +} + +static void forward_console(Server *s, const char *identifier, const char *message, struct ucred *ucred) { + struct iovec iovec[4]; + char header_pid[16]; + int n = 0, fd; + char *ident_buf = NULL; + + assert(s); + assert(message); + + /* First: identifier and PID */ + if (ucred) { + if (!identifier) { + get_process_comm(ucred->pid, &ident_buf); + identifier = ident_buf; + } + + snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) ucred->pid); + char_array_0(header_pid); + + if (identifier) + IOVEC_SET_STRING(iovec[n++], identifier); + + IOVEC_SET_STRING(iovec[n++], header_pid); + } else if (identifier) { + IOVEC_SET_STRING(iovec[n++], identifier); + IOVEC_SET_STRING(iovec[n++], ": "); + } + + /* Third: message */ + IOVEC_SET_STRING(iovec[n++], message); + IOVEC_SET_STRING(iovec[n++], "\n"); + + fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) { + log_debug("Failed to open /dev/console for logging: %s", strerror(errno)); + goto finish; + } + + if (writev(fd, iovec, n) < 0) + log_debug("Failed to write to /dev/console for logging: %s", strerror(errno)); + + close_nointr_nofail(fd); + +finish: + free(ident_buf); +} + +static void read_identifier(const char **buf, char **identifier, char **pid) { + const char *p; + char *t; + size_t l, e; + + assert(buf); + assert(identifier); + assert(pid); + + p = *buf; + + p += strspn(p, WHITESPACE); + l = strcspn(p, WHITESPACE); + + if (l <= 0 || + p[l-1] != ':') + return; + + e = l; + l--; + + if (p[l-1] == ']') { + size_t k = l-1; + + for (;;) { + + if (p[k] == '[') { + t = strndup(p+k+1, l-k-2); + if (t) + *pid = t; + + l = k; + break; + } + + if (k == 0) + break; + + k--; + } + } + + t = strndup(p, l); + if (t) + *identifier = t; + + *buf = p + e; + *buf += strspn(*buf, WHITESPACE); +} + +static void process_syslog_message(Server *s, const char *buf, struct ucred *ucred, struct timeval *tv, const char *label, size_t label_len) { + char *message = NULL, *syslog_priority = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *syslog_pid = NULL; + struct iovec iovec[N_IOVEC_META_FIELDS + 6]; + unsigned n = 0; + int priority = LOG_USER | LOG_INFO; + char *identifier = NULL, *pid = NULL; + + assert(s); + assert(buf); + + if (s->forward_to_syslog) + forward_syslog_raw(s, buf, ucred, tv); + + parse_syslog_priority((char**) &buf, &priority); + skip_syslog_date((char**) &buf); + read_identifier(&buf, &identifier, &pid); + + if (s->forward_to_kmsg) + forward_kmsg(s, priority, identifier, buf, ucred); + + if (s->forward_to_console) + forward_console(s, identifier, buf, ucred); + + IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=syslog"); + + if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0) + IOVEC_SET_STRING(iovec[n++], syslog_priority); + + if (priority & LOG_FACMASK) + if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0) + IOVEC_SET_STRING(iovec[n++], syslog_facility); + + if (identifier) { + syslog_identifier = strappend("SYSLOG_IDENTIFIER=", identifier); + if (syslog_identifier) + IOVEC_SET_STRING(iovec[n++], syslog_identifier); + } + + if (pid) { + syslog_pid = strappend("SYSLOG_PID=", pid); + if (syslog_pid) + IOVEC_SET_STRING(iovec[n++], syslog_pid); + } + + message = strappend("MESSAGE=", buf); + if (message) + IOVEC_SET_STRING(iovec[n++], message); + + dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, label, label_len, priority); + + free(message); + free(identifier); + free(pid); + free(syslog_priority); + free(syslog_facility); + free(syslog_identifier); +} + +static bool valid_user_field(const char *p, size_t l) { + const char *a; + + /* We kinda enforce POSIX syntax recommendations for + environment variables here, but make a couple of additional + requirements. + + http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html */ + + /* No empty field names */ + if (l <= 0) + return false; + + /* Don't allow names longer than 64 chars */ + if (l > 64) + return false; + + /* Variables starting with an underscore are protected */ + if (p[0] == '_') + return false; + + /* Don't allow digits as first character */ + if (p[0] >= '0' && p[0] <= '9') + return false; + + /* Only allow A-Z0-9 and '_' */ + for (a = p; a < p + l; a++) + if (!((*a >= 'A' && *a <= 'Z') || + (*a >= '0' && *a <= '9') || + *a == '_')) + return false; + + return true; +} + +static void process_native_message( + Server *s, + const void *buffer, size_t buffer_size, + struct ucred *ucred, + struct timeval *tv, + const char *label, size_t label_len) { + + struct iovec *iovec = NULL; + unsigned n = 0, m = 0, j, tn = (unsigned) -1; + const char *p; + size_t remaining; + int priority = LOG_INFO; + char *identifier = NULL, *message = NULL; + + assert(s); + assert(buffer || n == 0); + + p = buffer; + remaining = buffer_size; + + while (remaining > 0) { + const char *e, *q; + + e = memchr(p, '\n', remaining); + + if (!e) { + /* Trailing noise, let's ignore it, and flush what we collected */ + log_debug("Received message with trailing noise, ignoring."); + break; + } + + if (e == p) { + /* Entry separator */ + dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, priority); + n = 0; + priority = LOG_INFO; + + p++; + remaining--; + continue; + } + + if (*p == '.' || *p == '#') { + /* Ignore control commands for now, and + * comments too. */ + remaining -= (e - p) + 1; + p = e + 1; + continue; + } + + /* A property follows */ + + if (n+N_IOVEC_META_FIELDS >= m) { + struct iovec *c; + unsigned u; + + u = MAX((n+N_IOVEC_META_FIELDS+1) * 2U, 4U); + c = realloc(iovec, u * sizeof(struct iovec)); + if (!c) { + log_error("Out of memory"); + break; + } + + iovec = c; + m = u; + } + + q = memchr(p, '=', e - p); + if (q) { + if (valid_user_field(p, q - p)) { + size_t l; + + l = e - p; + + /* If the field name starts with an + * underscore, skip the variable, + * since that indidates a trusted + * field */ + iovec[n].iov_base = (char*) p; + iovec[n].iov_len = l; + n++; + + /* We need to determine the priority + * of this entry for the rate limiting + * logic */ + if (l == 10 && + memcmp(p, "PRIORITY=", 9) == 0 && + p[9] >= '0' && p[9] <= '9') + priority = (priority & LOG_FACMASK) | (p[9] - '0'); + + else if (l == 17 && + memcmp(p, "SYSLOG_FACILITY=", 16) == 0 && + p[16] >= '0' && p[16] <= '9') + priority = (priority & LOG_PRIMASK) | ((p[16] - '0') << 3); + + else if (l == 18 && + memcmp(p, "SYSLOG_FACILITY=", 16) == 0 && + p[16] >= '0' && p[16] <= '9' && + p[17] >= '0' && p[17] <= '9') + priority = (priority & LOG_PRIMASK) | (((p[16] - '0')*10 + (p[17] - '0')) << 3); + + else if (l >= 12 && + memcmp(p, "SYSLOG_IDENTIFIER=", 11) == 0) { + char *t; + + t = strndup(p + 11, l - 11); + if (t) { + free(identifier); + identifier = t; + } + } else if (l >= 8 && + memcmp(p, "MESSAGE=", 8) == 0) { + char *t; + + t = strndup(p + 8, l - 8); + if (t) { + free(message); + message = t; + } + } + } + + remaining -= (e - p) + 1; + p = e + 1; + continue; + } else { + le64_t l_le; + uint64_t l; + char *k; + + if (remaining < e - p + 1 + sizeof(uint64_t) + 1) { + log_debug("Failed to parse message, ignoring."); + break; + } + + memcpy(&l_le, e + 1, sizeof(uint64_t)); + l = le64toh(l_le); + + if (remaining < e - p + 1 + sizeof(uint64_t) + l + 1 || + e[1+sizeof(uint64_t)+l] != '\n') { + log_debug("Failed to parse message, ignoring."); + break; + } + + k = malloc((e - p) + 1 + l); + if (!k) { + log_error("Out of memory"); + break; + } + + memcpy(k, p, e - p); + k[e - p] = '='; + memcpy(k + (e - p) + 1, e + 1 + sizeof(uint64_t), l); + + if (valid_user_field(p, e - p)) { + iovec[n].iov_base = k; + iovec[n].iov_len = (e - p) + 1 + l; + n++; + } else + free(k); + + remaining -= (e - p) + 1 + sizeof(uint64_t) + l + 1; + p = e + 1 + sizeof(uint64_t) + l + 1; + } + } + + if (n <= 0) + goto finish; + + tn = n++; + IOVEC_SET_STRING(iovec[tn], "_TRANSPORT=journal"); + + if (message) { + if (s->forward_to_syslog) + forward_syslog(s, priority, identifier, message, ucred, tv); + + if (s->forward_to_kmsg) + forward_kmsg(s, priority, identifier, message, ucred); + + if (s->forward_to_console) + forward_console(s, identifier, message, ucred); + } + + dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, priority); + +finish: + for (j = 0; j < n; j++) { + if (j == tn) + continue; + + if (iovec[j].iov_base < buffer || + (const uint8_t*) iovec[j].iov_base >= (const uint8_t*) buffer + buffer_size) + free(iovec[j].iov_base); + } + + free(iovec); + free(identifier); + free(message); +} + +static void process_native_file( + Server *s, + int fd, + struct ucred *ucred, + struct timeval *tv, + const char *label, size_t label_len) { + + struct stat st; + void *p; + ssize_t n; + + assert(s); + assert(fd >= 0); + + /* Data is in the passed file, since it didn't fit in a + * datagram. We can't map the file here, since clients might + * then truncate it and trigger a SIGBUS for us. So let's + * stupidly read it */ + + if (fstat(fd, &st) < 0) { + log_error("Failed to stat passed file, ignoring: %m"); + return; + } + + if (!S_ISREG(st.st_mode)) { + log_error("File passed is not regular. Ignoring."); + return; + } + + if (st.st_size <= 0) + return; + + if (st.st_size > ENTRY_SIZE_MAX) { + log_error("File passed too large. Ignoring."); + return; + } + + p = malloc(st.st_size); + if (!p) { + log_error("Out of memory"); + return; + } + + n = pread(fd, p, st.st_size, 0); + if (n < 0) + log_error("Failed to read file, ignoring: %s", strerror(-n)); + else if (n > 0) + process_native_message(s, p, n, ucred, tv, label, label_len); + + free(p); +} + +static int stdout_stream_log(StdoutStream *s, const char *p) { + struct iovec iovec[N_IOVEC_META_FIELDS + 5]; + char *message = NULL, *syslog_priority = NULL, *syslog_facility = NULL, *syslog_identifier = NULL; + unsigned n = 0; + int priority; + char *label = NULL; + size_t label_len = 0; + + assert(s); + assert(p); + + if (isempty(p)) + return 0; + + priority = s->priority; + + if (s->level_prefix) + parse_syslog_priority((char**) &p, &priority); + + if (s->forward_to_syslog || s->server->forward_to_syslog) + forward_syslog(s->server, fixup_priority(priority), s->identifier, p, &s->ucred, NULL); + + if (s->forward_to_kmsg || s->server->forward_to_kmsg) + forward_kmsg(s->server, priority, s->identifier, p, &s->ucred); + + if (s->forward_to_console || s->server->forward_to_console) + forward_console(s->server, s->identifier, p, &s->ucred); + + IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=stdout"); + + if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0) + IOVEC_SET_STRING(iovec[n++], syslog_priority); + + if (priority & LOG_FACMASK) + if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0) + IOVEC_SET_STRING(iovec[n++], syslog_facility); + + if (s->identifier) { + syslog_identifier = strappend("SYSLOG_IDENTIFIER=", s->identifier); + if (syslog_identifier) + IOVEC_SET_STRING(iovec[n++], syslog_identifier); + } + + message = strappend("MESSAGE=", p); + if (message) + IOVEC_SET_STRING(iovec[n++], message); + +#ifdef HAVE_SELINUX + if (s->security_context) { + label = (char*) s->security_context; + label_len = strlen((char*) s->security_context); + } +#endif + + dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, label, label_len, priority); + + free(message); + free(syslog_priority); + free(syslog_facility); + free(syslog_identifier); + + return 0; +} + +static int stdout_stream_line(StdoutStream *s, char *p) { + int r; + + assert(s); + assert(p); + + p = strstrip(p); + + switch (s->state) { + + case STDOUT_STREAM_IDENTIFIER: + if (isempty(p)) + s->identifier = NULL; + else { + s->identifier = strdup(p); + if (!s->identifier) { + log_error("Out of memory"); + return -ENOMEM; + } + } + + s->state = STDOUT_STREAM_PRIORITY; + return 0; + + case STDOUT_STREAM_PRIORITY: + r = safe_atoi(p, &s->priority); + if (r < 0 || s->priority <= 0 || s->priority >= 999) { + log_warning("Failed to parse log priority line."); + return -EINVAL; + } + + s->state = STDOUT_STREAM_LEVEL_PREFIX; + return 0; + + case STDOUT_STREAM_LEVEL_PREFIX: + r = parse_boolean(p); + if (r < 0) { + log_warning("Failed to parse level prefix line."); + return -EINVAL; + } + + s->level_prefix = !!r; + s->state = STDOUT_STREAM_FORWARD_TO_SYSLOG; + return 0; + + case STDOUT_STREAM_FORWARD_TO_SYSLOG: + r = parse_boolean(p); + if (r < 0) { + log_warning("Failed to parse forward to syslog line."); + return -EINVAL; + } + + s->forward_to_syslog = !!r; + s->state = STDOUT_STREAM_FORWARD_TO_KMSG; + return 0; + + case STDOUT_STREAM_FORWARD_TO_KMSG: + r = parse_boolean(p); + if (r < 0) { + log_warning("Failed to parse copy to kmsg line."); + return -EINVAL; + } + + s->forward_to_kmsg = !!r; + s->state = STDOUT_STREAM_FORWARD_TO_CONSOLE; + return 0; + + case STDOUT_STREAM_FORWARD_TO_CONSOLE: + r = parse_boolean(p); + if (r < 0) { + log_warning("Failed to parse copy to console line."); + return -EINVAL; + } + + s->forward_to_console = !!r; + s->state = STDOUT_STREAM_RUNNING; + return 0; + + case STDOUT_STREAM_RUNNING: + return stdout_stream_log(s, p); + } + + assert_not_reached("Unknown stream state"); +} + +static int stdout_stream_scan(StdoutStream *s, bool force_flush) { + char *p; + size_t remaining; + int r; + + assert(s); + + p = s->buffer; + remaining = s->length; + for (;;) { + char *end; + size_t skip; + + end = memchr(p, '\n', remaining); + if (end) + skip = end - p + 1; + else if (remaining >= sizeof(s->buffer) - 1) { + end = p + sizeof(s->buffer) - 1; + skip = remaining; + } else + break; + + *end = 0; + + r = stdout_stream_line(s, p); + if (r < 0) + return r; + + remaining -= skip; + p += skip; + } + + if (force_flush && remaining > 0) { + p[remaining] = 0; + r = stdout_stream_line(s, p); + if (r < 0) + return r; + + p += remaining; + remaining = 0; + } + + if (p > s->buffer) { + memmove(s->buffer, p, remaining); + s->length = remaining; + } + + return 0; +} + +static int stdout_stream_process(StdoutStream *s) { + ssize_t l; + int r; + + assert(s); + + l = read(s->fd, s->buffer+s->length, sizeof(s->buffer)-1-s->length); + if (l < 0) { + + if (errno == EAGAIN) + return 0; + + log_warning("Failed to read from stream: %m"); + return -errno; + } + + if (l == 0) { + r = stdout_stream_scan(s, true); + if (r < 0) + return r; + + return 0; + } + + s->length += l; + r = stdout_stream_scan(s, false); + if (r < 0) + return r; + + return 1; + +} + +static void stdout_stream_free(StdoutStream *s) { + assert(s); + + if (s->server) { + assert(s->server->n_stdout_streams > 0); + s->server->n_stdout_streams --; + LIST_REMOVE(StdoutStream, stdout_stream, s->server->stdout_streams, s); + } + + if (s->fd >= 0) { + if (s->server) + epoll_ctl(s->server->epoll_fd, EPOLL_CTL_DEL, s->fd, NULL); + + close_nointr_nofail(s->fd); + } + +#ifdef HAVE_SELINUX + if (s->security_context) + freecon(s->security_context); +#endif + + free(s->identifier); + free(s); +} + +static int stdout_stream_new(Server *s) { + StdoutStream *stream; + int fd, r; + socklen_t len; + struct epoll_event ev; + + assert(s); + + fd = accept4(s->stdout_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); + if (fd < 0) { + if (errno == EAGAIN) + return 0; + + log_error("Failed to accept stdout connection: %m"); + return -errno; + } + + if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) { + log_warning("Too many stdout streams, refusing connection."); + close_nointr_nofail(fd); + return 0; + } + + stream = new0(StdoutStream, 1); + if (!stream) { + log_error("Out of memory."); + close_nointr_nofail(fd); + return -ENOMEM; + } + + stream->fd = fd; + + len = sizeof(stream->ucred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &stream->ucred, &len) < 0) { + log_error("Failed to determine peer credentials: %m"); + r = -errno; + goto fail; + } + +#ifdef HAVE_SELINUX + if (getpeercon(fd, &stream->security_context) < 0) + log_error("Failed to determine peer security context."); +#endif + + if (shutdown(fd, SHUT_WR) < 0) { + log_error("Failed to shutdown writing side of socket: %m"); + r = -errno; + goto fail; + } + + zero(ev); + ev.data.ptr = stream; + ev.events = EPOLLIN; + if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) { + log_error("Failed to add stream to event loop: %m"); + r = -errno; + goto fail; + } + + stream->server = s; + LIST_PREPEND(StdoutStream, stdout_stream, s->stdout_streams, stream); + s->n_stdout_streams ++; + + return 0; + +fail: + stdout_stream_free(stream); + return r; +} + +static int parse_kernel_timestamp(char **_p, usec_t *t) { + usec_t r; + int k, i; + char *p; + + assert(_p); + assert(*_p); + assert(t); + + p = *_p; + + if (strlen(p) < 14 || p[0] != '[' || p[13] != ']' || p[6] != '.') + return 0; + + r = 0; + + for (i = 1; i <= 5; i++) { + r *= 10; + + if (p[i] == ' ') + continue; + + k = undecchar(p[i]); + if (k < 0) + return 0; + + r += k; + } + + for (i = 7; i <= 12; i++) { + r *= 10; + + k = undecchar(p[i]); + if (k < 0) + return 0; + + r += k; + } + + *t = r; + *_p += 14; + *_p += strspn(*_p, WHITESPACE); + + return 1; +} + +static void proc_kmsg_line(Server *s, const char *p) { + struct iovec iovec[N_IOVEC_META_FIELDS + 7]; + char *message = NULL, *syslog_priority = NULL, *syslog_pid = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *source_time = NULL; + int priority = LOG_KERN | LOG_INFO; + unsigned n = 0; + usec_t usec; + char *identifier = NULL, *pid = NULL; + + assert(s); + assert(p); + + if (isempty(p)) + return; + + parse_syslog_priority((char **) &p, &priority); + + if (s->forward_to_kmsg && (priority & LOG_FACMASK) != LOG_KERN) + return; + + if (parse_kernel_timestamp((char **) &p, &usec) > 0) { + if (asprintf(&source_time, "_SOURCE_MONOTONIC_TIMESTAMP=%llu", + (unsigned long long) usec) >= 0) + IOVEC_SET_STRING(iovec[n++], source_time); + } + + IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=kernel"); + + if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0) + IOVEC_SET_STRING(iovec[n++], syslog_priority); + + if ((priority & LOG_FACMASK) == LOG_KERN) { + + if (s->forward_to_syslog) + forward_syslog(s, priority, "kernel", p, NULL, NULL); + + IOVEC_SET_STRING(iovec[n++], "SYSLOG_IDENTIFIER=kernel"); + } else { + read_identifier(&p, &identifier, &pid); + + if (s->forward_to_syslog) + forward_syslog(s, priority, identifier, p, NULL, NULL); + + if (identifier) { + syslog_identifier = strappend("SYSLOG_IDENTIFIER=", identifier); + if (syslog_identifier) + IOVEC_SET_STRING(iovec[n++], syslog_identifier); + } + + if (pid) { + syslog_pid = strappend("SYSLOG_PID=", pid); + if (syslog_pid) + IOVEC_SET_STRING(iovec[n++], syslog_pid); + } + + if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0) + IOVEC_SET_STRING(iovec[n++], syslog_facility); + } + + message = strappend("MESSAGE=", p); + if (message) + IOVEC_SET_STRING(iovec[n++], message); + + dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, NULL, 0, priority); + + free(message); + free(syslog_priority); + free(syslog_identifier); + free(syslog_pid); + free(syslog_facility); + free(source_time); + free(identifier); + free(pid); +} + +static void proc_kmsg_scan(Server *s) { + char *p; + size_t remaining; + + assert(s); + + p = s->proc_kmsg_buffer; + remaining = s->proc_kmsg_length; + for (;;) { + char *end; + size_t skip; + + end = memchr(p, '\n', remaining); + if (end) + skip = end - p + 1; + else if (remaining >= sizeof(s->proc_kmsg_buffer) - 1) { + end = p + sizeof(s->proc_kmsg_buffer) - 1; + skip = remaining; + } else + break; + + *end = 0; + + proc_kmsg_line(s, p); + + remaining -= skip; + p += skip; + } + + if (p > s->proc_kmsg_buffer) { + memmove(s->proc_kmsg_buffer, p, remaining); + s->proc_kmsg_length = remaining; + } +} + +static int system_journal_open(Server *s) { + int r; + char *fn; + sd_id128_t machine; + char ids[33]; + + r = sd_id128_get_machine(&machine); + if (r < 0) + return r; + + sd_id128_to_string(machine, ids); + + if (!s->system_journal) { + + /* First try to create the machine path, but not the prefix */ + fn = strappend("/var/log/journal/", ids); + if (!fn) + return -ENOMEM; + (void) mkdir(fn, 0755); + free(fn); + + /* The create the system journal file */ + fn = join("/var/log/journal/", ids, "/system.journal", NULL); + if (!fn) + return -ENOMEM; + + r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, NULL, &s->system_journal); + free(fn); + + if (r >= 0) { + journal_default_metrics(&s->system_metrics, s->system_journal->fd); + + s->system_journal->metrics = s->system_metrics; + s->system_journal->compress = s->compress; + + server_fix_perms(s, s->system_journal, 0); + } else if (r < 0) { + + if (r != -ENOENT && r != -EROFS) + log_warning("Failed to open system journal: %s", strerror(-r)); + + r = 0; + } + } + + if (!s->runtime_journal) { + + fn = join("/run/log/journal/", ids, "/system.journal", NULL); + if (!fn) + return -ENOMEM; + + if (s->system_journal) { + + /* Try to open the runtime journal, but only + * if it already exists, so that we can flush + * it into the system journal */ + + r = journal_file_open(fn, O_RDWR, 0640, NULL, &s->runtime_journal); + free(fn); + + if (r < 0) { + if (r != -ENOENT) + log_warning("Failed to open runtime journal: %s", strerror(-r)); + + r = 0; + } + + } else { + + /* OK, we really need the runtime journal, so create + * it if necessary. */ + + (void) mkdir_parents(fn, 0755); + r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, NULL, &s->runtime_journal); + free(fn); + + if (r < 0) { + log_error("Failed to open runtime journal: %s", strerror(-r)); + return r; + } + } + + if (s->runtime_journal) { + journal_default_metrics(&s->runtime_metrics, s->runtime_journal->fd); + + s->runtime_journal->metrics = s->runtime_metrics; + s->runtime_journal->compress = s->compress; + + server_fix_perms(s, s->runtime_journal, 0); + } + } + + return r; +} + +static int server_flush_to_var(Server *s) { + char path[] = "/run/log/journal/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + Object *o = NULL; + int r; + sd_id128_t machine; + sd_journal *j; + usec_t ts; + + assert(s); + + if (!s->runtime_journal) + return 0; + + ts = now(CLOCK_MONOTONIC); + if (s->var_available_timestamp + RECHECK_VAR_AVAILABLE_USEC > ts) + return 0; + + s->var_available_timestamp = ts; + + system_journal_open(s); + + if (!s->system_journal) + return 0; + + log_info("Flushing to /var..."); + + r = sd_id128_get_machine(&machine); + if (r < 0) { + log_error("Failed to get machine id: %s", strerror(-r)); + return r; + } + + r = sd_journal_open(&j, SD_JOURNAL_RUNTIME_ONLY); + if (r < 0) { + log_error("Failed to read runtime journal: %s", strerror(-r)); + return r; + } + + SD_JOURNAL_FOREACH(j) { + JournalFile *f; + + f = j->current_file; + assert(f && f->current_offset > 0); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) { + log_error("Can't read entry: %s", strerror(-r)); + goto finish; + } + + r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL); + if (r == -E2BIG) { + log_info("Allocation limit reached."); + + journal_file_post_change(s->system_journal); + server_rotate(s); + server_vacuum(s); + + r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL); + } + + if (r < 0) { + log_error("Can't write entry: %s", strerror(-r)); + goto finish; + } + } + +finish: + journal_file_post_change(s->system_journal); + + journal_file_close(s->runtime_journal); + s->runtime_journal = NULL; + + if (r >= 0) { + sd_id128_to_string(machine, path + 17); + rm_rf(path, false, true, false); + } + + return r; +} + +static int server_read_proc_kmsg(Server *s) { + ssize_t l; + assert(s); + assert(s->proc_kmsg_fd >= 0); + + l = read(s->proc_kmsg_fd, s->proc_kmsg_buffer + s->proc_kmsg_length, sizeof(s->proc_kmsg_buffer) - 1 - s->proc_kmsg_length); + if (l < 0) { + + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_error("Failed to read from kernel: %m"); + return -errno; + } + + s->proc_kmsg_length += l; + + proc_kmsg_scan(s); + return 1; +} + +static int server_flush_proc_kmsg(Server *s) { + int r; + + assert(s); + + if (s->proc_kmsg_fd < 0) + return 0; + + log_info("Flushing /proc/kmsg..."); + + for (;;) { + r = server_read_proc_kmsg(s); + if (r < 0) + return r; + + if (r == 0) + break; + } + + return 0; +} + +static int process_event(Server *s, struct epoll_event *ev) { + assert(s); + + if (ev->data.fd == s->signal_fd) { + struct signalfd_siginfo sfsi; + ssize_t n; + + if (ev->events != EPOLLIN) { + log_info("Got invalid event from epoll."); + return -EIO; + } + + n = read(s->signal_fd, &sfsi, sizeof(sfsi)); + if (n != sizeof(sfsi)) { + + if (n >= 0) + return -EIO; + + if (errno == EINTR || errno == EAGAIN) + return 1; + + return -errno; + } + + if (sfsi.ssi_signo == SIGUSR1) { + server_flush_to_var(s); + return 0; + } + + log_debug("Received SIG%s", signal_to_string(sfsi.ssi_signo)); + return 0; + + } else if (ev->data.fd == s->proc_kmsg_fd) { + int r; + + if (ev->events != EPOLLIN) { + log_info("Got invalid event from epoll."); + return -EIO; + } + + r = server_read_proc_kmsg(s); + if (r < 0) + return r; + + return 1; + + } else if (ev->data.fd == s->native_fd || + ev->data.fd == s->syslog_fd) { + + if (ev->events != EPOLLIN) { + log_info("Got invalid event from epoll."); + return -EIO; + } + + for (;;) { + struct msghdr msghdr; + struct iovec iovec; + struct ucred *ucred = NULL; + struct timeval *tv = NULL; + struct cmsghdr *cmsg; + char *label = NULL; + size_t label_len = 0; + union { + struct cmsghdr cmsghdr; + + /* We use NAME_MAX space for the + * SELinux label here. The kernel + * currently enforces no limit, but + * according to suggestions from the + * SELinux people this will change and + * it will probably be identical to + * NAME_MAX. For now we use that, but + * this should be updated one day when + * the final limit is known.*/ + uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) + + CMSG_SPACE(sizeof(struct timeval)) + + CMSG_SPACE(sizeof(int)) + /* fd */ + CMSG_SPACE(NAME_MAX)]; /* selinux label */ + } control; + ssize_t n; + int v; + int *fds = NULL; + unsigned n_fds = 0; + + if (ioctl(ev->data.fd, SIOCINQ, &v) < 0) { + log_error("SIOCINQ failed: %m"); + return -errno; + } + + if (s->buffer_size < (size_t) v) { + void *b; + size_t l; + + l = MAX(LINE_MAX + (size_t) v, s->buffer_size * 2); + b = realloc(s->buffer, l+1); + + if (!b) { + log_error("Couldn't increase buffer."); + return -ENOMEM; + } + + s->buffer_size = l; + s->buffer = b; + } + + zero(iovec); + iovec.iov_base = s->buffer; + iovec.iov_len = s->buffer_size; + + zero(control); + zero(msghdr); + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + n = recvmsg(ev->data.fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + if (n < 0) { + + if (errno == EINTR || errno == EAGAIN) + return 1; + + log_error("recvmsg() failed: %m"); + return -errno; + } + + for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg; cmsg = CMSG_NXTHDR(&msghdr, cmsg)) { + + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_CREDENTIALS && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) + ucred = (struct ucred*) CMSG_DATA(cmsg); + else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_SECURITY) { + label = (char*) CMSG_DATA(cmsg); + label_len = cmsg->cmsg_len - CMSG_LEN(0); + } else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SO_TIMESTAMP && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) + tv = (struct timeval*) CMSG_DATA(cmsg); + else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + fds = (int*) CMSG_DATA(cmsg); + n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + } + } + + if (ev->data.fd == s->syslog_fd) { + char *e; + + if (n > 0 && n_fds == 0) { + e = memchr(s->buffer, '\n', n); + if (e) + *e = 0; + else + s->buffer[n] = 0; + + process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len); + } else if (n_fds > 0) + log_warning("Got file descriptors via syslog socket. Ignoring."); + + } else { + if (n > 0 && n_fds == 0) + process_native_message(s, s->buffer, n, ucred, tv, label, label_len); + else if (n == 0 && n_fds == 1) + process_native_file(s, fds[0], ucred, tv, label, label_len); + else if (n_fds > 0) + log_warning("Got too many file descriptors via native socket. Ignoring."); + } + + close_many(fds, n_fds); + } + + return 1; + + } else if (ev->data.fd == s->stdout_fd) { + + if (ev->events != EPOLLIN) { + log_info("Got invalid event from epoll."); + return -EIO; + } + + stdout_stream_new(s); + return 1; + + } else { + StdoutStream *stream; + + if ((ev->events|EPOLLIN|EPOLLHUP) != (EPOLLIN|EPOLLHUP)) { + log_info("Got invalid event from epoll."); + return -EIO; + } + + /* If it is none of the well-known fds, it must be an + * stdout stream fd. Note that this is a bit ugly here + * (since we rely that none of the well-known fds + * could be interpreted as pointer), but nonetheless + * safe, since the well-known fds would never get an + * fd > 4096, i.e. beyond the first memory page */ + + stream = ev->data.ptr; + + if (stdout_stream_process(stream) <= 0) + stdout_stream_free(stream); + + return 1; + } + + log_error("Unknown event."); + return 0; +} + +static int open_syslog_socket(Server *s) { + union sockaddr_union sa; + int one, r; + struct epoll_event ev; + + assert(s); + + if (s->syslog_fd < 0) { + + s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (s->syslog_fd < 0) { + log_error("socket() failed: %m"); + return -errno; + } + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, "/dev/log", sizeof(sa.un.sun_path)); + + unlink(sa.un.sun_path); + + r = bind(s->syslog_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path)); + if (r < 0) { + log_error("bind() failed: %m"); + return -errno; + } + + chmod(sa.un.sun_path, 0666); + } else + fd_nonblock(s->syslog_fd, 1); + + one = 1; + r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); + if (r < 0) { + log_error("SO_PASSCRED failed: %m"); + return -errno; + } + +#ifdef HAVE_SELINUX + one = 1; + r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)); + if (r < 0) + log_warning("SO_PASSSEC failed: %m"); +#endif + + one = 1; + r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)); + if (r < 0) { + log_error("SO_TIMESTAMP failed: %m"); + return -errno; + } + + zero(ev); + ev.events = EPOLLIN; + ev.data.fd = s->syslog_fd; + if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->syslog_fd, &ev) < 0) { + log_error("Failed to add syslog server fd to epoll object: %m"); + return -errno; + } + + return 0; +} + +static int open_native_socket(Server*s) { + union sockaddr_union sa; + int one, r; + struct epoll_event ev; + + assert(s); + + if (s->native_fd < 0) { + + s->native_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (s->native_fd < 0) { + log_error("socket() failed: %m"); + return -errno; + } + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, "/run/systemd/journal/socket", sizeof(sa.un.sun_path)); + + unlink(sa.un.sun_path); + + r = bind(s->native_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path)); + if (r < 0) { + log_error("bind() failed: %m"); + return -errno; + } + + chmod(sa.un.sun_path, 0666); + } else + fd_nonblock(s->native_fd, 1); + + one = 1; + r = setsockopt(s->native_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); + if (r < 0) { + log_error("SO_PASSCRED failed: %m"); + return -errno; + } + +#ifdef HAVE_SELINUX + one = 1; + r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)); + if (r < 0) + log_warning("SO_PASSSEC failed: %m"); +#endif + + one = 1; + r = setsockopt(s->native_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)); + if (r < 0) { + log_error("SO_TIMESTAMP failed: %m"); + return -errno; + } + + zero(ev); + ev.events = EPOLLIN; + ev.data.fd = s->native_fd; + if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->native_fd, &ev) < 0) { + log_error("Failed to add native server fd to epoll object: %m"); + return -errno; + } + + return 0; +} + +static int open_stdout_socket(Server *s) { + union sockaddr_union sa; + int r; + struct epoll_event ev; + + assert(s); + + if (s->stdout_fd < 0) { + + s->stdout_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (s->stdout_fd < 0) { + log_error("socket() failed: %m"); + return -errno; + } + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, "/run/systemd/journal/stdout", sizeof(sa.un.sun_path)); + + unlink(sa.un.sun_path); + + r = bind(s->stdout_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path)); + if (r < 0) { + log_error("bind() failed: %m"); + return -errno; + } + + chmod(sa.un.sun_path, 0666); + + if (listen(s->stdout_fd, SOMAXCONN) < 0) { + log_error("liste() failed: %m"); + return -errno; + } + } else + fd_nonblock(s->stdout_fd, 1); + + zero(ev); + ev.events = EPOLLIN; + ev.data.fd = s->stdout_fd; + if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->stdout_fd, &ev) < 0) { + log_error("Failed to add stdout server fd to epoll object: %m"); + return -errno; + } + + return 0; +} + +static int open_proc_kmsg(Server *s) { + struct epoll_event ev; + + assert(s); + + if (!s->import_proc_kmsg) + return 0; + + + s->proc_kmsg_fd = open("/proc/kmsg", O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (s->proc_kmsg_fd < 0) { + log_warning("Failed to open /proc/kmsg, ignoring: %m"); + return 0; + } + + zero(ev); + ev.events = EPOLLIN; + ev.data.fd = s->proc_kmsg_fd; + if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->proc_kmsg_fd, &ev) < 0) { + log_error("Failed to add /proc/kmsg fd to epoll object: %m"); + return -errno; + } + + return 0; +} + +static int open_signalfd(Server *s) { + sigset_t mask; + struct epoll_event ev; + + assert(s); + + assert_se(sigemptyset(&mask) == 0); + sigset_add_many(&mask, SIGINT, SIGTERM, SIGUSR1, -1); + assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); + + s->signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (s->signal_fd < 0) { + log_error("signalfd(): %m"); + return -errno; + } + + zero(ev); + ev.events = EPOLLIN; + ev.data.fd = s->signal_fd; + + if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->signal_fd, &ev) < 0) { + log_error("epoll_ctl(): %m"); + return -errno; + } + + return 0; +} + +static int server_parse_proc_cmdline(Server *s) { + char *line, *w, *state; + int r; + size_t l; + + if (detect_container(NULL) > 0) + return 0; + + r = read_one_line_file("/proc/cmdline", &line); + if (r < 0) { + log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r)); + return 0; + } + + FOREACH_WORD_QUOTED(w, l, line, state) { + char *word; + + word = strndup(w, l); + if (!word) { + r = -ENOMEM; + goto finish; + } + + if (startswith(word, "systemd_journald.forward_to_syslog=")) { + r = parse_boolean(word + 35); + if (r < 0) + log_warning("Failed to parse forward to syslog switch %s. Ignoring.", word + 35); + else + s->forward_to_syslog = r; + } else if (startswith(word, "systemd_journald.forward_to_kmsg=")) { + r = parse_boolean(word + 33); + if (r < 0) + log_warning("Failed to parse forward to kmsg switch %s. Ignoring.", word + 33); + else + s->forward_to_kmsg = r; + } else if (startswith(word, "systemd_journald.forward_to_console=")) { + r = parse_boolean(word + 36); + if (r < 0) + log_warning("Failed to parse forward to console switch %s. Ignoring.", word + 36); + else + s->forward_to_console = r; + } + + free(word); + } + + r = 0; + +finish: + free(line); + return r; +} + +static int server_parse_config_file(Server *s) { + FILE *f; + const char *fn; + int r; + + assert(s); + + fn = "/etc/systemd/journald.conf"; + f = fopen(fn, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + log_warning("Failed to open configuration file %s: %m", fn); + return -errno; + } + + r = config_parse(fn, f, "Journal\0", config_item_perf_lookup, (void*) journald_gperf_lookup, false, s); + if (r < 0) + log_warning("Failed to parse configuration file: %s", strerror(-r)); + + fclose(f); + + return r; +} + +static int server_init(Server *s) { + int n, r, fd; + + assert(s); + + zero(*s); + s->syslog_fd = s->native_fd = s->stdout_fd = s->signal_fd = s->epoll_fd = s->proc_kmsg_fd = -1; + s->compress = true; + + s->rate_limit_interval = DEFAULT_RATE_LIMIT_INTERVAL; + s->rate_limit_burst = DEFAULT_RATE_LIMIT_BURST; + + s->forward_to_syslog = true; + s->import_proc_kmsg = true; + + memset(&s->system_metrics, 0xFF, sizeof(s->system_metrics)); + memset(&s->runtime_metrics, 0xFF, sizeof(s->runtime_metrics)); + + server_parse_config_file(s); + server_parse_proc_cmdline(s); + + s->user_journals = hashmap_new(trivial_hash_func, trivial_compare_func); + if (!s->user_journals) { + log_error("Out of memory."); + return -ENOMEM; + } + + s->epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (s->epoll_fd < 0) { + log_error("Failed to create epoll object: %m"); + return -errno; + } + + n = sd_listen_fds(true); + if (n < 0) { + log_error("Failed to read listening file descriptors from environment: %s", strerror(-n)); + return n; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { + + if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/socket", 0) > 0) { + + if (s->native_fd >= 0) { + log_error("Too many native sockets passed."); + return -EINVAL; + } + + s->native_fd = fd; + + } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, "/run/systemd/journal/stdout", 0) > 0) { + + if (s->stdout_fd >= 0) { + log_error("Too many stdout sockets passed."); + return -EINVAL; + } + + s->stdout_fd = fd; + + } else if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/dev/log", 0) > 0) { + + if (s->syslog_fd >= 0) { + log_error("Too many /dev/log sockets passed."); + return -EINVAL; + } + + s->syslog_fd = fd; + + } else { + log_error("Unknown socket passed."); + return -EINVAL; + } + } + + r = open_syslog_socket(s); + if (r < 0) + return r; + + r = open_native_socket(s); + if (r < 0) + return r; + + r = open_stdout_socket(s); + if (r < 0) + return r; + + r = open_proc_kmsg(s); + if (r < 0) + return r; + + r = open_signalfd(s); + if (r < 0) + return r; + + s->rate_limit = journal_rate_limit_new(s->rate_limit_interval, s->rate_limit_burst); + if (!s->rate_limit) + return -ENOMEM; + + r = system_journal_open(s); + if (r < 0) + return r; + + return 0; +} + +static void server_done(Server *s) { + JournalFile *f; + assert(s); + + while (s->stdout_streams) + stdout_stream_free(s->stdout_streams); + + if (s->system_journal) + journal_file_close(s->system_journal); + + if (s->runtime_journal) + journal_file_close(s->runtime_journal); + + while ((f = hashmap_steal_first(s->user_journals))) + journal_file_close(f); + + hashmap_free(s->user_journals); + + if (s->epoll_fd >= 0) + close_nointr_nofail(s->epoll_fd); + + if (s->signal_fd >= 0) + close_nointr_nofail(s->signal_fd); + + if (s->syslog_fd >= 0) + close_nointr_nofail(s->syslog_fd); + + if (s->native_fd >= 0) + close_nointr_nofail(s->native_fd); + + if (s->stdout_fd >= 0) + close_nointr_nofail(s->stdout_fd); + + if (s->proc_kmsg_fd >= 0) + close_nointr_nofail(s->proc_kmsg_fd); + + if (s->rate_limit) + journal_rate_limit_free(s->rate_limit); + + free(s->buffer); +} + +int main(int argc, char *argv[]) { + Server server; + int r; + + /* if (getppid() != 1) { */ + /* log_error("This program should be invoked by init only."); */ + /* return EXIT_FAILURE; */ + /* } */ + + if (argc > 1) { + log_error("This program does not take arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_CONSOLE); + log_set_facility(LOG_SYSLOG); + log_parse_environment(); + log_open(); + + umask(0022); + + r = server_init(&server); + if (r < 0) + goto finish; + + server_vacuum(&server); + server_flush_to_var(&server); + server_flush_proc_kmsg(&server); + + log_debug("systemd-journald running as pid %lu", (unsigned long) getpid()); + driver_message(&server, SD_MESSAGE_JOURNAL_START, "Journal started"); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + for (;;) { + struct epoll_event event; + + r = epoll_wait(server.epoll_fd, &event, 1, -1); + if (r < 0) { + + if (errno == EINTR) + continue; + + log_error("epoll_wait() failed: %m"); + r = -errno; + goto finish; + } else if (r == 0) + break; + + r = process_event(&server, &event); + if (r < 0) + goto finish; + else if (r == 0) + break; + } + + log_debug("systemd-journald stopped as pid %lu", (unsigned long) getpid()); + driver_message(&server, SD_MESSAGE_JOURNAL_STOP, "Journal stopped"); + +finish: + sd_notify(false, + "STATUS=Shutting down..."); + + server_done(&server); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/journal/journald.conf b/src/journal/journald.conf new file mode 100644 index 000000000..710b0aa94 --- /dev/null +++ b/src/journal/journald.conf @@ -0,0 +1,25 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# See system-journald.conf(5) for details + +[Journal] +#Compress=yes +#RateLimitInterval=10s +#RateLimitBurst=200 +#SystemMaxUse= +#SystemKeepFree= +#SystemMaxFileSize= +#SystemMinFileSize= +#RuntimeMaxUse= +#RuntimeKeepFree= +#RuntimeMaxFileSize= +#RuntimeMinFileSize= +#ForwardToSyslog=yes +#ForwardToKMsg=no +#ForwardToConsole=no +#ImportKernel=yes diff --git a/src/journal/journald.h b/src/journal/journald.h new file mode 100644 index 000000000..6160991ed --- /dev/null +++ b/src/journal/journald.h @@ -0,0 +1,86 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foojournaldhfoo +#define foojournaldhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "journal-file.h" +#include "hashmap.h" +#include "util.h" +#include "journal-rate-limit.h" +#include "list.h" + +typedef struct StdoutStream StdoutStream; + +typedef struct Server { + int epoll_fd; + int signal_fd; + int syslog_fd; + int native_fd; + int stdout_fd; + int proc_kmsg_fd; + + JournalFile *runtime_journal; + JournalFile *system_journal; + Hashmap *user_journals; + + uint64_t seqnum; + + char *buffer; + size_t buffer_size; + + JournalRateLimit *rate_limit; + usec_t rate_limit_interval; + unsigned rate_limit_burst; + + JournalMetrics runtime_metrics; + JournalMetrics system_metrics; + + bool compress; + + bool forward_to_kmsg; + bool forward_to_syslog; + bool forward_to_console; + + bool import_proc_kmsg; + char proc_kmsg_buffer[LINE_MAX+1]; + size_t proc_kmsg_length; + + uint64_t cached_available_space; + usec_t cached_available_space_timestamp; + + uint64_t var_available_timestamp; + + gid_t file_gid; + bool file_gid_valid; + + LIST_HEAD(StdoutStream, stdout_streams); + unsigned n_stdout_streams; +} Server; + +/* gperf lookup function */ +const struct ConfigPerfItem* journald_gperf_lookup(const char *key, unsigned length); + +#endif diff --git a/src/journal/libsystemd-journal.pc.in b/src/journal/libsystemd-journal.pc.in new file mode 100644 index 000000000..13cc8208d --- /dev/null +++ b/src/journal/libsystemd-journal.pc.in @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: systemd +Description: systemd Journal Utility Library +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ +Requires: libsystemd-id128 = @PACKAGE_VERSION@ +Libs: -L${libdir} -lsystemd-journal +Cflags: -I${includedir} diff --git a/src/journal/libsystemd-journal.sym b/src/journal/libsystemd-journal.sym new file mode 100644 index 000000000..cd434aea2 --- /dev/null +++ b/src/journal/libsystemd-journal.sym @@ -0,0 +1,53 @@ +/*** + This file is part of systemd. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +***/ + +/* Original symbols from systemd v38 */ + +LIBSYSTEMD_JOURNAL_38 { +global: + sd_journal_print; + sd_journal_printv; + sd_journal_send; + sd_journal_sendv; + sd_journal_stream_fd; + sd_journal_open; + sd_journal_close; + sd_journal_previous; + sd_journal_next; + sd_journal_previous_skip; + sd_journal_next_skip; + sd_journal_get_realtime_usec; + sd_journal_get_monotonic_usec; + sd_journal_get_data; + sd_journal_enumerate_data; + sd_journal_restart_data; + sd_journal_add_match; + sd_journal_flush_matches; + sd_journal_seek_head; + sd_journal_seek_tail; + sd_journal_seek_monotonic_usec; + sd_journal_seek_realtime_usec; + sd_journal_seek_cursor; + sd_journal_get_cursor; + sd_journal_query_unique; + sd_journal_enumerate_unique; + sd_journal_restart_unique; + sd_journal_get_fd; + sd_journal_process; +local: + *; +}; + +LIBSYSTEMD_JOURNAL_45 { +global: + sd_journal_print_with_location; + sd_journal_printv_with_location; + sd_journal_send_with_location; + sd_journal_sendv_with_location; +} LIBSYSTEMD_JOURNAL_38; diff --git a/src/journal/lookup3.c b/src/journal/lookup3.c new file mode 100644 index 000000000..b90093a5e --- /dev/null +++ b/src/journal/lookup3.c @@ -0,0 +1,1003 @@ +/* Slightly modified by Lennart Poettering, to avoid name clashes, and + * unexport a few functions. */ + +#include "lookup3.h" + +/* +------------------------------------------------------------------------------- +lookup3.c, by Bob Jenkins, May 2006, Public Domain. + +These are functions for producing 32-bit hashes for hash table lookup. +hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() +are externally useful functions. Routines to test the hash are included +if SELF_TEST is defined. You can use this free for any purpose. It's in +the public domain. It has no warranty. + +You probably want to use hashlittle(). hashlittle() and hashbig() +hash byte arrays. hashlittle() is is faster than hashbig() on +little-endian machines. Intel and AMD are little-endian machines. +On second thought, you probably want hashlittle2(), which is identical to +hashlittle() except it returns two 32-bit hashes for the price of one. +You could implement hashbig2() if you wanted but I haven't bothered here. + +If you want to find a hash of, say, exactly 7 integers, do + a = i1; b = i2; c = i3; + mix(a,b,c); + a += i4; b += i5; c += i6; + mix(a,b,c); + a += i7; + final(a,b,c); +then use c as the hash value. If you have a variable length array of +4-byte integers to hash, use hashword(). If you have a byte array (like +a character string), use hashlittle(). If you have several byte arrays, or +a mix of things, see the comments above hashlittle(). + +Why is this so big? I read 12 bytes at a time into 3 4-byte integers, +then mix those integers. This is fast (you can do a lot more thorough +mixing with 12*3 instructions on 3 integers than you can with 3 instructions +on 1 byte), but shoehorning those bytes into integers efficiently is messy. +------------------------------------------------------------------------------- +*/ +/* #define SELF_TEST 1 */ + +#include /* defines printf for tests */ +#include /* defines time_t for timings in the test */ +#include /* defines uint32_t etc */ +#include /* attempt to define endianness */ +#ifdef linux +# include /* attempt to define endianness */ +#endif + +/* + * My best guess at if you are big-endian or little-endian. This may + * need adjustment. + */ +#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) +# define HASH_LITTLE_ENDIAN 1 +# define HASH_BIG_ENDIAN 0 +#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) +# define HASH_LITTLE_ENDIAN 0 +# define HASH_BIG_ENDIAN 1 +#else +# define HASH_LITTLE_ENDIAN 0 +# define HASH_BIG_ENDIAN 0 +#endif + +#define hashsize(n) ((uint32_t)1<<(n)) +#define hashmask(n) (hashsize(n)-1) +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. + +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). + +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. + +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. + +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c + +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} + +/* +-------------------------------------------------------------------- + This works on all machines. To be useful, it requires + -- that the key be an array of uint32_t's, and + -- that the length be the number of uint32_t's in the key + + The function hashword() is identical to hashlittle() on little-endian + machines, and identical to hashbig() on big-endian machines, + except that the length has to be measured in uint32_ts rather than in + bytes. hashlittle() is more complicated than hashword() only because + hashlittle() has to dance around fitting the key bytes into registers. +-------------------------------------------------------------------- +*/ +uint32_t jenkins_hashword( +const uint32_t *k, /* the key, an array of uint32_t values */ +size_t length, /* the length of the key, in uint32_ts */ +uint32_t initval) /* the previous hash, or an arbitrary value */ +{ + uint32_t a,b,c; + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + (((uint32_t)length)<<2) + initval; + + /*------------------------------------------------- handle most of the key */ + while (length > 3) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 3; + k += 3; + } + + /*------------------------------------------- handle the last 3 uint32_t's */ + switch(length) /* all the case statements fall through */ + { + case 3 : c+=k[2]; + case 2 : b+=k[1]; + case 1 : a+=k[0]; + final(a,b,c); + case 0: /* case 0: nothing left to add */ + break; + } + /*------------------------------------------------------ report the result */ + return c; +} + + +/* +-------------------------------------------------------------------- +hashword2() -- same as hashword(), but take two seeds and return two +32-bit values. pc and pb must both be nonnull, and *pc and *pb must +both be initialized with seeds. If you pass in (*pb)==0, the output +(*pc) will be the same as the return value from hashword(). +-------------------------------------------------------------------- +*/ +void jenkins_hashword2 ( +const uint32_t *k, /* the key, an array of uint32_t values */ +size_t length, /* the length of the key, in uint32_ts */ +uint32_t *pc, /* IN: seed OUT: primary hash value */ +uint32_t *pb) /* IN: more seed OUT: secondary hash value */ +{ + uint32_t a,b,c; + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)(length<<2)) + *pc; + c += *pb; + + /*------------------------------------------------- handle most of the key */ + while (length > 3) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 3; + k += 3; + } + + /*------------------------------------------- handle the last 3 uint32_t's */ + switch(length) /* all the case statements fall through */ + { + case 3 : c+=k[2]; + case 2 : b+=k[1]; + case 1 : a+=k[0]; + final(a,b,c); + case 0: /* case 0: nothing left to add */ + break; + } + /*------------------------------------------------------ report the result */ + *pc=c; *pb=b; +} + + +/* +------------------------------------------------------------------------------- +hashlittle() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + length : the length of the key, counting by bytes + initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Two keys differing by one or two bits will have +totally different hash values. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (uint8_t **)k, do it like this: + for (i=0, h=0; i 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : return c; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : return c; + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : return c; /* zero length requires no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; + case 11: c+=((uint32_t)k[10])<<16; + case 10: c+=((uint32_t)k[9])<<8; + case 9 : c+=k[8]; + case 8 : b+=((uint32_t)k[7])<<24; + case 7 : b+=((uint32_t)k[6])<<16; + case 6 : b+=((uint32_t)k[5])<<8; + case 5 : b+=k[4]; + case 4 : a+=((uint32_t)k[3])<<24; + case 3 : a+=((uint32_t)k[2])<<16; + case 2 : a+=((uint32_t)k[1])<<8; + case 1 : a+=k[0]; + break; + case 0 : return c; + } + } + + final(a,b,c); + return c; +} + + +/* + * hashlittle2: return 2 32-bit hash values + * + * This is identical to hashlittle(), except it returns two 32-bit hash + * values instead of just one. This is good enough for hash table + * lookup with 2^^64 buckets, or if you want a second hash if you're not + * happy with the first, or if you want a probably-unique 64-bit ID for + * the key. *pc is better mixed than *pb, so use *pc first. If you want + * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)". + */ +void jenkins_hashlittle2( + const void *key, /* the key to hash */ + size_t length, /* length of the key */ + uint32_t *pc, /* IN: primary initval, OUT: primary hash */ + uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */ +{ + uint32_t a,b,c; /* internal state */ + union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; + c += *pb; + + u.ptr = key; + if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { + const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ + + /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; + case 11: c+=((uint32_t)k[10])<<16; + case 10: c+=((uint32_t)k[9])<<8; + case 9 : c+=k[8]; + case 8 : b+=((uint32_t)k[7])<<24; + case 7 : b+=((uint32_t)k[6])<<16; + case 6 : b+=((uint32_t)k[5])<<8; + case 5 : b+=k[4]; + case 4 : a+=((uint32_t)k[3])<<24; + case 3 : a+=((uint32_t)k[2])<<16; + case 2 : a+=((uint32_t)k[1])<<8; + case 1 : a+=k[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + } + + final(a,b,c); + *pc=c; *pb=b; +} + + + +/* + * hashbig(): + * This is the same as hashword() on big-endian machines. It is different + * from hashlittle() on all machines. hashbig() takes advantage of + * big-endian byte ordering. + */ +uint32_t jenkins_hashbig( const void *key, size_t length, uint32_t initval) +{ + uint32_t a,b,c; + union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */ + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)length) + initval; + + u.ptr = key; + if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) { + const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ + + /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]<<8" actually reads beyond the end of the string, but + * then shifts out the part it's not allowed to read. Because the + * string is aligned, the illegal read is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff00; a+=k[0]; break; + case 6 : b+=k[1]&0xffff0000; a+=k[0]; break; + case 5 : b+=k[1]&0xff000000; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff00; break; + case 2 : a+=k[0]&0xffff0000; break; + case 1 : a+=k[0]&0xff000000; break; + case 0 : return c; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) /* all the case statements fall through */ + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<8; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<16; /* fall through */ + case 9 : c+=((uint32_t)k8[8])<<24; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<8; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<16; /* fall through */ + case 5 : b+=((uint32_t)k8[4])<<24; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<8; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<16; /* fall through */ + case 1 : a+=((uint32_t)k8[0])<<24; break; + case 0 : return c; + } + +#endif /* !VALGRIND */ + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += ((uint32_t)k[0])<<24; + a += ((uint32_t)k[1])<<16; + a += ((uint32_t)k[2])<<8; + a += ((uint32_t)k[3]); + b += ((uint32_t)k[4])<<24; + b += ((uint32_t)k[5])<<16; + b += ((uint32_t)k[6])<<8; + b += ((uint32_t)k[7]); + c += ((uint32_t)k[8])<<24; + c += ((uint32_t)k[9])<<16; + c += ((uint32_t)k[10])<<8; + c += ((uint32_t)k[11]); + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=k[11]; + case 11: c+=((uint32_t)k[10])<<8; + case 10: c+=((uint32_t)k[9])<<16; + case 9 : c+=((uint32_t)k[8])<<24; + case 8 : b+=k[7]; + case 7 : b+=((uint32_t)k[6])<<8; + case 6 : b+=((uint32_t)k[5])<<16; + case 5 : b+=((uint32_t)k[4])<<24; + case 4 : a+=k[3]; + case 3 : a+=((uint32_t)k[2])<<8; + case 2 : a+=((uint32_t)k[1])<<16; + case 1 : a+=((uint32_t)k[0])<<24; + break; + case 0 : return c; + } + } + + final(a,b,c); + return c; +} + + +#ifdef SELF_TEST + +/* used for timings */ +void driver1() +{ + uint8_t buf[256]; + uint32_t i; + uint32_t h=0; + time_t a,z; + + time(&a); + for (i=0; i<256; ++i) buf[i] = 'x'; + for (i=0; i<1; ++i) + { + h = hashlittle(&buf[0],1,h); + } + time(&z); + if (z-a > 0) printf("time %d %.8x\n", z-a, h); +} + +/* check that every input bit changes every output bit half the time */ +#define HASHSTATE 1 +#define HASHLEN 1 +#define MAXPAIR 60 +#define MAXLEN 70 +void driver2() +{ + uint8_t qa[MAXLEN+1], qb[MAXLEN+2], *a = &qa[0], *b = &qb[1]; + uint32_t c[HASHSTATE], d[HASHSTATE], i=0, j=0, k, l, m=0, z; + uint32_t e[HASHSTATE],f[HASHSTATE],g[HASHSTATE],h[HASHSTATE]; + uint32_t x[HASHSTATE],y[HASHSTATE]; + uint32_t hlen; + + printf("No more than %d trials should ever be needed \n",MAXPAIR/2); + for (hlen=0; hlen < MAXLEN; ++hlen) + { + z=0; + for (i=0; i>(8-j)); + c[0] = hashlittle(a, hlen, m); + b[i] ^= ((k+1)<>(8-j)); + d[0] = hashlittle(b, hlen, m); + /* check every bit is 1, 0, set, and not set at least once */ + for (l=0; lz) z=k; + if (k==MAXPAIR) + { + printf("Some bit didn't change: "); + printf("%.8x %.8x %.8x %.8x %.8x %.8x ", + e[0],f[0],g[0],h[0],x[0],y[0]); + printf("i %d j %d m %d len %d\n", i, j, m, hlen); + } + if (z==MAXPAIR) goto done; + } + } + } + done: + if (z < MAXPAIR) + { + printf("Mix success %2d bytes %2d initvals ",i,m); + printf("required %d trials\n", z/2); + } + } + printf("\n"); +} + +/* Check for reading beyond the end of the buffer and alignment problems */ +void driver3() +{ + uint8_t buf[MAXLEN+20], *b; + uint32_t len; + uint8_t q[] = "This is the time for all good men to come to the aid of their country..."; + uint32_t h; + uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country..."; + uint32_t i; + uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country..."; + uint32_t j; + uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country..."; + uint32_t ref,x,y; + uint8_t *p; + + printf("Endianness. These lines should all be the same (for values filled in):\n"); + printf("%.8x %.8x %.8x\n", + hashword((const uint32_t *)q, (sizeof(q)-1)/4, 13), + hashword((const uint32_t *)q, (sizeof(q)-5)/4, 13), + hashword((const uint32_t *)q, (sizeof(q)-9)/4, 13)); + p = q; + printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n", + hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13), + hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13), + hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13), + hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13), + hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13), + hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13)); + p = &qq[1]; + printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n", + hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13), + hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13), + hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13), + hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13), + hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13), + hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13)); + p = &qqq[2]; + printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n", + hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13), + hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13), + hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13), + hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13), + hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13), + hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13)); + p = &qqqq[3]; + printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n", + hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13), + hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13), + hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13), + hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13), + hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13), + hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13)); + printf("\n"); + + /* check that hashlittle2 and hashlittle produce the same results */ + i=47; j=0; + hashlittle2(q, sizeof(q), &i, &j); + if (hashlittle(q, sizeof(q), 47) != i) + printf("hashlittle2 and hashlittle mismatch\n"); + + /* check that hashword2 and hashword produce the same results */ + len = 0xdeadbeef; + i=47, j=0; + hashword2(&len, 1, &i, &j); + if (hashword(&len, 1, 47) != i) + printf("hashword2 and hashword mismatch %x %x\n", + i, hashword(&len, 1, 47)); + + /* check hashlittle doesn't read before or after the ends of the string */ + for (h=0, b=buf+1; h<8; ++h, ++b) + { + for (i=0; i +#include + +uint32_t jenkins_hashword(const uint32_t *k, size_t length, uint32_t initval); +void jenkins_hashword2(const uint32_t *k, size_t length, uint32_t *pc, uint32_t *pb); + +uint32_t jenkins_hashlittle(const void *key, size_t length, uint32_t initval); +void jenkins_hashlittle2(const void *key, size_t length, uint32_t *pc, uint32_t *pb); + +uint32_t jenkins_hashbig(const void *key, size_t length, uint32_t initval); + +static inline uint64_t hash64(const void *data, size_t length) { + uint32_t a = 0, b = 0; + + jenkins_hashlittle2(data, length, &a, &b); + + return ((uint64_t) a << 32ULL) | (uint64_t) b; +} + +#endif diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c new file mode 100644 index 000000000..92ba57822 --- /dev/null +++ b/src/journal/sd-journal.c @@ -0,0 +1,1636 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "sd-journal.h" +#include "journal-def.h" +#include "journal-file.h" +#include "hashmap.h" +#include "list.h" +#include "lookup3.h" +#include "compress.h" +#include "journal-internal.h" + +#define JOURNAL_FILES_MAX 1024 + +static void detach_location(sd_journal *j) { + Iterator i; + JournalFile *f; + + assert(j); + + j->current_file = NULL; + j->current_field = 0; + + HASHMAP_FOREACH(f, j->files, i) + f->current_offset = 0; +} + +static void reset_location(sd_journal *j) { + assert(j); + + detach_location(j); + zero(j->current_location); +} + +static void init_location(Location *l, JournalFile *f, Object *o) { + assert(l); + assert(f); + assert(o->object.type == OBJECT_ENTRY); + + l->type = LOCATION_DISCRETE; + l->seqnum = le64toh(o->entry.seqnum); + l->seqnum_id = f->header->seqnum_id; + l->realtime = le64toh(o->entry.realtime); + l->monotonic = le64toh(o->entry.monotonic); + l->boot_id = o->entry.boot_id; + l->xor_hash = le64toh(o->entry.xor_hash); + + l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true; +} + +static void set_location(sd_journal *j, JournalFile *f, Object *o, uint64_t offset) { + assert(j); + assert(f); + assert(o); + + init_location(&j->current_location, f, o); + + j->current_file = f; + j->current_field = 0; + + f->current_offset = offset; +} + +static int same_field(const void *_a, size_t s, const void *_b, size_t t) { + const uint8_t *a = _a, *b = _b; + size_t j; + bool a_good = false, b_good = false, different = false; + + for (j = 0; j < s && j < t; j++) { + + if (a[j] == '=') + a_good = true; + if (b[j] == '=') + b_good = true; + if (a[j] != b[j]) + different = true; + + if (a_good && b_good) + return different ? 0 : 1; + } + + return -EINVAL; +} + +_public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) { + Match *m, *after = NULL; + le64_t le_hash; + + if (!j) + return -EINVAL; + if (!data) + return -EINVAL; + if (size <= 0) + return -EINVAL; + + le_hash = htole64(hash64(data, size)); + + LIST_FOREACH(matches, m, j->matches) { + int r; + + if (m->le_hash == le_hash && + m->size == size && + memcmp(m->data, data, size) == 0) + return 0; + + r = same_field(data, size, m->data, m->size); + if (r < 0) + return r; + else if (r > 0) + after = m; + } + + m = new0(Match, 1); + if (!m) + return -ENOMEM; + + m->size = size; + + m->data = malloc(m->size); + if (!m->data) { + free(m); + return -ENOMEM; + } + + memcpy(m->data, data, size); + m->le_hash = le_hash; + + /* Matches for the same fields we order adjacent to each + * other */ + LIST_INSERT_AFTER(Match, matches, j->matches, after, m); + j->n_matches ++; + + detach_location(j); + + return 0; +} + +_public_ void sd_journal_flush_matches(sd_journal *j) { + if (!j) + return; + + while (j->matches) { + Match *m = j->matches; + + LIST_REMOVE(Match, matches, j->matches, m); + free(m->data); + free(m); + } + + j->n_matches = 0; + + detach_location(j); +} + +static int compare_order(JournalFile *af, Object *ao, + JournalFile *bf, Object *bo) { + + uint64_t a, b; + + assert(af); + assert(ao); + assert(bf); + assert(bo); + + /* We operate on two different files here, hence we can access + * two objects at the same time, which we normally can't. + * + * If contents and timestamps match, these entries are + * identical, even if the seqnum does not match */ + + if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id) && + ao->entry.monotonic == bo->entry.monotonic && + ao->entry.realtime == bo->entry.realtime && + ao->entry.xor_hash == bo->entry.xor_hash) + return 0; + + if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) { + + /* If this is from the same seqnum source, compare + * seqnums */ + a = le64toh(ao->entry.seqnum); + b = le64toh(bo->entry.seqnum); + + if (a < b) + return -1; + if (a > b) + return 1; + + /* Wow! This is weird, different data but the same + * seqnums? Something is borked, but let's make the + * best of it and compare by time. */ + } + + if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) { + + /* If the boot id matches compare monotonic time */ + a = le64toh(ao->entry.monotonic); + b = le64toh(bo->entry.monotonic); + + if (a < b) + return -1; + if (a > b) + return 1; + } + + /* Otherwise compare UTC time */ + a = le64toh(ao->entry.realtime); + b = le64toh(ao->entry.realtime); + + if (a < b) + return -1; + if (a > b) + return 1; + + /* Finally, compare by contents */ + a = le64toh(ao->entry.xor_hash); + b = le64toh(ao->entry.xor_hash); + + if (a < b) + return -1; + if (a > b) + return 1; + + return 0; +} + +static int compare_with_location(JournalFile *af, Object *ao, Location *l) { + uint64_t a; + + assert(af); + assert(ao); + assert(l); + assert(l->type == LOCATION_DISCRETE); + + if (l->monotonic_set && + sd_id128_equal(ao->entry.boot_id, l->boot_id) && + l->realtime_set && + le64toh(ao->entry.realtime) == l->realtime && + l->xor_hash_set && + le64toh(ao->entry.xor_hash) == l->xor_hash) + return 0; + + if (l->seqnum_set && + sd_id128_equal(af->header->seqnum_id, l->seqnum_id)) { + + a = le64toh(ao->entry.seqnum); + + if (a < l->seqnum) + return -1; + if (a > l->seqnum) + return 1; + } + + if (l->monotonic_set && + sd_id128_equal(ao->entry.boot_id, l->boot_id)) { + + a = le64toh(ao->entry.monotonic); + + if (a < l->monotonic) + return -1; + if (a > l->monotonic) + return 1; + } + + if (l->realtime_set) { + + a = le64toh(ao->entry.realtime); + + if (a < l->realtime) + return -1; + if (a > l->realtime) + return 1; + } + + if (l->xor_hash_set) { + a = le64toh(ao->entry.xor_hash); + + if (a < l->xor_hash) + return -1; + if (a > l->xor_hash) + return 1; + } + + return 0; +} + +static int find_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) { + Object *o = NULL; + uint64_t p = 0; + int r; + + assert(j); + + if (!j->matches) { + /* No matches is simple */ + + if (j->current_location.type == LOCATION_HEAD) + r = journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p); + else if (j->current_location.type == LOCATION_TAIL) + r = journal_file_next_entry(f, NULL, 0, DIRECTION_UP, &o, &p); + else if (j->current_location.seqnum_set && + sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id)) + r = journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, &o, &p); + else if (j->current_location.monotonic_set) { + r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p); + + if (r == -ENOENT) { + /* boot id unknown in this file */ + if (j->current_location.realtime_set) + r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p); + else + r = journal_file_next_entry(f, NULL, 0, direction, &o, &p); + } + } else if (j->current_location.realtime_set) + r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p); + else + r = journal_file_next_entry(f, NULL, 0, direction, &o, &p); + + if (r <= 0) + return r; + + } else { + Match *m, *term_match = NULL; + Object *to = NULL; + uint64_t tp = 0; + + /* We have matches, first, let's jump to the monotonic + * position if we have any, since it implies a + * match. */ + + if (j->current_location.type == LOCATION_DISCRETE && + j->current_location.monotonic_set) { + + r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p); + if (r <= 0) + return r == -ENOENT ? 0 : r; + } + + LIST_FOREACH(matches, m, j->matches) { + Object *c, *d; + uint64_t cp, dp; + + r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), &d, &dp); + if (r <= 0) + return r; + + if (j->current_location.type == LOCATION_HEAD) + r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, &c, &cp); + else if (j->current_location.type == LOCATION_TAIL) + r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, &c, &cp); + else if (j->current_location.seqnum_set && + sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id)) + r = journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, &c, &cp); + else if (j->current_location.realtime_set) + r = journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, &c, &cp); + else + r = journal_file_next_entry_for_data(f, NULL, 0, dp, direction, &c, &cp); + + if (r < 0) + return r; + + if (!term_match) { + term_match = m; + + if (r > 0) { + to = c; + tp = cp; + } + } else if (same_field(term_match->data, term_match->size, m->data, m->size)) { + + /* Same field as previous match... */ + if (r > 0) { + + /* Find the earliest of the OR matches */ + + if (!to || + (direction == DIRECTION_DOWN && cp < tp) || + (direction == DIRECTION_UP && cp > tp)) { + to = c; + tp = cp; + } + + } + + } else { + + /* Previous term is finished, did anything match? */ + if (!to) + return 0; + + /* Find the last of the AND matches */ + if (!o || + (direction == DIRECTION_DOWN && tp > p) || + (direction == DIRECTION_UP && tp < p)) { + o = to; + p = tp; + } + + term_match = m; + + if (r > 0) { + to = c; + tp = cp; + } else { + to = NULL; + tp = 0; + } + } + } + + /* Last term is finished, did anything match? */ + if (!to) + return 0; + + if (!o || + (direction == DIRECTION_DOWN && tp > p) || + (direction == DIRECTION_UP && tp < p)) { + o = to; + p = tp; + } + + if (!o) + return 0; + } + + if (ret) + *ret = o; + + if (offset) + *offset = p; + + return 1; +} + +static int next_with_matches(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) { + int r; + uint64_t cp; + Object *c; + + assert(j); + assert(f); + assert(ret); + assert(offset); + + c = *ret; + cp = *offset; + + if (!j->matches) { + /* No matches is easy */ + + r = journal_file_next_entry(f, c, cp, direction, &c, &cp); + if (r <= 0) + return r; + + if (ret) + *ret = c; + if (offset) + *offset = cp; + return 1; + } + + /* So there are matches we have to adhere to, let's find the + * first entry that matches all of them */ + + for (;;) { + uint64_t np, n; + bool found, term_result = false; + Match *m, *term_match = NULL; + Object *npo = NULL; + + n = journal_file_entry_n_items(c); + + /* Make sure we don't match the entry we are starting + * from. */ + found = cp != *offset; + + np = 0; + LIST_FOREACH(matches, m, j->matches) { + uint64_t q, k; + Object *qo = NULL; + + /* Let's check if this is the beginning of a + * new term, i.e. has a different field prefix + * as the preceeding match. */ + if (!term_match) { + term_match = m; + term_result = false; + } else if (!same_field(term_match->data, term_match->size, m->data, m->size)) { + if (!term_result) + found = false; + + term_match = m; + term_result = false; + } + + for (k = 0; k < n; k++) + if (c->entry.items[k].hash == m->le_hash) + break; + + if (k >= n) { + /* Hmm, didn't find any field that + * matched this rule, so ignore this + * match. Go on with next match */ + continue; + } + + term_result = true; + + /* Hmm, so, this field matched, let's remember + * where we'd have to try next, in case the other + * matches are not OK */ + + r = journal_file_next_entry_for_data(f, c, cp, le64toh(c->entry.items[k].object_offset), direction, &qo, &q); + /* This pointer is invalidated if the window was + * remapped. May need to re-fetch it later */ + c = NULL; + if (r < 0) + return r; + + if (r > 0) { + + if (direction == DIRECTION_DOWN) { + if (q > np) { + np = q; + npo = qo; + } + } else { + if (np == 0 || q < np) { + np = q; + npo = qo; + } + } + } + } + + /* Check the last term */ + if (term_match && !term_result) + found = false; + + /* Did this entry match against all matches? */ + if (found) { + if (ret) { + if (c == NULL) { + /* Re-fetch the entry */ + r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c); + if (r < 0) + return r; + } + *ret = c; + } + if (offset) + *offset = cp; + return 1; + } + + /* Did we find a subsequent entry? */ + if (np == 0) + return 0; + + /* Hmm, ok, this entry only matched partially, so + * let's try another one */ + cp = np; + c = npo; + } +} + +static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) { + Object *c; + uint64_t cp; + int compare_value, r; + + assert(j); + assert(f); + + if (f->current_offset > 0) { + cp = f->current_offset; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c); + if (r < 0) + return r; + + r = next_with_matches(j, f, direction, &c, &cp); + if (r <= 0) + return r; + + compare_value = 1; + } else { + r = find_location(j, f, direction, &c, &cp); + if (r <= 0) + return r; + + compare_value = 0; + } + + for (;;) { + bool found; + + if (j->current_location.type == LOCATION_DISCRETE) { + int k; + + k = compare_with_location(f, c, &j->current_location); + if (direction == DIRECTION_DOWN) + found = k >= compare_value; + else + found = k <= -compare_value; + } else + found = true; + + if (found) { + if (ret) + *ret = c; + if (offset) + *offset = cp; + return 1; + } + + r = next_with_matches(j, f, direction, &c, &cp); + if (r <= 0) + return r; + } +} + +static int real_journal_next(sd_journal *j, direction_t direction) { + JournalFile *f, *new_current = NULL; + Iterator i; + int r; + uint64_t new_offset = 0; + Object *new_entry = NULL; + + if (!j) + return -EINVAL; + + HASHMAP_FOREACH(f, j->files, i) { + Object *o; + uint64_t p; + bool found; + + r = next_beyond_location(j, f, direction, &o, &p); + if (r < 0) + return r; + else if (r == 0) + continue; + + if (!new_current) + found = true; + else { + int k; + + k = compare_order(f, o, new_current, new_entry); + + if (direction == DIRECTION_DOWN) + found = k < 0; + else + found = k > 0; + } + + if (found) { + new_current = f; + new_entry = o; + new_offset = p; + } + } + + if (!new_current) + return 0; + + set_location(j, new_current, new_entry, new_offset); + + return 1; +} + +_public_ int sd_journal_next(sd_journal *j) { + return real_journal_next(j, DIRECTION_DOWN); +} + +_public_ int sd_journal_previous(sd_journal *j) { + return real_journal_next(j, DIRECTION_UP); +} + +static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) { + int c = 0, r; + + if (!j) + return -EINVAL; + + if (skip == 0) { + /* If this is not a discrete skip, then at least + * resolve the current location */ + if (j->current_location.type != LOCATION_DISCRETE) + return real_journal_next(j, direction); + + return 0; + } + + do { + r = real_journal_next(j, direction); + if (r < 0) + return r; + + if (r == 0) + return c; + + skip--; + c++; + } while (skip > 0); + + return c; +} + +_public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) { + return real_journal_next_skip(j, DIRECTION_DOWN, skip); +} + +_public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) { + return real_journal_next_skip(j, DIRECTION_UP, skip); +} + +_public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) { + Object *o; + int r; + char bid[33], sid[33]; + + if (!j) + return -EINVAL; + if (!cursor) + return -EINVAL; + + if (!j->current_file || j->current_file->current_offset <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o); + if (r < 0) + return r; + + sd_id128_to_string(j->current_file->header->seqnum_id, sid); + sd_id128_to_string(o->entry.boot_id, bid); + + if (asprintf(cursor, + "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s", + sid, (unsigned long long) le64toh(o->entry.seqnum), + bid, (unsigned long long) le64toh(o->entry.monotonic), + (unsigned long long) le64toh(o->entry.realtime), + (unsigned long long) le64toh(o->entry.xor_hash), + file_name_from_path(j->current_file->path)) < 0) + return -ENOMEM; + + return 1; +} + +_public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) { + char *w; + size_t l; + char *state; + unsigned long long seqnum, monotonic, realtime, xor_hash; + bool + seqnum_id_set = false, + seqnum_set = false, + boot_id_set = false, + monotonic_set = false, + realtime_set = false, + xor_hash_set = false; + sd_id128_t seqnum_id, boot_id; + + if (!j) + return -EINVAL; + if (!cursor) + return -EINVAL; + + FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) { + char *item; + int k = 0; + + if (l < 2 || w[1] != '=') + return -EINVAL; + + item = strndup(w, l); + if (!item) + return -ENOMEM; + + switch (w[0]) { + + case 's': + seqnum_id_set = true; + k = sd_id128_from_string(w+2, &seqnum_id); + break; + + case 'i': + seqnum_set = true; + if (sscanf(w+2, "%llx", &seqnum) != 1) + k = -EINVAL; + break; + + case 'b': + boot_id_set = true; + k = sd_id128_from_string(w+2, &boot_id); + break; + + case 'm': + monotonic_set = true; + if (sscanf(w+2, "%llx", &monotonic) != 1) + k = -EINVAL; + break; + + case 't': + realtime_set = true; + if (sscanf(w+2, "%llx", &realtime) != 1) + k = -EINVAL; + break; + + case 'x': + xor_hash_set = true; + if (sscanf(w+2, "%llx", &xor_hash) != 1) + k = -EINVAL; + break; + } + + free(item); + + if (k < 0) + return k; + } + + if ((!seqnum_set || !seqnum_id_set) && + (!monotonic_set || !boot_id_set) && + !realtime_set) + return -EINVAL; + + reset_location(j); + + j->current_location.type = LOCATION_DISCRETE; + + if (realtime_set) { + j->current_location.realtime = (uint64_t) realtime; + j->current_location.realtime_set = true; + } + + if (seqnum_set && seqnum_id_set) { + j->current_location.seqnum = (uint64_t) seqnum; + j->current_location.seqnum_id = seqnum_id; + j->current_location.seqnum_set = true; + } + + if (monotonic_set && boot_id_set) { + j->current_location.monotonic = (uint64_t) monotonic; + j->current_location.boot_id = boot_id; + j->current_location.monotonic_set = true; + } + + if (xor_hash_set) { + j->current_location.xor_hash = (uint64_t) xor_hash; + j->current_location.xor_hash_set = true; + } + + return 0; +} + +_public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) { + if (!j) + return -EINVAL; + + reset_location(j); + j->current_location.type = LOCATION_DISCRETE; + j->current_location.boot_id = boot_id; + j->current_location.monotonic = usec; + j->current_location.monotonic_set = true; + + return 0; +} + +_public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) { + if (!j) + return -EINVAL; + + reset_location(j); + j->current_location.type = LOCATION_DISCRETE; + j->current_location.realtime = usec; + j->current_location.realtime_set = true; + + return 0; +} + +_public_ int sd_journal_seek_head(sd_journal *j) { + if (!j) + return -EINVAL; + + reset_location(j); + j->current_location.type = LOCATION_HEAD; + + return 0; +} + +_public_ int sd_journal_seek_tail(sd_journal *j) { + if (!j) + return -EINVAL; + + reset_location(j); + j->current_location.type = LOCATION_TAIL; + + return 0; +} + +static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) { + char *fn; + int r; + JournalFile *f; + + assert(j); + assert(prefix); + assert(filename); + + if ((j->flags & SD_JOURNAL_SYSTEM_ONLY) && + !startswith(filename, "system.journal")) + return 0; + + if (dir) + fn = join(prefix, "/", dir, "/", filename, NULL); + else + fn = join(prefix, "/", filename, NULL); + + if (!fn) + return -ENOMEM; + + if (hashmap_get(j->files, fn)) { + free(fn); + return 0; + } + + if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) { + log_debug("Too many open journal files, not adding %s, ignoring.", fn); + free(fn); + return 0; + } + + r = journal_file_open(fn, O_RDONLY, 0, NULL, &f); + free(fn); + + if (r < 0) { + if (errno == ENOENT) + return 0; + + return r; + } + + /* journal_file_dump(f); */ + + r = hashmap_put(j->files, f->path, f); + if (r < 0) { + journal_file_close(f); + return r; + } + + log_debug("File %s got added.", f->path); + + return 0; +} + +static int remove_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) { + char *fn; + JournalFile *f; + + assert(j); + assert(prefix); + assert(filename); + + if (dir) + fn = join(prefix, "/", dir, "/", filename, NULL); + else + fn = join(prefix, "/", filename, NULL); + + if (!fn) + return -ENOMEM; + + f = hashmap_get(j->files, fn); + free(fn); + + if (!f) + return 0; + + hashmap_remove(j->files, f->path); + journal_file_close(f); + + log_debug("File %s got removed.", f->path); + return 0; +} + +static int add_directory(sd_journal *j, const char *prefix, const char *dir) { + char *fn; + int r; + DIR *d; + int wd; + sd_id128_t id, mid; + + assert(j); + assert(prefix); + assert(dir); + + if ((j->flags & SD_JOURNAL_LOCAL_ONLY) && + (sd_id128_from_string(dir, &id) < 0 || + sd_id128_get_machine(&mid) < 0 || + !sd_id128_equal(id, mid))) + return 0; + + fn = join(prefix, "/", dir, NULL); + if (!fn) + return -ENOMEM; + + d = opendir(fn); + + if (!d) { + free(fn); + if (errno == ENOENT) + return 0; + + return -errno; + } + + wd = inotify_add_watch(j->inotify_fd, fn, + IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| + IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT| + IN_DONT_FOLLOW|IN_ONLYDIR); + if (wd > 0) { + if (hashmap_put(j->inotify_wd_dirs, INT_TO_PTR(wd), fn) < 0) + inotify_rm_watch(j->inotify_fd, wd); + else + fn = NULL; + } + + free(fn); + + for (;;) { + struct dirent buf, *de; + + r = readdir_r(d, &buf, &de); + if (r != 0 || !de) + break; + + if (!dirent_is_file_with_suffix(de, ".journal")) + continue; + + r = add_file(j, prefix, dir, de->d_name); + if (r < 0) + log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r)); + } + + closedir(d); + + log_debug("Directory %s/%s got added.", prefix, dir); + + return 0; +} + +static void remove_directory_wd(sd_journal *j, int wd) { + char *p; + + assert(j); + assert(wd > 0); + + if (j->inotify_fd >= 0) + inotify_rm_watch(j->inotify_fd, wd); + + p = hashmap_remove(j->inotify_wd_dirs, INT_TO_PTR(wd)); + + if (p) { + log_debug("Directory %s got removed.", p); + free(p); + } +} + +static void add_root_wd(sd_journal *j, const char *p) { + int wd; + char *k; + + assert(j); + assert(p); + + wd = inotify_add_watch(j->inotify_fd, p, + IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| + IN_DONT_FOLLOW|IN_ONLYDIR); + if (wd <= 0) + return; + + k = strdup(p); + if (!k || hashmap_put(j->inotify_wd_roots, INT_TO_PTR(wd), k) < 0) { + inotify_rm_watch(j->inotify_fd, wd); + free(k); + } +} + +static void remove_root_wd(sd_journal *j, int wd) { + char *p; + + assert(j); + assert(wd > 0); + + if (j->inotify_fd >= 0) + inotify_rm_watch(j->inotify_fd, wd); + + p = hashmap_remove(j->inotify_wd_roots, INT_TO_PTR(wd)); + + if (p) { + log_debug("Root %s got removed.", p); + free(p); + } +} + +_public_ int sd_journal_open(sd_journal **ret, int flags) { + sd_journal *j; + const char *p; + const char search_paths[] = + "/run/log/journal\0" + "/var/log/journal\0"; + int r; + + if (!ret) + return -EINVAL; + + if (flags & ~(SD_JOURNAL_LOCAL_ONLY| + SD_JOURNAL_RUNTIME_ONLY| + SD_JOURNAL_SYSTEM_ONLY)) + return -EINVAL; + + j = new0(sd_journal, 1); + if (!j) + return -ENOMEM; + + j->flags = flags; + + j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (j->inotify_fd < 0) { + r = -errno; + goto fail; + } + + j->files = hashmap_new(string_hash_func, string_compare_func); + if (!j->files) { + r = -ENOMEM; + goto fail; + } + + j->inotify_wd_dirs = hashmap_new(trivial_hash_func, trivial_compare_func); + j->inotify_wd_roots = hashmap_new(trivial_hash_func, trivial_compare_func); + + if (!j->inotify_wd_dirs || !j->inotify_wd_roots) { + r = -ENOMEM; + goto fail; + } + + /* We ignore most errors here, since the idea is to only open + * what's actually accessible, and ignore the rest. */ + + NULSTR_FOREACH(p, search_paths) { + DIR *d; + + if ((flags & SD_JOURNAL_RUNTIME_ONLY) && + !path_startswith(p, "/run")) + continue; + + d = opendir(p); + if (!d) { + if (errno != ENOENT) + log_debug("Failed to open %s: %m", p); + continue; + } + + add_root_wd(j, p); + + for (;;) { + struct dirent buf, *de; + sd_id128_t id; + + r = readdir_r(d, &buf, &de); + if (r != 0 || !de) + break; + + if (dirent_is_file_with_suffix(de, ".journal")) { + r = add_file(j, p, NULL, de->d_name); + if (r < 0) + log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r)); + + } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) && + sd_id128_from_string(de->d_name, &id) >= 0) { + + r = add_directory(j, p, de->d_name); + if (r < 0) + log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r)); + } + } + + closedir(d); + } + + *ret = j; + return 0; + +fail: + sd_journal_close(j); + + return r; +}; + +_public_ void sd_journal_close(sd_journal *j) { + if (!j) + return; + + if (j->inotify_wd_dirs) { + void *k; + + while ((k = hashmap_first_key(j->inotify_wd_dirs))) + remove_directory_wd(j, PTR_TO_INT(k)); + + hashmap_free(j->inotify_wd_dirs); + } + + if (j->inotify_wd_roots) { + void *k; + + while ((k = hashmap_first_key(j->inotify_wd_roots))) + remove_root_wd(j, PTR_TO_INT(k)); + + hashmap_free(j->inotify_wd_roots); + } + + if (j->files) { + JournalFile *f; + + while ((f = hashmap_steal_first(j->files))) + journal_file_close(f); + + hashmap_free(j->files); + } + + sd_journal_flush_matches(j); + + if (j->inotify_fd >= 0) + close_nointr_nofail(j->inotify_fd); + + free(j); +} + +_public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) { + Object *o; + JournalFile *f; + int r; + + if (!j) + return -EINVAL; + if (!ret) + return -EINVAL; + + f = j->current_file; + if (!f) + return -EADDRNOTAVAIL; + + if (f->current_offset <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) + return r; + + *ret = le64toh(o->entry.realtime); + return 0; +} + +_public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) { + Object *o; + JournalFile *f; + int r; + sd_id128_t id; + + if (!j) + return -EINVAL; + if (!ret) + return -EINVAL; + + f = j->current_file; + if (!f) + return -EADDRNOTAVAIL; + + if (f->current_offset <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) + return r; + + if (ret_boot_id) + *ret_boot_id = o->entry.boot_id; + else { + r = sd_id128_get_boot(&id); + if (r < 0) + return r; + + if (!sd_id128_equal(id, o->entry.boot_id)) + return -ESTALE; + } + + *ret = le64toh(o->entry.monotonic); + return 0; +} + +_public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) { + JournalFile *f; + uint64_t i, n; + size_t field_length; + int r; + Object *o; + + if (!j) + return -EINVAL; + if (!field) + return -EINVAL; + if (!data) + return -EINVAL; + if (!size) + return -EINVAL; + + if (isempty(field) || strchr(field, '=')) + return -EINVAL; + + f = j->current_file; + if (!f) + return -EADDRNOTAVAIL; + + if (f->current_offset <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) + return r; + + field_length = strlen(field); + + n = journal_file_entry_n_items(o); + for (i = 0; i < n; i++) { + uint64_t p, l; + le64_t le_hash; + size_t t; + + p = le64toh(o->entry.items[i].object_offset); + le_hash = o->entry.items[i].hash; + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + if (le_hash != o->data.hash) + return -EBADMSG; + + l = le64toh(o->object.size) - offsetof(Object, data.payload); + + if (o->object.flags & OBJECT_COMPRESSED) { + +#ifdef HAVE_XZ + if (uncompress_startswith(o->data.payload, l, + &f->compress_buffer, &f->compress_buffer_size, + field, field_length, '=')) { + + uint64_t rsize; + + if (!uncompress_blob(o->data.payload, l, + &f->compress_buffer, &f->compress_buffer_size, &rsize)) + return -EBADMSG; + + *data = f->compress_buffer; + *size = (size_t) rsize; + + return 0; + } +#else + return -EPROTONOSUPPORT; +#endif + + } else if (l >= field_length+1 && + memcmp(o->data.payload, field, field_length) == 0 && + o->data.payload[field_length] == '=') { + + t = (size_t) l; + + if ((uint64_t) t != l) + return -E2BIG; + + *data = o->data.payload; + *size = t; + + return 0; + } + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) + return r; + } + + return -ENOENT; +} + +_public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) { + JournalFile *f; + uint64_t p, l, n; + le64_t le_hash; + int r; + Object *o; + size_t t; + + if (!j) + return -EINVAL; + if (!data) + return -EINVAL; + if (!size) + return -EINVAL; + + f = j->current_file; + if (!f) + return -EADDRNOTAVAIL; + + if (f->current_offset <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) + return r; + + n = journal_file_entry_n_items(o); + if (j->current_field >= n) + return 0; + + p = le64toh(o->entry.items[j->current_field].object_offset); + le_hash = o->entry.items[j->current_field].hash; + r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (r < 0) + return r; + + if (le_hash != o->data.hash) + return -EBADMSG; + + l = le64toh(o->object.size) - offsetof(Object, data.payload); + t = (size_t) l; + + /* We can't read objects larger than 4G on a 32bit machine */ + if ((uint64_t) t != l) + return -E2BIG; + + if (o->object.flags & OBJECT_COMPRESSED) { +#ifdef HAVE_XZ + uint64_t rsize; + + if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize)) + return -EBADMSG; + + *data = f->compress_buffer; + *size = (size_t) rsize; +#else + return -EPROTONOSUPPORT; +#endif + } else { + *data = o->data.payload; + *size = t; + } + + j->current_field ++; + + return 1; +} + +_public_ void sd_journal_restart_data(sd_journal *j) { + if (!j) + return; + + j->current_field = 0; +} + +_public_ int sd_journal_get_fd(sd_journal *j) { + if (!j) + return -EINVAL; + + return j->inotify_fd; +} + +static void process_inotify_event(sd_journal *j, struct inotify_event *e) { + char *p; + int r; + + assert(j); + assert(e); + + /* Is this a subdirectory we watch? */ + p = hashmap_get(j->inotify_wd_dirs, INT_TO_PTR(e->wd)); + if (p) { + + if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) { + + /* Event for a journal file */ + + if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) { + r = add_file(j, p, NULL, e->name); + if (r < 0) + log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r)); + } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) { + + r = remove_file(j, p, NULL, e->name); + if (r < 0) + log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r)); + } + + } else if (e->len == 0) { + + /* Event for the directory itself */ + + if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)) + remove_directory_wd(j, e->wd); + } + + return; + } + + /* Must be the root directory then? */ + p = hashmap_get(j->inotify_wd_roots, INT_TO_PTR(e->wd)); + if (p) { + sd_id128_t id; + + if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) { + + /* Event for a journal file */ + + if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) { + r = add_file(j, p, NULL, e->name); + if (r < 0) + log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r)); + } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) { + + r = remove_file(j, p, NULL, e->name); + if (r < 0) + log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r)); + } + + } else if ((e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) { + + /* Event for subdirectory */ + + if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) { + + r = add_directory(j, p, e->name); + if (r < 0) + log_debug("Failed to add directory %s/%s: %s", p, e->name, strerror(-r)); + } + } + + return; + } + + if (e->mask & IN_IGNORED) + return; + + log_warning("Unknown inotify event."); +} + +_public_ int sd_journal_process(sd_journal *j) { + uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX]; + + if (!j) + return -EINVAL; + + for (;;) { + struct inotify_event *e; + ssize_t l; + + l = read(j->inotify_fd, buffer, sizeof(buffer)); + if (l < 0) { + if (errno == EINTR || errno == EAGAIN) + return 0; + + return -errno; + } + + e = (struct inotify_event*) buffer; + while (l > 0) { + size_t step; + + process_inotify_event(j, e); + + step = sizeof(struct inotify_event) + e->len; + assert(step <= (size_t) l); + + e = (struct inotify_event*) ((uint8_t*) e + step); + l -= step; + } + } +} + +/* _public_ int sd_journal_query_unique(sd_journal *j, const char *field) { */ +/* if (!j) */ +/* return -EINVAL; */ +/* if (!field) */ +/* return -EINVAL; */ + +/* return -ENOTSUP; */ +/* } */ + +/* _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { */ +/* if (!j) */ +/* return -EINVAL; */ +/* if (!data) */ +/* return -EINVAL; */ +/* if (!l) */ +/* return -EINVAL; */ + +/* return -ENOTSUP; */ +/* } */ + +/* _public_ void sd_journal_restart_unique(sd_journal *j) { */ +/* if (!j) */ +/* return; */ +/* } */ diff --git a/src/journal/sparse-endian.h b/src/journal/sparse-endian.h new file mode 100644 index 000000000..eb4dbf361 --- /dev/null +++ b/src/journal/sparse-endian.h @@ -0,0 +1,87 @@ +/* Copyright (c) 2012 Josh Triplett + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef SPARSE_ENDIAN_H +#define SPARSE_ENDIAN_H + +#include +#include + +#ifdef __CHECKER__ +#define __bitwise __attribute__((bitwise)) +#define __force __attribute__((force)) +#else +#define __bitwise +#define __force +#endif + +typedef uint16_t __bitwise le16_t; +typedef uint16_t __bitwise be16_t; +typedef uint32_t __bitwise le32_t; +typedef uint32_t __bitwise be32_t; +typedef uint64_t __bitwise le64_t; +typedef uint64_t __bitwise be64_t; + +#undef htobe16 +#undef htole16 +#undef be16toh +#undef le16toh +#undef htobe32 +#undef htole32 +#undef be32toh +#undef le32toh +#undef htobe64 +#undef htole64 +#undef be64toh +#undef le64toh + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define bswap_16_on_le(x) __bswap_16(x) +#define bswap_32_on_le(x) __bswap_32(x) +#define bswap_64_on_le(x) __bswap_64(x) +#define bswap_16_on_be(x) (x) +#define bswap_32_on_be(x) (x) +#define bswap_64_on_be(x) (x) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define bswap_16_on_le(x) (x) +#define bswap_32_on_le(x) (x) +#define bswap_64_on_le(x) (x) +#define bswap_16_on_be(x) __bswap_16(x) +#define bswap_32_on_be(x) __bswap_32(x) +#define bswap_64_on_be(x) __bswap_64(x) +#endif + +static inline le16_t htole16(uint16_t value) { return (le16_t __force) bswap_16_on_be(value); } +static inline le32_t htole32(uint32_t value) { return (le32_t __force) bswap_32_on_be(value); } +static inline le64_t htole64(uint64_t value) { return (le64_t __force) bswap_64_on_be(value); } + +static inline be16_t htobe16(uint16_t value) { return (be16_t __force) bswap_16_on_le(value); } +static inline be32_t htobe32(uint32_t value) { return (be32_t __force) bswap_32_on_le(value); } +static inline be64_t htobe64(uint64_t value) { return (be64_t __force) bswap_64_on_le(value); } + +static inline uint16_t le16toh(le16_t value) { return bswap_16_on_be((uint16_t __force)value); } +static inline uint32_t le32toh(le32_t value) { return bswap_32_on_be((uint32_t __force)value); } +static inline uint64_t le64toh(le64_t value) { return bswap_64_on_be((uint64_t __force)value); } + +static inline uint16_t be16toh(be16_t value) { return bswap_16_on_le((uint16_t __force)value); } +static inline uint32_t be32toh(be32_t value) { return bswap_32_on_le((uint32_t __force)value); } +static inline uint64_t be64toh(be64_t value) { return bswap_64_on_le((uint64_t __force)value); } + +#endif /* SPARSE_ENDIAN_H */ diff --git a/src/journal/test-journal-send.c b/src/journal/test-journal-send.c new file mode 100644 index 000000000..2f9987dcd --- /dev/null +++ b/src/journal/test-journal-send.c @@ -0,0 +1,32 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +int main(int argc, char *argv[]) { + sd_journal_print(LOG_INFO, "piepapo"); + + sd_journal_send("MESSAGE=foobar", + "VALUE=%i", 7, + NULL); + + return 0; +} diff --git a/src/journal/test-journal.c b/src/journal/test-journal.c new file mode 100644 index 000000000..a023509b7 --- /dev/null +++ b/src/journal/test-journal.c @@ -0,0 +1,120 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include + +#include "journal-file.h" +#include "log.h" + +int main(int argc, char *argv[]) { + dual_timestamp ts; + JournalFile *f; + struct iovec iovec; + static const char test[] = "test", test2[] = "test2"; + Object *o; + uint64_t p; + + log_set_max_level(LOG_DEBUG); + + unlink("test.journal"); + + assert_se(journal_file_open("test.journal", O_RDWR|O_CREAT, 0666, NULL, &f) == 0); + + dual_timestamp_get(&ts); + + iovec.iov_base = (void*) test; + iovec.iov_len = strlen(test); + assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); + + iovec.iov_base = (void*) test2; + iovec.iov_len = strlen(test2); + assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); + + iovec.iov_base = (void*) test; + iovec.iov_len = strlen(test); + assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); + + journal_file_dump(f); + + assert(journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p) == 1); + assert(le64toh(o->entry.seqnum) == 1); + + assert(journal_file_next_entry(f, o, p, DIRECTION_DOWN, &o, &p) == 1); + assert(le64toh(o->entry.seqnum) == 2); + + assert(journal_file_next_entry(f, o, p, DIRECTION_DOWN, &o, &p) == 1); + assert(le64toh(o->entry.seqnum) == 3); + + assert(journal_file_next_entry(f, o, p, DIRECTION_DOWN, &o, &p) == 0); + + assert(journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p) == 1); + assert(le64toh(o->entry.seqnum) == 1); + + assert(journal_file_skip_entry(f, o, p, 2, &o, &p) == 1); + assert(le64toh(o->entry.seqnum) == 3); + + assert(journal_file_skip_entry(f, o, p, -2, &o, &p) == 1); + assert(le64toh(o->entry.seqnum) == 1); + + assert(journal_file_skip_entry(f, o, p, -2, &o, &p) == 1); + assert(le64toh(o->entry.seqnum) == 1); + + assert(journal_file_find_data_object(f, test, strlen(test), NULL, &p) == 1); + assert(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_DOWN, &o, NULL) == 1); + assert(le64toh(o->entry.seqnum) == 1); + + assert(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_UP, &o, NULL) == 1); + assert(le64toh(o->entry.seqnum) == 3); + + assert(journal_file_find_data_object(f, test2, strlen(test2), NULL, &p) == 1); + assert(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_UP, &o, NULL) == 1); + assert(le64toh(o->entry.seqnum) == 2); + + assert(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_DOWN, &o, NULL) == 1); + assert(le64toh(o->entry.seqnum) == 2); + + assert(journal_file_find_data_object(f, "quux", 4, NULL, &p) == 0); + + assert(journal_file_move_to_entry_by_seqnum(f, 1, DIRECTION_DOWN, &o, NULL) == 1); + assert(le64toh(o->entry.seqnum) == 1); + + assert(journal_file_move_to_entry_by_seqnum(f, 3, DIRECTION_DOWN, &o, NULL) == 1); + assert(le64toh(o->entry.seqnum) == 3); + + assert(journal_file_move_to_entry_by_seqnum(f, 2, DIRECTION_DOWN, &o, NULL) == 1); + assert(le64toh(o->entry.seqnum) == 2); + + assert(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL) == 0); + + journal_file_rotate(&f); + journal_file_rotate(&f); + + journal_file_close(f); + + journal_directory_vacuum(".", 3000000, 0); + + log_error("Exiting..."); + + return 0; +} diff --git a/src/kmod-setup.c b/src/kmod-setup.c new file mode 100644 index 000000000..debf87130 --- /dev/null +++ b/src/kmod-setup.c @@ -0,0 +1,96 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "macro.h" +#include "execute.h" + +#include "kmod-setup.h" + +static const char * const kmod_table[] = { + "autofs4", "/sys/class/misc/autofs", + "ipv6", "/sys/module/ipv6", + "unix", "/proc/net/unix" +}; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +static void systemd_kmod_log(void *data, int priority, const char *file, int line, + const char *fn, const char *format, va_list args) +{ + log_meta(priority, file, line, fn, format, args); +} +#pragma GCC diagnostic pop + +int kmod_setup(void) { + unsigned i; + struct kmod_ctx *ctx = NULL; + struct kmod_module *mod; + int err; + + for (i = 0; i < ELEMENTSOF(kmod_table); i += 2) { + + if (access(kmod_table[i+1], F_OK) >= 0) + continue; + + log_debug("Your kernel apparently lacks built-in %s support. Might be a good idea to compile it in. " + "We'll now try to work around this by loading the module...", + kmod_table[i]); + + if (!ctx) { + ctx = kmod_new(NULL, NULL); + if (!ctx) { + log_error("Failed to allocate memory for kmod"); + return -ENOMEM; + } + + kmod_set_log_fn(ctx, systemd_kmod_log, NULL); + + kmod_load_resources(ctx); + } + + err = kmod_module_new_from_name(ctx, kmod_table[i], &mod); + if (err < 0) { + log_error("Failed to load module '%s'", kmod_table[i]); + continue; + } + + err = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); + if (err == 0) + log_info("Inserted module '%s'", kmod_module_get_name(mod)); + else if (err == KMOD_PROBE_APPLY_BLACKLIST) + log_info("Module '%s' is blacklisted", kmod_module_get_name(mod)); + else + log_error("Failed to insert '%s'", kmod_module_get_name(mod)); + + kmod_module_unref(mod); + } + + if (ctx) + kmod_unref(ctx); + + return 0; +} diff --git a/src/kmod-setup.h b/src/kmod-setup.h new file mode 100644 index 000000000..496aef3e1 --- /dev/null +++ b/src/kmod-setup.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fookmodsetuphfoo +#define fookmodsetuphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +int kmod_setup(void); + +#endif diff --git a/src/label.c b/src/label.c new file mode 100644 index 000000000..2c887a0fe --- /dev/null +++ b/src/label.c @@ -0,0 +1,413 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include + +#include "label.h" +#include "util.h" + +#ifdef HAVE_SELINUX +#include +#include + +static struct selabel_handle *label_hnd = NULL; + +static int use_selinux_cached = -1; + +static inline bool use_selinux(void) { + + if (use_selinux_cached < 0) + use_selinux_cached = is_selinux_enabled() > 0; + + return use_selinux_cached; +} + +void label_retest_selinux(void) { + use_selinux_cached = -1; +} + +#endif + +int label_init(void) { + int r = 0; + +#ifdef HAVE_SELINUX + usec_t before_timestamp, after_timestamp; + struct mallinfo before_mallinfo, after_mallinfo; + + if (!use_selinux()) + return 0; + + if (label_hnd) + return 0; + + before_mallinfo = mallinfo(); + before_timestamp = now(CLOCK_MONOTONIC); + + label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0); + if (!label_hnd) { + log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, + "Failed to initialize SELinux context: %m"); + r = security_getenforce() == 1 ? -errno : 0; + } else { + char timespan[FORMAT_TIMESPAN_MAX]; + int l; + + after_timestamp = now(CLOCK_MONOTONIC); + after_mallinfo = mallinfo(); + + l = after_mallinfo.uordblks > before_mallinfo.uordblks ? after_mallinfo.uordblks - before_mallinfo.uordblks : 0; + + log_info("Successfully loaded SELinux database in %s, size on heap is %iK.", + format_timespan(timespan, sizeof(timespan), after_timestamp - before_timestamp), + (l+1023)/1024); + } +#endif + + return r; +} + +int label_fix(const char *path, bool ignore_enoent) { + int r = 0; + +#ifdef HAVE_SELINUX + struct stat st; + security_context_t fcon; + + if (!use_selinux() || !label_hnd) + return 0; + + r = lstat(path, &st); + if (r == 0) { + r = selabel_lookup_raw(label_hnd, &fcon, path, st.st_mode); + + /* If there's no label to set, then exit without warning */ + if (r < 0 && errno == ENOENT) + return 0; + + if (r == 0) { + r = lsetfilecon(path, fcon); + freecon(fcon); + + /* If the FS doesn't support labels, then exit without warning */ + if (r < 0 && errno == ENOTSUP) + return 0; + } + } + + if (r < 0) { + /* Ignore ENOENT in some cases */ + if (ignore_enoent && errno == ENOENT) + return 0; + + log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, + "Unable to fix label of %s: %m", path); + r = security_getenforce() == 1 ? -errno : 0; + } +#endif + + return r; +} + +void label_finish(void) { + +#ifdef HAVE_SELINUX + if (use_selinux() && label_hnd) + selabel_close(label_hnd); +#endif +} + +int label_get_create_label_from_exe(const char *exe, char **label) { + + int r = 0; + +#ifdef HAVE_SELINUX + security_context_t mycon = NULL, fcon = NULL; + security_class_t sclass; + + if (!use_selinux()) { + *label = NULL; + return 0; + } + + r = getcon(&mycon); + if (r < 0) + goto fail; + + r = getfilecon(exe, &fcon); + if (r < 0) + goto fail; + + sclass = string_to_security_class("process"); + r = security_compute_create(mycon, fcon, sclass, (security_context_t *) label); + if (r == 0) + log_debug("SELinux Socket context for %s will be set to %s", exe, *label); + +fail: + if (r < 0 && security_getenforce() == 1) + r = -errno; + + freecon(mycon); + freecon(fcon); +#endif + + return r; +} + +int label_fifofile_set(const char *path) { + int r = 0; + +#ifdef HAVE_SELINUX + security_context_t filecon = NULL; + + if (!use_selinux() || !label_hnd) + return 0; + + r = selabel_lookup_raw(label_hnd, &filecon, path, S_IFIFO); + if (r < 0) + r = -errno; + else if (r == 0) { + r = setfscreatecon(filecon); + if (r < 0) { + log_error("Failed to set SELinux file context on %s: %m", path); + r = -errno; + } + + freecon(filecon); + } + + if (r < 0 && security_getenforce() == 0) + r = 0; +#endif + + return r; +} + +int label_symlinkfile_set(const char *path) { + int r = 0; + +#ifdef HAVE_SELINUX + security_context_t filecon = NULL; + + if (!use_selinux() || !label_hnd) + return 0; + + r = selabel_lookup_raw(label_hnd, &filecon, path, S_IFLNK); + if (r < 0) + r = -errno; + else if (r == 0) { + r = setfscreatecon(filecon); + if (r < 0) { + log_error("Failed to set SELinux file context on %s: %m", path); + r = -errno; + } + + freecon(filecon); + } + + if (r < 0 && security_getenforce() == 0) + r = 0; +#endif + + return r; +} + +int label_socket_set(const char *label) { + +#ifdef HAVE_SELINUX + if (!use_selinux()) + return 0; + + if (setsockcreatecon((security_context_t) label) < 0) { + log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, + "Failed to set SELinux context (%s) on socket: %m", label); + + if (security_getenforce() == 1) + return -errno; + } +#endif + + return 0; +} + +void label_file_clear(void) { + +#ifdef HAVE_SELINUX + if (!use_selinux()) + return; + + setfscreatecon(NULL); +#endif +} + +void label_socket_clear(void) { + +#ifdef HAVE_SELINUX + if (!use_selinux()) + return; + + setsockcreatecon(NULL); +#endif +} + +void label_free(const char *label) { + +#ifdef HAVE_SELINUX + if (!use_selinux()) + return; + + freecon((security_context_t) label); +#endif +} + +int label_mkdir(const char *path, mode_t mode) { + + /* Creates a directory and labels it according to the SELinux policy */ + +#ifdef HAVE_SELINUX + int r; + security_context_t fcon = NULL; + + if (!use_selinux() || !label_hnd) + goto skipped; + + if (path_is_absolute(path)) + r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFDIR); + else { + char *newpath; + + newpath = path_make_absolute_cwd(path); + if (!newpath) + return -ENOMEM; + + r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFDIR); + free(newpath); + } + + if (r == 0) + r = setfscreatecon(fcon); + + if (r < 0 && errno != ENOENT) { + log_error("Failed to set security context %s for %s: %m", fcon, path); + + if (security_getenforce() == 1) { + r = -errno; + goto finish; + } + } + + r = mkdir(path, mode); + if (r < 0) + r = -errno; + +finish: + setfscreatecon(NULL); + freecon(fcon); + + return r; + +skipped: +#endif + return mkdir(path, mode) < 0 ? -errno : 0; +} + +int label_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) { + + /* Binds a socket and label its file system object according to the SELinux policy */ + +#ifdef HAVE_SELINUX + int r; + security_context_t fcon = NULL; + const struct sockaddr_un *un; + char *path = NULL; + + assert(fd >= 0); + assert(addr); + assert(addrlen >= sizeof(sa_family_t)); + + if (!use_selinux() || !label_hnd) + goto skipped; + + /* Filter out non-local sockets */ + if (addr->sa_family != AF_UNIX) + goto skipped; + + /* Filter out anonymous sockets */ + if (addrlen < sizeof(sa_family_t) + 1) + goto skipped; + + /* Filter out abstract namespace sockets */ + un = (const struct sockaddr_un*) addr; + if (un->sun_path[0] == 0) + goto skipped; + + path = strndup(un->sun_path, addrlen - offsetof(struct sockaddr_un, sun_path)); + if (!path) + return -ENOMEM; + + if (path_is_absolute(path)) + r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFSOCK); + else { + char *newpath; + + newpath = path_make_absolute_cwd(path); + + if (!newpath) { + free(path); + return -ENOMEM; + } + + r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFSOCK); + free(newpath); + } + + if (r == 0) + r = setfscreatecon(fcon); + + if (r < 0 && errno != ENOENT) { + log_error("Failed to set security context %s for %s: %m", fcon, path); + + if (security_getenforce() == 1) { + r = -errno; + goto finish; + } + } + + r = bind(fd, addr, addrlen); + if (r < 0) + r = -errno; + +finish: + setfscreatecon(NULL); + freecon(fcon); + free(path); + + return r; + +skipped: +#endif + return bind(fd, addr, addrlen) < 0 ? -errno : 0; +} diff --git a/src/label.h b/src/label.h new file mode 100644 index 000000000..ead44837a --- /dev/null +++ b/src/label.h @@ -0,0 +1,51 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foolabelhfoo +#define foolabelhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +int label_init(void); +void label_finish(void); + +int label_fix(const char *path, bool ignore_enoent); + +int label_socket_set(const char *label); +void label_socket_clear(void); + +int label_fifofile_set(const char *path); +int label_symlinkfile_set(const char *path); +void label_file_clear(void); + +void label_free(const char *label); + +int label_get_create_label_from_exe(const char *exe, char **label); + +int label_mkdir(const char *path, mode_t mode); + +void label_retest_selinux(void); + +int label_bind(int fd, const struct sockaddr *addr, socklen_t addrlen); + +#endif diff --git a/src/libsystemd-daemon.pc.in b/src/libsystemd-daemon.pc.in new file mode 100644 index 000000000..8bb3a74c8 --- /dev/null +++ b/src/libsystemd-daemon.pc.in @@ -0,0 +1,19 @@ +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: systemd +Description: systemd Daemon Utility Library +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lsystemd-daemon +Cflags: -I${includedir} diff --git a/src/libsystemd-daemon.sym b/src/libsystemd-daemon.sym new file mode 100644 index 000000000..f44023893 --- /dev/null +++ b/src/libsystemd-daemon.sym @@ -0,0 +1,27 @@ +/*** + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: +***/ + +/* Original symbols from systemd v31 */ + +LIBSYSTEMD_DAEMON_31 { +global: + sd_booted; + sd_is_fifo; + sd_is_mq; + sd_is_socket; + sd_is_socket_inet; + sd_is_socket_unix; + sd_is_special; + sd_listen_fds; + sd_notify; + sd_notifyf; +local: + *; +}; diff --git a/src/libsystemd-id128.pc.in b/src/libsystemd-id128.pc.in new file mode 100644 index 000000000..4d984fdff --- /dev/null +++ b/src/libsystemd-id128.pc.in @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: systemd +Description: systemd 128 Bit ID Utility Library +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lsystemd-id128 +Cflags: -I${includedir} diff --git a/src/libsystemd-id128.sym b/src/libsystemd-id128.sym new file mode 100644 index 000000000..2373fe664 --- /dev/null +++ b/src/libsystemd-id128.sym @@ -0,0 +1,21 @@ +/*** + This file is part of systemd. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +***/ + +/* Original symbols from systemd v38 */ + +LIBSYSTEMD_ID128_38 { +global: + sd_id128_to_string; + sd_id128_from_string; + sd_id128_randomize; + sd_id128_get_machine; + sd_id128_get_boot; +local: + *; +}; diff --git a/src/linux/Makefile b/src/linux/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/linux/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/linux/auto_dev-ioctl.h b/src/linux/auto_dev-ioctl.h new file mode 100644 index 000000000..850f39b33 --- /dev/null +++ b/src/linux/auto_dev-ioctl.h @@ -0,0 +1,229 @@ +/* + * Copyright 2008 Red Hat, Inc. All rights reserved. + * Copyright 2008 Ian Kent + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + */ + +#ifndef _LINUX_AUTO_DEV_IOCTL_H +#define _LINUX_AUTO_DEV_IOCTL_H + +#include + +#ifdef __KERNEL__ +#include +#else +#include +#endif /* __KERNEL__ */ + +#define AUTOFS_DEVICE_NAME "autofs" + +#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 +#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 + +#define AUTOFS_DEVID_LEN 16 + +#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) + +/* + * An ioctl interface for autofs mount point control. + */ + +struct args_protover { + __u32 version; +}; + +struct args_protosubver { + __u32 sub_version; +}; + +struct args_openmount { + __u32 devid; +}; + +struct args_ready { + __u32 token; +}; + +struct args_fail { + __u32 token; + __s32 status; +}; + +struct args_setpipefd { + __s32 pipefd; +}; + +struct args_timeout { + __u64 timeout; +}; + +struct args_requester { + __u32 uid; + __u32 gid; +}; + +struct args_expire { + __u32 how; +}; + +struct args_askumount { + __u32 may_umount; +}; + +struct args_ismountpoint { + union { + struct args_in { + __u32 type; + } in; + struct args_out { + __u32 devid; + __u32 magic; + } out; + }; +}; + +/* + * All the ioctls use this structure. + * When sending a path size must account for the total length + * of the chunk of memory otherwise is is the size of the + * structure. + */ + +struct autofs_dev_ioctl { + __u32 ver_major; + __u32 ver_minor; + __u32 size; /* total size of data passed in + * including this struct */ + __s32 ioctlfd; /* automount command fd */ + + /* Command parameters */ + + union { + struct args_protover protover; + struct args_protosubver protosubver; + struct args_openmount openmount; + struct args_ready ready; + struct args_fail fail; + struct args_setpipefd setpipefd; + struct args_timeout timeout; + struct args_requester requester; + struct args_expire expire; + struct args_askumount askumount; + struct args_ismountpoint ismountpoint; + }; + + char path[0]; +}; + +static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) +{ + memset(in, 0, sizeof(struct autofs_dev_ioctl)); + in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; + in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; + in->size = sizeof(struct autofs_dev_ioctl); + in->ioctlfd = -1; + return; +} + +/* + * If you change this make sure you make the corresponding change + * to autofs-dev-ioctl.c:lookup_ioctl() + */ +enum { + /* Get various version info */ + AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, + AUTOFS_DEV_IOCTL_PROTOVER_CMD, + AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, + + /* Open mount ioctl fd */ + AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, + + /* Close mount ioctl fd */ + AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, + + /* Mount/expire status returns */ + AUTOFS_DEV_IOCTL_READY_CMD, + AUTOFS_DEV_IOCTL_FAIL_CMD, + + /* Activate/deactivate autofs mount */ + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, + AUTOFS_DEV_IOCTL_CATATONIC_CMD, + + /* Expiry timeout */ + AUTOFS_DEV_IOCTL_TIMEOUT_CMD, + + /* Get mount last requesting uid and gid */ + AUTOFS_DEV_IOCTL_REQUESTER_CMD, + + /* Check for eligible expire candidates */ + AUTOFS_DEV_IOCTL_EXPIRE_CMD, + + /* Request busy status */ + AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, + + /* Check if path is a mountpoint */ + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, +}; + +#define AUTOFS_IOCTL 0x93 + +#define AUTOFS_DEV_IOCTL_VERSION \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_PROTOVER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_OPENMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_READY \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_FAIL \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_SETPIPEFD \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_CATATONIC \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_TIMEOUT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_REQUESTER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_EXPIRE \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) + +#endif /* _LINUX_AUTO_DEV_IOCTL_H */ diff --git a/src/linux/fanotify.h b/src/linux/fanotify.h new file mode 100644 index 000000000..63531a6b4 --- /dev/null +++ b/src/linux/fanotify.h @@ -0,0 +1,98 @@ +#ifndef _LINUX_FANOTIFY_H +#define _LINUX_FANOTIFY_H + +#include + +/* the following events that user-space can register for */ +#define FAN_ACCESS 0x00000001 /* File was accessed */ +#define FAN_MODIFY 0x00000002 /* File was modified */ +#define FAN_CLOSE_WRITE 0x00000008 /* Unwrittable file closed */ +#define FAN_CLOSE_NOWRITE 0x00000010 /* Writtable file closed */ +#define FAN_OPEN 0x00000020 /* File was opened */ + +#define FAN_EVENT_ON_CHILD 0x08000000 /* interested in child events */ + +/* FIXME currently Q's have no limit.... */ +#define FAN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */ + +#define FAN_OPEN_PERM 0x00010000 /* File open in perm check */ +#define FAN_ACCESS_PERM 0x00020000 /* File accessed in perm check */ + +/* helper events */ +#define FAN_CLOSE (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */ + +/* flags used for fanotify_init() */ +#define FAN_CLOEXEC 0x00000001 +#define FAN_NONBLOCK 0x00000002 + +#define FAN_ALL_INIT_FLAGS (FAN_CLOEXEC | FAN_NONBLOCK) + +/* flags used for fanotify_modify_mark() */ +#define FAN_MARK_ADD 0x00000001 +#define FAN_MARK_REMOVE 0x00000002 +#define FAN_MARK_DONT_FOLLOW 0x00000004 +#define FAN_MARK_ONLYDIR 0x00000008 +#define FAN_MARK_MOUNT 0x00000010 +#define FAN_MARK_IGNORED_MASK 0x00000020 +#define FAN_MARK_IGNORED_SURV_MODIFY 0x00000040 +#define FAN_MARK_FLUSH 0x00000080 + +#define FAN_ALL_MARK_FLAGS (FAN_MARK_ADD |\ + FAN_MARK_REMOVE |\ + FAN_MARK_DONT_FOLLOW |\ + FAN_MARK_ONLYDIR |\ + FAN_MARK_MOUNT |\ + FAN_MARK_IGNORED_MASK |\ + FAN_MARK_IGNORED_SURV_MODIFY) + +/* + * All of the events - we build the list by hand so that we can add flags in + * the future and not break backward compatibility. Apps will get only the + * events that they originally wanted. Be sure to add new events here! + */ +#define FAN_ALL_EVENTS (FAN_ACCESS |\ + FAN_MODIFY |\ + FAN_CLOSE |\ + FAN_OPEN) + +/* + * All events which require a permission response from userspace + */ +#define FAN_ALL_PERM_EVENTS (FAN_OPEN_PERM |\ + FAN_ACCESS_PERM) + +#define FAN_ALL_OUTGOING_EVENTS (FAN_ALL_EVENTS |\ + FAN_ALL_PERM_EVENTS |\ + FAN_Q_OVERFLOW) + +#define FANOTIFY_METADATA_VERSION 2 + +struct fanotify_event_metadata { + __u32 event_len; + __u32 vers; + __u64 mask; + __s32 fd; + __s32 pid; +} __attribute__ ((packed)); + +struct fanotify_response { + __s32 fd; + __u32 response; +} __attribute__ ((packed)); + +/* Legit userspace responses to a _PERM event */ +#define FAN_ALLOW 0x01 +#define FAN_DENY 0x02 + +/* Helper functions to deal with fanotify_event_metadata buffers */ +#define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata)) + +#define FAN_EVENT_NEXT(meta, len) ((len) -= (meta)->event_len, \ + (struct fanotify_event_metadata*)(((char *)(meta)) + \ + (meta)->event_len)) + +#define FAN_EVENT_OK(meta, len) ((long)(len) >= (long)FAN_EVENT_METADATA_LEN && \ + (long)(meta)->event_len >= (long)FAN_EVENT_METADATA_LEN && \ + (long)(meta)->event_len <= (long)(len)) + +#endif /* _LINUX_FANOTIFY_H */ diff --git a/src/list.h b/src/list.h new file mode 100644 index 000000000..2bec8c9e7 --- /dev/null +++ b/src/list.h @@ -0,0 +1,128 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foolisthfoo +#define foolisthfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +/* The head of the linked list. Use this in the structure that shall + * contain the head of the linked list */ +#define LIST_HEAD(t,name) \ + t *name + +/* The pointers in the linked list's items. Use this in the item structure */ +#define LIST_FIELDS(t,name) \ + t *name##_next, *name##_prev + +/* Initialize the list's head */ +#define LIST_HEAD_INIT(t,head) \ + do { \ + (head) = NULL; } \ + while(false) + +/* Initialize a list item */ +#define LIST_INIT(t,name,item) \ + do { \ + t *_item = (item); \ + assert(_item); \ + _item->name##_prev = _item->name##_next = NULL; \ + } while(false) + +/* Prepend an item to the list */ +#define LIST_PREPEND(t,name,head,item) \ + do { \ + t **_head = &(head), *_item = (item); \ + assert(_item); \ + if ((_item->name##_next = *_head)) \ + _item->name##_next->name##_prev = _item; \ + _item->name##_prev = NULL; \ + *_head = _item; \ + } while(false) + +/* Remove an item from the list */ +#define LIST_REMOVE(t,name,head,item) \ + do { \ + t **_head = &(head), *_item = (item); \ + assert(_item); \ + if (_item->name##_next) \ + _item->name##_next->name##_prev = _item->name##_prev; \ + if (_item->name##_prev) \ + _item->name##_prev->name##_next = _item->name##_next; \ + else { \ + assert(*_head == _item); \ + *_head = _item->name##_next; \ + } \ + _item->name##_next = _item->name##_prev = NULL; \ + } while(false) + +/* Find the head of the list */ +#define LIST_FIND_HEAD(t,name,item,head) \ + do { \ + t *_item = (item); \ + assert(_item); \ + while (_item->name##_prev) \ + _item = _item->name##_prev; \ + (head) = _item; \ + } while (false) + +/* Find the head of the list */ +#define LIST_FIND_TAIL(t,name,item,tail) \ + do { \ + t *_item = (item); \ + assert(_item); \ + while (_item->name##_next) \ + _item = _item->name##_next; \ + (tail) = _item; \ + } while (false) + +/* Insert an item after another one (a = where, b = what) */ +#define LIST_INSERT_AFTER(t,name,head,a,b) \ + do { \ + t **_head = &(head), *_a = (a), *_b = (b); \ + assert(_b); \ + if (!_a) { \ + if ((_b->name##_next = *_head)) \ + _b->name##_next->name##_prev = _b; \ + _b->name##_prev = NULL; \ + *_head = _b; \ + } else { \ + if ((_b->name##_next = _a->name##_next)) \ + _b->name##_next->name##_prev = _b; \ + _b->name##_prev = _a; \ + _a->name##_next = _b; \ + } \ + } while(false) + +#define LIST_JUST_US(name,item) \ + (!(item)->name##_prev && !(item)->name##_next) \ + +#define LIST_FOREACH(name,i,head) \ + for ((i) = (head); (i); (i) = (i)->name##_next) + +#define LIST_FOREACH_SAFE(name,i,n,head) \ + for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n)) + +#define LIST_FOREACH_BEFORE(name,i,p) \ + for ((i) = (p)->name##_prev; (i); (i) = (i)->name##_prev) + +#define LIST_FOREACH_AFTER(name,i,p) \ + for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next) + +#endif diff --git a/src/load-dropin.c b/src/load-dropin.c new file mode 100644 index 000000000..d869ee0c7 --- /dev/null +++ b/src/load-dropin.c @@ -0,0 +1,150 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "unit.h" +#include "load-dropin.h" +#include "log.h" +#include "strv.h" +#include "unit-name.h" + +static int iterate_dir(Unit *u, const char *path, UnitDependency dependency) { + DIR *d; + struct dirent *de; + int r; + + assert(u); + assert(path); + + d = opendir(path); + if (!d) { + + if (errno == ENOENT) + return 0; + + return -errno; + } + + while ((de = readdir(d))) { + char *f; + + if (ignore_file(de->d_name)) + continue; + + f = join(path, "/", de->d_name, NULL); + if (!f) { + r = -ENOMEM; + goto finish; + } + + r = unit_add_dependency_by_name(u, dependency, de->d_name, f, true); + free(f); + + if (r < 0) + log_error("Cannot add dependency %s to %s, ignoring: %s", de->d_name, u->id, strerror(-r)); + } + + r = 0; + +finish: + closedir(d); + return r; +} + +static int process_dir(Unit *u, const char *unit_path, const char *name, const char *suffix, UnitDependency dependency) { + int r; + char *path; + + assert(u); + assert(unit_path); + assert(name); + assert(suffix); + + path = join(unit_path, "/", name, suffix, NULL); + if (!path) + return -ENOMEM; + + if (u->manager->unit_path_cache && + !set_get(u->manager->unit_path_cache, path)) + r = 0; + else + r = iterate_dir(u, path, dependency); + free(path); + + if (r < 0) + return r; + + if (u->instance) { + char *template; + /* Also try the template dir */ + + template = unit_name_template(name); + if (!template) + return -ENOMEM; + + path = join(unit_path, "/", template, suffix, NULL); + free(template); + + if (!path) + return -ENOMEM; + + if (u->manager->unit_path_cache && + !set_get(u->manager->unit_path_cache, path)) + r = 0; + else + r = iterate_dir(u, path, dependency); + free(path); + + if (r < 0) + return r; + } + + return 0; +} + +int unit_load_dropin(Unit *u) { + Iterator i; + char *t; + + assert(u); + + /* Load dependencies from supplementary drop-in directories */ + + SET_FOREACH(t, u->names, i) { + char **p; + + STRV_FOREACH(p, u->manager->lookup_paths.unit_path) { + int r; + + r = process_dir(u, *p, t, ".wants", UNIT_WANTS); + if (r < 0) + return r; + + r = process_dir(u, *p, t, ".requires", UNIT_REQUIRES); + if (r < 0) + return r; + } + } + + return 0; +} diff --git a/src/load-dropin.h b/src/load-dropin.h new file mode 100644 index 000000000..cf3a79938 --- /dev/null +++ b/src/load-dropin.h @@ -0,0 +1,31 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooloaddropinhfoo +#define fooloaddropinhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "unit.h" + +/* Read service data supplementary drop-in directories */ + +int unit_load_dropin(Unit *u); + +#endif diff --git a/src/load-fragment-gperf.gperf.m4 b/src/load-fragment-gperf.gperf.m4 new file mode 100644 index 000000000..4b02e3157 --- /dev/null +++ b/src/load-fragment-gperf.gperf.m4 @@ -0,0 +1,230 @@ +%{ +#include +#include "conf-parser.h" +#include "load-fragment.h" +#include "missing.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name load_fragment_gperf_hash +%define lookup-function-name load_fragment_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +m4_dnl Define the context options only once +m4_define(`EXEC_CONTEXT_CONFIG_ITEMS', +`$1.WorkingDirectory, config_parse_unit_path_printf, 0, offsetof($1, exec_context.working_directory) +$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.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) +$1.IOSchedulingPriority, config_parse_exec_io_priority, 0, offsetof($1, exec_context) +$1.CPUSchedulingPolicy, config_parse_exec_cpu_sched_policy, 0, offsetof($1, exec_context) +$1.CPUSchedulingPriority, config_parse_exec_cpu_sched_prio, 0, offsetof($1, exec_context) +$1.CPUSchedulingResetOnFork, config_parse_bool, 0, offsetof($1, exec_context.cpu_sched_reset_on_fork) +$1.CPUAffinity, config_parse_exec_cpu_affinity, 0, offsetof($1, exec_context) +$1.UMask, config_parse_mode, 0, offsetof($1, exec_context.umask) +$1.Environment, config_parse_unit_strv_printf, 0, offsetof($1, exec_context.environment) +$1.EnvironmentFile, config_parse_unit_env_file, 0, offsetof($1, exec_context.environment_files) +$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) +$1.TTYPath, config_parse_unit_path_printf, 0, offsetof($1, exec_context.tty_path) +$1.TTYReset, config_parse_bool, 0, offsetof($1, exec_context.tty_reset) +$1.TTYVHangup, config_parse_bool, 0, offsetof($1, exec_context.tty_vhangup) +$1.TTYVTDisallocate, config_parse_bool, 0, offsetof($1, exec_context.tty_vt_disallocate) +$1.SyslogIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.syslog_identifier) +$1.SyslogFacility, config_parse_facility, 0, offsetof($1, exec_context.syslog_priority) +$1.SyslogLevel, config_parse_level, 0, offsetof($1, exec_context.syslog_priority) +$1.SyslogLevelPrefix, config_parse_bool, 0, offsetof($1, exec_context.syslog_level_prefix) +$1.Capabilities, config_parse_exec_capabilities, 0, offsetof($1, exec_context) +$1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context) +$1.CapabilityBoundingSet, config_parse_exec_bounding_set, 0, offsetof($1, exec_context) +$1.TimerSlackNSec, config_parse_exec_timer_slack_nsec, 0, offsetof($1, exec_context) +$1.LimitCPU, config_parse_limit, RLIMIT_CPU, offsetof($1, exec_context.rlimit) +$1.LimitFSIZE, config_parse_limit, RLIMIT_FSIZE, offsetof($1, exec_context.rlimit) +$1.LimitDATA, config_parse_limit, RLIMIT_DATA, offsetof($1, exec_context.rlimit) +$1.LimitSTACK, config_parse_limit, RLIMIT_STACK, offsetof($1, exec_context.rlimit) +$1.LimitCORE, config_parse_limit, RLIMIT_CORE, offsetof($1, exec_context.rlimit) +$1.LimitRSS, config_parse_limit, RLIMIT_RSS, offsetof($1, exec_context.rlimit) +$1.LimitNOFILE, config_parse_limit, RLIMIT_NOFILE, offsetof($1, exec_context.rlimit) +$1.LimitAS, config_parse_limit, RLIMIT_AS, offsetof($1, exec_context.rlimit) +$1.LimitNPROC, config_parse_limit, RLIMIT_NPROC, offsetof($1, exec_context.rlimit) +$1.LimitMEMLOCK, config_parse_limit, RLIMIT_MEMLOCK, offsetof($1, exec_context.rlimit) +$1.LimitLOCKS, config_parse_limit, RLIMIT_LOCKS, offsetof($1, exec_context.rlimit) +$1.LimitSIGPENDING, config_parse_limit, RLIMIT_SIGPENDING, offsetof($1, exec_context.rlimit) +$1.LimitMSGQUEUE, config_parse_limit, RLIMIT_MSGQUEUE, offsetof($1, exec_context.rlimit) +$1.LimitNICE, config_parse_limit, RLIMIT_NICE, offsetof($1, exec_context.rlimit) +$1.LimitRTPRIO, config_parse_limit, RLIMIT_RTPRIO, offsetof($1, exec_context.rlimit) +$1.LimitRTTIME, config_parse_limit, RLIMIT_RTTIME, offsetof($1, exec_context.rlimit) +$1.ControlGroup, config_parse_unit_cgroup, 0, 0 +$1.ControlGroupAttribute, config_parse_unit_cgroup_attr, 0, 0 +$1.CPUShares, config_parse_unit_cpu_shares, 0, 0 +$1.MemoryLimit, config_parse_unit_memory_limit, 0, 0 +$1.MemorySoftLimit, config_parse_unit_memory_limit, 0, 0 +$1.DeviceAllow, config_parse_unit_device_allow, 0, 0 +$1.DeviceDeny, config_parse_unit_device_allow, 0, 0 +$1.BlockIOWeight, config_parse_unit_blkio_weight, 0, 0 +$1.BlockIOReadBandwidth, config_parse_unit_blkio_bandwidth, 0, 0 +$1.BlockIOWriteBandwidth, config_parse_unit_blkio_bandwidth, 0, 0 +$1.ReadWriteDirectories, config_parse_path_strv, 0, offsetof($1, exec_context.read_write_dirs) +$1.ReadOnlyDirectories, config_parse_path_strv, 0, offsetof($1, exec_context.read_only_dirs) +$1.InaccessibleDirectories, config_parse_path_strv, 0, offsetof($1, exec_context.inaccessible_dirs) +$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.MountFlags, config_parse_exec_mount_flags, 0, offsetof($1, exec_context) +$1.TCPWrapName, config_parse_unit_string_printf, 0, offsetof($1, exec_context.tcpwrap_name) +$1.PAMName, config_parse_unit_string_printf, 0, offsetof($1, exec_context.pam_name) +$1.KillMode, config_parse_kill_mode, 0, offsetof($1, exec_context.kill_mode) +$1.KillSignal, config_parse_kill_signal, 0, offsetof($1, exec_context.kill_signal) +$1.SendSIGKILL, config_parse_bool, 0, offsetof($1, exec_context.send_sigkill) +$1.IgnoreSIGPIPE, config_parse_bool, 0, offsetof($1, exec_context.ignore_sigpipe) +$1.UtmpIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.utmp_id) +$1.ControlGroupModify, config_parse_bool, 0, offsetof($1, exec_context.control_group_modify) +$1.ControlGroupPersistent, config_parse_tristate, 0, offsetof($1, exec_context.control_group_persistent)' +)m4_dnl +Unit.Names, config_parse_unit_names, 0, 0 +Unit.Description, config_parse_unit_string_printf, 0, offsetof(Unit, description) +Unit.Requires, config_parse_unit_deps, UNIT_REQUIRES, 0 +Unit.RequiresOverridable, config_parse_unit_deps, UNIT_REQUIRES_OVERRIDABLE, 0 +Unit.Requisite, config_parse_unit_deps, UNIT_REQUISITE, 0 +Unit.RequisiteOverridable, config_parse_unit_deps, UNIT_REQUISITE_OVERRIDABLE, 0 +Unit.Wants, config_parse_unit_deps, UNIT_WANTS, 0 +Unit.BindTo, config_parse_unit_deps, UNIT_BIND_TO, 0 +Unit.Conflicts, config_parse_unit_deps, UNIT_CONFLICTS, 0 +Unit.Before, config_parse_unit_deps, UNIT_BEFORE, 0 +Unit.After, config_parse_unit_deps, UNIT_AFTER, 0 +Unit.OnFailure, config_parse_unit_deps, UNIT_ON_FAILURE, 0 +Unit.PropagateReloadTo, config_parse_unit_deps, UNIT_PROPAGATE_RELOAD_TO, 0 +Unit.PropagateReloadFrom, config_parse_unit_deps, UNIT_PROPAGATE_RELOAD_FROM, 0 +Unit.StopWhenUnneeded, config_parse_bool, 0, offsetof(Unit, stop_when_unneeded) +Unit.RefuseManualStart, config_parse_bool, 0, offsetof(Unit, refuse_manual_start) +Unit.RefuseManualStop, config_parse_bool, 0, offsetof(Unit, refuse_manual_stop) +Unit.AllowIsolate, config_parse_bool, 0, offsetof(Unit, allow_isolate) +Unit.DefaultDependencies, config_parse_bool, 0, offsetof(Unit, default_dependencies) +Unit.OnFailureIsolate, config_parse_bool, 0, offsetof(Unit, on_failure_isolate) +Unit.IgnoreOnIsolate, config_parse_bool, 0, offsetof(Unit, ignore_on_isolate) +Unit.IgnoreOnSnapshot, config_parse_bool, 0, offsetof(Unit, ignore_on_snapshot) +Unit.JobTimeoutSec, config_parse_usec, 0, offsetof(Unit, job_timeout) +Unit.ConditionPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, 0 +Unit.ConditionPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, 0 +Unit.ConditionPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, 0 +Unit.ConditionPathIsSymbolicLink,config_parse_unit_condition_path, CONDITION_PATH_IS_SYMBOLIC_LINK,0 +Unit.ConditionPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, 0 +Unit.ConditionDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, 0 +Unit.ConditionFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, 0 +Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, 0 +Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, 0 +Unit.ConditionSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, 0 +Unit.ConditionCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, 0 +Unit.ConditionNull, config_parse_unit_condition_null, 0, 0 +m4_dnl +Service.PIDFile, config_parse_unit_path_printf, 0, offsetof(Service, pid_file) +Service.ExecStartPre, config_parse_exec, SERVICE_EXEC_START_PRE, offsetof(Service, exec_command) +Service.ExecStart, config_parse_exec, SERVICE_EXEC_START, offsetof(Service, exec_command) +Service.ExecStartPost, config_parse_exec, SERVICE_EXEC_START_POST, offsetof(Service, exec_command) +Service.ExecReload, config_parse_exec, SERVICE_EXEC_RELOAD, offsetof(Service, exec_command) +Service.ExecStop, config_parse_exec, SERVICE_EXEC_STOP, offsetof(Service, exec_command) +Service.ExecStopPost, config_parse_exec, SERVICE_EXEC_STOP_POST, offsetof(Service, exec_command) +Service.RestartSec, config_parse_usec, 0, offsetof(Service, restart_usec) +Service.TimeoutSec, config_parse_usec, 0, offsetof(Service, timeout_usec) +Service.WatchdogSec, config_parse_usec, 0, offsetof(Service, watchdog_usec) +Service.StartLimitInterval, config_parse_usec, 0, offsetof(Service, start_limit.interval) +Service.StartLimitBurst, config_parse_unsigned, 0, offsetof(Service, start_limit.burst) +Service.StartLimitAction, config_parse_start_limit_action, 0, offsetof(Service, start_limit_action) +Service.Type, config_parse_service_type, 0, offsetof(Service, type) +Service.Restart, config_parse_service_restart, 0, offsetof(Service, restart) +Service.PermissionsStartOnly, config_parse_bool, 0, offsetof(Service, permissions_start_only) +Service.RootDirectoryStartOnly, config_parse_bool, 0, offsetof(Service, root_directory_start_only) +Service.RemainAfterExit, config_parse_bool, 0, offsetof(Service, remain_after_exit) +Service.GuessMainPID, config_parse_bool, 0, offsetof(Service, guess_main_pid) +m4_ifdef(`HAVE_SYSV_COMPAT', +`Service.SysVStartPriority, config_parse_sysv_priority, 0, offsetof(Service, sysv_start_priority)', +`Service.SysVStartPriority, config_parse_warn_compat, 0, 0') +Service.NonBlocking, config_parse_bool, 0, offsetof(Service, exec_context.non_blocking) +Service.BusName, config_parse_unit_string_printf, 0, offsetof(Service, bus_name) +Service.NotifyAccess, config_parse_notify_access, 0, offsetof(Service, notify_access) +Service.Sockets, config_parse_service_sockets, 0, 0 +Service.FsckPassNo, config_parse_fsck_passno, 0, offsetof(Service, fsck_passno) +EXEC_CONTEXT_CONFIG_ITEMS(Service)m4_dnl +m4_dnl +Socket.ListenStream, config_parse_socket_listen, 0, 0 +Socket.ListenDatagram, config_parse_socket_listen, 0, 0 +Socket.ListenSequentialPacket, config_parse_socket_listen, 0, 0 +Socket.ListenFIFO, config_parse_socket_listen, 0, 0 +Socket.ListenNetlink, config_parse_socket_listen, 0, 0 +Socket.ListenSpecial, config_parse_socket_listen, 0, 0 +Socket.ListenMessageQueue, config_parse_socket_listen, 0, 0 +Socket.BindIPv6Only, config_parse_socket_bind, 0, 0, +Socket.Backlog, config_parse_unsigned, 0, offsetof(Socket, backlog) +Socket.BindToDevice, config_parse_socket_bindtodevice, 0, 0 +Socket.ExecStartPre, config_parse_exec, SOCKET_EXEC_START_PRE, offsetof(Socket, exec_command) +Socket.ExecStartPost, config_parse_exec, SOCKET_EXEC_START_POST, offsetof(Socket, exec_command) +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_usec, 0, offsetof(Socket, timeout_usec) +Socket.DirectoryMode, config_parse_mode, 0, offsetof(Socket, directory_mode) +Socket.SocketMode, config_parse_mode, 0, offsetof(Socket, socket_mode) +Socket.Accept, config_parse_bool, 0, offsetof(Socket, accept) +Socket.MaxConnections, config_parse_unsigned, 0, offsetof(Socket, max_connections) +Socket.KeepAlive, config_parse_bool, 0, offsetof(Socket, keep_alive) +Socket.Priority, config_parse_int, 0, offsetof(Socket, priority) +Socket.ReceiveBuffer, config_parse_bytes_size, 0, offsetof(Socket, receive_buffer) +Socket.SendBuffer, config_parse_bytes_size, 0, offsetof(Socket, send_buffer) +Socket.IPTOS, config_parse_ip_tos, 0, offsetof(Socket, ip_tos) +Socket.IPTTL, config_parse_int, 0, offsetof(Socket, ip_ttl) +Socket.Mark, config_parse_int, 0, offsetof(Socket, mark) +Socket.PipeSize, config_parse_bytes_size, 0, offsetof(Socket, pipe_size) +Socket.FreeBind, config_parse_bool, 0, offsetof(Socket, free_bind) +Socket.Transparent, config_parse_bool, 0, offsetof(Socket, transparent) +Socket.Broadcast, config_parse_bool, 0, offsetof(Socket, broadcast) +Socket.PassCredentials, config_parse_bool, 0, offsetof(Socket, pass_cred) +Socket.PassSecurity, config_parse_bool, 0, offsetof(Socket, pass_sec) +Socket.TCPCongestion, config_parse_string, 0, offsetof(Socket, tcp_congestion) +Socket.MessageQueueMaxMessages, config_parse_long, 0, offsetof(Socket, mq_maxmsg) +Socket.MessageQueueMessageSize, config_parse_long, 0, offsetof(Socket, mq_msgsize) +Socket.Service, config_parse_socket_service, 0, 0 +EXEC_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl +m4_dnl +Mount.What, config_parse_string, 0, offsetof(Mount, parameters_fragment.what) +Mount.Where, config_parse_path, 0, offsetof(Mount, where) +Mount.Options, config_parse_string, 0, offsetof(Mount, parameters_fragment.options) +Mount.Type, config_parse_string, 0, offsetof(Mount, parameters_fragment.fstype) +Mount.TimeoutSec, config_parse_usec, 0, offsetof(Mount, timeout_usec) +Mount.DirectoryMode, config_parse_mode, 0, offsetof(Mount, directory_mode) +EXEC_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl +m4_dnl +Automount.Where, config_parse_path, 0, offsetof(Automount, where) +Automount.DirectoryMode, config_parse_mode, 0, offsetof(Automount, directory_mode) +m4_dnl +Swap.What, config_parse_path, 0, offsetof(Swap, parameters_fragment.what) +Swap.Priority, config_parse_int, 0, offsetof(Swap, parameters_fragment.priority) +Swap.TimeoutSec, config_parse_usec, 0, offsetof(Swap, timeout_usec) +EXEC_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl +m4_dnl +Timer.OnActiveSec, config_parse_timer, 0, 0 +Timer.OnBootSec, config_parse_timer, 0, 0 +Timer.OnStartupSec, config_parse_timer, 0, 0 +Timer.OnUnitActiveSec, config_parse_timer, 0, 0 +Timer.OnUnitInactiveSec, config_parse_timer, 0, 0 +Timer.Unit, config_parse_timer_unit, 0, 0 +m4_dnl +Path.PathExists, config_parse_path_spec, 0, 0 +Path.PathExistsGlob, config_parse_path_spec, 0, 0 +Path.PathChanged, config_parse_path_spec, 0, 0 +Path.PathModified, config_parse_path_spec, 0, 0 +Path.DirectoryNotEmpty, config_parse_path_spec, 0, 0 +Path.Unit, config_parse_path_unit, 0, 0 +Path.MakeDirectory, config_parse_bool, 0, offsetof(Path, make_directory) +Path.DirectoryMode, config_parse_mode, 0, offsetof(Path, directory_mode) +m4_dnl The [Install] section is ignored here. +Install.Alias, NULL, 0, 0 +Install.WantedBy, NULL, 0, 0 +Install.Also, NULL, 0, 0 diff --git a/src/load-fragment.c b/src/load-fragment.c new file mode 100644 index 000000000..637c82b42 --- /dev/null +++ b/src/load-fragment.c @@ -0,0 +1,2445 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unit.h" +#include "strv.h" +#include "conf-parser.h" +#include "load-fragment.h" +#include "log.h" +#include "ioprio.h" +#include "securebits.h" +#include "missing.h" +#include "unit-name.h" +#include "bus-errors.h" +#include "utf8.h" + +#ifndef HAVE_SYSV_COMPAT +int config_parse_warn_compat( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + log_debug("[%s:%u] Support for option %s= has been disabled at compile time and is ignored", filename, line, lvalue); + return 0; +} +#endif + +int config_parse_unit_deps( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + UnitDependency d = ltype; + Unit *u = userdata; + char *w; + size_t l; + char *state; + + assert(filename); + assert(lvalue); + assert(rvalue); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t, *k; + int r; + + t = strndup(w, l); + if (!t) + return -ENOMEM; + + k = unit_name_printf(u, t); + free(t); + if (!k) + return -ENOMEM; + + r = unit_add_dependency_by_name(u, d, k, NULL, true); + if (r < 0) + log_error("[%s:%u] Failed to add dependency on %s, ignoring: %s", filename, line, k, strerror(-r)); + + free(k); + } + + return 0; +} + +int config_parse_unit_names( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = userdata; + char *w; + size_t l; + char *state; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t, *k; + int r; + + t = strndup(w, l); + if (!t) + return -ENOMEM; + + k = unit_name_printf(u, t); + free(t); + if (!k) + return -ENOMEM; + + r = unit_merge_by_name(u, k); + if (r < 0) + log_error("Failed to add name %s, ignoring: %s", k, strerror(-r)); + + free(k); + } + + return 0; +} + +int config_parse_unit_string_printf( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = userdata; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + k = unit_full_printf(u, rvalue); + if (!k) + return -ENOMEM; + + r = config_parse_string(filename, line, section, lvalue, ltype, k, data, userdata); + free (k); + + return r; +} + +int config_parse_unit_strv_printf( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = userdata; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + k = unit_full_printf(u, rvalue); + if (!k) + return -ENOMEM; + + r = config_parse_strv(filename, line, section, lvalue, ltype, k, data, userdata); + free(k); + + return r; +} + +int config_parse_unit_path_printf( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = userdata; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + k = unit_full_printf(u, rvalue); + if (!k) + return -ENOMEM; + + r = config_parse_path(filename, line, section, lvalue, ltype, k, data, userdata); + free(k); + + return r; +} + +int config_parse_socket_listen( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + SocketPort *p, *tail; + Socket *s; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + s = SOCKET(data); + + p = new0(SocketPort, 1); + if (!p) + return -ENOMEM; + + if (streq(lvalue, "ListenFIFO")) { + p->type = SOCKET_FIFO; + + if (!(p->path = unit_full_printf(UNIT(s), rvalue))) { + free(p); + return -ENOMEM; + } + + path_kill_slashes(p->path); + + } else if (streq(lvalue, "ListenSpecial")) { + p->type = SOCKET_SPECIAL; + + if (!(p->path = unit_full_printf(UNIT(s), rvalue))) { + free(p); + return -ENOMEM; + } + + path_kill_slashes(p->path); + + } else if (streq(lvalue, "ListenMessageQueue")) { + + p->type = SOCKET_MQUEUE; + + if (!(p->path = unit_full_printf(UNIT(s), rvalue))) { + free(p); + return -ENOMEM; + } + + path_kill_slashes(p->path); + + } else if (streq(lvalue, "ListenNetlink")) { + char *k; + int r; + + p->type = SOCKET_SOCKET; + k = unit_full_printf(UNIT(s), rvalue); + r = socket_address_parse_netlink(&p->address, k); + free(k); + + if (r < 0) { + log_error("[%s:%u] Failed to parse address value, ignoring: %s", filename, line, rvalue); + free(p); + return 0; + } + + } else { + char *k; + int r; + + p->type = SOCKET_SOCKET; + k = unit_full_printf(UNIT(s), rvalue); + r = socket_address_parse(&p->address, k); + free(k); + + if (r < 0) { + log_error("[%s:%u] Failed to parse address value, ignoring: %s", filename, line, rvalue); + free(p); + return 0; + } + + if (streq(lvalue, "ListenStream")) + p->address.type = SOCK_STREAM; + else if (streq(lvalue, "ListenDatagram")) + p->address.type = SOCK_DGRAM; + else { + assert(streq(lvalue, "ListenSequentialPacket")); + p->address.type = SOCK_SEQPACKET; + } + + if (socket_address_family(&p->address) != AF_LOCAL && p->address.type == SOCK_SEQPACKET) { + log_error("[%s:%u] Address family not supported, ignoring: %s", filename, line, rvalue); + free(p); + return 0; + } + } + + p->fd = -1; + + if (s->ports) { + LIST_FIND_TAIL(SocketPort, port, s->ports, tail); + LIST_INSERT_AFTER(SocketPort, port, s->ports, tail, p); + } else + LIST_PREPEND(SocketPort, port, s->ports, p); + + return 0; +} + +int config_parse_socket_bind( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Socket *s; + SocketAddressBindIPv6Only b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + s = SOCKET(data); + + if ((b = socket_address_bind_ipv6_only_from_string(rvalue)) < 0) { + int r; + + if ((r = parse_boolean(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse bind IPv6 only value, ignoring: %s", filename, line, rvalue); + return 0; + } + + s->bind_ipv6_only = r ? SOCKET_ADDRESS_IPV6_ONLY : SOCKET_ADDRESS_BOTH; + } else + s->bind_ipv6_only = b; + + return 0; +} + +int config_parse_exec_nice( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + int priority; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atoi(rvalue, &priority) < 0) { + log_error("[%s:%u] Failed to parse nice priority, ignoring: %s. ", filename, line, rvalue); + return 0; + } + + if (priority < PRIO_MIN || priority >= PRIO_MAX) { + log_error("[%s:%u] Nice priority out of range, ignoring: %s", filename, line, rvalue); + return 0; + } + + c->nice = priority; + c->nice_set = true; + + return 0; +} + +int config_parse_exec_oom_score_adjust( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + int oa; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atoi(rvalue, &oa) < 0) { + log_error("[%s:%u] Failed to parse the OOM score adjust value, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (oa < OOM_SCORE_ADJ_MIN || oa > OOM_SCORE_ADJ_MAX) { + log_error("[%s:%u] OOM score adjust value out of range, ignoring: %s", filename, line, rvalue); + return 0; + } + + c->oom_score_adjust = oa; + c->oom_score_adjust_set = true; + + return 0; +} + +int config_parse_exec( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecCommand **e = data, *nce; + char *path, **n; + unsigned k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(e); + + /* We accept an absolute path as first argument, or + * alternatively an absolute prefixed with @ to allow + * overriding of argv[0]. */ + + e += ltype; + + for (;;) { + char *w; + size_t l; + char *state; + bool honour_argv0 = false, ignore = false; + + path = NULL; + nce = NULL; + n = NULL; + + rvalue += strspn(rvalue, WHITESPACE); + + if (rvalue[0] == 0) + break; + + if (rvalue[0] == '-') { + ignore = true; + rvalue ++; + } + + if (rvalue[0] == '@') { + honour_argv0 = true; + rvalue ++; + } + + if (*rvalue != '/') { + log_error("[%s:%u] Invalid executable path in command line, ignoring: %s", filename, line, rvalue); + return 0; + } + + k = 0; + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + if (strncmp(w, ";", MAX(l, 1U)) == 0) + break; + + k++; + } + + n = new(char*, k + !honour_argv0); + if (!n) + return -ENOMEM; + + k = 0; + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + if (strncmp(w, ";", MAX(l, 1U)) == 0) + break; + + if (honour_argv0 && w == rvalue) { + assert(!path); + + path = strndup(w, l); + if (!path) { + r = -ENOMEM; + goto fail; + } + + if (!utf8_is_valid(path)) { + log_error("[%s:%u] Path is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue); + r = 0; + goto fail; + } + + } else { + char *c; + + c = n[k++] = cunescape_length(w, l); + if (!c) { + r = -ENOMEM; + goto fail; + } + + if (!utf8_is_valid(c)) { + log_error("[%s:%u] Path is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue); + r = 0; + goto fail; + } + } + } + + n[k] = NULL; + + if (!n[0]) { + log_error("[%s:%u] Invalid command line, ignoring: %s", filename, line, rvalue); + r = 0; + goto fail; + } + + if (!path) { + path = strdup(n[0]); + if (!path) { + r = -ENOMEM; + goto fail; + } + } + + assert(path_is_absolute(path)); + + nce = new0(ExecCommand, 1); + if (!nce) { + r = -ENOMEM; + goto fail; + } + + nce->argv = n; + nce->path = path; + nce->ignore = ignore; + + path_kill_slashes(nce->path); + + exec_command_append_list(e, nce); + + rvalue = state; + } + + return 0; + +fail: + n[k] = NULL; + strv_free(n); + free(path); + free(nce); + + return r; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier"); + +int config_parse_socket_bindtodevice( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Socket *s = data; + char *n; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (rvalue[0] && !streq(rvalue, "*")) { + if (!(n = strdup(rvalue))) + return -ENOMEM; + } else + n = NULL; + + free(s->bind_to_device); + s->bind_to_device = n; + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_output, exec_output, ExecOutput, "Failed to parse output specifier"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_input, exec_input, ExecInput, "Failed to parse input specifier"); + +int config_parse_facility( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + + int *o = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((x = log_facility_unshifted_from_string(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse log facility, ignoring: %s", filename, line, rvalue); + return 0; + } + + *o = (x << 3) | LOG_PRI(*o); + + return 0; +} + +int config_parse_level( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + + int *o = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((x = log_level_from_string(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse log level, ignoring: %s", filename, line, rvalue); + return 0; + } + + *o = (*o & LOG_FACMASK) | x; + return 0; +} + +int config_parse_exec_io_class( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + int x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((x = ioprio_class_from_string(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse IO scheduling class, ignoring: %s", filename, line, rvalue); + return 0; + } + + c->ioprio = IOPRIO_PRIO_VALUE(x, IOPRIO_PRIO_DATA(c->ioprio)); + c->ioprio_set = true; + + return 0; +} + +int config_parse_exec_io_priority( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + int i; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atoi(rvalue, &i) < 0 || i < 0 || i >= IOPRIO_BE_NR) { + log_error("[%s:%u] Failed to parse io priority, ignoring: %s", filename, line, rvalue); + return 0; + } + + c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_PRIO_CLASS(c->ioprio), i); + c->ioprio_set = true; + + return 0; +} + +int config_parse_exec_cpu_sched_policy( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + + ExecContext *c = data; + int x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((x = sched_policy_from_string(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse CPU scheduling policy, ignoring: %s", filename, line, rvalue); + return 0; + } + + c->cpu_sched_policy = x; + c->cpu_sched_set = true; + + return 0; +} + +int config_parse_exec_cpu_sched_prio( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + int i; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* On Linux RR/FIFO have the same range */ + if (safe_atoi(rvalue, &i) < 0 || i < sched_get_priority_min(SCHED_RR) || i > sched_get_priority_max(SCHED_RR)) { + log_error("[%s:%u] Failed to parse CPU scheduling priority, ignoring: %s", filename, line, rvalue); + return 0; + } + + c->cpu_sched_priority = i; + c->cpu_sched_set = true; + + return 0; +} + +int config_parse_exec_cpu_affinity( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + char *w; + size_t l; + char *state; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t; + int r; + unsigned cpu; + + if (!(t = strndup(w, l))) + return -ENOMEM; + + r = safe_atou(t, &cpu); + free(t); + + if (!(c->cpuset)) + if (!(c->cpuset = cpu_set_malloc(&c->cpuset_ncpus))) + return -ENOMEM; + + if (r < 0 || cpu >= c->cpuset_ncpus) { + log_error("[%s:%u] Failed to parse CPU affinity, ignoring: %s", filename, line, rvalue); + return 0; + } + + CPU_SET_S(cpu, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset); + } + + return 0; +} + +int config_parse_exec_capabilities( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + cap_t cap; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (!(cap = cap_from_text(rvalue))) { + if (errno == ENOMEM) + return -ENOMEM; + + log_error("[%s:%u] Failed to parse capabilities, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (c->capabilities) + cap_free(c->capabilities); + c->capabilities = cap; + + return 0; +} + +int config_parse_exec_secure_bits( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + char *w; + size_t l; + char *state; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + if (first_word(w, "keep-caps")) + c->secure_bits |= SECURE_KEEP_CAPS; + else if (first_word(w, "keep-caps-locked")) + c->secure_bits |= SECURE_KEEP_CAPS_LOCKED; + else if (first_word(w, "no-setuid-fixup")) + c->secure_bits |= SECURE_NO_SETUID_FIXUP; + else if (first_word(w, "no-setuid-fixup-locked")) + c->secure_bits |= SECURE_NO_SETUID_FIXUP_LOCKED; + else if (first_word(w, "noroot")) + c->secure_bits |= SECURE_NOROOT; + else if (first_word(w, "noroot-locked")) + c->secure_bits |= SECURE_NOROOT_LOCKED; + else { + log_error("[%s:%u] Failed to parse secure bits, ignoring: %s", filename, line, rvalue); + return 0; + } + } + + return 0; +} + +int config_parse_exec_bounding_set( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + char *w; + size_t l; + char *state; + bool invert = false; + uint64_t sum = 0; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (rvalue[0] == '~') { + invert = true; + rvalue++; + } + + /* Note that we store this inverted internally, since the + * kernel wants it like this. But we actually expose it + * non-inverted everywhere to have a fully normalized + * interface. */ + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t; + int r; + cap_value_t cap; + + if (!(t = strndup(w, l))) + return -ENOMEM; + + r = cap_from_name(t, &cap); + free(t); + + if (r < 0) { + log_error("[%s:%u] Failed to parse capability bounding set, ignoring: %s", filename, line, rvalue); + return 0; + } + + sum |= ((uint64_t) 1ULL) << (uint64_t) cap; + } + + if (invert) + c->capability_bounding_set_drop |= sum; + else + c->capability_bounding_set_drop |= ~sum; + + return 0; +} + +int config_parse_exec_timer_slack_nsec( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + unsigned long u; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atolu(rvalue, &u) < 0) { + log_error("[%s:%u] Failed to parse time slack value, ignoring: %s", filename, line, rvalue); + return 0; + } + + c->timer_slack_nsec = u; + + return 0; +} + +int config_parse_limit( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + struct rlimit **rl = data; + unsigned long long u; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + rl += ltype; + + if (streq(rvalue, "infinity")) + u = (unsigned long long) RLIM_INFINITY; + else if (safe_atollu(rvalue, &u) < 0) { + log_error("[%s:%u] Failed to parse resource value, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (!*rl) + if (!(*rl = new(struct rlimit, 1))) + return -ENOMEM; + + (*rl)->rlim_cur = (*rl)->rlim_max = (rlim_t) u; + return 0; +} + +int config_parse_unit_cgroup( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = userdata; + char *w; + size_t l; + char *state; + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t, *k; + int r; + + t = strndup(w, l); + if (!t) + return -ENOMEM; + + k = unit_full_printf(u, t); + free(t); + + if (!k) + return -ENOMEM; + + t = cunescape(k); + free(k); + + if (!t) + return -ENOMEM; + + r = unit_add_cgroup_from_text(u, t); + free(t); + + if (r < 0) { + log_error("[%s:%u] Failed to parse cgroup value, ignoring: %s", filename, line, rvalue); + return 0; + } + } + + return 0; +} + +#ifdef HAVE_SYSV_COMPAT +int config_parse_sysv_priority( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int *priority = data; + int i; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atoi(rvalue, &i) < 0 || i < 0) { + log_error("[%s:%u] Failed to parse SysV start priority, ignoring: %s", filename, line, rvalue); + return 0; + } + + *priority = (int) i; + return 0; +} +#endif + +int config_parse_fsck_passno( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int *passno = data; + int i; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atoi(rvalue, &i) || i < 0) { + log_error("[%s:%u] Failed to parse fsck pass number, ignoring: %s", filename, line, rvalue); + return 0; + } + + *passno = (int) i; + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_kill_mode, kill_mode, KillMode, "Failed to parse kill mode"); + +int config_parse_kill_signal( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int *sig = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(sig); + + if ((r = signal_from_string_try_harder(rvalue)) <= 0) { + log_error("[%s:%u] Failed to parse kill signal, ignoring: %s", filename, line, rvalue); + return 0; + } + + *sig = r; + return 0; +} + +int config_parse_exec_mount_flags( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + char *w; + size_t l; + char *state; + unsigned long flags = 0; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + if (strncmp(w, "shared", MAX(l, 6U)) == 0) + flags |= MS_SHARED; + else if (strncmp(w, "slave", MAX(l, 5U)) == 0) + flags |= MS_SLAVE; + else if (strncmp(w, "private", MAX(l, 7U)) == 0) + flags |= MS_PRIVATE; + else { + log_error("[%s:%u] Failed to parse mount flags, ignoring: %s", filename, line, rvalue); + return 0; + } + } + + c->mount_flags = flags; + return 0; +} + +int config_parse_timer( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Timer *t = data; + usec_t u; + TimerValue *v; + TimerBase b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((b = timer_base_from_string(lvalue)) < 0) { + log_error("[%s:%u] Failed to parse timer base, ignoring: %s", filename, line, lvalue); + return 0; + } + + if (parse_usec(rvalue, &u) < 0) { + log_error("[%s:%u] Failed to parse timer value, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (!(v = new0(TimerValue, 1))) + return -ENOMEM; + + v->base = b; + v->value = u; + + LIST_PREPEND(TimerValue, value, t->values, v); + + return 0; +} + +int config_parse_timer_unit( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Timer *t = data; + int r; + DBusError error; + Unit *u; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + dbus_error_init(&error); + + if (endswith(rvalue, ".timer")) { + log_error("[%s:%u] Unit cannot be of type timer, ignoring: %s", filename, line, rvalue); + return 0; + } + + r = manager_load_unit(UNIT(t)->manager, rvalue, NULL, NULL, &u); + if (r < 0) { + log_error("[%s:%u] Failed to load unit %s, ignoring: %s", filename, line, rvalue, bus_error(&error, r)); + dbus_error_free(&error); + return 0; + } + + unit_ref_set(&t->unit, u); + + return 0; +} + +int config_parse_path_spec( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Path *p = data; + PathSpec *s; + PathType b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((b = path_type_from_string(lvalue)) < 0) { + log_error("[%s:%u] Failed to parse path type, ignoring: %s", filename, line, lvalue); + return 0; + } + + if (!path_is_absolute(rvalue)) { + log_error("[%s:%u] Path is not absolute, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (!(s = new0(PathSpec, 1))) + return -ENOMEM; + + if (!(s->path = strdup(rvalue))) { + free(s); + return -ENOMEM; + } + + path_kill_slashes(s->path); + + s->type = b; + s->inotify_fd = -1; + + LIST_PREPEND(PathSpec, spec, p->specs, s); + + return 0; +} + +int config_parse_path_unit( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Path *t = data; + int r; + DBusError error; + Unit *u; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + dbus_error_init(&error); + + if (endswith(rvalue, ".path")) { + log_error("[%s:%u] Unit cannot be of type path, ignoring: %s", filename, line, rvalue); + return 0; + } + + if ((r = manager_load_unit(UNIT(t)->manager, rvalue, NULL, &error, &u)) < 0) { + log_error("[%s:%u] Failed to load unit %s, ignoring: %s", filename, line, rvalue, bus_error(&error, r)); + dbus_error_free(&error); + return 0; + } + + unit_ref_set(&t->unit, u); + + return 0; +} + +int config_parse_socket_service( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Socket *s = data; + int r; + DBusError error; + Unit *x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + dbus_error_init(&error); + + if (!endswith(rvalue, ".service")) { + log_error("[%s:%u] Unit must be of type service, ignoring: %s", filename, line, rvalue); + return 0; + } + + r = manager_load_unit(UNIT(s)->manager, rvalue, NULL, &error, &x); + if (r < 0) { + log_error("[%s:%u] Failed to load unit %s, ignoring: %s", filename, line, rvalue, bus_error(&error, r)); + dbus_error_free(&error); + return 0; + } + + unit_ref_set(&s->service, x); + + return 0; +} + +int config_parse_service_sockets( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Service *s = data; + int r; + char *state, *w; + size_t l; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t, *k; + + t = strndup(w, l); + if (!t) + return -ENOMEM; + + k = unit_name_printf(UNIT(s), t); + free(t); + + if (!k) + return -ENOMEM; + + if (!endswith(k, ".socket")) { + log_error("[%s:%u] Unit must be of type socket, ignoring: %s", filename, line, rvalue); + free(k); + continue; + } + + r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_WANTS, UNIT_AFTER, k, NULL, true); + if (r < 0) + log_error("[%s:%u] Failed to add dependency on %s, ignoring: %s", filename, line, k, strerror(-r)); + + r = unit_add_dependency_by_name(UNIT(s), UNIT_TRIGGERED_BY, k, NULL, true); + if (r < 0) + return r; + + free(k); + } + + return 0; +} + +int config_parse_unit_env_file( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char ***env = data, **k; + Unit *u = userdata; + char *s; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + s = unit_full_printf(u, rvalue); + if (!s) + return -ENOMEM; + + if (!path_is_absolute(s[0] == '-' ? s + 1 : s)) { + log_error("[%s:%u] Path '%s' is not absolute, ignoring.", filename, line, s); + free(s); + return 0; + } + + k = strv_append(*env, s); + free(s); + if (!k) + return -ENOMEM; + + strv_free(*env); + *env = k; + + return 0; +} + +int config_parse_ip_tos( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int *ip_tos = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((x = ip_tos_from_string(rvalue)) < 0) + if (safe_atoi(rvalue, &x) < 0) { + log_error("[%s:%u] Failed to parse IP TOS value, ignoring: %s", filename, line, rvalue); + return 0; + } + + *ip_tos = x; + return 0; +} + +int config_parse_unit_condition_path( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ConditionType cond = ltype; + Unit *u = data; + bool trigger, negate; + Condition *c; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + trigger = rvalue[0] == '|'; + if (trigger) + rvalue++; + + negate = rvalue[0] == '!'; + if (negate) + rvalue++; + + if (!path_is_absolute(rvalue)) { + log_error("[%s:%u] Path in condition not absolute, ignoring: %s", filename, line, rvalue); + return 0; + } + + c = condition_new(cond, rvalue, trigger, negate); + if (!c) + return -ENOMEM; + + LIST_PREPEND(Condition, conditions, u->conditions, c); + return 0; +} + +int config_parse_unit_condition_string( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ConditionType cond = ltype; + Unit *u = data; + bool trigger, negate; + Condition *c; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((trigger = rvalue[0] == '|')) + rvalue++; + + if ((negate = rvalue[0] == '!')) + rvalue++; + + if (!(c = condition_new(cond, rvalue, trigger, negate))) + return -ENOMEM; + + LIST_PREPEND(Condition, conditions, u->conditions, c); + return 0; +} + +int config_parse_unit_condition_null( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = data; + Condition *c; + bool trigger, negate; + int b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((trigger = rvalue[0] == '|')) + rvalue++; + + if ((negate = rvalue[0] == '!')) + rvalue++; + + if ((b = parse_boolean(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse boolean value in condition, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (!b) + negate = !negate; + + if (!(c = condition_new(CONDITION_NULL, NULL, trigger, negate))) + return -ENOMEM; + + LIST_PREPEND(Condition, conditions, u->conditions, c); + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_start_limit_action, start_limit_action, StartLimitAction, "Failed to parse start limit action specifier"); + +int config_parse_unit_cgroup_attr( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = data; + char **l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + l = strv_split_quoted(rvalue); + if (!l) + return -ENOMEM; + + if (strv_length(l) != 2) { + log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + r = unit_add_cgroup_attribute(u, NULL, l[0], l[1], NULL); + strv_free(l); + + if (r < 0) { + log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { + Unit *u = data; + int r; + unsigned long ul; + char *t; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atolu(rvalue, &ul) < 0 || ul < 1) { + log_error("[%s:%u] Failed to parse CPU shares value, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (asprintf(&t, "%lu", ul) < 0) + return -ENOMEM; + + r = unit_add_cgroup_attribute(u, "cpu", "cpu.shares", t, NULL); + free(t); + + if (r < 0) { + log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { + Unit *u = data; + int r; + off_t sz; + char *t; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (parse_bytes(rvalue, &sz) < 0 || sz <= 0) { + log_error("[%s:%u] Failed to parse memory limit value, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (asprintf(&t, "%llu", (unsigned long long) sz) < 0) + return -ENOMEM; + + r = unit_add_cgroup_attribute(u, + "memory", + streq(lvalue, "MemorySoftLimit") ? "memory.soft_limit_in_bytes" : "memory.limit_in_bytes", + t, NULL); + free(t); + + if (r < 0) { + log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +static int device_map(const char *controller, const char *name, const char *value, char **ret) { + char **l; + + assert(controller); + assert(name); + assert(value); + assert(ret); + + l = strv_split_quoted(value); + if (!l) + return -ENOMEM; + + assert(strv_length(l) >= 1); + + if (streq(l[0], "*")) { + + if (asprintf(ret, "a *:*%s%s", + isempty(l[1]) ? "" : " ", strempty(l[1])) < 0) { + strv_free(l); + return -ENOMEM; + } + + } else { + struct stat st; + + if (stat(l[0], &st) < 0) { + log_warning("Couldn't stat device %s", l[0]); + strv_free(l); + return -errno; + } + + if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { + log_warning("%s is not a device.", l[0]); + strv_free(l); + return -ENODEV; + } + + if (asprintf(ret, "%c %u:%u%s%s", + S_ISCHR(st.st_mode) ? 'c' : 'b', + major(st.st_rdev), minor(st.st_rdev), + isempty(l[1]) ? "" : " ", strempty(l[1])) < 0) { + + strv_free(l); + return -ENOMEM; + } + } + + strv_free(l); + return 0; +} + +int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { + Unit *u = data; + char **l; + int r; + unsigned k; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + l = strv_split_quoted(rvalue); + if (!l) + return -ENOMEM; + + k = strv_length(l); + if (k < 1 || k > 2) { + log_error("[%s:%u] Failed to parse device value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (!streq(l[0], "*") && !path_startswith(l[0], "/dev")) { + log_error("[%s:%u] Device node path not absolute, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (!isempty(l[1]) && !in_charset(l[1], "rwm")) { + log_error("[%s:%u] Device access string invalid, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + strv_free(l); + + r = unit_add_cgroup_attribute(u, "devices", + streq(lvalue, "DeviceAllow") ? "devices.allow" : "devices.deny", + rvalue, device_map); + + if (r < 0) { + log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +static int blkio_map(const char *controller, const char *name, const char *value, char **ret) { + struct stat st; + char **l; + dev_t d; + + assert(controller); + assert(name); + assert(value); + assert(ret); + + l = strv_split_quoted(value); + if (!l) + return -ENOMEM; + + assert(strv_length(l) == 2); + + if (stat(l[0], &st) < 0) { + log_warning("Couldn't stat device %s", l[0]); + strv_free(l); + return -errno; + } + + if (S_ISBLK(st.st_mode)) + d = st.st_rdev; + else if (major(st.st_dev) != 0) { + /* If this is not a device node then find the block + * device this file is stored on */ + d = st.st_dev; + + /* If this is a partition, try to get the originating + * block device */ + block_get_whole_disk(d, &d); + } else { + log_warning("%s is not a block device and file system block device cannot be determined or is not local.", l[0]); + strv_free(l); + return -ENODEV; + } + + if (asprintf(ret, "%u:%u %s", major(d), minor(d), l[1]) < 0) { + strv_free(l); + return -ENOMEM; + } + + strv_free(l); + return 0; +} + +int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { + Unit *u = data; + int r; + unsigned long ul; + const char *device = NULL, *weight; + unsigned k; + char *t, **l; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + l = strv_split_quoted(rvalue); + if (!l) + return -ENOMEM; + + k = strv_length(l); + if (k < 1 || k > 2) { + log_error("[%s:%u] Failed to parse weight value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (k == 1) + weight = l[0]; + else { + device = l[0]; + weight = l[1]; + } + + if (device && !path_is_absolute(device)) { + log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (safe_atolu(weight, &ul) < 0 || ul < 10 || ul > 1000) { + log_error("[%s:%u] Failed to parse block IO weight value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (device) + r = asprintf(&t, "%s %lu", device, ul); + else + r = asprintf(&t, "%lu", ul); + strv_free(l); + + if (r < 0) + return -ENOMEM; + + if (device) + r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight_device", t, blkio_map); + else + r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight", t, NULL); + free(t); + + if (r < 0) { + log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { + Unit *u = data; + int r; + off_t bytes; + unsigned k; + char *t, **l; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + l = strv_split_quoted(rvalue); + if (!l) + return -ENOMEM; + + k = strv_length(l); + if (k != 2) { + log_error("[%s:%u] Failed to parse bandwidth value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (!path_is_absolute(l[0])) { + log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (parse_bytes(l[1], &bytes) < 0 || bytes <= 0) { + log_error("[%s:%u] Failed to parse block IO bandwith value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + r = asprintf(&t, "%s %llu", l[0], (unsigned long long) bytes); + strv_free(l); + + if (r < 0) + return -ENOMEM; + + r = unit_add_cgroup_attribute(u, "blkio", + streq(lvalue, "BlockIOReadBandwidth") ? "blkio.read_bps_device" : "blkio.write_bps_device", + t, blkio_map); + free(t); + + if (r < 0) { + log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + + +#define FOLLOW_MAX 8 + +static int open_follow(char **filename, FILE **_f, Set *names, char **_final) { + unsigned c = 0; + int fd, r; + FILE *f; + char *id = NULL; + + assert(filename); + assert(*filename); + assert(_f); + assert(names); + + /* This will update the filename pointer if the loaded file is + * reached by a symlink. The old string will be freed. */ + + for (;;) { + char *target, *name; + + if (c++ >= FOLLOW_MAX) + return -ELOOP; + + path_kill_slashes(*filename); + + /* Add the file name we are currently looking at to + * the names of this unit, but only if it is a valid + * unit name. */ + name = file_name_from_path(*filename); + + if (unit_name_is_valid(name, true)) { + + id = set_get(names, name); + if (!id) { + id = strdup(name); + if (!id) + return -ENOMEM; + + r = set_put(names, id); + if (r < 0) { + free(id); + return r; + } + } + } + + /* Try to open the file name, but don't if its a symlink */ + if ((fd = open(*filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW)) >= 0) + break; + + if (errno != ELOOP) + return -errno; + + /* Hmm, so this is a symlink. Let's read the name, and follow it manually */ + if ((r = readlink_and_make_absolute(*filename, &target)) < 0) + return r; + + free(*filename); + *filename = target; + } + + if (!(f = fdopen(fd, "re"))) { + r = -errno; + close_nointr_nofail(fd); + return r; + } + + *_f = f; + *_final = id; + return 0; +} + +static int merge_by_names(Unit **u, Set *names, const char *id) { + char *k; + int r; + + assert(u); + assert(*u); + assert(names); + + /* Let's try to add in all symlink names we found */ + while ((k = set_steal_first(names))) { + + /* First try to merge in the other name into our + * unit */ + if ((r = unit_merge_by_name(*u, k)) < 0) { + Unit *other; + + /* Hmm, we couldn't merge the other unit into + * ours? Then let's try it the other way + * round */ + + other = manager_get_unit((*u)->manager, k); + free(k); + + if (other) + if ((r = unit_merge(other, *u)) >= 0) { + *u = other; + return merge_by_names(u, names, NULL); + } + + return r; + } + + if (id == k) + unit_choose_id(*u, id); + + free(k); + } + + return 0; +} + +static int load_from_path(Unit *u, const char *path) { + int r; + Set *symlink_names; + FILE *f = NULL; + char *filename = NULL, *id = NULL; + Unit *merged; + struct stat st; + + assert(u); + assert(path); + + symlink_names = set_new(string_hash_func, string_compare_func); + if (!symlink_names) + return -ENOMEM; + + if (path_is_absolute(path)) { + + if (!(filename = strdup(path))) { + r = -ENOMEM; + goto finish; + } + + if ((r = open_follow(&filename, &f, symlink_names, &id)) < 0) { + free(filename); + filename = NULL; + + if (r != -ENOENT) + goto finish; + } + + } else { + char **p; + + STRV_FOREACH(p, u->manager->lookup_paths.unit_path) { + + /* Instead of opening the path right away, we manually + * follow all symlinks and add their name to our unit + * name set while doing so */ + if (!(filename = path_make_absolute(path, *p))) { + r = -ENOMEM; + goto finish; + } + + if (u->manager->unit_path_cache && + !set_get(u->manager->unit_path_cache, filename)) + r = -ENOENT; + else + r = open_follow(&filename, &f, symlink_names, &id); + + if (r < 0) { + char *sn; + + free(filename); + filename = NULL; + + if (r != -ENOENT) + goto finish; + + /* Empty the symlink names for the next run */ + while ((sn = set_steal_first(symlink_names))) + free(sn); + + continue; + } + + break; + } + } + + if (!filename) { + /* Hmm, no suitable file found? */ + r = 0; + goto finish; + } + + merged = u; + if ((r = merge_by_names(&merged, symlink_names, id)) < 0) + goto finish; + + if (merged != u) { + u->load_state = UNIT_MERGED; + r = 0; + goto finish; + } + + zero(st); + if (fstat(fileno(f), &st) < 0) { + r = -errno; + goto finish; + } + + if (null_or_empty(&st)) + u->load_state = UNIT_MASKED; + else { + /* Now, parse the file contents */ + r = config_parse(filename, f, UNIT_VTABLE(u)->sections, config_item_perf_lookup, (void*) load_fragment_gperf_lookup, false, u); + if (r < 0) + goto finish; + + u->load_state = UNIT_LOADED; + } + + free(u->fragment_path); + u->fragment_path = filename; + filename = NULL; + + u->fragment_mtime = timespec_load(&st.st_mtim); + + r = 0; + +finish: + set_free_free(symlink_names); + free(filename); + + if (f) + fclose(f); + + return r; +} + +int unit_load_fragment(Unit *u) { + int r; + Iterator i; + const char *t; + + assert(u); + assert(u->load_state == UNIT_STUB); + assert(u->id); + + /* First, try to find the unit under its id. We always look + * for unit files in the default directories, to make it easy + * to override things by placing things in /etc/systemd/system */ + if ((r = load_from_path(u, u->id)) < 0) + return r; + + /* Try to find an alias we can load this with */ + if (u->load_state == UNIT_STUB) + SET_FOREACH(t, u->names, i) { + + if (t == u->id) + continue; + + if ((r = load_from_path(u, t)) < 0) + return r; + + if (u->load_state != UNIT_STUB) + break; + } + + /* And now, try looking for it under the suggested (originally linked) path */ + if (u->load_state == UNIT_STUB && u->fragment_path) { + + if ((r = load_from_path(u, u->fragment_path)) < 0) + return r; + + if (u->load_state == UNIT_STUB) { + /* Hmm, this didn't work? Then let's get rid + * of the fragment path stored for us, so that + * we don't point to an invalid location. */ + free(u->fragment_path); + u->fragment_path = NULL; + } + } + + /* Look for a template */ + if (u->load_state == UNIT_STUB && u->instance) { + char *k; + + if (!(k = unit_name_template(u->id))) + return -ENOMEM; + + r = load_from_path(u, k); + free(k); + + if (r < 0) + return r; + + if (u->load_state == UNIT_STUB) + SET_FOREACH(t, u->names, i) { + + if (t == u->id) + continue; + + if (!(k = unit_name_template(t))) + return -ENOMEM; + + r = load_from_path(u, k); + free(k); + + if (r < 0) + return r; + + if (u->load_state != UNIT_STUB) + break; + } + } + + return 0; +} + +void unit_dump_config_items(FILE *f) { + static const struct { + const ConfigParserCallback callback; + const char *rvalue; + } table[] = { + { config_parse_int, "INTEGER" }, + { config_parse_unsigned, "UNSIGNED" }, + { config_parse_bytes_size, "SIZE" }, + { config_parse_bool, "BOOLEAN" }, + { config_parse_string, "STRING" }, + { config_parse_path, "PATH" }, + { config_parse_unit_path_printf, "PATH" }, + { config_parse_strv, "STRING [...]" }, + { config_parse_exec_nice, "NICE" }, + { config_parse_exec_oom_score_adjust, "OOMSCOREADJUST" }, + { config_parse_exec_io_class, "IOCLASS" }, + { config_parse_exec_io_priority, "IOPRIORITY" }, + { config_parse_exec_cpu_sched_policy, "CPUSCHEDPOLICY" }, + { config_parse_exec_cpu_sched_prio, "CPUSCHEDPRIO" }, + { config_parse_exec_cpu_affinity, "CPUAFFINITY" }, + { config_parse_mode, "MODE" }, + { config_parse_unit_env_file, "FILE" }, + { config_parse_output, "OUTPUT" }, + { config_parse_input, "INPUT" }, + { config_parse_facility, "FACILITY" }, + { config_parse_level, "LEVEL" }, + { config_parse_exec_capabilities, "CAPABILITIES" }, + { config_parse_exec_secure_bits, "SECUREBITS" }, + { config_parse_exec_bounding_set, "BOUNDINGSET" }, + { config_parse_exec_timer_slack_nsec, "TIMERSLACK" }, + { config_parse_limit, "LIMIT" }, + { config_parse_unit_cgroup, "CGROUP [...]" }, + { config_parse_unit_deps, "UNIT [...]" }, + { config_parse_unit_names, "UNIT [...]" }, + { config_parse_exec, "PATH [ARGUMENT [...]]" }, + { config_parse_service_type, "SERVICETYPE" }, + { config_parse_service_restart, "SERVICERESTART" }, +#ifdef HAVE_SYSV_COMPAT + { config_parse_sysv_priority, "SYSVPRIORITY" }, +#else + { config_parse_warn_compat, "NOTSUPPORTED" }, +#endif + { config_parse_kill_mode, "KILLMODE" }, + { config_parse_kill_signal, "SIGNAL" }, + { config_parse_socket_listen, "SOCKET [...]" }, + { config_parse_socket_bind, "SOCKETBIND" }, + { config_parse_socket_bindtodevice, "NETWORKINTERFACE" }, + { config_parse_usec, "SECONDS" }, + { config_parse_path_strv, "PATH [...]" }, + { config_parse_exec_mount_flags, "MOUNTFLAG [...]" }, + { config_parse_unit_string_printf, "STRING" }, + { config_parse_timer, "TIMER" }, + { config_parse_timer_unit, "NAME" }, + { config_parse_path_spec, "PATH" }, + { config_parse_path_unit, "UNIT" }, + { config_parse_notify_access, "ACCESS" }, + { config_parse_ip_tos, "TOS" }, + { config_parse_unit_condition_path, "CONDITION" }, + { config_parse_unit_condition_string, "CONDITION" }, + { config_parse_unit_condition_null, "CONDITION" }, + }; + + const char *prev = NULL; + const char *i; + + assert(f); + + NULSTR_FOREACH(i, load_fragment_gperf_nulstr) { + const char *rvalue = "OTHER", *lvalue; + unsigned j; + size_t prefix_len; + const char *dot; + const ConfigPerfItem *p; + + assert_se(p = load_fragment_gperf_lookup(i, strlen(i))); + + dot = strchr(i, '.'); + lvalue = dot ? dot + 1 : i; + prefix_len = dot-i; + + if (dot) + if (!prev || strncmp(prev, i, prefix_len+1) != 0) { + if (prev) + fputc('\n', f); + + fprintf(f, "[%.*s]\n", (int) prefix_len, i); + } + + for (j = 0; j < ELEMENTSOF(table); j++) + if (p->parse == table[j].callback) { + rvalue = table[j].rvalue; + break; + } + + fprintf(f, "%s=%s\n", lvalue, rvalue); + prev = i; + } +} diff --git a/src/load-fragment.h b/src/load-fragment.h new file mode 100644 index 000000000..79fc76da9 --- /dev/null +++ b/src/load-fragment.h @@ -0,0 +1,91 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooloadfragmenthfoo +#define fooloadfragmenthfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "unit.h" + +/* Read service data from .desktop file style configuration fragments */ + +int unit_load_fragment(Unit *u); + +void unit_dump_config_items(FILE *f); + +int config_parse_warn_compat(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_deps(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_names(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_string_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_strv_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_path_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_socket_listen(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_socket_bind(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_nice(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_oom_score_adjust(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_service_type(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_service_restart(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_socket_bindtodevice(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_output(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_input(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_facility(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_level(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_io_class(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_io_priority(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_cpu_sched_policy(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_cpu_sched_prio(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_cpu_affinity(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_capabilities(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_secure_bits(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_bounding_set(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_timer_slack_nsec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_cgroup(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_sysv_priority(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_fsck_passno(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_kill_signal(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_mount_flags(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_timer(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_timer_unit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_path_spec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_path_unit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_socket_service(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_service_sockets(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_env_file(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_ip_tos(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_condition_path(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_condition_string(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_condition_null(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_kill_mode(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_notify_access(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_start_limit_action(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_cgroup_attr(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); + +/* gperf prototypes */ +const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length); +extern const char load_fragment_gperf_nulstr[]; + +#endif diff --git a/src/locale-setup.c b/src/locale-setup.c new file mode 100644 index 000000000..7f692e9c5 --- /dev/null +++ b/src/locale-setup.c @@ -0,0 +1,251 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "locale-setup.h" +#include "util.h" +#include "macro.h" +#include "virt.h" + +enum { + /* We don't list LC_ALL here on purpose. People should be + * using LANG instead. */ + + VARIABLE_LANG, + VARIABLE_LANGUAGE, + VARIABLE_LC_CTYPE, + VARIABLE_LC_NUMERIC, + VARIABLE_LC_TIME, + VARIABLE_LC_COLLATE, + VARIABLE_LC_MONETARY, + VARIABLE_LC_MESSAGES, + VARIABLE_LC_PAPER, + VARIABLE_LC_NAME, + VARIABLE_LC_ADDRESS, + VARIABLE_LC_TELEPHONE, + VARIABLE_LC_MEASUREMENT, + VARIABLE_LC_IDENTIFICATION, + _VARIABLE_MAX +}; + +static const char * const variable_names[_VARIABLE_MAX] = { + [VARIABLE_LANG] = "LANG", + [VARIABLE_LANGUAGE] = "LANGUAGE", + [VARIABLE_LC_CTYPE] = "LC_CTYPE", + [VARIABLE_LC_NUMERIC] = "LC_NUMERIC", + [VARIABLE_LC_TIME] = "LC_TIME", + [VARIABLE_LC_COLLATE] = "LC_COLLATE", + [VARIABLE_LC_MONETARY] = "LC_MONETARY", + [VARIABLE_LC_MESSAGES] = "LC_MESSAGES", + [VARIABLE_LC_PAPER] = "LC_PAPER", + [VARIABLE_LC_NAME] = "LC_NAME", + [VARIABLE_LC_ADDRESS] = "LC_ADDRESS", + [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE", + [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT", + [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION" +}; + +int locale_setup(void) { + char *variables[_VARIABLE_MAX]; + int r = 0, i; + + zero(variables); + + if (detect_container(NULL) <= 0) + if ((r = parse_env_file("/proc/cmdline", WHITESPACE, +#if defined(TARGET_FEDORA) || defined(TARGET_MEEGO) + "LANG", &variables[VARIABLE_LANG], +#endif + "locale.LANG", &variables[VARIABLE_LANG], + "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE], + "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE], + "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], + "locale.LC_TIME", &variables[VARIABLE_LC_TIME], + "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE], + "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY], + "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], + "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER], + "locale.LC_NAME", &variables[VARIABLE_LC_NAME], + "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], + "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], + "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], + "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /proc/cmdline: %s", strerror(-r)); + } + + /* Hmm, nothing set on the kernel cmd line? Then let's + * try /etc/locale.conf */ + if (r <= 0 && + (r = parse_env_file("/etc/locale.conf", NEWLINE, + "LANG", &variables[VARIABLE_LANG], + "LANGUAGE", &variables[VARIABLE_LANGUAGE], + "LC_CTYPE", &variables[VARIABLE_LC_CTYPE], + "LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], + "LC_TIME", &variables[VARIABLE_LC_TIME], + "LC_COLLATE", &variables[VARIABLE_LC_COLLATE], + "LC_MONETARY", &variables[VARIABLE_LC_MONETARY], + "LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], + "LC_PAPER", &variables[VARIABLE_LC_PAPER], + "LC_NAME", &variables[VARIABLE_LC_NAME], + "LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], + "LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], + "LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], + "LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/locale.conf: %s", strerror(-r)); + } + +#if defined(TARGET_FEDORA) || defined(TARGET_ALTLINUX) || defined(TARGET_MEEGO) + if (r <= 0 && + (r = parse_env_file("/etc/sysconfig/i18n", NEWLINE, + "LANG", &variables[VARIABLE_LANG], + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r)); + } + +#elif defined(TARGET_SUSE) + if (r <= 0 && + (r = parse_env_file("/etc/sysconfig/language", NEWLINE, + "RC_LANG", &variables[VARIABLE_LANG], + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/language: %s", strerror(-r)); + } + +#elif defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) + if (r <= 0 && + (r = parse_env_file("/etc/default/locale", NEWLINE, + "LANG", &variables[VARIABLE_LANG], + "LC_CTYPE", &variables[VARIABLE_LC_CTYPE], + "LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], + "LC_TIME", &variables[VARIABLE_LC_TIME], + "LC_COLLATE", &variables[VARIABLE_LC_COLLATE], + "LC_MONETARY", &variables[VARIABLE_LC_MONETARY], + "LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], + "LC_PAPER", &variables[VARIABLE_LC_PAPER], + "LC_NAME", &variables[VARIABLE_LC_NAME], + "LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], + "LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], + "LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], + "LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/default/locale: %s", strerror(-r)); + } + +#elif defined(TARGET_ARCH) + if (r <= 0 && + (r = parse_env_file("/etc/rc.conf", NEWLINE, + "LOCALE", &variables[VARIABLE_LANG], + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/rc.conf: %s", strerror(-r)); + } + +#elif defined(TARGET_GENTOO) + /* Gentoo's openrc expects locale variables in /etc/env.d/ + * These files are later compiled by env-update into shell + * export commands at /etc/profile.env, with variables being + * exported by openrc's runscript (so /etc/init.d/) + */ + if (r <= 0 && + (r = parse_env_file("/etc/profile.env", NEWLINE, + "export LANG", &variables[VARIABLE_LANG], + "export LC_CTYPE", &variables[VARIABLE_LC_CTYPE], + "export LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], + "export LC_TIME", &variables[VARIABLE_LC_TIME], + "export LC_COLLATE", &variables[VARIABLE_LC_COLLATE], + "export LC_MONETARY", &variables[VARIABLE_LC_MONETARY], + "export LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], + "export LC_PAPER", &variables[VARIABLE_LC_PAPER], + "export LC_NAME", &variables[VARIABLE_LC_NAME], + "export LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], + "export LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], + "export LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], + "export LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/profile.env: %s", strerror(-r)); + } +#elif defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA ) + if (r <= 0 && + (r = parse_env_file("/etc/sysconfig/i18n", NEWLINE, + "LANG", &variables[VARIABLE_LANG], + "LC_CTYPE", &variables[VARIABLE_LC_CTYPE], + "LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], + "LC_TIME", &variables[VARIABLE_LC_TIME], + "LC_COLLATE", &variables[VARIABLE_LC_COLLATE], + "LC_MONETARY", &variables[VARIABLE_LC_MONETARY], + "LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], + "LC_PAPER", &variables[VARIABLE_LC_PAPER], + "LC_NAME", &variables[VARIABLE_LC_NAME], + "LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], + "LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], + "LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], + "LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r)); + } + +#endif + + if (!variables[VARIABLE_LANG]) { + if (!(variables[VARIABLE_LANG] = strdup("C"))) { + r = -ENOMEM; + goto finish; + } + } + + for (i = 0; i < _VARIABLE_MAX; i++) { + + if (variables[i]) { + if (setenv(variable_names[i], variables[i], 1) < 0) { + r = -errno; + goto finish; + } + } else + unsetenv(variable_names[i]); + } + + r = 0; + +finish: + for (i = 0; i < _VARIABLE_MAX; i++) + free(variables[i]); + + return r; +} diff --git a/src/locale-setup.h b/src/locale-setup.h new file mode 100644 index 000000000..09a6bc682 --- /dev/null +++ b/src/locale-setup.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foolocalesetuphfoo +#define foolocalesetuphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +int locale_setup(void); + +#endif diff --git a/src/locale/.gitignore b/src/locale/.gitignore new file mode 100644 index 000000000..b1e0ba755 --- /dev/null +++ b/src/locale/.gitignore @@ -0,0 +1 @@ +org.freedesktop.locale1.policy diff --git a/src/locale/Makefile b/src/locale/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/locale/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/locale/generate-kbd-model-map b/src/locale/generate-kbd-model-map new file mode 100755 index 000000000..624c5179f --- /dev/null +++ b/src/locale/generate-kbd-model-map @@ -0,0 +1,33 @@ +#!/usr/bin/python + +import sys +import system_config_keyboard.keyboard_models + +def strdash(s): + return s.strip() or '-' + +def tab_extend(s, n=1): + s = strdash(s) + k = len(s) // 8 + + if k >= n: + f = 1 + else: + f = n - k + + return s + '\t'*f + + +models = system_config_keyboard.keyboard_models.KeyboardModels().get_models() + +print "# Generated from system-config-keyboard's model list" +print "# consolelayout\t\txlayout\txmodel\t\txvariant\txoptions" + +for key, value in reversed(models.items()): + options = "terminate:ctrl_alt_bksp" + if value[4]: + options += ',' + value[4] + + print ''.join((tab_extend(key, 3), tab_extend(value[1]), + tab_extend(value[2], 2), tab_extend(value[3], 2), + options)) diff --git a/src/locale/kbd-model-map b/src/locale/kbd-model-map new file mode 100644 index 000000000..a89588026 --- /dev/null +++ b/src/locale/kbd-model-map @@ -0,0 +1,72 @@ +# Generated from system-config-keyboard's model list +# consolelayout xlayout xmodel xvariant xoptions +sg ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp +nl nl pc105 - terminate:ctrl_alt_bksp +mk-utf mkd,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +trq tr pc105 - terminate:ctrl_alt_bksp +guj in,us pc105 guj terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +uk gb pc105 - terminate:ctrl_alt_bksp +is-latin1 is pc105 - terminate:ctrl_alt_bksp +de de pc105 - terminate:ctrl_alt_bksp +gur gur,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +la-latin1 latam pc105 - terminate:ctrl_alt_bksp +us us pc105+inet - terminate:ctrl_alt_bksp +ko kr pc105 - terminate:ctrl_alt_bksp +ro-std ro pc105 std terminate:ctrl_alt_bksp +de-latin1 de pc105 - terminate:ctrl_alt_bksp +tml-inscript in,us pc105 tam terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +slovene si pc105 - terminate:ctrl_alt_bksp +hu101 hu pc105 qwerty terminate:ctrl_alt_bksp +jp106 jp jp106 - terminate:ctrl_alt_bksp +croat hr pc105 - terminate:ctrl_alt_bksp +ben-probhat in,us pc105 ben_probhat terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +fi-latin1 fi pc105 - terminate:ctrl_alt_bksp +it2 it pc105 - terminate:ctrl_alt_bksp +hu hu pc105 - terminate:ctrl_alt_bksp +sr-latin rs pc105 latin terminate:ctrl_alt_bksp +fi fi pc105 - terminate:ctrl_alt_bksp +fr_CH ch pc105 fr terminate:ctrl_alt_bksp +dk-latin1 dk pc105 - terminate:ctrl_alt_bksp +fr fr pc105 - terminate:ctrl_alt_bksp +it it pc105 - terminate:ctrl_alt_bksp +tml-uni in,us pc105 tam_TAB terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +ua-utf ua,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +fr-latin1 fr pc105 - terminate:ctrl_alt_bksp +sg-latin1 ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp +be-latin1 be pc105 - terminate:ctrl_alt_bksp +dk dk pc105 - terminate:ctrl_alt_bksp +fr-pc fr pc105 - terminate:ctrl_alt_bksp +bg_pho-utf8 bg,us pc105 ,phonetic terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +it-ibm it pc105 - terminate:ctrl_alt_bksp +cz-us-qwertz cz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +ar-digits ara,us pc105 digits terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +br-abnt2 br abnt2 - terminate:ctrl_alt_bksp +ro ro pc105 - terminate:ctrl_alt_bksp +us-acentos us pc105 intl terminate:ctrl_alt_bksp +pt-latin1 pt pc105 - terminate:ctrl_alt_bksp +ro-std-cedilla ro pc105 std_cedilla terminate:ctrl_alt_bksp +tj tj pc105 - terminate:ctrl_alt_bksp +ar-qwerty ara,us pc105 qwerty terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +ar-azerty-digits ara,us pc105 azerty_digits terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +ben in,us pc105 ben terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +de-latin1-nodeadkeys de pc105 nodeadkeys terminate:ctrl_alt_bksp +no no pc105 - terminate:ctrl_alt_bksp +bg_bds-utf8 bg,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +dvorak us pc105 dvorak terminate:ctrl_alt_bksp +ru ru,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +cz-lat2 cz pc105 qwerty terminate:ctrl_alt_bksp +pl2 pl pc105 - terminate:ctrl_alt_bksp +es es pc105 - terminate:ctrl_alt_bksp +ro-cedilla ro pc105 cedilla terminate:ctrl_alt_bksp +ie ie pc105 - terminate:ctrl_alt_bksp +ar-azerty ara,us pc105 azerty terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +ar-qwerty-digits ara,us pc105 qwerty_digits terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +et ee pc105 - terminate:ctrl_alt_bksp +sk-qwerty sk pc105 - terminate:ctrl_alt_bksp,qwerty +dev dev,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +fr-latin9 fr pc105 latin9 terminate:ctrl_alt_bksp +fr_CH-latin1 ch pc105 fr terminate:ctrl_alt_bksp +cf ca(fr) pc105 - terminate:ctrl_alt_bksp +sv-latin1 se pc105 - terminate:ctrl_alt_bksp +sr-cy rs pc105 - terminate:ctrl_alt_bksp +gr gr,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll diff --git a/src/locale/localed.c b/src/locale/localed.c new file mode 100644 index 000000000..e9f9f8687 --- /dev/null +++ b/src/locale/localed.c @@ -0,0 +1,1437 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include +#include +#include + +#include "util.h" +#include "strv.h" +#include "dbus-common.h" +#include "polkit.h" +#include "def.h" + +#define INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + BUS_PEER_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.locale1\0" + +const char locale_interface[] _introspect_("locale1") = INTERFACE; + +enum { + /* We don't list LC_ALL here on purpose. People should be + * using LANG instead. */ + + PROP_LANG, + PROP_LANGUAGE, + PROP_LC_CTYPE, + PROP_LC_NUMERIC, + PROP_LC_TIME, + PROP_LC_COLLATE, + PROP_LC_MONETARY, + PROP_LC_MESSAGES, + PROP_LC_PAPER, + PROP_LC_NAME, + PROP_LC_ADDRESS, + PROP_LC_TELEPHONE, + PROP_LC_MEASUREMENT, + PROP_LC_IDENTIFICATION, + _PROP_MAX +}; + +static const char * const names[_PROP_MAX] = { + [PROP_LANG] = "LANG", + [PROP_LANGUAGE] = "LANGUAGE", + [PROP_LC_CTYPE] = "LC_CTYPE", + [PROP_LC_NUMERIC] = "LC_NUMERIC", + [PROP_LC_TIME] = "LC_TIME", + [PROP_LC_COLLATE] = "LC_COLLATE", + [PROP_LC_MONETARY] = "LC_MONETARY", + [PROP_LC_MESSAGES] = "LC_MESSAGES", + [PROP_LC_PAPER] = "LC_PAPER", + [PROP_LC_NAME] = "LC_NAME", + [PROP_LC_ADDRESS] = "LC_ADDRESS", + [PROP_LC_TELEPHONE] = "LC_TELEPHONE", + [PROP_LC_MEASUREMENT] = "LC_MEASUREMENT", + [PROP_LC_IDENTIFICATION] = "LC_IDENTIFICATION" +}; + +static char *data[_PROP_MAX] = { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +typedef struct State { + char *x11_layout, *x11_model, *x11_variant, *x11_options; + char *vc_keymap, *vc_keymap_toggle; +} State; + +static State state; + +static usec_t remain_until = 0; + +static int free_and_set(char **s, const char *v) { + int r; + char *t; + + assert(s); + + r = strdup_or_null(isempty(v) ? NULL : v, &t); + if (r < 0) + return r; + + free(*s); + *s = t; + + return 0; +} + +static void free_data_locale(void) { + int p; + + for (p = 0; p < _PROP_MAX; p++) { + free(data[p]); + data[p] = NULL; + } +} + +static void free_data_x11(void) { + free(state.x11_layout); + free(state.x11_model); + free(state.x11_variant); + free(state.x11_options); + + state.x11_layout = state.x11_model = state.x11_variant = state.x11_options = NULL; +} + +static void free_data_vconsole(void) { + free(state.vc_keymap); + free(state.vc_keymap_toggle); + + state.vc_keymap = state.vc_keymap_toggle = NULL; +} + +static void simplify(void) { + int p; + + for (p = 1; p < _PROP_MAX; p++) + if (isempty(data[p]) || streq_ptr(data[PROP_LANG], data[p])) { + free(data[p]); + data[p] = NULL; + } +} + +static int read_data_locale(void) { + int r; + + free_data_locale(); + + r = parse_env_file("/etc/locale.conf", NEWLINE, + "LANG", &data[PROP_LANG], + "LANGUAGE", &data[PROP_LANGUAGE], + "LC_CTYPE", &data[PROP_LC_CTYPE], + "LC_NUMERIC", &data[PROP_LC_NUMERIC], + "LC_TIME", &data[PROP_LC_TIME], + "LC_COLLATE", &data[PROP_LC_COLLATE], + "LC_MONETARY", &data[PROP_LC_MONETARY], + "LC_MESSAGES", &data[PROP_LC_MESSAGES], + "LC_PAPER", &data[PROP_LC_PAPER], + "LC_NAME", &data[PROP_LC_NAME], + "LC_ADDRESS", &data[PROP_LC_ADDRESS], + "LC_TELEPHONE", &data[PROP_LC_TELEPHONE], + "LC_MEASUREMENT", &data[PROP_LC_MEASUREMENT], + "LC_IDENTIFICATION", &data[PROP_LC_IDENTIFICATION], + NULL); + + if (r == -ENOENT) { + int p; + + /* Fill in what we got passed from systemd. */ + + for (p = 0; p < _PROP_MAX; p++) { + char *e, *d; + + assert(names[p]); + + e = getenv(names[p]); + if (e) { + d = strdup(e); + if (!d) + return -ENOMEM; + } else + d = NULL; + + free(data[p]); + data[p] = d; + } + + r = 0; + } + + simplify(); + return r; +} + +static void free_data(void) { + free_data_locale(); + free_data_vconsole(); + free_data_x11(); +} + +static int read_data_vconsole(void) { + int r; + + free_data_vconsole(); + + r = parse_env_file("/etc/vconsole.conf", NEWLINE, + "KEYMAP", &state.vc_keymap, + "KEYMAP_TOGGLE", &state.vc_keymap_toggle, + NULL); + + if (r < 0 && r != -ENOENT) + return r; + + return 0; +} + +static int read_data_x11(void) { + FILE *f; + char line[LINE_MAX]; + bool in_section = false; + + free_data_x11(); + + f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re"); + if (!f) { + if (errno == ENOENT) { + +#ifdef TARGET_FEDORA + f = fopen("/etc/X11/xorg.conf.d/00-system-setup-keyboard.conf", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + else + return -errno; + } +#else + return 0; +#endif + + } else + return -errno; + } + + while (fgets(line, sizeof(line), f)) { + char *l; + + char_array_0(line); + l = strstrip(line); + + if (l[0] == 0 || l[0] == '#') + continue; + + if (in_section && first_word(l, "Option")) { + char **a; + + a = strv_split_quoted(l); + if (!a) { + fclose(f); + return -ENOMEM; + } + + if (strv_length(a) == 3) { + + if (streq(a[1], "XkbLayout")) { + free(state.x11_layout); + state.x11_layout = a[2]; + a[2] = NULL; + } else if (streq(a[1], "XkbModel")) { + free(state.x11_model); + state.x11_model = a[2]; + a[2] = NULL; + } else if (streq(a[1], "XkbVariant")) { + free(state.x11_variant); + state.x11_variant = a[2]; + a[2] = NULL; + } else if (streq(a[1], "XkbOptions")) { + free(state.x11_options); + state.x11_options = a[2]; + a[2] = NULL; + } + } + + strv_free(a); + + } else if (!in_section && first_word(l, "Section")) { + char **a; + + a = strv_split_quoted(l); + if (!a) { + fclose(f); + return -ENOMEM; + } + + if (strv_length(a) == 2 && streq(a[1], "InputClass")) + in_section = true; + + strv_free(a); + } else if (in_section && first_word(l, "EndSection")) + in_section = false; + } + + fclose(f); + + return 0; +} + +static int read_data(void) { + int r, q, p; + + r = read_data_locale(); + q = read_data_vconsole(); + p = read_data_x11(); + + return r < 0 ? r : q < 0 ? q : p; +} + +static int write_data_locale(void) { + int r, p; + char **l = NULL; + + r = load_env_file("/etc/locale.conf", &l); + if (r < 0 && r != -ENOENT) + return r; + + for (p = 0; p < _PROP_MAX; p++) { + char *t, **u; + + assert(names[p]); + + if (isempty(data[p])) { + l = strv_env_unset(l, names[p]); + continue; + } + + if (asprintf(&t, "%s=%s", names[p], data[p]) < 0) { + strv_free(l); + return -ENOMEM; + } + + u = strv_env_set(l, t); + free(t); + strv_free(l); + + if (!u) + return -ENOMEM; + + l = u; + } + + if (strv_isempty(l)) { + strv_free(l); + + if (unlink("/etc/locale.conf") < 0) + return errno == ENOENT ? 0 : -errno; + + return 0; + } + + r = write_env_file("/etc/locale.conf", l); + strv_free(l); + + return r; +} + +static void push_data(DBusConnection *bus) { + char **l_set = NULL, **l_unset = NULL, **t; + int c_set = 0, c_unset = 0, p; + DBusError error; + DBusMessage *m = NULL, *reply = NULL; + DBusMessageIter iter, sub; + + dbus_error_init(&error); + + assert(bus); + + l_set = new0(char*, _PROP_MAX); + l_unset = new0(char*, _PROP_MAX); + if (!l_set || !l_unset) { + log_error("Out of memory"); + goto finish; + } + + for (p = 0; p < _PROP_MAX; p++) { + assert(names[p]); + + if (isempty(data[p])) + l_unset[c_set++] = (char*) names[p]; + else { + char *s; + + if (asprintf(&s, "%s=%s", names[p], data[p]) < 0) { + log_error("Out of memory"); + goto finish; + } + + l_set[c_unset++] = s; + } + } + + assert(c_set + c_unset == _PROP_MAX); + m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnsetAndSetEnvironment"); + if (!m) { + log_error("Could not allocate message."); + goto finish; + } + + dbus_message_iter_init_append(m, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) { + log_error("Out of memory."); + goto finish; + } + + STRV_FOREACH(t, l_unset) + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, t)) { + log_error("Out of memory."); + goto finish; + } + + if (!dbus_message_iter_close_container(&iter, &sub) || + !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) { + log_error("Out of memory."); + goto finish; + } + + STRV_FOREACH(t, l_set) + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, t)) { + log_error("Out of memory."); + goto finish; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) { + log_error("Out of memory."); + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to set locale information: %s", bus_error_message(&error)); + goto finish; + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + strv_free(l_set); + free(l_unset); +} + +static int write_data_vconsole(void) { + int r; + char **l = NULL; + + r = load_env_file("/etc/vconsole.conf", &l); + if (r < 0 && r != -ENOENT) + return r; + + if (isempty(state.vc_keymap)) + l = strv_env_unset(l, "KEYMAP"); + else { + char *s, **u; + + s = strappend("KEYMAP=", state.vc_keymap); + if (!s) { + strv_free(l); + return -ENOMEM; + } + + u = strv_env_set(l, s); + free(s); + strv_free(l); + + if (!u) + return -ENOMEM; + + l = u; + } + + if (isempty(state.vc_keymap_toggle)) + l = strv_env_unset(l, "KEYMAP_TOGGLE"); + else { + char *s, **u; + + s = strappend("KEYMAP_TOGGLE=", state.vc_keymap_toggle); + if (!s) { + strv_free(l); + return -ENOMEM; + } + + u = strv_env_set(l, s); + free(s); + strv_free(l); + + if (!u) + return -ENOMEM; + + l = u; + } + + if (strv_isempty(l)) { + strv_free(l); + + if (unlink("/etc/vconsole.conf") < 0) + return errno == ENOENT ? 0 : -errno; + + return 0; + } + + r = write_env_file("/etc/vconsole.conf", l); + strv_free(l); + + return r; +} + +static int write_data_x11(void) { + FILE *f; + char *temp_path; + int r; + + if (isempty(state.x11_layout) && + isempty(state.x11_model) && + isempty(state.x11_variant) && + isempty(state.x11_options)) { + +#ifdef TARGET_FEDORA + unlink("/etc/X11/xorg.conf.d/00-system-setup-keyboard.conf"); + + /* Symlink this to /dev/null, so that s-s-k (if it is + * still running) doesn't recreate this. */ + symlink("/dev/null", "/etc/X11/xorg.conf.d/00-system-setup-keyboard.conf"); +#endif + + if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) + return errno == ENOENT ? 0 : -errno; + + return 0; + } + + mkdir_parents("/etc/X11/xorg.conf.d", 0755); + + r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path); + if (r < 0) + return r; + + fchmod(fileno(f), 0644); + + fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n" + "# manually too freely.\n" + "Section \"InputClass\"\n" + " Identifier \"system-keyboard\"\n" + " MatchIsKeyboard \"on\"\n", f); + + if (!isempty(state.x11_layout)) + fprintf(f, " Option \"XkbLayout\" \"%s\"\n", state.x11_layout); + + if (!isempty(state.x11_model)) + fprintf(f, " Option \"XkbModel\" \"%s\"\n", state.x11_model); + + if (!isempty(state.x11_variant)) + fprintf(f, " Option \"XkbVariant\" \"%s\"\n", state.x11_variant); + + if (!isempty(state.x11_options)) + fprintf(f, " Option \"XkbOptions\" \"%s\"\n", state.x11_options); + + fputs("EndSection\n", f); + fflush(f); + + if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) { + r = -errno; + unlink("/etc/X11/xorg.conf.d/00-keyboard.conf"); + unlink(temp_path); + } else { + +#ifdef TARGET_FEDORA + unlink("/etc/X11/xorg.conf.d/00-system-setup-keyboard.conf"); + + /* Symlink this to /dev/null, so that s-s-k (if it is + * still running) doesn't recreate this. */ + symlink("/dev/null", "/etc/X11/xorg.conf.d/00-system-setup-keyboard.conf"); +#endif + + r = 0; + } + + fclose(f); + free(temp_path); + + return r; +} + +static int load_vconsole_keymap(DBusConnection *bus, DBusError *error) { + DBusMessage *m = NULL, *reply = NULL; + const char *name = "systemd-vconsole-setup.service", *mode = "replace"; + int r; + DBusError _error; + + assert(bus); + + if (!error) { + dbus_error_init(&_error); + error = &_error; + } + + m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "RestartUnit"); + if (!m) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(error)); + r = -EIO; + goto finish; + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + if (error == &_error) + dbus_error_free(error); + + return r; +} + +static char *strnulldash(const char *s) { + return s == NULL || *s == 0 || (s[0] == '-' && s[1] == 0) ? NULL : (char*) s; +} + +static int read_next_mapping(FILE *f, unsigned *n, char ***a) { + assert(f); + assert(n); + assert(a); + + for (;;) { + char line[LINE_MAX]; + char *l, **b; + + errno = 0; + if (!fgets(line, sizeof(line), f)) { + + if (ferror(f)) + return errno ? -errno : -EIO; + + return 0; + } + + (*n) ++; + + l = strstrip(line); + if (l[0] == 0 || l[0] == '#') + continue; + + b = strv_split_quoted(l); + if (!b) + return -ENOMEM; + + if (strv_length(b) < 5) { + log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n); + strv_free(b); + continue; + + } + + *a = b; + return 1; + } +} + +static int convert_vconsole_to_x11(DBusConnection *connection) { + bool modified = false; + + assert(connection); + + if (isempty(state.vc_keymap)) { + + modified = + !isempty(state.x11_layout) || + !isempty(state.x11_model) || + !isempty(state.x11_variant) || + !isempty(state.x11_options); + + free_data_x11(); + } else { + FILE *f; + unsigned n = 0; + + f = fopen(SYSTEMD_KBD_MODEL_MAP, "re"); + if (!f) + return -errno; + + for (;;) { + char **a; + int r; + + r = read_next_mapping(f, &n, &a); + if (r < 0) { + fclose(f); + return r; + } + + if (r == 0) + break; + + if (!streq(state.vc_keymap, a[0])) { + strv_free(a); + continue; + } + + if (!streq_ptr(state.x11_layout, strnulldash(a[1])) || + !streq_ptr(state.x11_model, strnulldash(a[2])) || + !streq_ptr(state.x11_variant, strnulldash(a[3])) || + !streq_ptr(state.x11_options, strnulldash(a[4]))) { + + if (free_and_set(&state.x11_layout, strnulldash(a[1])) < 0 || + free_and_set(&state.x11_model, strnulldash(a[2])) < 0 || + free_and_set(&state.x11_variant, strnulldash(a[3])) < 0 || + free_and_set(&state.x11_options, strnulldash(a[4])) < 0) { + strv_free(a); + fclose(f); + return -ENOMEM; + } + + modified = true; + } + + strv_free(a); + break; + } + + fclose(f); + } + + if (modified) { + dbus_bool_t b; + DBusMessage *changed; + int r; + + r = write_data_x11(); + if (r < 0) + log_error("Failed to set X11 keyboard layout: %s", strerror(-r)); + + changed = bus_properties_changed_new( + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "X11Layout\0" + "X11Model\0" + "X11Variant\0" + "X11Options\0"); + + if (!changed) + return -ENOMEM; + + b = dbus_connection_send(connection, changed, NULL); + dbus_message_unref(changed); + + if (!b) + return -ENOMEM; + } + + return 0; +} + +static int convert_x11_to_vconsole(DBusConnection *connection) { + bool modified = false; + + assert(connection); + + if (isempty(state.x11_layout)) { + + modified = + !isempty(state.vc_keymap) || + !isempty(state.vc_keymap_toggle); + + free_data_x11(); + } else { + FILE *f; + unsigned n = 0; + unsigned best_matching = 0; + char *new_keymap = NULL; + + f = fopen(SYSTEMD_KBD_MODEL_MAP, "re"); + if (!f) + return -errno; + + for (;;) { + char **a; + unsigned matching = 0; + int r; + + r = read_next_mapping(f, &n, &a); + if (r < 0) { + fclose(f); + return r; + } + + if (r == 0) + break; + + /* Determine how well matching this entry is */ + if (streq_ptr(state.x11_layout, a[1])) + /* If we got an exact match, this is best */ + matching = 10; + else { + size_t x; + + x = strcspn(state.x11_layout, ","); + + /* We have multiple X layouts, look + * for an entry that matches our key + * with the everything but the first + * layout stripped off. */ + if (x > 0 && + strlen(a[1]) == x && + strncmp(state.x11_layout, a[1], x) == 0) + matching = 5; + else { + size_t w; + + /* If that didn't work, strip + * off the other layouts from + * the entry, too */ + + w = strcspn(a[1], ","); + + if (x > 0 && x == w && + memcmp(state.x11_layout, a[1], x) == 0) + matching = 1; + } + } + + if (matching > 0 && + streq_ptr(state.x11_model, a[2])) { + matching++; + + if (streq_ptr(state.x11_variant, a[3])) { + matching++; + + if (streq_ptr(state.x11_options, a[4])) + matching++; + } + } + + /* The best matching entry so far, then let's + * save that */ + if (matching > best_matching) { + best_matching = matching; + + free(new_keymap); + new_keymap = strdup(a[0]); + + if (!new_keymap) { + strv_free(a); + fclose(f); + return -ENOMEM; + } + } + + strv_free(a); + } + + fclose(f); + + if (!streq_ptr(state.vc_keymap, new_keymap)) { + free(state.vc_keymap); + state.vc_keymap = new_keymap; + + free(state.vc_keymap_toggle); + state.vc_keymap_toggle = NULL; + + modified = true; + } else + free(new_keymap); + } + + if (modified) { + dbus_bool_t b; + DBusMessage *changed; + int r; + + r = write_data_vconsole(); + if (r < 0) + log_error("Failed to set virtual console keymap: %s", strerror(-r)); + + changed = bus_properties_changed_new( + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "VConsoleKeymap\0" + "VConsoleKeymapToggle\0"); + + if (!changed) + return -ENOMEM; + + b = dbus_connection_send(connection, changed, NULL); + dbus_message_unref(changed); + + if (!b) + return -ENOMEM; + + return load_vconsole_keymap(connection, NULL); + } + + return 0; +} + +static int append_locale(DBusMessageIter *i, const char *property, void *userdata) { + int r, c = 0, p; + char **l; + + l = new0(char*, _PROP_MAX+1); + if (!l) + return -ENOMEM; + + for (p = 0; p < _PROP_MAX; p++) { + char *t; + + if (isempty(data[p])) + continue; + + if (asprintf(&t, "%s=%s", names[p], data[p]) < 0) { + strv_free(l); + return -ENOMEM; + } + + l[c++] = t; + } + + r = bus_property_append_strv(i, property, (void*) l); + strv_free(l); + + return r; +} + +static const BusProperty bus_locale_properties[] = { + { "Locale", append_locale, "as", 0 }, + { "X11Layout", bus_property_append_string, "s", offsetof(State, x11_layout), true }, + { "X11Model", bus_property_append_string, "s", offsetof(State, x11_model), true }, + { "X11Variant", bus_property_append_string, "s", offsetof(State, x11_variant), true }, + { "X11Options", bus_property_append_string, "s", offsetof(State, x11_options), true }, + { "VConsoleKeymap", bus_property_append_string, "s", offsetof(State, vc_keymap), true }, + { "VConsoleKeymapToggle", bus_property_append_string, "s", offsetof(State, vc_keymap_toggle), true }, + { NULL, } +}; + +static const BusBoundProperties bps[] = { + { "org.freedesktop.locale1", bus_locale_properties, &state }, + { NULL, } +}; + +static DBusHandlerResult locale_message_handler( + DBusConnection *connection, + DBusMessage *message, + void *userdata) { + + DBusMessage *reply = NULL, *changed = NULL; + DBusError error; + int r; + + assert(connection); + assert(message); + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetLocale")) { + char **l = NULL, **i; + dbus_bool_t interactive; + DBusMessageIter iter; + bool modified = false; + bool passed[_PROP_MAX]; + int p; + + if (!dbus_message_iter_init(message, &iter)) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + + r = bus_parse_strv_iter(&iter, &l); + if (r < 0) { + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) { + strv_free(l); + return bus_send_error_reply(connection, message, NULL, -EINVAL); + } + + dbus_message_iter_get_basic(&iter, &interactive); + + zero(passed); + + /* Check whether a variable changed and if so valid */ + STRV_FOREACH(i, l) { + bool valid = false; + + for (p = 0; p < _PROP_MAX; p++) { + size_t k; + + k = strlen(names[p]); + if (startswith(*i, names[p]) && (*i)[k] == '=') { + valid = true; + passed[p] = true; + + if (!streq_ptr(*i + k + 1, data[p])) + modified = true; + + break; + } + } + + if (!valid) { + strv_free(l); + return bus_send_error_reply(connection, message, NULL, -EINVAL); + } + } + + /* Check whether a variable is unset */ + if (!modified) { + for (p = 0; p < _PROP_MAX; p++) + if (!isempty(data[p]) && !passed[p]) { + modified = true; + break; + } + } + + if (modified) { + + r = verify_polkit(connection, message, "org.freedesktop.locale1.set-locale", interactive, NULL, &error); + if (r < 0) { + strv_free(l); + return bus_send_error_reply(connection, message, &error, r); + } + + STRV_FOREACH(i, l) { + for (p = 0; p < _PROP_MAX; p++) { + size_t k; + + k = strlen(names[p]); + if (startswith(*i, names[p]) && (*i)[k] == '=') { + char *t; + + t = strdup(*i + k + 1); + if (!t) { + strv_free(l); + goto oom; + } + + free(data[p]); + data[p] = t; + + break; + } + } + } + + strv_free(l); + + for (p = 0; p < _PROP_MAX; p++) { + if (passed[p]) + continue; + + free(data[p]); + data[p] = NULL; + } + + simplify(); + + r = write_data_locale(); + if (r < 0) { + log_error("Failed to set locale: %s", strerror(-r)); + return bus_send_error_reply(connection, message, NULL, r); + } + + push_data(connection); + + log_info("Changed locale information."); + + changed = bus_properties_changed_new( + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "Locale\0"); + if (!changed) + goto oom; + } + } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetVConsoleKeyboard")) { + + const char *keymap, *keymap_toggle; + dbus_bool_t convert, interactive; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &keymap, + DBUS_TYPE_STRING, &keymap_toggle, + DBUS_TYPE_BOOLEAN, &convert, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(keymap)) + keymap = NULL; + + if (isempty(keymap_toggle)) + keymap_toggle = NULL; + + if (!streq_ptr(keymap, state.vc_keymap) || + !streq_ptr(keymap_toggle, state.vc_keymap_toggle)) { + + r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (free_and_set(&state.vc_keymap, keymap) < 0 || + free_and_set(&state.vc_keymap_toggle, keymap_toggle) < 0) + goto oom; + + r = write_data_vconsole(); + if (r < 0) { + log_error("Failed to set virtual console keymap: %s", strerror(-r)); + return bus_send_error_reply(connection, message, NULL, r); + } + + log_info("Changed virtual console keymap to '%s'", strempty(state.vc_keymap)); + + r = load_vconsole_keymap(connection, NULL); + if (r < 0) + log_error("Failed to request keymap reload: %s", strerror(-r)); + + changed = bus_properties_changed_new( + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "VConsoleKeymap\0" + "VConsoleKeymapToggle\0"); + if (!changed) + goto oom; + + if (convert) { + r = convert_vconsole_to_x11(connection); + + if (r < 0) + log_error("Failed to convert keymap data: %s", strerror(-r)); + } + } + + } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetX11Keyboard")) { + + const char *layout, *model, *variant, *options; + dbus_bool_t convert, interactive; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &layout, + DBUS_TYPE_STRING, &model, + DBUS_TYPE_STRING, &variant, + DBUS_TYPE_STRING, &options, + DBUS_TYPE_BOOLEAN, &convert, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(layout)) + layout = NULL; + + if (isempty(model)) + model = NULL; + + if (isempty(variant)) + variant = NULL; + + if (isempty(options)) + options = NULL; + + if (!streq_ptr(layout, state.x11_layout) || + !streq_ptr(model, state.x11_model) || + !streq_ptr(variant, state.x11_variant) || + !streq_ptr(options, state.x11_options)) { + + r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (free_and_set(&state.x11_layout, layout) < 0 || + free_and_set(&state.x11_model, model) < 0 || + free_and_set(&state.x11_variant, variant) < 0 || + free_and_set(&state.x11_options, options) < 0) + goto oom; + + r = write_data_x11(); + if (r < 0) { + log_error("Failed to set X11 keyboard layout: %s", strerror(-r)); + return bus_send_error_reply(connection, message, NULL, r); + } + + log_info("Changed X11 keyboard layout to '%s'", strempty(state.x11_layout)); + + changed = bus_properties_changed_new( + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "X11Layout\0" + "X11Model\0" + "X11Variant\0" + "X11Options\0"); + if (!changed) + goto oom; + + if (convert) { + r = convert_x11_to_vconsole(connection); + + if (r < 0) + log_error("Failed to convert keymap data: %s", strerror(-r)); + } + } + } else + return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + reply = NULL; + + if (changed) { + + if (!dbus_connection_send(connection, changed, NULL)) + goto oom; + + dbus_message_unref(changed); + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + if (changed) + dbus_message_unref(changed); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static int connect_bus(DBusConnection **_bus) { + static const DBusObjectPathVTable locale_vtable = { + .message_function = locale_message_handler + }; + DBusError error; + DBusConnection *bus = NULL; + int r; + + assert(_bus); + + dbus_error_init(&error); + + bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + if (!bus) { + log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error)); + r = -ECONNREFUSED; + goto fail; + } + + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + if (!dbus_connection_register_object_path(bus, "/org/freedesktop/locale1", &locale_vtable, NULL) || + !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) { + log_error("Not enough memory"); + r = -ENOMEM; + goto fail; + } + + r = dbus_bus_request_name(bus, "org.freedesktop.locale1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to register name on bus: %s", bus_error_message(&error)); + r = -EEXIST; + goto fail; + } + + if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + log_error("Failed to acquire name."); + r = -EEXIST; + goto fail; + } + + if (_bus) + *_bus = bus; + + return 0; + +fail: + dbus_connection_close(bus); + dbus_connection_unref(bus); + + dbus_error_free(&error); + + return r; +} + +int main(int argc, char *argv[]) { + int r; + DBusConnection *bus = NULL; + bool exiting = false; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc == 2 && streq(argv[1], "--introspect")) { + fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "\n", stdout); + fputs(locale_interface, stdout); + fputs("\n", stdout); + return 0; + } + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + r = read_data(); + if (r < 0) { + log_error("Failed to read locale data: %s", strerror(-r)); + goto finish; + } + + r = connect_bus(&bus); + if (r < 0) + goto finish; + + remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC; + for (;;) { + + if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC))) + break; + + if (!exiting && remain_until < now(CLOCK_MONOTONIC)) { + exiting = true; + bus_async_unregister_and_exit(bus, "org.freedesktop.locale1"); + } + } + + r = 0; + +finish: + free_data(); + + if (bus) { + dbus_connection_flush(bus); + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/locale/org.freedesktop.locale1.conf b/src/locale/org.freedesktop.locale1.conf new file mode 100644 index 000000000..68273311e --- /dev/null +++ b/src/locale/org.freedesktop.locale1.conf @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/locale/org.freedesktop.locale1.policy.in b/src/locale/org.freedesktop.locale1.policy.in new file mode 100644 index 000000000..1ac50bf86 --- /dev/null +++ b/src/locale/org.freedesktop.locale1.policy.in @@ -0,0 +1,39 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Set system locale + <_message>Authentication is required to set the system locale. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Set system keyboard settings + <_message>Authentication is required to set the system keyboard settings. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + diff --git a/src/locale/org.freedesktop.locale1.service b/src/locale/org.freedesktop.locale1.service new file mode 100644 index 000000000..29bd58245 --- /dev/null +++ b/src/locale/org.freedesktop.locale1.service @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[D-BUS Service] +Name=org.freedesktop.locale1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.locale1.service diff --git a/src/log.c b/src/log.c new file mode 100644 index 000000000..9fffc1dbc --- /dev/null +++ b/src/log.c @@ -0,0 +1,747 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "util.h" +#include "macro.h" +#include "socket-util.h" + +#define SNDBUF_SIZE (8*1024*1024) + +static LogTarget log_target = LOG_TARGET_CONSOLE; +static int log_max_level = LOG_INFO; +static int log_facility = LOG_DAEMON; + +static int console_fd = STDERR_FILENO; +static int syslog_fd = -1; +static int kmsg_fd = -1; +static int journal_fd = -1; + +static bool syslog_is_stream = false; + +static bool show_color = false; +static bool show_location = false; + +/* Akin to glibc's __abort_msg; which is private and we hence cannot + * use here. */ +static char *log_abort_msg = NULL; + +void log_close_console(void) { + + if (console_fd < 0) + return; + + if (getpid() == 1) { + if (console_fd >= 3) + close_nointr_nofail(console_fd); + + console_fd = -1; + } +} + +static int log_open_console(void) { + + if (console_fd >= 0) + return 0; + + if (getpid() == 1) { + + console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (console_fd < 0) { + log_error("Failed to open /dev/console for logging: %s", strerror(-console_fd)); + return console_fd; + } + + log_debug("Successfully opened /dev/console for logging."); + } else + console_fd = STDERR_FILENO; + + return 0; +} + +void log_close_kmsg(void) { + + if (kmsg_fd < 0) + return; + + close_nointr_nofail(kmsg_fd); + kmsg_fd = -1; +} + +static int log_open_kmsg(void) { + + if (kmsg_fd >= 0) + return 0; + + kmsg_fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (kmsg_fd < 0) { + log_error("Failed to open /dev/kmsg for logging: %s", strerror(errno)); + return -errno; + } + + log_debug("Successfully opened /dev/kmsg for logging."); + + return 0; +} + +void log_close_syslog(void) { + + if (syslog_fd < 0) + return; + + close_nointr_nofail(syslog_fd); + syslog_fd = -1; +} + +static int create_log_socket(int type) { + int fd; + + /* All output to the syslog/journal fds we do asynchronously, + * and if the buffers are full we just drop the messages */ + + fd = socket(AF_UNIX, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return -errno; + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + return fd; +} + +static int log_open_syslog(void) { + union sockaddr_union sa; + int r; + + if (syslog_fd >= 0) + return 0; + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, "/dev/log", sizeof(sa.un.sun_path)); + + syslog_fd = create_log_socket(SOCK_DGRAM); + if (syslog_fd < 0) { + r = syslog_fd; + goto fail; + } + + if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { + close_nointr_nofail(syslog_fd); + + /* Some legacy syslog systems still use stream + * sockets. They really shouldn't. But what can we + * do... */ + syslog_fd = create_log_socket(SOCK_STREAM); + if (syslog_fd < 0) { + r = syslog_fd; + goto fail; + } + + if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { + r = -errno; + goto fail; + } + + syslog_is_stream = true; + } else + syslog_is_stream = false; + + log_debug("Successfully opened syslog for logging."); + + return 0; + +fail: + log_close_syslog(); + log_debug("Failed to open syslog for logging: %s", strerror(-r)); + return r; +} + +void log_close_journal(void) { + + if (journal_fd < 0) + return; + + close_nointr_nofail(journal_fd); + journal_fd = -1; +} + +static int log_open_journal(void) { + union sockaddr_union sa; + int r; + + if (journal_fd >= 0) + return 0; + + journal_fd = create_log_socket(SOCK_DGRAM); + if (journal_fd < 0) { + r = journal_fd; + goto fail; + } + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, "/run/systemd/journal/socket", sizeof(sa.un.sun_path)); + + if (connect(journal_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { + r = -errno; + goto fail; + } + + log_debug("Successfully opened journal for logging."); + + return 0; + +fail: + log_close_journal(); + log_debug("Failed to open journal for logging: %s", strerror(-r)); + return r; +} + +int log_open(void) { + int r; + + /* If we don't use the console we close it here, to not get + * killed by SAK. If we don't use syslog we close it here so + * that we are not confused by somebody deleting the socket in + * the fs. If we don't use /dev/kmsg we still keep it open, + * because there is no reason to close it. */ + + if (log_target == LOG_TARGET_NULL) { + log_close_journal(); + log_close_syslog(); + log_close_console(); + return 0; + } + + if (log_target != LOG_TARGET_AUTO || + getpid() == 1 || + isatty(STDERR_FILENO) <= 0) { + + if (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_JOURNAL) { + r = log_open_journal(); + if (r >= 0) { + log_close_syslog(); + log_close_console(); + return r; + } + } + + if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_SYSLOG) { + r = log_open_syslog(); + if (r >= 0) { + log_close_journal(); + log_close_console(); + return r; + } + } + + if (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_KMSG) { + r = log_open_kmsg(); + if (r >= 0) { + log_close_journal(); + log_close_syslog(); + log_close_console(); + return r; + } + } + } + + log_close_journal(); + log_close_syslog(); + + /* Get the real /dev/console if we are PID=1, hence reopen */ + log_close_console(); + return log_open_console(); +} + +void log_set_target(LogTarget target) { + assert(target >= 0); + assert(target < _LOG_TARGET_MAX); + + log_target = target; +} + +void log_close(void) { + log_close_journal(); + log_close_syslog(); + log_close_kmsg(); + log_close_console(); +} + +void log_forget_fds(void) { + console_fd = kmsg_fd = syslog_fd = journal_fd = -1; +} + +void log_set_max_level(int level) { + assert((level & LOG_PRIMASK) == level); + + log_max_level = level; +} + +void log_set_facility(int facility) { + log_facility = facility; +} + +static int write_to_console( + int level, + const char*file, + int line, + const char *func, + const char *buffer) { + + char location[64]; + struct iovec iovec[5]; + unsigned n = 0; + bool highlight; + + if (console_fd < 0) + return 0; + + highlight = LOG_PRI(level) <= LOG_ERR && show_color; + + zero(iovec); + + if (show_location) { + snprintf(location, sizeof(location), "(%s:%u) ", file, line); + char_array_0(location); + IOVEC_SET_STRING(iovec[n++], location); + } + + if (highlight) + IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_RED_ON); + IOVEC_SET_STRING(iovec[n++], buffer); + if (highlight) + IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_OFF); + IOVEC_SET_STRING(iovec[n++], "\n"); + + if (writev(console_fd, iovec, n) < 0) + return -errno; + + return 1; +} + +static int write_to_syslog( + int level, + const char*file, + int line, + const char *func, + const char *buffer) { + + char header_priority[16], header_time[64], header_pid[16]; + struct iovec iovec[5]; + struct msghdr msghdr; + time_t t; + struct tm *tm; + + if (syslog_fd < 0) + return 0; + + snprintf(header_priority, sizeof(header_priority), "<%i>", level); + char_array_0(header_priority); + + t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC); + if (!(tm = localtime(&t))) + return -EINVAL; + + if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0) + return -EINVAL; + + snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) getpid()); + char_array_0(header_pid); + + zero(iovec); + IOVEC_SET_STRING(iovec[0], header_priority); + IOVEC_SET_STRING(iovec[1], header_time); + IOVEC_SET_STRING(iovec[2], program_invocation_short_name); + IOVEC_SET_STRING(iovec[3], header_pid); + IOVEC_SET_STRING(iovec[4], buffer); + + /* When using syslog via SOCK_STREAM separate the messages by NUL chars */ + if (syslog_is_stream) + iovec[4].iov_len++; + + zero(msghdr); + msghdr.msg_iov = iovec; + msghdr.msg_iovlen = ELEMENTSOF(iovec); + + for (;;) { + ssize_t n; + + n = sendmsg(syslog_fd, &msghdr, MSG_NOSIGNAL); + if (n < 0) + return -errno; + + if (!syslog_is_stream || + (size_t) n >= IOVEC_TOTAL_SIZE(iovec, ELEMENTSOF(iovec))) + break; + + IOVEC_INCREMENT(iovec, ELEMENTSOF(iovec), n); + } + + return 1; +} + +static int write_to_kmsg( + int level, + const char*file, + int line, + const char *func, + const char *buffer) { + + char header_priority[16], header_pid[16]; + struct iovec iovec[5]; + + if (kmsg_fd < 0) + return 0; + + snprintf(header_priority, sizeof(header_priority), "<%i>", level); + char_array_0(header_priority); + + snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) getpid()); + char_array_0(header_pid); + + zero(iovec); + IOVEC_SET_STRING(iovec[0], header_priority); + IOVEC_SET_STRING(iovec[1], program_invocation_short_name); + IOVEC_SET_STRING(iovec[2], header_pid); + IOVEC_SET_STRING(iovec[3], buffer); + IOVEC_SET_STRING(iovec[4], "\n"); + + if (writev(kmsg_fd, iovec, ELEMENTSOF(iovec)) < 0) + return -errno; + + return 1; +} + +static int write_to_journal( + int level, + const char*file, + int line, + const char *func, + const char *buffer) { + + char header[LINE_MAX]; + struct iovec iovec[3]; + struct msghdr mh; + + if (journal_fd < 0) + return 0; + + snprintf(header, sizeof(header), + "PRIORITY=%i\n" + "SYSLOG_FACILITY=%i\n" + "CODE_FILE=%s\n" + "CODE_LINE=%i\n" + "CODE_FUNCTION=%s\n" + "MESSAGE=", + LOG_PRI(level), + LOG_FAC(level), + file, + line, + func); + + char_array_0(header); + + zero(iovec); + IOVEC_SET_STRING(iovec[0], header); + IOVEC_SET_STRING(iovec[1], buffer); + IOVEC_SET_STRING(iovec[2], "\n"); + + zero(mh); + mh.msg_iov = iovec; + mh.msg_iovlen = ELEMENTSOF(iovec); + + if (sendmsg(journal_fd, &mh, MSG_NOSIGNAL) < 0) + return -errno; + + return 1; +} + +static int log_dispatch( + int level, + const char*file, + int line, + const char *func, + char *buffer) { + + int r = 0; + + if (log_target == LOG_TARGET_NULL) + return 0; + + /* Patch in LOG_DAEMON facility if necessary */ + if ((level & LOG_FACMASK) == 0) + level = log_facility | LOG_PRI(level); + + do { + char *e; + int k = 0; + + buffer += strspn(buffer, NEWLINE); + + if (buffer[0] == 0) + break; + + if ((e = strpbrk(buffer, NEWLINE))) + *(e++) = 0; + + if (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_JOURNAL) { + + k = write_to_journal(level, file, line, func, buffer); + if (k < 0) { + if (k != -EAGAIN) + log_close_journal(); + log_open_kmsg(); + } else if (k > 0) + r++; + } + + if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_SYSLOG) { + + k = write_to_syslog(level, file, line, func, buffer); + if (k < 0) { + if (k != -EAGAIN) + log_close_syslog(); + log_open_kmsg(); + } else if (k > 0) + r++; + } + + if (k <= 0 && + (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_KMSG)) { + + k = write_to_kmsg(level, file, line, func, buffer); + if (k < 0) { + log_close_kmsg(); + log_open_console(); + } else if (k > 0) + r++; + } + + if (k <= 0) { + k = write_to_console(level, file, line, func, buffer); + if (k < 0) + return k; + } + + buffer = e; + } while (buffer); + + return r; +} + +int log_dump_internal( + int level, + const char*file, + int line, + const char *func, + char *buffer) { + + int saved_errno, r; + + /* This modifies the buffer... */ + + if (_likely_(LOG_PRI(level) > log_max_level)) + return 0; + + saved_errno = errno; + r = log_dispatch(level, file, line, func, buffer); + errno = saved_errno; + + return r; +} + +int log_metav( + int level, + const char*file, + int line, + const char *func, + const char *format, + va_list ap) { + + char buffer[LINE_MAX]; + int saved_errno, r; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return 0; + + saved_errno = errno; + vsnprintf(buffer, sizeof(buffer), format, ap); + char_array_0(buffer); + + r = log_dispatch(level, file, line, func, buffer); + errno = saved_errno; + + return r; +} + +int log_meta( + int level, + const char*file, + int line, + const char *func, + const char *format, ...) { + + int r; + va_list ap; + + va_start(ap, format); + r = log_metav(level, file, line, func, format, ap); + va_end(ap); + + return r; +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +_noreturn_ static void log_assert(const char *text, const char *file, int line, const char *func, const char *format) { + static char buffer[LINE_MAX]; + + snprintf(buffer, sizeof(buffer), format, text, file, line, func); + + char_array_0(buffer); + log_abort_msg = buffer; + + log_dispatch(LOG_CRIT, file, line, func, buffer); + abort(); +} +#pragma GCC diagnostic pop + +_noreturn_ void log_assert_failed(const char *text, const char *file, int line, const char *func) { + log_assert(text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting."); +} + +_noreturn_ void log_assert_failed_unreachable(const char *text, const char *file, int line, const char *func) { + log_assert(text, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting."); +} + +int log_set_target_from_string(const char *e) { + LogTarget t; + + t = log_target_from_string(e); + if (t < 0) + return -EINVAL; + + log_set_target(t); + return 0; +} + +int log_set_max_level_from_string(const char *e) { + int t; + + t = log_level_from_string(e); + if (t < 0) + return t; + + log_set_max_level(t); + return 0; +} + +void log_parse_environment(void) { + const char *e; + + if ((e = getenv("SYSTEMD_LOG_TARGET"))) + if (log_set_target_from_string(e) < 0) + log_warning("Failed to parse log target %s. Ignoring.", e); + + if ((e = getenv("SYSTEMD_LOG_LEVEL"))) + if (log_set_max_level_from_string(e) < 0) + log_warning("Failed to parse log level %s. Ignoring.", e); + + if ((e = getenv("SYSTEMD_LOG_COLOR"))) + if (log_show_color_from_string(e) < 0) + log_warning("Failed to parse bool %s. Ignoring.", e); + + if ((e = getenv("SYSTEMD_LOG_LOCATION"))) + if (log_show_location_from_string(e) < 0) + log_warning("Failed to parse bool %s. Ignoring.", e); +} + +LogTarget log_get_target(void) { + return log_target; +} + +int log_get_max_level(void) { + return log_max_level; +} + +void log_show_color(bool b) { + show_color = b; +} + +void log_show_location(bool b) { + show_location = b; +} + +int log_show_color_from_string(const char *e) { + int t; + + t = parse_boolean(e); + if (t < 0) + return t; + + log_show_color(t); + return 0; +} + +int log_show_location_from_string(const char *e) { + int t; + + t = parse_boolean(e); + if (t < 0) + return t; + + log_show_location(t); + return 0; +} + +static const char *const log_target_table[] = { + [LOG_TARGET_CONSOLE] = "console", + [LOG_TARGET_KMSG] = "kmsg", + [LOG_TARGET_JOURNAL] = "journal", + [LOG_TARGET_JOURNAL_OR_KMSG] = "journal-or-kmsg", + [LOG_TARGET_SYSLOG] = "syslog", + [LOG_TARGET_SYSLOG_OR_KMSG] = "syslog-or-kmsg", + [LOG_TARGET_AUTO] = "auto", + [LOG_TARGET_NULL] = "null" +}; + +DEFINE_STRING_TABLE_LOOKUP(log_target, LogTarget); diff --git a/src/log.h b/src/log.h new file mode 100644 index 000000000..3283808ff --- /dev/null +++ b/src/log.h @@ -0,0 +1,111 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foologhfoo +#define foologhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "macro.h" + +typedef enum LogTarget{ + LOG_TARGET_CONSOLE, + LOG_TARGET_KMSG, + LOG_TARGET_JOURNAL, + LOG_TARGET_JOURNAL_OR_KMSG, + LOG_TARGET_SYSLOG, + LOG_TARGET_SYSLOG_OR_KMSG, + LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */ + LOG_TARGET_NULL, + _LOG_TARGET_MAX, + _LOG_TARGET_INVALID = -1 +} LogTarget; + +void log_set_target(LogTarget target); +void log_set_max_level(int level); +void log_set_facility(int facility); + +int log_set_target_from_string(const char *e); +int log_set_max_level_from_string(const char *e); + +void log_show_color(bool b); +void log_show_location(bool b); + +int log_show_color_from_string(const char *e); +int log_show_location_from_string(const char *e); + +LogTarget log_get_target(void); +int log_get_max_level(void); + +int log_open(void); +void log_close(void); +void log_forget_fds(void); + +void log_close_syslog(void); +void log_close_journal(void); +void log_close_kmsg(void); +void log_close_console(void); + +void log_parse_environment(void); + +int log_meta( + int level, + const char*file, + int line, + const char *func, + const char *format, ...) _printf_attr_(5,6); + +int log_metav( + int level, + const char*file, + int line, + const char *func, + const char *format, + va_list ap); + +_noreturn_ void log_assert_failed(const char *text, const char *file, int line, const char *func); +_noreturn_ void log_assert_failed_unreachable(const char *text, const char *file, int line, const char *func); + +/* This modifies the buffer passed! */ +int log_dump_internal( + int level, + const char*file, + int line, + const char *func, + char *buffer); + +#define log_full(level, ...) log_meta(level, __FILE__, __LINE__, __func__, __VA_ARGS__) + +#define log_debug(...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define log_info(...) log_meta(LOG_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define log_notice(...) log_meta(LOG_NOTICE, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define log_warning(...) log_meta(LOG_WARNING, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define log_error(...) log_meta(LOG_ERR, __FILE__, __LINE__, __func__, __VA_ARGS__) + +/* This modifies the buffer passed! */ +#define log_dump(level, buffer) log_dump_internal(level, __FILE__, __LINE__, __func__, buffer) + +const char *log_target_to_string(LogTarget target); +LogTarget log_target_from_string(const char *s); + +#endif diff --git a/src/login/.gitignore b/src/login/.gitignore new file mode 100644 index 000000000..1c0f3995e --- /dev/null +++ b/src/login/.gitignore @@ -0,0 +1,3 @@ +logind-gperf.c +org.freedesktop.login1.policy +73-seat-late.rules diff --git a/src/login/70-uaccess.rules b/src/login/70-uaccess.rules new file mode 100644 index 000000000..693249226 --- /dev/null +++ b/src/login/70-uaccess.rules @@ -0,0 +1,72 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +ACTION=="remove", GOTO="uaccess_end" +ENV{MAJOR}=="", GOTO="uaccess_end" + +# PTP/MTP protocol devices, cameras, portable media players +SUBSYSTEM=="usb", ENV{ID_USB_INTERFACES}=="", ENV{DEVTYPE}=="usb_device", IMPORT{program}="usb_id --export %p" +SUBSYSTEM=="usb", ENV{ID_USB_INTERFACES}=="*:060101:*", TAG+="uaccess" + +# Digicams with proprietary protocol +ENV{ID_GPHOTO2}=="*?", TAG+="uaccess" + +# SCSI and USB scanners +ENV{libsane_matched}=="yes", TAG+="uaccess" + +# HPLIP devices (necessary for ink level check and HP tool maintenance) +ENV{ID_HPLIP}=="1", TAG+="uaccess" + +# optical drives +SUBSYSTEM=="block", ENV{ID_CDROM}=="1", TAG+="uaccess" +SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="4|5", TAG+="uaccess" + +# Sound devices +SUBSYSTEM=="sound", TAG+="uaccess" + +# ffado is an userspace driver for firewire sound cards +SUBSYSTEM=="firewire", ENV{ID_FFADO}=="1", TAG+="uaccess" + +# Webcams, frame grabber, TV cards +SUBSYSTEM=="video4linux", TAG+="uaccess" +SUBSYSTEM=="dvb", TAG+="uaccess" + +# IIDC devices: industrial cameras and some webcams +SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x00010*", TAG+="uaccess" +SUBSYSTEM=="firewire", ATTR{units}=="*0x00b09d:0x00010*", TAG+="uaccess" +# AV/C devices: camcorders, set-top boxes, TV sets, audio devices, and more +SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x010001*", TAG+="uaccess" +SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x014001*", TAG+="uaccess" + +# DRI video devices +SUBSYSTEM=="drm", KERNEL=="card*", TAG+="uaccess" + +# KVM +SUBSYSTEM=="misc", KERNEL=="kvm", TAG+="uaccess" + +# smart-card readers +ENV{ID_SMARTCARD_READER}=="*?", TAG+="uaccess" + +# PDA devices +ENV{ID_PDA}=="*?", TAG+="uaccess" + +# Programmable remote control +ENV{ID_REMOTE_CONTROL}=="1", TAG+="uaccess" + +# joysticks +SUBSYSTEM=="input", ENV{ID_INPUT_JOYSTICK}=="?*", TAG+="uaccess" + +# color measurement devices +ENV{COLOR_MEASUREMENT_DEVICE}=="*?", TAG+="uaccess" + +# DDC/CI device, usually high-end monitors such as the DreamColor +ENV{DDC_DEVICE}=="*?", TAG+="uaccess" + +# media player raw devices (for user-mode drivers, Android SDK, etc.) +SUBSYSTEM=="usb", ENV{ID_MEDIA_PLAYER}=="?*", TAG+="uaccess" + +LABEL="uaccess_end" diff --git a/src/login/71-seat.rules b/src/login/71-seat.rules new file mode 100644 index 000000000..04ccac757 --- /dev/null +++ b/src/login/71-seat.rules @@ -0,0 +1,25 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +ACTION=="remove", GOTO="seat_end" + +TAG=="uaccess", SUBSYSTEM!="sound", TAG+="seat" +SUBSYSTEM=="sound", KERNEL=="card*", TAG+="seat" +SUBSYSTEM=="input", KERNEL=="input*", TAG+="seat" +SUBSYSTEM=="graphics", KERNEL=="fb[0-9]*", TAG+="seat" +SUBSYSTEM=="usb", ATTR{bDeviceClass}=="09", TAG+="seat" + +# 'Plugable' USB hub, sound, network, graphics adapter +SUBSYSTEM=="usb", ATTR{idVendor}=="2230", ATTR{idProduct}=="000[13]", ENV{ID_AUTOSEAT}="1" + +# Mimo 720, with integrated USB hub, displaylink graphics, and e2i touchscreen +SUBSYSTEM=="usb", ATTR{idVendor}=="058f", ATTR{idProduct}=="6254", ENV{ID_AUTOSEAT}="1" + +TAG=="seat", ENV{ID_PATH}=="", IMPORT{program}="path_id %p" +TAG=="seat", ENV{ID_FOR_SEAT}=="", ENV{ID_PATH_TAG}!="", ENV{ID_FOR_SEAT}="$env{SUBSYSTEM}-$env{ID_PATH_TAG}" + +LABEL="seat_end" diff --git a/src/login/73-seat-late.rules.in b/src/login/73-seat-late.rules.in new file mode 100644 index 000000000..0847932d7 --- /dev/null +++ b/src/login/73-seat-late.rules.in @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +ACTION=="remove", GOTO="seat_late_end" + +ENV{ID_SEAT}=="", ENV{ID_AUTOSEAT}=="1", ENV{ID_FOR_SEAT}!="", ENV{ID_SEAT}="seat-$env{ID_FOR_SEAT}" +ENV{ID_SEAT}=="", IMPORT{parent}="ID_SEAT" + +ENV{ID_SEAT}!="", TAG+="$env{ID_SEAT}" + +TAG=="uaccess", ENV{MAJOR}!="", RUN+="@rootlibexecdir@/systemd-uaccess $env{DEVNAME} $env{ID_SEAT}" + +LABEL="seat_late_end" diff --git a/src/login/Makefile b/src/login/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/login/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/login/libsystemd-login.pc.in b/src/login/libsystemd-login.pc.in new file mode 100644 index 000000000..cd36a9cb3 --- /dev/null +++ b/src/login/libsystemd-login.pc.in @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: systemd +Description: systemd Login Utility Library +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lsystemd-login +Cflags: -I${includedir} diff --git a/src/login/libsystemd-login.sym b/src/login/libsystemd-login.sym new file mode 100644 index 000000000..a5e6c1e75 --- /dev/null +++ b/src/login/libsystemd-login.sym @@ -0,0 +1,48 @@ +/*** + This file is part of systemd. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +***/ + +/* Original symbols from systemd v31 */ + +LIBSYSTEMD_LOGIN_31 { +global: + sd_get_seats; + sd_get_sessions; + sd_get_uids; + sd_login_monitor_flush; + sd_login_monitor_get_fd; + sd_login_monitor_new; + sd_login_monitor_unref; + sd_pid_get_owner_uid; + sd_pid_get_session; + sd_seat_can_multi_session; + sd_seat_get_active; + sd_seat_get_sessions; + sd_session_get_seat; + sd_session_get_uid; + sd_session_is_active; + sd_uid_get_seats; + sd_uid_get_sessions; + sd_uid_get_state; + sd_uid_is_on_seat; +local: + *; +}; + +LIBSYSTEMD_LOGIN_38 { +global: + sd_pid_get_unit; + sd_session_get_service; +} LIBSYSTEMD_LOGIN_31; + +LIBSYSTEMD_LOGIN_43 { +global: + sd_session_get_type; + sd_session_get_class; + sd_session_get_display; +} LIBSYSTEMD_LOGIN_38; diff --git a/src/login/loginctl.c b/src/login/loginctl.c new file mode 100644 index 000000000..30e97e352 --- /dev/null +++ b/src/login/loginctl.c @@ -0,0 +1,1926 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "util.h" +#include "macro.h" +#include "pager.h" +#include "dbus-common.h" +#include "build.h" +#include "strv.h" +#include "cgroup-show.h" +#include "sysfs-show.h" + +static char **arg_property = NULL; +static bool arg_all = false; +static bool arg_no_pager = false; +static const char *arg_kill_who = NULL; +static int arg_signal = SIGTERM; +static enum transport { + TRANSPORT_NORMAL, + TRANSPORT_SSH, + TRANSPORT_POLKIT +} arg_transport = TRANSPORT_NORMAL; +static const char *arg_host = NULL; + +static bool on_tty(void) { + static int t = -1; + + /* Note that this is invoked relatively early, before we start + * the pager. That means the value we return reflects whether + * we originally were started on a tty, not if we currently + * are. But this is intended, since we want colour and so on + * when run in our own pager. */ + + if (_unlikely_(t < 0)) + t = isatty(STDOUT_FILENO) > 0; + + return t; +} + +static void pager_open_if_enabled(void) { + + /* Cache result before we open the pager */ + on_tty(); + + if (!arg_no_pager) + pager_open(); +} + +static int list_sessions(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + DBusMessageIter iter, sub, sub2; + unsigned k = 0; + + dbus_error_init(&error); + + assert(bus); + + pager_open_if_enabled(); + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "ListSessions"); + if (!m) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (on_tty()) + printf("%10s %10s %-16s %-16s\n", "SESSION", "UID", "USER", "SEAT"); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *id, *user, *seat, *object; + uint32_t uid; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &uid, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &user, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &seat, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + printf("%10s %10u %-16s %-16s\n", id, (unsigned) uid, user, seat); + + k++; + + dbus_message_iter_next(&sub); + } + + if (on_tty()) + printf("\n%u sessions listed.\n", k); + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int list_users(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + DBusMessageIter iter, sub, sub2; + unsigned k = 0; + + dbus_error_init(&error); + + assert(bus); + + pager_open_if_enabled(); + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "ListUsers"); + if (!m) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (on_tty()) + printf("%10s %-16s\n", "UID", "USER"); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *user, *object; + uint32_t uid; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &uid, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &user, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + printf("%10u %-16s\n", (unsigned) uid, user); + + k++; + + dbus_message_iter_next(&sub); + } + + if (on_tty()) + printf("\n%u users listed.\n", k); + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int list_seats(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + DBusMessageIter iter, sub, sub2; + unsigned k = 0; + + dbus_error_init(&error); + + assert(bus); + + pager_open_if_enabled(); + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "ListSeats"); + if (!m) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (on_tty()) + printf("%-16s\n", "SEAT"); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *seat, *object; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &seat, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + printf("%-16s\n", seat); + + k++; + + dbus_message_iter_next(&sub); + } + + if (on_tty()) + printf("\n%u seats listed.\n", k); + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +typedef struct SessionStatusInfo { + const char *id; + uid_t uid; + const char *name; + usec_t timestamp; + const char *control_group; + int vtnr; + const char *seat; + const char *tty; + const char *display; + bool remote; + const char *remote_host; + const char *remote_user; + const char *service; + pid_t leader; + const char *type; + const char *class; + bool active; +} SessionStatusInfo; + +typedef struct UserStatusInfo { + uid_t uid; + const char *name; + usec_t timestamp; + const char *control_group; + const char *state; + char **sessions; + const char *display; +} UserStatusInfo; + +typedef struct SeatStatusInfo { + const char *id; + const char *active_session; + char **sessions; +} SeatStatusInfo; + +static void print_session_status_info(SessionStatusInfo *i) { + char since1[FORMAT_TIMESTAMP_PRETTY_MAX], *s1; + char since2[FORMAT_TIMESTAMP_MAX], *s2; + assert(i); + + printf("%s - ", strna(i->id)); + + if (i->name) + printf("%s (%u)\n", i->name, (unsigned) i->uid); + else + printf("%u\n", (unsigned) i->uid); + + s1 = format_timestamp_pretty(since1, sizeof(since1), i->timestamp); + s2 = format_timestamp(since2, sizeof(since2), i->timestamp); + + if (s1) + printf("\t Since: %s; %s\n", s2, s1); + else if (s2) + printf("\t Since: %s\n", s2); + + if (i->leader > 0) { + char *t = NULL; + + printf("\t Leader: %u", (unsigned) i->leader); + + get_process_comm(i->leader, &t); + if (t) { + printf(" (%s)", t); + free(t); + } + + printf("\n"); + } + + if (i->seat) { + printf("\t Seat: %s", i->seat); + + if (i->vtnr > 0) + printf("; vc%i", i->vtnr); + + printf("\n"); + } + + if (i->tty) + printf("\t TTY: %s\n", i->tty); + else if (i->display) + printf("\t Display: %s\n", i->display); + + if (i->remote_host && i->remote_user) + printf("\t Remote: %s@%s\n", i->remote_user, i->remote_host); + else if (i->remote_host) + printf("\t Remote: %s\n", i->remote_host); + else if (i->remote_user) + printf("\t Remote: user %s\n", i->remote_user); + else if (i->remote) + printf("\t Remote: Yes\n"); + + if (i->service) { + printf("\t Service: %s", i->service); + + if (i->type) + printf("; type %s", i->type); + + if (i->class) + printf("; class %s", i->class); + + printf("\n"); + } else if (i->type) { + printf("\t Type: %s\n", i->type); + + if (i->class) + printf("; class %s", i->class); + } else if (i->class) + printf("\t Class: %s\n", i->class); + + + printf("\t Active: %s\n", yes_no(i->active)); + + if (i->control_group) { + unsigned c; + + printf("\t CGroup: %s\n", i->control_group); + + if (arg_transport != TRANSPORT_SSH) { + c = columns(); + if (c > 18) + c -= 18; + else + c = 0; + + show_cgroup_by_path(i->control_group, "\t\t ", c, false); + } + } +} + +static void print_user_status_info(UserStatusInfo *i) { + char since1[FORMAT_TIMESTAMP_PRETTY_MAX], *s1; + char since2[FORMAT_TIMESTAMP_MAX], *s2; + assert(i); + + if (i->name) + printf("%s (%u)\n", i->name, (unsigned) i->uid); + else + printf("%u\n", (unsigned) i->uid); + + s1 = format_timestamp_pretty(since1, sizeof(since1), i->timestamp); + s2 = format_timestamp(since2, sizeof(since2), i->timestamp); + + if (s1) + printf("\t Since: %s; %s\n", s2, s1); + else if (s2) + printf("\t Since: %s\n", s2); + + if (!isempty(i->state)) + printf("\t State: %s\n", i->state); + + if (!strv_isempty(i->sessions)) { + char **l; + printf("\tSessions:"); + + STRV_FOREACH(l, i->sessions) { + if (streq_ptr(*l, i->display)) + printf(" *%s", *l); + else + printf(" %s", *l); + } + + printf("\n"); + } + + if (i->control_group) { + unsigned c; + + printf("\t CGroup: %s\n", i->control_group); + + if (arg_transport != TRANSPORT_SSH) { + c = columns(); + if (c > 18) + c -= 18; + else + c = 0; + + show_cgroup_by_path(i->control_group, "\t\t ", c, false); + } + } +} + +static void print_seat_status_info(SeatStatusInfo *i) { + assert(i); + + printf("%s\n", strna(i->id)); + + if (!strv_isempty(i->sessions)) { + char **l; + printf("\tSessions:"); + + STRV_FOREACH(l, i->sessions) { + if (streq_ptr(*l, i->active_session)) + printf(" *%s", *l); + else + printf(" %s", *l); + } + + printf("\n"); + } + + if (arg_transport != TRANSPORT_SSH) { + unsigned c; + + c = columns(); + if (c > 21) + c -= 21; + else + c = 0; + + printf("\t Devices:\n"); + + show_sysfs(i->id, "\t\t ", c); + } +} + +static int status_property_session(const char *name, DBusMessageIter *iter, SessionStatusInfo *i) { + assert(name); + assert(iter); + assert(i); + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_STRING: { + const char *s; + + dbus_message_iter_get_basic(iter, &s); + + if (!isempty(s)) { + if (streq(name, "Id")) + i->id = s; + else if (streq(name, "Name")) + i->name = s; + else if (streq(name, "ControlGroupPath")) + i->control_group = s; + else if (streq(name, "TTY")) + i->tty = s; + else if (streq(name, "Display")) + i->display = s; + else if (streq(name, "RemoteHost")) + i->remote_host = s; + else if (streq(name, "RemoteUser")) + i->remote_user = s; + else if (streq(name, "Service")) + i->service = s; + else if (streq(name, "Type")) + i->type = s; + else if (streq(name, "Class")) + i->class = s; + } + break; + } + + case DBUS_TYPE_UINT32: { + uint32_t u; + + dbus_message_iter_get_basic(iter, &u); + + if (streq(name, "VTNr")) + i->vtnr = (int) u; + else if (streq(name, "Leader")) + i->leader = (pid_t) u; + + break; + } + + case DBUS_TYPE_BOOLEAN: { + dbus_bool_t b; + + dbus_message_iter_get_basic(iter, &b); + + if (streq(name, "Remote")) + i->remote = b; + else if (streq(name, "Active")) + i->active = b; + + break; + } + + case DBUS_TYPE_UINT64: { + uint64_t u; + + dbus_message_iter_get_basic(iter, &u); + + if (streq(name, "Timestamp")) + i->timestamp = (usec_t) u; + + break; + } + + case DBUS_TYPE_STRUCT: { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32 && streq(name, "User")) { + uint32_t u; + + dbus_message_iter_get_basic(&sub, &u); + i->uid = (uid_t) u; + + } else if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "Seat")) { + const char *s; + + dbus_message_iter_get_basic(&sub, &s); + + if (!isempty(s)) + i->seat = s; + } + + break; + } + } + + return 0; +} + +static int status_property_user(const char *name, DBusMessageIter *iter, UserStatusInfo *i) { + assert(name); + assert(iter); + assert(i); + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_STRING: { + const char *s; + + dbus_message_iter_get_basic(iter, &s); + + if (!isempty(s)) { + if (streq(name, "Name")) + i->name = s; + else if (streq(name, "ControlGroupPath")) + i->control_group = s; + else if (streq(name, "State")) + i->state = s; + } + break; + } + + case DBUS_TYPE_UINT32: { + uint32_t u; + + dbus_message_iter_get_basic(iter, &u); + + if (streq(name, "UID")) + i->uid = (uid_t) u; + + break; + } + + case DBUS_TYPE_UINT64: { + uint64_t u; + + dbus_message_iter_get_basic(iter, &u); + + if (streq(name, "Timestamp")) + i->timestamp = (usec_t) u; + + break; + } + + case DBUS_TYPE_STRUCT: { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "Display")) { + const char *s; + + dbus_message_iter_get_basic(&sub, &s); + + if (!isempty(s)) + i->display = s; + } + + break; + } + + case DBUS_TYPE_ARRAY: { + + if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *id; + const char *path; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) { + char **l; + + l = strv_append(i->sessions, id); + if (!l) + return -ENOMEM; + + strv_free(i->sessions); + i->sessions = l; + } + + dbus_message_iter_next(&sub); + } + + return 0; + } + } + } + + return 0; +} + +static int status_property_seat(const char *name, DBusMessageIter *iter, SeatStatusInfo *i) { + assert(name); + assert(iter); + assert(i); + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_STRING: { + const char *s; + + dbus_message_iter_get_basic(iter, &s); + + if (!isempty(s)) { + if (streq(name, "Id")) + i->id = s; + } + break; + } + + case DBUS_TYPE_STRUCT: { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "ActiveSession")) { + const char *s; + + dbus_message_iter_get_basic(&sub, &s); + + if (!isempty(s)) + i->active_session = s; + } + + break; + } + + case DBUS_TYPE_ARRAY: { + + if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *id; + const char *path; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) { + char **l; + + l = strv_append(i->sessions, id); + if (!l) + return -ENOMEM; + + strv_free(i->sessions); + i->sessions = l; + } + + dbus_message_iter_next(&sub); + } + + return 0; + } + } + } + + return 0; +} + +static int print_property(const char *name, DBusMessageIter *iter) { + assert(name); + assert(iter); + + if (arg_property && !strv_find(arg_property, name)) + return 0; + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_STRUCT: { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && + (streq(name, "Display") || streq(name, "ActiveSession"))) { + const char *s; + + dbus_message_iter_get_basic(&sub, &s); + + if (arg_all || !isempty(s)) + printf("%s=%s\n", name, s); + return 0; + } + break; + } + + case DBUS_TYPE_ARRAY: + + if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) { + DBusMessageIter sub, sub2; + bool found = false; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *id; + const char *path; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) { + if (found) + printf(" %s", id); + else { + printf("%s=%s", name, id); + found = true; + } + } + + dbus_message_iter_next(&sub); + } + + if (!found && arg_all) + printf("%s=\n", name); + else if (found) + printf("\n"); + + return 0; + } + + break; + } + + if (generic_print_property(name, iter, arg_all) > 0) + return 0; + + if (arg_all) + printf("%s=[unprintable]\n", name); + + return 0; +} + +static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) { + DBusMessage *m = NULL, *reply = NULL; + const char *interface = ""; + int r; + DBusError error; + DBusMessageIter iter, sub, sub2, sub3; + SessionStatusInfo session_info; + UserStatusInfo user_info; + SeatStatusInfo seat_info; + + assert(bus); + assert(path); + assert(new_line); + + zero(session_info); + zero(user_info); + zero(seat_info); + + dbus_error_init(&error); + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + path, + "org.freedesktop.DBus.Properties", + "GetAll"); + if (!m) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (*new_line) + printf("\n"); + + *new_line = true; + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *name; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub2, &sub3); + + if (show_properties) + r = print_property(name, &sub3); + else if (strstr(verb, "session")) + r = status_property_session(name, &sub3, &session_info); + else if (strstr(verb, "user")) + r = status_property_user(name, &sub3, &user_info); + else + r = status_property_seat(name, &sub3, &seat_info); + + if (r < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_next(&sub); + } + + if (!show_properties) { + if (strstr(verb, "session")) + print_session_status_info(&session_info); + else if (strstr(verb, "user")) + print_user_status_info(&user_info); + else + print_seat_status_info(&seat_info); + } + + strv_free(seat_info.sessions); + strv_free(user_info.sessions); + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int show(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL, *reply = NULL; + int r, ret = 0; + DBusError error; + unsigned i; + bool show_properties, new_line = false; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + show_properties = !strstr(args[0], "status"); + + if (show_properties) + pager_open_if_enabled(); + + if (show_properties && n <= 1) { + /* If not argument is specified inspect the manager + * itself */ + + ret = show_one(args[0], bus, "/org/freedesktop/login1", show_properties, &new_line); + goto finish; + } + + for (i = 1; i < n; i++) { + const char *path = NULL; + + if (strstr(args[0], "session")) { + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "GetSession"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + } else if (strstr(args[0], "user")) { + uid_t uid; + uint32_t u; + + ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL); + if (ret < 0) { + log_error("User %s unknown.", args[i]); + goto finish; + } + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "GetUser"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + u = (uint32_t) uid; + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &u, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + } else { + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "GetSeat"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + r = show_one(args[0], bus, path, show_properties, &new_line); + if (r != 0) + ret = r; + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return ret; +} + +static int activate(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + for (i = 1; i < n; i++) { + DBusMessage *reply; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + streq(args[0], "lock-session") ? "LockSession" : + streq(args[0], "unlock-session") ? "UnlockSession" : + streq(args[0], "terminate-session") ? "TerminateSession" : + "ActivateSession"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int kill_session(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + if (!arg_kill_who) + arg_kill_who = "all"; + + for (i = 1; i < n; i++) { + DBusMessage *reply; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "KillSession"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_STRING, &arg_kill_who, + DBUS_TYPE_INT32, arg_signal, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int enable_linger(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + dbus_bool_t b, interactive = true; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + b = streq(args[0], "enable-linger"); + + for (i = 1; i < n; i++) { + DBusMessage *reply; + uint32_t u; + uid_t uid; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "SetUserLinger"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL); + if (ret < 0) { + log_error("Failed to resolve user %s: %s", args[i], strerror(-ret)); + goto finish; + } + + u = (uint32_t) uid; + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &u, + DBUS_TYPE_BOOLEAN, &b, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + + ret = 0; + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int terminate_user(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + for (i = 1; i < n; i++) { + uint32_t u; + uid_t uid; + DBusMessage *reply; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "TerminateUser"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL); + if (ret < 0) { + log_error("Failed to look up user %s: %s", args[i], strerror(-ret)); + goto finish; + } + + u = (uint32_t) uid; + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &u, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + + ret = 0; + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int kill_user(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + if (!arg_kill_who) + arg_kill_who = "all"; + + for (i = 1; i < n; i++) { + DBusMessage *reply; + uid_t uid; + uint32_t u; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "KillUser"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL); + if (ret < 0) { + log_error("Failed to look up user %s: %s", args[i], strerror(-ret)); + goto finish; + } + + u = (uint32_t) uid; + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &u, + DBUS_TYPE_INT32, arg_signal, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + + ret = 0; + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int attach(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + dbus_bool_t interactive = true; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + for (i = 2; i < n; i++) { + DBusMessage *reply; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "AttachDevice"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &args[1], + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int flush_devices(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL, *reply = NULL; + int ret = 0; + DBusError error; + dbus_bool_t interactive = true; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "FlushDevices"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return ret; +} + +static int terminate_seat(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + for (i = 1; i < n; i++) { + DBusMessage *reply; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "TerminateSeat"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int help(void) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Send control commands to or query the login manager.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -p --property=NAME Show only properties by this name\n" + " -a --all Show all properties, including empty ones\n" + " --kill-who=WHO Who to send signal to\n" + " -s --signal=SIGNAL Which signal to send\n" + " -H --host=[USER@]HOST\n" + " Show information for remote host\n" + " -P --privileged Acquire privileges before execution\n" + " --no-pager Do not pipe output into a pager\n\n" + "Commands:\n" + " list-sessions List sessions\n" + " session-status [ID...] Show session status\n" + " show-session [ID...] Show properties of one or more sessions\n" + " activate [ID] Activate a session\n" + " lock-session [ID...] Screen lock one or more sessions\n" + " unlock-session [ID...] Screen unlock one or more sessions\n" + " terminate-session [ID...] Terminate one or more sessions\n" + " kill-session [ID...] Send signal to processes of a session\n" + " list-users List users\n" + " user-status [USER...] Show user status\n" + " show-user [USER...] Show properties of one or more users\n" + " enable-linger [USER...] Enable linger state of one or more users\n" + " disable-linger [USER...] Disable linger state of one or more users\n" + " terminate-user [USER...] Terminate all sessions of one or more users\n" + " kill-user [USER...] Send signal to processes of a user\n" + " list-seats List seats\n" + " seat-status [NAME...] Show seat status\n" + " show-seat [NAME...] Show properties of one or more seats\n" + " attach [NAME] [DEVICE...] Attach one or more devices to a seat\n" + " flush-devices Flush all device associations\n" + " terminate-seat [NAME...] Terminate all sessions on one or more seats\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_KILL_WHO + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "property", required_argument, NULL, 'p' }, + { "all", no_argument, NULL, 'a' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "kill-who", required_argument, NULL, ARG_KILL_WHO }, + { "signal", required_argument, NULL, 's' }, + { "host", required_argument, NULL, 'H' }, + { "privileged",no_argument, NULL, 'P' }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hp:as:H:P", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(DISTRIBUTION); + puts(SYSTEMD_FEATURES); + return 0; + + case 'p': { + char **l; + + l = strv_append(arg_property, optarg); + if (!l) + return -ENOMEM; + + strv_free(arg_property); + arg_property = l; + + /* If the user asked for a particular + * property, show it to him, even if it is + * empty. */ + arg_all = true; + break; + } + + case 'a': + arg_all = true; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_KILL_WHO: + arg_kill_who = optarg; + break; + + case 's': + arg_signal = signal_from_string_try_harder(optarg); + if (arg_signal < 0) { + log_error("Failed to parse signal string %s.", optarg); + return -EINVAL; + } + break; + + case 'P': + arg_transport = TRANSPORT_POLKIT; + break; + + case 'H': + arg_transport = TRANSPORT_SSH; + arg_host = optarg; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + return 1; +} + +static int loginctl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) { + + static const struct { + const char* verb; + const enum { + MORE, + LESS, + EQUAL + } argc_cmp; + const int argc; + int (* const dispatch)(DBusConnection *bus, char **args, unsigned n); + } verbs[] = { + { "list-sessions", LESS, 1, list_sessions }, + { "session-status", MORE, 2, show }, + { "show-session", MORE, 1, show }, + { "activate", EQUAL, 2, activate }, + { "lock-session", MORE, 2, activate }, + { "unlock-session", MORE, 2, activate }, + { "terminate-session", MORE, 2, activate }, + { "kill-session", MORE, 2, kill_session }, + { "list-users", EQUAL, 1, list_users }, + { "user-status", MORE, 2, show }, + { "show-user", MORE, 1, show }, + { "enable-linger", MORE, 2, enable_linger }, + { "disable-linger", MORE, 2, enable_linger }, + { "terminate-user", MORE, 2, terminate_user }, + { "kill-user", MORE, 2, kill_user }, + { "list-seats", EQUAL, 1, list_seats }, + { "seat-status", MORE, 2, show }, + { "show-seat", MORE, 1, show }, + { "attach", MORE, 3, attach }, + { "flush-devices", EQUAL, 1, flush_devices }, + { "terminate-seat", MORE, 2, terminate_seat }, + }; + + int left; + unsigned i; + + assert(argc >= 0); + assert(argv); + assert(error); + + left = argc - optind; + + if (left <= 0) + /* Special rule: no arguments means "list-sessions" */ + i = 0; + else { + if (streq(argv[optind], "help")) { + help(); + return 0; + } + + for (i = 0; i < ELEMENTSOF(verbs); i++) + if (streq(argv[optind], verbs[i].verb)) + break; + + if (i >= ELEMENTSOF(verbs)) { + log_error("Unknown operation %s", argv[optind]); + return -EINVAL; + } + } + + switch (verbs[i].argc_cmp) { + + case EQUAL: + if (left != verbs[i].argc) { + log_error("Invalid number of arguments."); + return -EINVAL; + } + + break; + + case MORE: + if (left < verbs[i].argc) { + log_error("Too few arguments."); + return -EINVAL; + } + + break; + + case LESS: + if (left > verbs[i].argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + break; + + default: + assert_not_reached("Unknown comparison operator."); + } + + if (!bus) { + log_error("Failed to get D-Bus connection: %s", error->message); + return -EIO; + } + + return verbs[i].dispatch(bus, argv + optind, left); +} + +int main(int argc, char*argv[]) { + int r, retval = EXIT_FAILURE; + DBusConnection *bus = NULL; + DBusError error; + + dbus_error_init(&error); + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r < 0) + goto finish; + else if (r == 0) { + retval = EXIT_SUCCESS; + goto finish; + } + + if (arg_transport == TRANSPORT_NORMAL) + bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + else if (arg_transport == TRANSPORT_POLKIT) + bus_connect_system_polkit(&bus, &error); + else if (arg_transport == TRANSPORT_SSH) + bus_connect_system_ssh(NULL, arg_host, &bus, &error); + else + assert_not_reached("Uh, invalid transport..."); + + r = loginctl_main(bus, argc, argv, &error); + retval = r < 0 ? EXIT_FAILURE : r; + +finish: + if (bus) { + dbus_connection_flush(bus); + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + dbus_error_free(&error); + dbus_shutdown(); + + strv_free(arg_property); + + pager_close(); + + return retval; +} diff --git a/src/login/logind-acl.c b/src/login/logind-acl.c new file mode 100644 index 000000000..eb8a48d19 --- /dev/null +++ b/src/login/logind-acl.c @@ -0,0 +1,248 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "logind-acl.h" +#include "util.h" +#include "acl-util.h" + +static int flush_acl(acl_t acl) { + acl_entry_t i; + int found; + bool changed = false; + + assert(acl); + + for (found = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); + found > 0; + found = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) { + + acl_tag_t tag; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (tag != ACL_USER) + continue; + + if (acl_delete_entry(acl, i) < 0) + return -errno; + + changed = true; + } + + if (found < 0) + return -errno; + + return changed; +} + +int devnode_acl(const char *path, + bool flush, + bool del, uid_t old_uid, + bool add, uid_t new_uid) { + + acl_t acl; + int r = 0; + bool changed = false; + + assert(path); + + acl = acl_get_file(path, ACL_TYPE_ACCESS); + if (!acl) + return -errno; + + if (flush) { + + r = flush_acl(acl); + if (r < 0) + goto finish; + if (r > 0) + changed = true; + + } else if (del && old_uid > 0) { + acl_entry_t entry; + + r = acl_find_uid(acl, old_uid, &entry); + if (r < 0) + goto finish; + + if (r > 0) { + if (acl_delete_entry(acl, entry) < 0) { + r = -errno; + goto finish; + } + + changed = true; + } + } + + if (add && new_uid > 0) { + acl_entry_t entry; + acl_permset_t permset; + int rd, wt; + + r = acl_find_uid(acl, new_uid, &entry); + if (r < 0) + goto finish; + + if (r == 0) { + if (acl_create_entry(&acl, &entry) < 0) { + r = -errno; + goto finish; + } + + if (acl_set_tag_type(entry, ACL_USER) < 0 || + acl_set_qualifier(entry, &new_uid) < 0) { + r = -errno; + goto finish; + } + } + + if (acl_get_permset(entry, &permset) < 0) { + r = -errno; + goto finish; + } + + rd = acl_get_perm(permset, ACL_READ); + if (rd < 0) { + r = -errno; + goto finish; + } + + wt = acl_get_perm(permset, ACL_WRITE); + if (wt < 0) { + r = -errno; + goto finish; + } + + if (!rd || !wt) { + + if (acl_add_perm(permset, ACL_READ|ACL_WRITE) < 0) { + r = -errno; + goto finish; + } + + changed = true; + } + } + + if (!changed) + goto finish; + + if (acl_calc_mask(&acl) < 0) { + r = -errno; + goto finish; + } + + if (acl_set_file(path, ACL_TYPE_ACCESS, acl) < 0) { + r = -errno; + goto finish; + } + + r = 0; + +finish: + acl_free(acl); + + return r; +} + +int devnode_acl_all(struct udev *udev, + const char *seat, + bool flush, + bool del, uid_t old_uid, + bool add, uid_t new_uid) { + + struct udev_list_entry *item = NULL, *first = NULL; + struct udev_enumerate *e; + int r; + + assert(udev); + + if (isempty(seat)) + seat = "seat0"; + + e = udev_enumerate_new(udev); + if (!e) + return -ENOMEM; + + /* We can only match by one tag in libudev. We choose + * "uaccess" for that. If we could match for two tags here we + * could add the seat name as second match tag, but this would + * be hardly optimizable in libudev, and hence checking the + * second tag manually in our loop is a good solution. */ + + r = udev_enumerate_add_match_tag(e, "uaccess"); + if (r < 0) + goto finish; + + r = udev_enumerate_scan_devices(e); + if (r < 0) + goto finish; + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + struct udev_device *d; + const char *node, *sn; + + d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!d) { + r = -ENOMEM; + goto finish; + } + + sn = udev_device_get_property_value(d, "ID_SEAT"); + if (isempty(sn)) + sn = "seat0"; + + if (!streq(seat, sn)) { + udev_device_unref(d); + continue; + } + + node = udev_device_get_devnode(d); + if (!node) { + /* In case people mistag devices with nodes, we need to ignore this */ + udev_device_unref(d); + continue; + } + + log_debug("Fixing up %s for seat %s...", node, sn); + + r = devnode_acl(node, flush, del, old_uid, add, new_uid); + udev_device_unref(d); + + if (r < 0) + goto finish; + } + +finish: + if (e) + udev_enumerate_unref(e); + + return r; +} diff --git a/src/login/logind-acl.h b/src/login/logind-acl.h new file mode 100644 index 000000000..72740f5b9 --- /dev/null +++ b/src/login/logind-acl.h @@ -0,0 +1,60 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foologindaclhfoo +#define foologindaclhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#ifdef HAVE_ACL + +int devnode_acl(const char *path, + bool flush, + bool del, uid_t old_uid, + bool add, uid_t new_uid); + +int devnode_acl_all(struct udev *udev, + const char *seat, + bool flush, + bool del, uid_t old_uid, + bool add, uid_t new_uid); +#else + +static inline int devnode_acl(const char *path, + bool flush, + bool del, uid_t old_uid, + bool add, uid_t new_uid) { + return 0; +} + +static inline int devnode_acl_all(struct udev *udev, + const char *seat, + bool flush, + bool del, uid_t old_uid, + bool add, uid_t new_uid) { + return 0; +} + +#endif + +#endif diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c new file mode 100644 index 000000000..ea6b89faa --- /dev/null +++ b/src/login/logind-dbus.c @@ -0,0 +1,1697 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "logind.h" +#include "dbus-common.h" +#include "strv.h" +#include "polkit.h" +#include "special.h" + +#define BUS_MANAGER_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION_BEGIN \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_MANAGER_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE + +#define INTROSPECTION_END \ + "\n" + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.login1.Manager\0" + +static int bus_manager_append_idle_hint(DBusMessageIter *i, const char *property, void *data) { + Manager *m = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(m); + + b = manager_get_idle_hint(m, NULL) > 0; + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_manager_append_idle_hint_since(DBusMessageIter *i, const char *property, void *data) { + Manager *m = data; + dual_timestamp t; + uint64_t u; + + assert(i); + assert(property); + assert(m); + + manager_get_idle_hint(m, &t); + u = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u)) + return -ENOMEM; + + return 0; +} + +static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMessage **_reply) { + Session *session = NULL; + User *user = NULL; + const char *type, *class, *seat, *tty, *display, *remote_user, *remote_host, *service; + uint32_t uid, leader, audit_id = 0; + dbus_bool_t remote, kill_processes; + char **controllers = NULL, **reset_controllers = NULL; + SessionType t; + SessionClass c; + Seat *s; + DBusMessageIter iter; + int r; + char *id = NULL, *p; + uint32_t vtnr = 0; + int fifo_fd = -1; + DBusMessage *reply = NULL; + bool b; + + assert(m); + assert(message); + assert(_reply); + + if (!dbus_message_iter_init(message, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &uid); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &leader); + + if (leader <= 0 || + !dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &service); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &type); + t = session_type_from_string(type); + + if (t < 0 || + !dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &class); + if (isempty(class)) + c = SESSION_USER; + else + c = session_class_from_string(class); + + if (c < 0 || + !dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &seat); + + if (isempty(seat)) + s = NULL; + else { + s = hashmap_get(m->seats, seat); + if (!s) + return -ENOENT; + } + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &vtnr); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &tty); + + if (tty_is_vc(tty)) { + int v; + + if (!s) + s = m->vtconsole; + else if (s != m->vtconsole) + return -EINVAL; + + v = vtnr_from_tty(tty); + + if (v <= 0) + return v < 0 ? v : -EINVAL; + + if (vtnr <= 0) + vtnr = (uint32_t) v; + else if (vtnr != (uint32_t) v) + return -EINVAL; + + } else if (!isempty(tty) && s && seat_is_vtconsole(s)) + return -EINVAL; + + if (s) { + if (seat_can_multi_session(s)) { + if (vtnr <= 0 || vtnr > 63) + return -EINVAL; + } else { + if (vtnr > 0) + return -EINVAL; + } + } + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &display); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &remote); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &remote_user); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &remote_host); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + r = bus_parse_strv_iter(&iter, &controllers); + if (r < 0) + return -EINVAL; + + if (strv_contains(controllers, "systemd") || + !dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING) { + r = -EINVAL; + goto fail; + } + + r = bus_parse_strv_iter(&iter, &reset_controllers); + if (r < 0) + goto fail; + + if (strv_contains(reset_controllers, "systemd") || + !dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) { + r = -EINVAL; + goto fail; + } + + dbus_message_iter_get_basic(&iter, &kill_processes); + + r = manager_add_user_by_uid(m, uid, &user); + if (r < 0) + goto fail; + + audit_session_from_pid(leader, &audit_id); + + if (audit_id > 0) { + asprintf(&id, "%lu", (unsigned long) audit_id); + + if (!id) { + r = -ENOMEM; + goto fail; + } + + session = hashmap_get(m->sessions, id); + + if (session) { + free(id); + + fifo_fd = session_create_fifo(session); + if (fifo_fd < 0) { + r = fifo_fd; + goto fail; + } + + /* Session already exists, client is probably + * something like "su" which changes uid but + * is still the same audit session */ + + reply = dbus_message_new_method_return(message); + if (!reply) { + r = -ENOMEM; + goto fail; + } + + p = session_bus_path(session); + if (!p) { + r = -ENOMEM; + goto fail; + } + + seat = session->seat ? session->seat->id : ""; + vtnr = session->vtnr; + b = dbus_message_append_args( + reply, + DBUS_TYPE_STRING, &session->id, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_STRING, &session->user->runtime_path, + DBUS_TYPE_UNIX_FD, &fifo_fd, + DBUS_TYPE_STRING, &seat, + DBUS_TYPE_UINT32, &vtnr, + DBUS_TYPE_INVALID); + free(p); + + if (!b) { + r = -ENOMEM; + goto fail; + } + + close_nointr_nofail(fifo_fd); + *_reply = reply; + + strv_free(controllers); + strv_free(reset_controllers); + + return 0; + } + + } else { + do { + free(id); + asprintf(&id, "c%lu", ++m->session_counter); + + if (!id) { + r = -ENOMEM; + goto fail; + } + + } while (hashmap_get(m->sessions, id)); + } + + r = manager_add_session(m, user, id, &session); + free(id); + if (r < 0) + goto fail; + + session->leader = leader; + session->audit_id = audit_id; + session->type = t; + session->class = c; + session->remote = remote; + session->controllers = controllers; + session->reset_controllers = reset_controllers; + session->kill_processes = kill_processes; + session->vtnr = vtnr; + + controllers = reset_controllers = NULL; + + if (!isempty(tty)) { + session->tty = strdup(tty); + if (!session->tty) { + r = -ENOMEM; + goto fail; + } + } + + if (!isempty(display)) { + session->display = strdup(display); + if (!session->display) { + r = -ENOMEM; + goto fail; + } + } + + if (!isempty(remote_user)) { + session->remote_user = strdup(remote_user); + if (!session->remote_user) { + r = -ENOMEM; + goto fail; + } + } + + if (!isempty(remote_host)) { + session->remote_host = strdup(remote_host); + if (!session->remote_host) { + r = -ENOMEM; + goto fail; + } + } + + if (!isempty(service)) { + session->service = strdup(service); + if (!session->service) { + r = -ENOMEM; + goto fail; + } + } + + fifo_fd = session_create_fifo(session); + if (fifo_fd < 0) { + r = fifo_fd; + goto fail; + } + + if (s) { + r = seat_attach_session(s, session); + if (r < 0) + goto fail; + } + + r = session_start(session); + if (r < 0) + goto fail; + + reply = dbus_message_new_method_return(message); + if (!reply) { + r = -ENOMEM; + goto fail; + } + + p = session_bus_path(session); + if (!p) { + r = -ENOMEM; + goto fail; + } + + seat = s ? s->id : ""; + b = dbus_message_append_args( + reply, + DBUS_TYPE_STRING, &session->id, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_STRING, &session->user->runtime_path, + DBUS_TYPE_UNIX_FD, &fifo_fd, + DBUS_TYPE_STRING, &seat, + DBUS_TYPE_UINT32, &vtnr, + DBUS_TYPE_INVALID); + free(p); + + if (!b) { + r = -ENOMEM; + goto fail; + } + + close_nointr_nofail(fifo_fd); + *_reply = reply; + + return 0; + +fail: + strv_free(controllers); + strv_free(reset_controllers); + + if (session) + session_add_to_gc_queue(session); + + if (user) + user_add_to_gc_queue(user); + + if (fifo_fd >= 0) + close_nointr_nofail(fifo_fd); + + if (reply) + dbus_message_unref(reply); + + return r; +} + +static int trigger_device(Manager *m, struct udev_device *d) { + struct udev_enumerate *e; + struct udev_list_entry *first, *item; + int r; + + assert(m); + + e = udev_enumerate_new(m->udev); + if (!e) { + r = -ENOMEM; + goto finish; + } + + if (d) { + if (udev_enumerate_add_match_parent(e, d) < 0) { + r = -EIO; + goto finish; + } + } + + if (udev_enumerate_scan_devices(e) < 0) { + r = -EIO; + goto finish; + } + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + char *t; + const char *p; + + p = udev_list_entry_get_name(item); + + t = strappend(p, "/uevent"); + if (!t) { + r = -ENOMEM; + goto finish; + } + + write_one_line_file(t, "change"); + free(t); + } + + r = 0; + +finish: + if (e) + udev_enumerate_unref(e); + + return r; +} + +static int attach_device(Manager *m, const char *seat, const char *sysfs) { + struct udev_device *d; + char *rule = NULL, *file = NULL; + const char *id_for_seat; + int r; + + assert(m); + assert(seat); + assert(sysfs); + + d = udev_device_new_from_syspath(m->udev, sysfs); + if (!d) + return -ENODEV; + + if (!udev_device_has_tag(d, "seat")) { + r = -ENODEV; + goto finish; + } + + id_for_seat = udev_device_get_property_value(d, "ID_FOR_SEAT"); + if (!id_for_seat) { + r = -ENODEV; + goto finish; + } + + if (asprintf(&file, "/etc/udev/rules.d/72-seat-%s.rules", id_for_seat) < 0) { + r = -ENOMEM; + goto finish; + } + + if (asprintf(&rule, "TAG==\"seat\", ENV{ID_FOR_SEAT}==\"%s\", ENV{ID_SEAT}=\"%s\"", id_for_seat, seat) < 0) { + r = -ENOMEM; + goto finish; + } + + mkdir_p("/etc/udev/rules.d", 0755); + r = write_one_line_file_atomic(file, rule); + if (r < 0) + goto finish; + + r = trigger_device(m, d); + +finish: + free(rule); + free(file); + + if (d) + udev_device_unref(d); + + return r; +} + +static int flush_devices(Manager *m) { + DIR *d; + + assert(m); + + d = opendir("/etc/udev/rules.d"); + if (!d) { + if (errno != ENOENT) + log_warning("Failed to open /etc/udev/rules.d: %m"); + } else { + struct dirent *de; + + while ((de = readdir(d))) { + + if (!dirent_is_file(de)) + continue; + + if (!startswith(de->d_name, "72-seat-")) + continue; + + if (!endswith(de->d_name, ".rules")) + continue; + + if (unlinkat(dirfd(d), de->d_name, 0) < 0) + log_warning("Failed to unlink %s: %m", de->d_name); + } + + closedir(d); + } + + return trigger_device(m, NULL); +} + +static int have_multiple_sessions( + DBusConnection *connection, + Manager *m, + DBusMessage *message, + DBusError *error) { + + Session *s; + + assert(m); + + if (hashmap_size(m->sessions) > 1) + return true; + + /* Hmm, there's only one session, but let's make sure it + * actually belongs to the user who is asking. If not, better + * be safe than sorry. */ + + s = hashmap_first(m->sessions); + if (s) { + unsigned long ul; + + ul = dbus_bus_get_unix_user(connection, dbus_message_get_sender(message), error); + if (ul == (unsigned long) -1) + return -EIO; + + return s->user->uid != ul; + } + + return false; +} + +static const BusProperty bus_login_manager_properties[] = { + { "ControlGroupHierarchy", bus_property_append_string, "s", offsetof(Manager, cgroup_path), true }, + { "Controllers", bus_property_append_strv, "as", offsetof(Manager, controllers), true }, + { "ResetControllers", bus_property_append_strv, "as", offsetof(Manager, reset_controllers), true }, + { "NAutoVTs", bus_property_append_unsigned, "u", offsetof(Manager, n_autovts) }, + { "KillOnlyUsers", bus_property_append_strv, "as", offsetof(Manager, kill_only_users), true }, + { "KillExcludeUsers", bus_property_append_strv, "as", offsetof(Manager, kill_exclude_users), true }, + { "KillUserProcesses", bus_property_append_bool, "b", offsetof(Manager, kill_user_processes) }, + { "IdleHint", bus_manager_append_idle_hint, "b", 0 }, + { "IdleSinceHint", bus_manager_append_idle_hint_since, "t", 0 }, + { "IdleSinceHintMonotonic", bus_manager_append_idle_hint_since, "t", 0 }, + { NULL, } +}; + +static DBusHandlerResult manager_message_handler( + DBusConnection *connection, + DBusMessage *message, + void *userdata) { + + Manager *m = userdata; + + DBusError error; + DBusMessage *reply = NULL; + int r; + + assert(connection); + assert(message); + assert(m); + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "GetSession")) { + const char *name; + char *p; + Session *session; + bool b; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + session = hashmap_get(m->sessions, name); + if (!session) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + p = session_bus_path(session); + if (!p) + goto oom; + + b = dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID); + free(p); + + if (!b) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "GetSessionByPID")) { + uint32_t pid; + char *p; + Session *session; + bool b; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &pid, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + r = manager_get_session_by_pid(m, pid, &session); + if (r <= 0) + return bus_send_error_reply(connection, message, NULL, r < 0 ? r : -ENOENT); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + p = session_bus_path(session); + if (!p) + goto oom; + + b = dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID); + free(p); + + if (!b) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "GetUser")) { + uint32_t uid; + char *p; + User *user; + bool b; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &uid, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + user = hashmap_get(m->users, ULONG_TO_PTR((unsigned long) uid)); + if (!user) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + p = user_bus_path(user); + if (!p) + goto oom; + + b = dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID); + free(p); + + if (!b) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "GetSeat")) { + const char *name; + char *p; + Seat *seat; + bool b; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + seat = hashmap_get(m->seats, name); + if (!seat) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + p = seat_bus_path(seat); + if (!p) + goto oom; + + b = dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID); + free(p); + + if (!b) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "ListSessions")) { + char *p; + Session *session; + Iterator i; + DBusMessageIter iter, sub; + const char *empty = ""; + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(susso)", &sub)) + goto oom; + + HASHMAP_FOREACH(session, m->sessions, i) { + DBusMessageIter sub2; + uint32_t uid; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) + goto oom; + + uid = session->user->uid; + + p = session_bus_path(session); + if (!p) + goto oom; + + if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &session->id) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &uid) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &session->user->name) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, session->seat ? (const char**) &session->seat->id : &empty) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + goto oom; + } + + free(p); + + if (!dbus_message_iter_close_container(&sub, &sub2)) + goto oom; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "ListUsers")) { + char *p; + User *user; + Iterator i; + DBusMessageIter iter, sub; + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(uso)", &sub)) + goto oom; + + HASHMAP_FOREACH(user, m->users, i) { + DBusMessageIter sub2; + uint32_t uid; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) + goto oom; + + uid = user->uid; + + p = user_bus_path(user); + if (!p) + goto oom; + + if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &uid) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &user->name) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + goto oom; + } + + free(p); + + if (!dbus_message_iter_close_container(&sub, &sub2)) + goto oom; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "ListSeats")) { + char *p; + Seat *seat; + Iterator i; + DBusMessageIter iter, sub; + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(so)", &sub)) + goto oom; + + HASHMAP_FOREACH(seat, m->seats, i) { + DBusMessageIter sub2; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) + goto oom; + + p = seat_bus_path(seat); + if (!p) + goto oom; + + if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &seat->id) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + goto oom; + } + + free(p); + + if (!dbus_message_iter_close_container(&sub, &sub2)) + goto oom; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "CreateSession")) { + + r = bus_manager_create_session(m, message, &reply); + + /* Don't delay the work on OOM here, since it might be + * triggered by a low RLIMIT_NOFILE here (since we + * send a dupped fd to the client), and we'd rather + * see this fail quickly then be retried later */ + + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "ReleaseSession")) { + const char *name; + Session *session; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + session = hashmap_get(m->sessions, name); + if (!session) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + /* We use the FIFO to detect stray sessions where the + process invoking PAM dies abnormally. We need to make + sure that that process is not killed if at the clean + end of the session it closes the FIFO. Hence, with + this call explicitly turn off the FIFO logic, so that + the PAM code can finish clean up on its own */ + session_remove_fifo(session); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "ActivateSession")) { + const char *name; + Session *session; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + session = hashmap_get(m->sessions, name); + if (!session) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + r = session_activate(session); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "ActivateSessionOnSeat")) { + const char *session_name, *seat_name; + Session *session; + Seat *seat; + + /* Same as ActivateSession() but refuses to work if + * the seat doesn't match */ + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &session_name, + DBUS_TYPE_STRING, &seat_name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + session = hashmap_get(m->sessions, session_name); + if (!session) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + seat = hashmap_get(m->seats, seat_name); + if (!seat) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + if (session->seat != seat) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + r = session_activate(session); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "LockSession") || + dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "UnlockSession")) { + const char *name; + Session *session; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + session = hashmap_get(m->sessions, name); + if (!session) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + if (session_send_lock(session, streq(dbus_message_get_member(message), "LockSession")) < 0) + goto oom; + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "KillSession")) { + const char *swho; + int32_t signo; + KillWho who; + const char *name; + Session *session; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &swho, + DBUS_TYPE_INT32, &signo, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if (signo <= 0 || signo >= _NSIG) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + session = hashmap_get(m->sessions, name); + if (!session) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + r = session_kill(session, who, signo); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "KillUser")) { + uint32_t uid; + User *user; + int32_t signo; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &uid, + DBUS_TYPE_INT32, &signo, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (signo <= 0 || signo >= _NSIG) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + user = hashmap_get(m->users, ULONG_TO_PTR((unsigned long) uid)); + if (!user) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + r = user_kill(user, signo); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "TerminateSession")) { + const char *name; + Session *session; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + session = hashmap_get(m->sessions, name); + if (!session) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + r = session_stop(session); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "TerminateUser")) { + uint32_t uid; + User *user; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &uid, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + user = hashmap_get(m->users, ULONG_TO_PTR((unsigned long) uid)); + if (!user) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + r = user_stop(user); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "TerminateSeat")) { + const char *name; + Seat *seat; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + seat = hashmap_get(m->seats, name); + if (!seat) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + r = seat_stop_sessions(seat); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "SetUserLinger")) { + uint32_t uid; + struct passwd *pw; + dbus_bool_t b, interactive; + char *path; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &uid, + DBUS_TYPE_BOOLEAN, &b, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + errno = 0; + pw = getpwuid(uid); + if (!pw) + return bus_send_error_reply(connection, message, NULL, errno ? -errno : -EINVAL); + + r = verify_polkit(connection, message, "org.freedesktop.login1.set-user-linger", interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + mkdir_p("/var/lib/systemd", 0755); + + r = safe_mkdir("/var/lib/systemd/linger", 0755, 0, 0); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + path = strappend("/var/lib/systemd/linger/", pw->pw_name); + if (!path) + goto oom; + + if (b) { + User *u; + + r = touch(path); + free(path); + + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (manager_add_user_by_uid(m, uid, &u) >= 0) + user_start(u); + + } else { + User *u; + + r = unlink(path); + free(path); + + if (r < 0 && errno != ENOENT) + return bus_send_error_reply(connection, message, &error, -errno); + + u = hashmap_get(m->users, ULONG_TO_PTR((unsigned long) uid)); + if (u) + user_add_to_gc_queue(u); + } + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "AttachDevice")) { + const char *sysfs, *seat; + dbus_bool_t interactive; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &seat, + DBUS_TYPE_STRING, &sysfs, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!path_startswith(sysfs, "/sys") || !seat_name_is_valid(seat)) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + + r = verify_polkit(connection, message, "org.freedesktop.login1.attach-device", interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + r = attach_device(m, seat, sysfs); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "FlushDevices")) { + dbus_bool_t interactive; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + r = verify_polkit(connection, message, "org.freedesktop.login1.flush-devices", interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + r = flush_devices(m); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "PowerOff") || + dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "Reboot")) { + dbus_bool_t interactive; + bool multiple_sessions; + DBusMessage *forward, *freply; + const char *name; + const char *mode = "replace"; + const char *action; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + r = have_multiple_sessions(connection, m, message, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + multiple_sessions = r > 0; + + if (streq(dbus_message_get_member(message), "PowerOff")) { + if (multiple_sessions) + action = "org.freedesktop.login1.power-off-multiple-sessions"; + else + action = "org.freedesktop.login1.power-off"; + + name = SPECIAL_POWEROFF_TARGET; + } else { + if (multiple_sessions) + action = "org.freedesktop.login1.reboot-multiple-sessions"; + else + action = "org.freedesktop.login1.reboot"; + + name = SPECIAL_REBOOT_TARGET; + } + + r = verify_polkit(connection, message, action, interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + forward = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit"); + if (!forward) + return bus_send_error_reply(connection, message, NULL, -ENOMEM); + + if (!dbus_message_append_args(forward, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID)) { + dbus_message_unref(forward); + return bus_send_error_reply(connection, message, NULL, -ENOMEM); + } + + freply = dbus_connection_send_with_reply_and_block(connection, forward, -1, &error); + dbus_message_unref(forward); + + if (!freply) + return bus_send_error_reply(connection, message, &error, -EIO); + + dbus_message_unref(freply); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "CanPowerOff") || + dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "CanReboot")) { + + bool multiple_sessions, challenge, b; + const char *t, *action; + + r = have_multiple_sessions(connection, m, message, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + multiple_sessions = r > 0; + + if (streq(dbus_message_get_member(message), "CanPowerOff")) { + if (multiple_sessions) + action = "org.freedesktop.login1.power-off-multiple-sessions"; + else + action = "org.freedesktop.login1.power-off"; + + } else { + if (multiple_sessions) + action = "org.freedesktop.login1.reboot-multiple-sessions"; + else + action = "org.freedesktop.login1.reboot"; + } + + r = verify_polkit(connection, message, action, false, &challenge, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + t = r > 0 ? "yes" : + challenge ? "challenge" : + "no"; + + b = dbus_message_append_args( + reply, + DBUS_TYPE_STRING, &t, + DBUS_TYPE_INVALID); + if (!b) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { + char *introspection = NULL; + FILE *f; + Iterator i; + Session *session; + Seat *seat; + User *user; + size_t size; + char *p; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + /* We roll our own introspection code here, instead of + * relying on bus_default_message_handler() because we + * need to generate our introspection string + * dynamically. */ + + if (!(f = open_memstream(&introspection, &size))) + goto oom; + + fputs(INTROSPECTION_BEGIN, f); + + HASHMAP_FOREACH(seat, m->seats, i) { + p = bus_path_escape(seat->id); + + if (p) { + fprintf(f, "", p); + free(p); + } + } + + HASHMAP_FOREACH(user, m->users, i) + fprintf(f, "", (unsigned long long) user->uid); + + HASHMAP_FOREACH(session, m->sessions, i) { + p = bus_path_escape(session->id); + + if (p) { + fprintf(f, "", p); + free(p); + } + } + + fputs(INTROSPECTION_END, f); + + if (ferror(f)) { + fclose(f); + free(introspection); + goto oom; + } + + fclose(f); + + if (!introspection) + goto oom; + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { + free(introspection); + goto oom; + } + + free(introspection); + } else { + const BusBoundProperties bps[] = { + { "org.freedesktop.login1.Manager", bus_login_manager_properties, m }, + { NULL, } + }; + return bus_default_message_handler(connection, message, NULL, INTERFACES_LIST, bps); + } + + if (reply) { + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +const DBusObjectPathVTable bus_manager_vtable = { + .message_function = manager_message_handler +}; + +DBusHandlerResult bus_message_filter( + DBusConnection *connection, + DBusMessage *message, + void *userdata) { + + Manager *m = userdata; + DBusError error; + + assert(m); + assert(connection); + assert(message); + + dbus_error_init(&error); + + if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Agent", "Released")) { + const char *cgroup; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &cgroup, + DBUS_TYPE_INVALID)) + log_error("Failed to parse Released message: %s", bus_error_message(&error)); + else + manager_cgroup_notify_empty(m, cgroup); + } + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +int manager_send_changed(Manager *manager, const char *properties) { + DBusMessage *m; + int r = -ENOMEM; + + assert(manager); + + m = bus_properties_changed_new("/org/freedesktop/login1", "org.freedesktop.login1.Manager", properties); + if (!m) + goto finish; + + if (!dbus_connection_send(manager->bus, m, NULL)) + goto finish; + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + return r; +} diff --git a/src/login/logind-device.c b/src/login/logind-device.c new file mode 100644 index 000000000..bbd370fbf --- /dev/null +++ b/src/login/logind-device.c @@ -0,0 +1,86 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "logind-device.h" +#include "util.h" + +Device* device_new(Manager *m, const char *sysfs) { + Device *d; + + assert(m); + assert(sysfs); + + d = new0(Device, 1); + if (!d) + return NULL; + + d->sysfs = strdup(sysfs); + if (!d->sysfs) { + free(d); + return NULL; + } + + if (hashmap_put(m->devices, d->sysfs, d) < 0) { + free(d->sysfs); + free(d); + return NULL; + } + + d->manager = m; + dual_timestamp_get(&d->timestamp); + + return d; +} + +void device_free(Device *d) { + assert(d); + + device_detach(d); + + hashmap_remove(d->manager->devices, d->sysfs); + + free(d->sysfs); + free(d); +} + +void device_detach(Device *d) { + assert(d); + + if (d->seat) + LIST_REMOVE(Device, devices, d->seat->devices, d); + + seat_add_to_gc_queue(d->seat); + d->seat = NULL; +} + +void device_attach(Device *d, Seat *s) { + assert(d); + assert(s); + + if (d->seat) + device_detach(d); + + d->seat = s; + LIST_PREPEND(Device, devices, s->devices, d); +} diff --git a/src/login/logind-device.h b/src/login/logind-device.h new file mode 100644 index 000000000..e25a5344e --- /dev/null +++ b/src/login/logind-device.h @@ -0,0 +1,48 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foologinddevicehfoo +#define foologinddevicehfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Device Device; + +#include "list.h" +#include "util.h" +#include "logind.h" +#include "logind-seat.h" + +struct Device { + Manager *manager; + + char *sysfs; + Seat *seat; + + dual_timestamp timestamp; + + LIST_FIELDS(struct Device, devices); +}; + +Device* device_new(Manager *m, const char *sysfs); +void device_free(Device *d); +void device_attach(Device *d, Seat *s); +void device_detach(Device *d); + +#endif diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf new file mode 100644 index 000000000..940fe10e8 --- /dev/null +++ b/src/login/logind-gperf.gperf @@ -0,0 +1,22 @@ +%{ +#include +#include "conf-parser.h" +#include "logind.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name logind_gperf_hash +%define lookup-function-name logind_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Login.NAutoVTs, config_parse_unsigned, 0, offsetof(Manager, n_autovts) +Login.KillUserProcesses, config_parse_bool, 0, offsetof(Manager, kill_user_processes) +Login.KillOnlyUsers, config_parse_strv, 0, offsetof(Manager, kill_only_users) +Login.KillExcludeUsers, config_parse_strv, 0, offsetof(Manager, kill_exclude_users) +Login.Controllers, config_parse_strv, 0, offsetof(Manager, controllers) +Login.ResetControllers, config_parse_strv, 0, offsetof(Manager, reset_controllers) diff --git a/src/login/logind-seat-dbus.c b/src/login/logind-seat-dbus.c new file mode 100644 index 000000000..95ef5ffdb --- /dev/null +++ b/src/login/logind-seat-dbus.c @@ -0,0 +1,408 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "logind.h" +#include "logind-seat.h" +#include "dbus-common.h" +#include "util.h" + +#define BUS_SEAT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_SEAT_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.login1.Seat\0" + +static int bus_seat_append_active(DBusMessageIter *i, const char *property, void *data) { + DBusMessageIter sub; + Seat *s = data; + const char *id, *path; + char *p = NULL; + + assert(i); + assert(property); + assert(s); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub)) + return -ENOMEM; + + if (s->active) { + id = s->active->id; + path = p = session_bus_path(s->active); + + if (!p) + return -ENOMEM; + } else { + id = ""; + path = "/"; + } + + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &id) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &path)) { + free(p); + return -ENOMEM; + } + + free(p); + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_seat_append_sessions(DBusMessageIter *i, const char *property, void *data) { + DBusMessageIter sub, sub2; + Seat *s = data; + Session *session; + + assert(i); + assert(property); + assert(s); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(so)", &sub)) + return -ENOMEM; + + LIST_FOREACH(sessions_by_seat, session, s->sessions) { + char *p; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) + return -ENOMEM; + + p = session_bus_path(session); + if (!p) + return -ENOMEM; + + if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &session->id) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + return -ENOMEM; + } + + free(p); + + if (!dbus_message_iter_close_container(&sub, &sub2)) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_seat_append_multi_session(DBusMessageIter *i, const char *property, void *data) { + Seat *s = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(s); + + b = seat_can_multi_session(s); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_seat_append_idle_hint(DBusMessageIter *i, const char *property, void *data) { + Seat *s = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(s); + + b = seat_get_idle_hint(s, NULL) > 0; + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_seat_append_idle_hint_since(DBusMessageIter *i, const char *property, void *data) { + Seat *s = data; + dual_timestamp t; + uint64_t k; + + assert(i); + assert(property); + assert(s); + + seat_get_idle_hint(s, &t); + k = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &k)) + return -ENOMEM; + + return 0; +} + +static int get_seat_for_path(Manager *m, const char *path, Seat **_s) { + Seat *s; + char *id; + + assert(m); + assert(path); + assert(_s); + + if (!startswith(path, "/org/freedesktop/login1/seat/")) + return -EINVAL; + + id = bus_path_unescape(path + 29); + if (!id) + return -ENOMEM; + + s = hashmap_get(m->seats, id); + free(id); + + if (!s) + return -ENOENT; + + *_s = s; + return 0; +} + +static const BusProperty bus_login_seat_properties[] = { + { "Id", bus_property_append_string, "s", offsetof(Seat, id), true }, + { "ActiveSession", bus_seat_append_active, "(so)", 0 }, + { "CanMultiSession", bus_seat_append_multi_session, "b", 0 }, + { "Sessions", bus_seat_append_sessions, "a(so)", 0 }, + { "IdleHint", bus_seat_append_idle_hint, "b", 0 }, + { "IdleSinceHint", bus_seat_append_idle_hint_since, "t", 0 }, + { "IdleSinceHintMonotonic", bus_seat_append_idle_hint_since, "t", 0 }, + { NULL, } +}; + +static DBusHandlerResult seat_message_dispatch( + Seat *s, + DBusConnection *connection, + DBusMessage *message) { + + DBusError error; + DBusMessage *reply = NULL; + int r; + + assert(s); + assert(connection); + assert(message); + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.login1.Seat", "Terminate")) { + + r = seat_stop_sessions(s); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Seat", "ActivateSession")) { + const char *name; + Session *session; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + session = hashmap_get(s->manager->sessions, name); + if (!session || session->seat != s) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + r = session_activate(session); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + } else { + const BusBoundProperties bps[] = { + { "org.freedesktop.login1.Seat", bus_login_seat_properties, s }, + { NULL, } + }; + return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); + } + + if (reply) { + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static DBusHandlerResult seat_message_handler( + DBusConnection *connection, + DBusMessage *message, + void *userdata) { + + Manager *m = userdata; + Seat *s; + int r; + + r = get_seat_for_path(m, dbus_message_get_path(message), &s); + if (r < 0) { + + if (r == -ENOMEM) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (r == -ENOENT) { + DBusError e; + + dbus_error_init(&e); + dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown seat"); + return bus_send_error_reply(connection, message, &e, r); + } + + return bus_send_error_reply(connection, message, NULL, r); + } + + return seat_message_dispatch(s, connection, message); +} + +const DBusObjectPathVTable bus_seat_vtable = { + .message_function = seat_message_handler +}; + +char *seat_bus_path(Seat *s) { + char *t, *r; + + assert(s); + + t = bus_path_escape(s->id); + if (!t) + return NULL; + + r = strappend("/org/freedesktop/login1/seat/", t); + free(t); + + return r; +} + +int seat_send_signal(Seat *s, bool new_seat) { + DBusMessage *m; + int r = -ENOMEM; + char *p = NULL; + + assert(s); + + m = dbus_message_new_signal("/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + new_seat ? "SeatNew" : "SeatRemoved"); + + if (!m) + return -ENOMEM; + + p = seat_bus_path(s); + if (!p) + goto finish; + + if (!dbus_message_append_args( + m, + DBUS_TYPE_STRING, &s->id, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID)) + goto finish; + + if (!dbus_connection_send(s->manager->bus, m, NULL)) + goto finish; + + r = 0; + +finish: + dbus_message_unref(m); + free(p); + + return r; +} + +int seat_send_changed(Seat *s, const char *properties) { + DBusMessage *m; + int r = -ENOMEM; + char *p = NULL; + + assert(s); + + if (!s->started) + return 0; + + p = seat_bus_path(s); + if (!p) + return -ENOMEM; + + m = bus_properties_changed_new(p, "org.freedesktop.login1.Seat", properties); + if (!m) + goto finish; + + if (!dbus_connection_send(s->manager->bus, m, NULL)) + goto finish; + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + free(p); + + return r; +} diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c new file mode 100644 index 000000000..be37c1cc2 --- /dev/null +++ b/src/login/logind-seat.c @@ -0,0 +1,514 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "logind-seat.h" +#include "logind-acl.h" +#include "util.h" + +Seat *seat_new(Manager *m, const char *id) { + Seat *s; + + assert(m); + assert(id); + + s = new0(Seat, 1); + if (!s) + return NULL; + + s->state_file = strappend("/run/systemd/seats/", id); + if (!s->state_file) { + free(s); + return NULL; + } + + s->id = file_name_from_path(s->state_file); + s->manager = m; + + if (hashmap_put(m->seats, s->id, s) < 0) { + free(s->state_file); + free(s); + return NULL; + } + + return s; +} + +void seat_free(Seat *s) { + assert(s); + + if (s->in_gc_queue) + LIST_REMOVE(Seat, gc_queue, s->manager->seat_gc_queue, s); + + while (s->sessions) + session_free(s->sessions); + + assert(!s->active); + + while (s->devices) + device_free(s->devices); + + hashmap_remove(s->manager->seats, s->id); + + free(s->state_file); + free(s); +} + +int seat_save(Seat *s) { + int r; + FILE *f; + char *temp_path; + + assert(s); + + if (!s->started) + return 0; + + r = safe_mkdir("/run/systemd/seats", 0755, 0, 0); + if (r < 0) + goto finish; + + r = fopen_temporary(s->state_file, &f, &temp_path); + if (r < 0) + goto finish; + + fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n" + "IS_VTCONSOLE=%i\n" + "CAN_MULTI_SESSION=%i\n", + seat_is_vtconsole(s), + seat_can_multi_session(s)); + + if (s->active) { + assert(s->active->user); + + fprintf(f, + "ACTIVE=%s\n" + "ACTIVE_UID=%lu\n", + s->active->id, + (unsigned long) s->active->user->uid); + } + + if (s->sessions) { + Session *i; + + fputs("SESSIONS=", f); + LIST_FOREACH(sessions_by_seat, i, s->sessions) { + fprintf(f, + "%s%c", + i->id, + i->sessions_by_seat_next ? ' ' : '\n'); + } + + fputs("UIDS=", f); + LIST_FOREACH(sessions_by_seat, i, s->sessions) + fprintf(f, + "%lu%c", + (unsigned long) i->user->uid, + i->sessions_by_seat_next ? ' ' : '\n'); + } + + fflush(f); + + if (ferror(f) || rename(temp_path, s->state_file) < 0) { + r = -errno; + unlink(s->state_file); + unlink(temp_path); + } + + fclose(f); + free(temp_path); + +finish: + if (r < 0) + log_error("Failed to save seat data for %s: %s", s->id, strerror(-r)); + + return r; +} + +int seat_load(Seat *s) { + assert(s); + + /* There isn't actually anything to read here ... */ + + return 0; +} + +static int vt_allocate(int vtnr) { + int fd, r; + char *p; + + assert(vtnr >= 1); + + if (asprintf(&p, "/dev/tty%i", vtnr) < 0) + return -ENOMEM; + + fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC); + free(p); + + r = fd < 0 ? -errno : 0; + + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +int seat_preallocate_vts(Seat *s) { + int r = 0; + unsigned i; + + assert(s); + assert(s->manager); + + log_debug("Preallocating VTs..."); + + if (s->manager->n_autovts <= 0) + return 0; + + if (!seat_can_multi_session(s)) + return 0; + + for (i = 1; i <= s->manager->n_autovts; i++) { + int q; + + q = vt_allocate(i); + if (q < 0) { + log_error("Failed to preallocate VT %i: %s", i, strerror(-q)); + r = q; + } + } + + return r; +} + +int seat_apply_acls(Seat *s, Session *old_active) { + int r; + + assert(s); + + r = devnode_acl_all(s->manager->udev, + s->id, + false, + !!old_active, old_active ? old_active->user->uid : 0, + !!s->active, s->active ? s->active->user->uid : 0); + + if (r < 0) + log_error("Failed to apply ACLs: %s", strerror(-r)); + + return r; +} + +int seat_set_active(Seat *s, Session *session) { + Session *old_active; + + assert(s); + assert(!session || session->seat == s); + + if (session == s->active) + return 0; + + old_active = s->active; + s->active = session; + + seat_apply_acls(s, old_active); + + if (session && session->started) + session_send_changed(session, "Active\0"); + + if (!session || session->started) + seat_send_changed(s, "ActiveSession\0"); + + seat_save(s); + + if (session) { + session_save(session); + user_save(session->user); + } + + if (old_active) { + session_save(old_active); + user_save(old_active->user); + } + + return 0; +} + +int seat_active_vt_changed(Seat *s, int vtnr) { + Session *i, *new_active = NULL; + int r; + + assert(s); + assert(vtnr >= 1); + + if (!seat_can_multi_session(s)) + return -EINVAL; + + log_debug("VT changed to %i", vtnr); + + LIST_FOREACH(sessions_by_seat, i, s->sessions) + if (i->vtnr == vtnr) { + new_active = i; + break; + } + + r = seat_set_active(s, new_active); + manager_spawn_autovt(s->manager, vtnr); + + return r; +} + +int seat_read_active_vt(Seat *s) { + char t[64]; + ssize_t k; + int r, vtnr; + + assert(s); + + if (!seat_can_multi_session(s)) + return 0; + + lseek(s->manager->console_active_fd, SEEK_SET, 0); + + k = read(s->manager->console_active_fd, t, sizeof(t)-1); + if (k <= 0) { + log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF"); + return k < 0 ? -errno : -EIO; + } + + t[k] = 0; + truncate_nl(t); + + if (!startswith(t, "tty")) { + log_error("Hm, /sys/class/tty/tty0/active is badly formatted."); + return -EIO; + } + + r = safe_atoi(t+3, &vtnr); + if (r < 0) { + log_error("Failed to parse VT number %s", t+3); + return r; + } + + if (vtnr <= 0) { + log_error("VT number invalid: %s", t+3); + return -EIO; + } + + return seat_active_vt_changed(s, vtnr); +} + +int seat_start(Seat *s) { + assert(s); + + if (s->started) + return 0; + + log_info("New seat %s.", s->id); + + /* Initialize VT magic stuff */ + seat_preallocate_vts(s); + + /* Read current VT */ + seat_read_active_vt(s); + + s->started = true; + + /* Save seat data */ + seat_save(s); + + seat_send_signal(s, true); + + return 0; +} + +int seat_stop(Seat *s) { + int r = 0; + + assert(s); + + if (s->started) + log_info("Removed seat %s.", s->id); + + seat_stop_sessions(s); + + unlink(s->state_file); + seat_add_to_gc_queue(s); + + if (s->started) + seat_send_signal(s, false); + + s->started = false; + + return r; +} + +int seat_stop_sessions(Seat *s) { + Session *session; + int r = 0, k; + + assert(s); + + LIST_FOREACH(sessions_by_seat, session, s->sessions) { + k = session_stop(session); + if (k < 0) + r = k; + } + + return r; +} + +int seat_attach_session(Seat *s, Session *session) { + assert(s); + assert(session); + assert(!session->seat); + + session->seat = s; + LIST_PREPEND(Session, sessions_by_seat, s->sessions, session); + + seat_send_changed(s, "Sessions\0"); + + /* Note that even if a seat is not multi-session capable it + * still might have multiple sessions on it since old, dead + * sessions might continue to be tracked until all their + * processes are gone. The most recently added session + * (i.e. the first in s->sessions) is the one that matters. */ + + if (!seat_can_multi_session(s)) + seat_set_active(s, session); + + return 0; +} + +bool seat_is_vtconsole(Seat *s) { + assert(s); + + return s->manager->vtconsole == s; +} + +bool seat_can_multi_session(Seat *s) { + assert(s); + + if (!seat_is_vtconsole(s)) + return false; + + /* If we can't watch which VT is in the foreground, we don't + * support VT switching */ + + return s->manager->console_active_fd >= 0; +} + +int seat_get_idle_hint(Seat *s, dual_timestamp *t) { + Session *session; + bool idle_hint = true; + dual_timestamp ts = { 0, 0 }; + + assert(s); + + LIST_FOREACH(sessions_by_seat, session, s->sessions) { + dual_timestamp k; + int ih; + + ih = session_get_idle_hint(session, &k); + if (ih < 0) + return ih; + + if (!ih) { + if (!idle_hint) { + if (k.monotonic < ts.monotonic) + ts = k; + } else { + idle_hint = false; + ts = k; + } + } else if (idle_hint) { + + if (k.monotonic > ts.monotonic) + ts = k; + } + } + + if (t) + *t = ts; + + return idle_hint; +} + +int seat_check_gc(Seat *s, bool drop_not_started) { + assert(s); + + if (drop_not_started && !s->started) + return 0; + + if (seat_is_vtconsole(s)) + return 1; + + return !!s->devices; +} + +void seat_add_to_gc_queue(Seat *s) { + assert(s); + + if (s->in_gc_queue) + return; + + LIST_PREPEND(Seat, gc_queue, s->manager->seat_gc_queue, s); + s->in_gc_queue = true; +} + +static bool seat_name_valid_char(char c) { + return + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '-' || + c == '_'; +} + +bool seat_name_is_valid(const char *name) { + const char *p; + + assert(name); + + if (!startswith(name, "seat")) + return false; + + if (!name[4]) + return false; + + for (p = name; *p; p++) + if (!seat_name_valid_char(*p)) + return false; + + if (strlen(name) > 255) + return false; + + return true; +} diff --git a/src/login/logind-seat.h b/src/login/logind-seat.h new file mode 100644 index 000000000..3b2c7f096 --- /dev/null +++ b/src/login/logind-seat.h @@ -0,0 +1,83 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foologindseathfoo +#define foologindseathfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Seat Seat; + +#include "list.h" +#include "util.h" +#include "logind.h" +#include "logind-device.h" +#include "logind-session.h" + +struct Seat { + Manager *manager; + char *id; + + char *state_file; + + LIST_HEAD(Device, devices); + + Session *active; + LIST_HEAD(Session, sessions); + + bool in_gc_queue:1; + bool started:1; + + LIST_FIELDS(Seat, gc_queue); +}; + +Seat *seat_new(Manager *m, const char *id); +void seat_free(Seat *s); + +int seat_save(Seat *s); +int seat_load(Seat *s); + +int seat_apply_acls(Seat *s, Session *old_active); +int seat_set_active(Seat *s, Session *session); +int seat_active_vt_changed(Seat *s, int vtnr); +int seat_read_active_vt(Seat *s); +int seat_preallocate_vts(Seat *s); + +int seat_attach_session(Seat *s, Session *session); + +bool seat_is_vtconsole(Seat *s); +bool seat_can_multi_session(Seat *s); +int seat_get_idle_hint(Seat *s, dual_timestamp *t); + +int seat_start(Seat *s); +int seat_stop(Seat *s); +int seat_stop_sessions(Seat *s); + +int seat_check_gc(Seat *s, bool drop_not_started); +void seat_add_to_gc_queue(Seat *s); + +bool seat_name_is_valid(const char *name); +char *seat_bus_path(Seat *s); + +extern const DBusObjectPathVTable bus_seat_vtable; + +int seat_send_signal(Seat *s, bool new_seat); +int seat_send_changed(Seat *s, const char *properties); + +#endif diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c new file mode 100644 index 000000000..102f8ac99 --- /dev/null +++ b/src/login/logind-session-dbus.c @@ -0,0 +1,528 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "logind.h" +#include "logind-session.h" +#include "dbus-common.h" +#include "util.h" + +#define BUS_SESSION_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_SESSION_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.login1.Session\0" + +static int bus_session_append_seat(DBusMessageIter *i, const char *property, void *data) { + DBusMessageIter sub; + Session *s = data; + const char *id, *path; + char *p = NULL; + + assert(i); + assert(property); + assert(s); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub)) + return -ENOMEM; + + if (s->seat) { + id = s->seat->id; + path = p = seat_bus_path(s->seat); + + if (!p) + return -ENOMEM; + } else { + id = ""; + path = "/"; + } + + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &id) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &path)) { + free(p); + return -ENOMEM; + } + + free(p); + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_session_append_user(DBusMessageIter *i, const char *property, void *data) { + DBusMessageIter sub; + User *u = data; + char *p = NULL; + + assert(i); + assert(property); + assert(u); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub)) + return -ENOMEM; + + p = user_bus_path(u); + if (!p) + return -ENOMEM; + + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u->uid) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + return -ENOMEM; + } + + free(p); + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_session_append_active(DBusMessageIter *i, const char *property, void *data) { + Session *s = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(s); + + b = session_is_active(s); + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_session_append_idle_hint(DBusMessageIter *i, const char *property, void *data) { + Session *s = data; + int b; + + assert(i); + assert(property); + assert(s); + + b = session_get_idle_hint(s, NULL) > 0; + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_session_append_idle_hint_since(DBusMessageIter *i, const char *property, void *data) { + Session *s = data; + dual_timestamp t; + uint64_t u; + + assert(i); + assert(property); + assert(s); + + session_get_idle_hint(s, &t); + u = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u)) + return -ENOMEM; + + return 0; +} + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_session_append_type, session_type, SessionType); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_session_append_class, session_class, SessionClass); + +static int get_session_for_path(Manager *m, const char *path, Session **_s) { + Session *s; + char *id; + + assert(m); + assert(path); + assert(_s); + + if (!startswith(path, "/org/freedesktop/login1/session/")) + return -EINVAL; + + id = bus_path_unescape(path + 32); + if (!id) + return -ENOMEM; + + s = hashmap_get(m->sessions, id); + free(id); + + if (!s) + return -ENOENT; + + *_s = s; + return 0; +} + +static const BusProperty bus_login_session_properties[] = { + { "Id", bus_property_append_string, "s", offsetof(Session, id), true }, + { "Timestamp", bus_property_append_usec, "t", offsetof(Session, timestamp.realtime) }, + { "TimestampMonotonic", bus_property_append_usec, "t", offsetof(Session, timestamp.monotonic) }, + { "ControlGroupPath", bus_property_append_string, "s", offsetof(Session, cgroup_path), true }, + { "VTNr", bus_property_append_uint32, "u", offsetof(Session, vtnr) }, + { "Seat", bus_session_append_seat, "(so)", 0 }, + { "TTY", bus_property_append_string, "s", offsetof(Session, tty), true }, + { "Display", bus_property_append_string, "s", offsetof(Session, display), true }, + { "Remote", bus_property_append_bool, "b", offsetof(Session, remote) }, + { "RemoteUser", bus_property_append_string, "s", offsetof(Session, remote_user), true }, + { "RemoteHost", bus_property_append_string, "s", offsetof(Session, remote_host), true }, + { "Service", bus_property_append_string, "s", offsetof(Session, service), true }, + { "Leader", bus_property_append_pid, "u", offsetof(Session, leader) }, + { "Audit", bus_property_append_uint32, "u", offsetof(Session, audit_id) }, + { "Type", bus_session_append_type, "s", offsetof(Session, type) }, + { "Class", bus_session_append_class, "s", offsetof(Session, class) }, + { "Active", bus_session_append_active, "b", 0 }, + { "Controllers", bus_property_append_strv, "as", offsetof(Session, controllers), true }, + { "ResetControllers", bus_property_append_strv, "as", offsetof(Session, reset_controllers), true }, + { "KillProcesses", bus_property_append_bool, "b", offsetof(Session, kill_processes) }, + { "IdleHint", bus_session_append_idle_hint, "b", 0 }, + { "IdleSinceHint", bus_session_append_idle_hint_since, "t", 0 }, + { "IdleSinceHintMonotonic", bus_session_append_idle_hint_since, "t", 0 }, + { NULL, } +}; + +static const BusProperty bus_login_session_user_properties[] = { + { "User", bus_session_append_user, "(uo)", 0 }, + { "Name", bus_property_append_string, "s", offsetof(User, name), true }, + { NULL, } +}; + +static DBusHandlerResult session_message_dispatch( + Session *s, + DBusConnection *connection, + DBusMessage *message) { + + DBusError error; + DBusMessage *reply = NULL; + int r; + + assert(s); + assert(connection); + assert(message); + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Terminate")) { + + r = session_stop(s); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Activate")) { + + r = session_activate(s); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Lock") || + dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Unlock")) { + + if (session_send_lock(s, streq(dbus_message_get_member(message), "Lock")) < 0) + goto oom; + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "SetIdleHint")) { + dbus_bool_t b; + unsigned long ul; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_BOOLEAN, &b, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + ul = dbus_bus_get_unix_user(connection, dbus_message_get_sender(message), &error); + if (ul == (unsigned long) -1) + return bus_send_error_reply(connection, message, &error, -EIO); + + if (ul != 0 && ul != s->user->uid) + return bus_send_error_reply(connection, message, NULL, -EPERM); + + session_set_idle_hint(s, b); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Kill")) { + const char *swho; + int32_t signo; + KillWho who; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &swho, + DBUS_TYPE_INT32, &signo, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if (signo <= 0 || signo >= _NSIG) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + r = session_kill(s, who, signo); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else { + const BusBoundProperties bps[] = { + { "org.freedesktop.login1.Session", bus_login_session_properties, s }, + { "org.freedesktop.login1.Session", bus_login_session_user_properties, s->user }, + { NULL, } + }; + return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); + } + + if (reply) { + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static DBusHandlerResult session_message_handler( + DBusConnection *connection, + DBusMessage *message, + void *userdata) { + + Manager *m = userdata; + Session *s; + int r; + + r = get_session_for_path(m, dbus_message_get_path(message), &s); + if (r < 0) { + + if (r == -ENOMEM) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (r == -ENOENT) { + DBusError e; + + dbus_error_init(&e); + dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown session"); + return bus_send_error_reply(connection, message, &e, r); + } + + return bus_send_error_reply(connection, message, NULL, r); + } + + return session_message_dispatch(s, connection, message); +} + +const DBusObjectPathVTable bus_session_vtable = { + .message_function = session_message_handler +}; + +char *session_bus_path(Session *s) { + char *t, *r; + + assert(s); + + t = bus_path_escape(s->id); + if (!t) + return NULL; + + r = strappend("/org/freedesktop/login1/session/", t); + free(t); + + return r; +} + +int session_send_signal(Session *s, bool new_session) { + DBusMessage *m; + int r = -ENOMEM; + char *p = NULL; + + assert(s); + + m = dbus_message_new_signal("/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + new_session ? "SessionNew" : "SessionRemoved"); + + if (!m) + return -ENOMEM; + + p = session_bus_path(s); + if (!p) + goto finish; + + if (!dbus_message_append_args( + m, + DBUS_TYPE_STRING, &s->id, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID)) + goto finish; + + if (!dbus_connection_send(s->manager->bus, m, NULL)) + goto finish; + + r = 0; + +finish: + dbus_message_unref(m); + free(p); + + return r; +} + +int session_send_changed(Session *s, const char *properties) { + DBusMessage *m; + int r = -ENOMEM; + char *p = NULL; + + assert(s); + + if (!s->started) + return 0; + + p = session_bus_path(s); + if (!p) + return -ENOMEM; + + m = bus_properties_changed_new(p, "org.freedesktop.login1.Session", properties); + if (!m) + goto finish; + + if (!dbus_connection_send(s->manager->bus, m, NULL)) + goto finish; + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + free(p); + + return r; +} + +int session_send_lock(Session *s, bool lock) { + DBusMessage *m; + bool b; + char *p; + + assert(s); + + p = session_bus_path(s); + if (!p) + return -ENOMEM; + + m = dbus_message_new_signal(p, "org.freedesktop.login1.Session", lock ? "Lock" : "Unlock"); + free(p); + + if (!m) + return -ENOMEM; + + b = dbus_connection_send(s->manager->bus, m, NULL); + dbus_message_unref(m); + + if (!b) + return -ENOMEM; + + return 0; +} diff --git a/src/login/logind-session.c b/src/login/logind-session.c new file mode 100644 index 000000000..4e0af8656 --- /dev/null +++ b/src/login/logind-session.c @@ -0,0 +1,982 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "logind-session.h" +#include "strv.h" +#include "util.h" +#include "cgroup-util.h" + +#define IDLE_THRESHOLD_USEC (5*USEC_PER_MINUTE) + +Session* session_new(Manager *m, User *u, const char *id) { + Session *s; + + assert(m); + assert(id); + + s = new0(Session, 1); + if (!s) + return NULL; + + s->state_file = strappend("/run/systemd/sessions/", id); + if (!s->state_file) { + free(s); + return NULL; + } + + s->id = file_name_from_path(s->state_file); + + if (hashmap_put(m->sessions, s->id, s) < 0) { + free(s->id); + free(s); + return NULL; + } + + s->manager = m; + s->fifo_fd = -1; + s->user = u; + + LIST_PREPEND(Session, sessions_by_user, u->sessions, s); + + return s; +} + +void session_free(Session *s) { + assert(s); + + if (s->in_gc_queue) + LIST_REMOVE(Session, gc_queue, s->manager->session_gc_queue, s); + + if (s->user) { + LIST_REMOVE(Session, sessions_by_user, s->user->sessions, s); + + if (s->user->display == s) + s->user->display = NULL; + } + + if (s->seat) { + if (s->seat->active == s) + s->seat->active = NULL; + + LIST_REMOVE(Session, sessions_by_seat, s->seat->sessions, s); + } + + if (s->cgroup_path) + hashmap_remove(s->manager->cgroups, s->cgroup_path); + + free(s->cgroup_path); + strv_free(s->controllers); + + free(s->tty); + free(s->display); + free(s->remote_host); + free(s->remote_user); + free(s->service); + + hashmap_remove(s->manager->sessions, s->id); + + session_remove_fifo(s); + + free(s->state_file); + free(s); +} + +int session_save(Session *s) { + FILE *f; + int r = 0; + char *temp_path; + + assert(s); + + if (!s->started) + return 0; + + r = safe_mkdir("/run/systemd/sessions", 0755, 0, 0); + if (r < 0) + goto finish; + + r = fopen_temporary(s->state_file, &f, &temp_path); + if (r < 0) + goto finish; + + assert(s->user); + + fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n" + "UID=%lu\n" + "USER=%s\n" + "ACTIVE=%i\n" + "REMOTE=%i\n" + "KILL_PROCESSES=%i\n", + (unsigned long) s->user->uid, + s->user->name, + session_is_active(s), + s->remote, + s->kill_processes); + + if (s->type >= 0) + fprintf(f, + "TYPE=%s\n", + session_type_to_string(s->type)); + + if (s->class >= 0) + fprintf(f, + "CLASS=%s\n", + session_class_to_string(s->class)); + + if (s->cgroup_path) + fprintf(f, + "CGROUP=%s\n", + s->cgroup_path); + + if (s->fifo_path) + fprintf(f, + "FIFO=%s\n", + s->fifo_path); + + if (s->seat) + fprintf(f, + "SEAT=%s\n", + s->seat->id); + + if (s->tty) + fprintf(f, + "TTY=%s\n", + s->tty); + + if (s->display) + fprintf(f, + "DISPLAY=%s\n", + s->display); + + if (s->remote_host) + fprintf(f, + "REMOTE_HOST=%s\n", + s->remote_host); + + if (s->remote_user) + fprintf(f, + "REMOTE_USER=%s\n", + s->remote_user); + + if (s->service) + fprintf(f, + "SERVICE=%s\n", + s->service); + + if (s->seat && seat_can_multi_session(s->seat)) + fprintf(f, + "VTNR=%i\n", + s->vtnr); + + if (s->leader > 0) + fprintf(f, + "LEADER=%lu\n", + (unsigned long) s->leader); + + if (s->audit_id > 0) + fprintf(f, + "AUDIT=%llu\n", + (unsigned long long) s->audit_id); + + fflush(f); + + if (ferror(f) || rename(temp_path, s->state_file) < 0) { + r = -errno; + unlink(s->state_file); + unlink(temp_path); + } + + fclose(f); + free(temp_path); + +finish: + if (r < 0) + log_error("Failed to save session data for %s: %s", s->id, strerror(-r)); + + return r; +} + +int session_load(Session *s) { + char *remote = NULL, + *kill_processes = NULL, + *seat = NULL, + *vtnr = NULL, + *leader = NULL, + *audit_id = NULL, + *type = NULL, + *class = NULL; + + int k, r; + + assert(s); + + r = parse_env_file(s->state_file, NEWLINE, + "REMOTE", &remote, + "KILL_PROCESSES", &kill_processes, + "CGROUP", &s->cgroup_path, + "FIFO", &s->fifo_path, + "SEAT", &seat, + "TTY", &s->tty, + "DISPLAY", &s->display, + "REMOTE_HOST", &s->remote_host, + "REMOTE_USER", &s->remote_user, + "SERVICE", &s->service, + "VTNR", &vtnr, + "LEADER", &leader, + "TYPE", &type, + "CLASS", &class, + NULL); + + if (r < 0) + goto finish; + + if (remote) { + k = parse_boolean(remote); + if (k >= 0) + s->remote = k; + } + + if (kill_processes) { + k = parse_boolean(kill_processes); + if (k >= 0) + s->kill_processes = k; + } + + if (seat && !s->seat) { + Seat *o; + + o = hashmap_get(s->manager->seats, seat); + if (o) + seat_attach_session(o, s); + } + + if (vtnr && s->seat && seat_can_multi_session(s->seat)) { + int v; + + k = safe_atoi(vtnr, &v); + if (k >= 0 && v >= 1) + s->vtnr = v; + } + + if (leader) { + pid_t pid; + + k = parse_pid(leader, &pid); + if (k >= 0 && pid >= 1) { + s->leader = pid; + + audit_session_from_pid(pid, &s->audit_id); + } + } + + if (type) { + SessionType t; + + t = session_type_from_string(type); + if (t >= 0) + s->type = t; + } + + if (class) { + SessionClass c; + + c = session_class_from_string(class); + if (c >= 0) + s->class = c; + } + + if (s->fifo_path) { + int fd; + + /* If we open an unopened pipe for reading we will not + get an EOF. to trigger an EOF we hence open it for + reading, but close it right-away which then will + trigger the EOF. */ + + fd = session_create_fifo(s); + if (fd >= 0) + close_nointr_nofail(fd); + } + + +finish: + free(remote); + free(kill_processes); + free(seat); + free(vtnr); + free(leader); + free(audit_id); + + return r; +} + +int session_activate(Session *s) { + int r; + + assert(s); + + if (s->vtnr < 0) + return -ENOTSUP; + + if (!s->seat) + return -ENOTSUP; + + if (s->seat->active == s) + return 0; + + assert(seat_is_vtconsole(s->seat)); + + r = chvt(s->vtnr); + if (r < 0) + return r; + + return seat_set_active(s->seat, s); +} + +static int session_link_x11_socket(Session *s) { + char *t, *f, *c; + size_t k; + + assert(s); + assert(s->user); + assert(s->user->runtime_path); + + if (s->user->display) + return 0; + + if (!s->display || !display_is_local(s->display)) + return 0; + + k = strspn(s->display+1, "0123456789"); + f = new(char, sizeof("/tmp/.X11-unix/X") + k); + if (!f) { + log_error("Out of memory"); + return -ENOMEM; + } + + c = stpcpy(f, "/tmp/.X11-unix/X"); + memcpy(c, s->display+1, k); + c[k] = 0; + + if (access(f, F_OK) < 0) { + log_warning("Session %s has display %s with nonexisting socket %s.", s->id, s->display, f); + free(f); + return -ENOENT; + } + + /* Note that this cannot be in a subdir to avoid + * vulnerabilities since we are privileged but the runtime + * path is owned by the user */ + + t = strappend(s->user->runtime_path, "/X11-display"); + if (!t) { + log_error("Out of memory"); + free(f); + return -ENOMEM; + } + + if (link(f, t) < 0) { + if (errno == EEXIST) { + unlink(t); + + if (link(f, t) >= 0) + goto done; + } + + if (symlink(f, t) < 0) { + + if (errno == EEXIST) { + unlink(t); + + if (symlink(f, t) >= 0) + goto done; + } + + log_error("Failed to link %s to %s: %m", f, t); + free(f); + free(t); + return -errno; + } + } + +done: + log_info("Linked %s to %s.", f, t); + free(f); + free(t); + + s->user->display = s; + + return 0; +} + +static int session_create_one_group(Session *s, const char *controller, const char *path) { + int r; + + assert(s); + assert(controller); + assert(path); + + if (s->leader > 0) { + r = cg_create_and_attach(controller, path, s->leader); + if (r < 0) + r = cg_create(controller, path); + } else + r = cg_create(controller, path); + + if (r < 0) + return r; + + r = cg_set_task_access(controller, path, 0644, s->user->uid, s->user->gid, -1); + if (r >= 0) + r = cg_set_group_access(controller, path, 0755, s->user->uid, s->user->gid); + + return r; +} + +static int session_create_cgroup(Session *s) { + char **k; + char *p; + int r; + + assert(s); + assert(s->user); + assert(s->user->cgroup_path); + + if (!s->cgroup_path) { + if (asprintf(&p, "%s/%s", s->user->cgroup_path, s->id) < 0) { + log_error("Out of memory"); + return -ENOMEM; + } + } else + p = s->cgroup_path; + + r = session_create_one_group(s, SYSTEMD_CGROUP_CONTROLLER, p); + if (r < 0) { + log_error("Failed to create "SYSTEMD_CGROUP_CONTROLLER":%s: %s", p, strerror(-r)); + free(p); + s->cgroup_path = NULL; + return r; + } + + s->cgroup_path = p; + + STRV_FOREACH(k, s->controllers) { + + if (strv_contains(s->reset_controllers, *k)) + continue; + + r = session_create_one_group(s, *k, p); + if (r < 0) + log_warning("Failed to create %s:%s: %s", *k, p, strerror(-r)); + } + + STRV_FOREACH(k, s->manager->controllers) { + + if (strv_contains(s->reset_controllers, *k) || + strv_contains(s->manager->reset_controllers, *k) || + strv_contains(s->controllers, *k)) + continue; + + r = session_create_one_group(s, *k, p); + if (r < 0) + log_warning("Failed to create %s:%s: %s", *k, p, strerror(-r)); + } + + if (s->leader > 0) { + + STRV_FOREACH(k, s->reset_controllers) { + r = cg_attach(*k, "/", s->leader); + if (r < 0) + log_warning("Failed to reset controller %s: %s", *k, strerror(-r)); + + } + + STRV_FOREACH(k, s->manager->reset_controllers) { + + if (strv_contains(s->reset_controllers, *k) || + strv_contains(s->controllers, *k)) + continue; + + r = cg_attach(*k, "/", s->leader); + if (r < 0) + log_warning("Failed to reset controller %s: %s", *k, strerror(-r)); + + } + } + + hashmap_put(s->manager->cgroups, s->cgroup_path, s); + + return 0; +} + +int session_start(Session *s) { + int r; + + assert(s); + assert(s->user); + + if (s->started) + return 0; + + r = user_start(s->user); + if (r < 0) + return r; + + log_full(s->type == SESSION_TTY || s->type == SESSION_X11 ? LOG_INFO : LOG_DEBUG, + "New session %s of user %s.", s->id, s->user->name); + + /* Create cgroup */ + r = session_create_cgroup(s); + if (r < 0) + return r; + + /* Create X11 symlink */ + session_link_x11_socket(s); + + dual_timestamp_get(&s->timestamp); + + if (s->seat) + seat_read_active_vt(s->seat); + + s->started = true; + + /* Save session data */ + session_save(s); + user_save(s->user); + + session_send_signal(s, true); + + if (s->seat) { + seat_save(s->seat); + + if (s->seat->active == s) + seat_send_changed(s->seat, "Sessions\0ActiveSession\0"); + else + seat_send_changed(s->seat, "Sessions\0"); + } + + user_send_changed(s->user, "Sessions\0"); + + return 0; +} + +static bool session_shall_kill(Session *s) { + assert(s); + + if (!s->kill_processes) + return false; + + if (strv_contains(s->manager->kill_exclude_users, s->user->name)) + return false; + + if (strv_isempty(s->manager->kill_only_users)) + return true; + + return strv_contains(s->manager->kill_only_users, s->user->name); +} + +static int session_terminate_cgroup(Session *s) { + int r; + char **k; + + assert(s); + + if (!s->cgroup_path) + return 0; + + cg_trim(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, false); + + if (session_shall_kill(s)) { + + r = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, true); + if (r < 0) + log_error("Failed to kill session cgroup: %s", strerror(-r)); + + } else { + if (s->leader > 0) { + Session *t; + + /* We still send a HUP to the leader process, + * even if we are not supposed to kill the + * whole cgroup. But let's first check the + * leader still exists and belongs to our + * session... */ + + r = manager_get_session_by_pid(s->manager, s->leader, &t); + if (r > 0 && t == s) { + kill(s->leader, SIGTERM); /* for normal processes */ + kill(s->leader, SIGHUP); /* for shells */ + kill(s->leader, SIGCONT); /* in case they are stopped */ + } + } + + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, true); + if (r < 0) + log_error("Failed to check session cgroup: %s", strerror(-r)); + else if (r > 0) { + r = cg_delete(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path); + if (r < 0) + log_error("Failed to delete session cgroup: %s", strerror(-r)); + } + } + + STRV_FOREACH(k, s->user->manager->controllers) + cg_trim(*k, s->cgroup_path, true); + + hashmap_remove(s->manager->cgroups, s->cgroup_path); + + free(s->cgroup_path); + s->cgroup_path = NULL; + + return 0; +} + +static int session_unlink_x11_socket(Session *s) { + char *t; + int r; + + assert(s); + assert(s->user); + + if (s->user->display != s) + return 0; + + s->user->display = NULL; + + t = strappend(s->user->runtime_path, "/X11-display"); + if (!t) { + log_error("Out of memory"); + return -ENOMEM; + } + + r = unlink(t); + free(t); + + return r < 0 ? -errno : 0; +} + +int session_stop(Session *s) { + int r = 0, k; + + assert(s); + + if (s->started) + log_full(s->type == SESSION_TTY || s->type == SESSION_X11 ? LOG_INFO : LOG_DEBUG, + "Removed session %s.", s->id); + + /* Kill cgroup */ + k = session_terminate_cgroup(s); + if (k < 0) + r = k; + + /* Remove X11 symlink */ + session_unlink_x11_socket(s); + + unlink(s->state_file); + session_add_to_gc_queue(s); + user_add_to_gc_queue(s->user); + + if (s->started) + session_send_signal(s, false); + + if (s->seat) { + if (s->seat->active == s) + seat_set_active(s->seat, NULL); + + seat_send_changed(s->seat, "Sessions\0"); + } + + user_send_changed(s->user, "Sessions\0"); + + s->started = false; + + return r; +} + +bool session_is_active(Session *s) { + assert(s); + + if (!s->seat) + return true; + + return s->seat->active == s; +} + +int session_get_idle_hint(Session *s, dual_timestamp *t) { + char *p; + struct stat st; + usec_t u, n; + bool b; + int k; + + assert(s); + + if (s->idle_hint) { + if (t) + *t = s->idle_hint_timestamp; + + return s->idle_hint; + } + + if (isempty(s->tty)) + goto dont_know; + + if (s->tty[0] != '/') { + p = strappend("/dev/", s->tty); + if (!p) + return -ENOMEM; + } else + p = NULL; + + if (!startswith(p ? p : s->tty, "/dev/")) { + free(p); + goto dont_know; + } + + k = lstat(p ? p : s->tty, &st); + free(p); + + if (k < 0) + goto dont_know; + + u = timespec_load(&st.st_atim); + n = now(CLOCK_REALTIME); + b = u + IDLE_THRESHOLD_USEC < n; + + if (t) + dual_timestamp_from_realtime(t, u + b ? IDLE_THRESHOLD_USEC : 0); + + return b; + +dont_know: + if (t) + *t = s->idle_hint_timestamp; + + return 0; +} + +void session_set_idle_hint(Session *s, bool b) { + assert(s); + + if (s->idle_hint == b) + return; + + s->idle_hint = b; + dual_timestamp_get(&s->idle_hint_timestamp); + + session_send_changed(s, + "IdleHint\0" + "IdleSinceHint\0" + "IdleSinceHintMonotonic\0"); + + if (s->seat) + seat_send_changed(s->seat, + "IdleHint\0" + "IdleSinceHint\0" + "IdleSinceHintMonotonic\0"); + + user_send_changed(s->user, + "IdleHint\0" + "IdleSinceHint\0" + "IdleSinceHintMonotonic\0"); + + manager_send_changed(s->manager, + "IdleHint\0" + "IdleSinceHint\0" + "IdleSinceHintMonotonic\0"); +} + +int session_create_fifo(Session *s) { + int r; + + assert(s); + + /* Create FIFO */ + if (!s->fifo_path) { + r = safe_mkdir("/run/systemd/sessions", 0755, 0, 0); + if (r < 0) + return r; + + if (asprintf(&s->fifo_path, "/run/systemd/sessions/%s.ref", s->id) < 0) + return -ENOMEM; + + if (mkfifo(s->fifo_path, 0600) < 0 && errno != EEXIST) + return -errno; + } + + /* Open reading side */ + if (s->fifo_fd < 0) { + struct epoll_event ev; + + s->fifo_fd = open(s->fifo_path, O_RDONLY|O_CLOEXEC|O_NDELAY); + if (s->fifo_fd < 0) + return -errno; + + r = hashmap_put(s->manager->fifo_fds, INT_TO_PTR(s->fifo_fd + 1), s); + if (r < 0) + return r; + + zero(ev); + ev.events = 0; + ev.data.u32 = FD_FIFO_BASE + s->fifo_fd; + + if (epoll_ctl(s->manager->epoll_fd, EPOLL_CTL_ADD, s->fifo_fd, &ev) < 0) + return -errno; + } + + /* Open writing side */ + r = open(s->fifo_path, O_WRONLY|O_CLOEXEC|O_NDELAY); + if (r < 0) + return -errno; + + return r; +} + +void session_remove_fifo(Session *s) { + assert(s); + + if (s->fifo_fd >= 0) { + assert_se(hashmap_remove(s->manager->fifo_fds, INT_TO_PTR(s->fifo_fd + 1)) == s); + assert_se(epoll_ctl(s->manager->epoll_fd, EPOLL_CTL_DEL, s->fifo_fd, NULL) == 0); + close_nointr_nofail(s->fifo_fd); + s->fifo_fd = -1; + } + + if (s->fifo_path) { + unlink(s->fifo_path); + free(s->fifo_path); + s->fifo_path = NULL; + } +} + +int session_check_gc(Session *s, bool drop_not_started) { + int r; + + assert(s); + + if (drop_not_started && !s->started) + return 0; + + if (s->fifo_fd >= 0) { + + r = pipe_eof(s->fifo_fd); + if (r < 0) + return r; + + if (r == 0) + return 1; + } + + if (s->cgroup_path) { + + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, false); + if (r < 0) + return r; + + if (r <= 0) + return 1; + } + + return 0; +} + +void session_add_to_gc_queue(Session *s) { + assert(s); + + if (s->in_gc_queue) + return; + + LIST_PREPEND(Session, gc_queue, s->manager->session_gc_queue, s); + s->in_gc_queue = true; +} + +int session_kill(Session *s, KillWho who, int signo) { + int r = 0; + Set *pid_set = NULL; + + assert(s); + + if (!s->cgroup_path) + return -ESRCH; + + if (s->leader <= 0 && who == KILL_LEADER) + return -ESRCH; + + if (s->leader > 0) + if (kill(s->leader, signo) < 0) + r = -errno; + + if (who == KILL_ALL) { + int q; + + pid_set = set_new(trivial_hash_func, trivial_compare_func); + if (!pid_set) + return -ENOMEM; + + if (s->leader > 0) { + q = set_put(pid_set, LONG_TO_PTR(s->leader)); + if (q < 0) + r = q; + } + + q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, signo, false, true, false, pid_set); + if (q < 0) + if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + } + + if (pid_set) + set_free(pid_set); + + return r; +} + +static const char* const session_type_table[_SESSION_TYPE_MAX] = { + [SESSION_TTY] = "tty", + [SESSION_X11] = "x11", + [SESSION_UNSPECIFIED] = "unspecified" +}; + +DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType); + +static const char* const session_class_table[_SESSION_CLASS_MAX] = { + [SESSION_USER] = "user", + [SESSION_GREETER] = "greeter", + [SESSION_LOCK_SCREEN] = "lock-screen" +}; + +DEFINE_STRING_TABLE_LOOKUP(session_class, SessionClass); + +static const char* const kill_who_table[_KILL_WHO_MAX] = { + [KILL_LEADER] = "leader", + [KILL_ALL] = "all" +}; + +DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/login/logind-session.h b/src/login/logind-session.h new file mode 100644 index 000000000..d0b8c87fa --- /dev/null +++ b/src/login/logind-session.h @@ -0,0 +1,136 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foologindsessionhfoo +#define foologindsessionhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Session Session; + +#include "list.h" +#include "util.h" +#include "logind.h" +#include "logind-seat.h" +#include "logind-user.h" + +typedef enum SessionType { + SESSION_UNSPECIFIED, + SESSION_TTY, + SESSION_X11, + _SESSION_TYPE_MAX, + _SESSION_TYPE_INVALID = -1 +} SessionType; + +typedef enum SessionClass { + SESSION_USER, + SESSION_GREETER, + SESSION_LOCK_SCREEN, + _SESSION_CLASS_MAX, + _SESSION_CLASS_INVALID = -1 +} SessionClass; + +typedef enum KillWho { + KILL_LEADER, + KILL_ALL, + _KILL_WHO_MAX, + _KILL_WHO_INVALID = -1 +} KillWho; + +struct Session { + Manager *manager; + + char *id; + SessionType type; + SessionClass class; + + char *state_file; + + User *user; + + dual_timestamp timestamp; + + char *tty; + char *display; + + bool remote; + char *remote_user; + char *remote_host; + + char *service; + + int vtnr; + Seat *seat; + + pid_t leader; + uint32_t audit_id; + + int fifo_fd; + char *fifo_path; + + char *cgroup_path; + char **controllers, **reset_controllers; + + bool idle_hint; + dual_timestamp idle_hint_timestamp; + + bool kill_processes; + bool in_gc_queue:1; + bool started:1; + + LIST_FIELDS(Session, sessions_by_user); + LIST_FIELDS(Session, sessions_by_seat); + + LIST_FIELDS(Session, gc_queue); +}; + +Session *session_new(Manager *m, User *u, const char *id); +void session_free(Session *s); +int session_check_gc(Session *s, bool drop_not_started); +void session_add_to_gc_queue(Session *s); +int session_activate(Session *s); +bool session_is_active(Session *s); +int session_get_idle_hint(Session *s, dual_timestamp *t); +void session_set_idle_hint(Session *s, bool b); +int session_create_fifo(Session *s); +void session_remove_fifo(Session *s); +int session_start(Session *s); +int session_stop(Session *s); +int session_save(Session *s); +int session_load(Session *s); +int session_kill(Session *s, KillWho who, int signo); + +char *session_bus_path(Session *s); + +extern const DBusObjectPathVTable bus_session_vtable; + +int session_send_signal(Session *s, bool new_session); +int session_send_changed(Session *s, const char *properties); +int session_send_lock(Session *s, bool lock); + +const char* session_type_to_string(SessionType t); +SessionType session_type_from_string(const char *s); + +const char* session_class_to_string(SessionClass t); +SessionClass session_class_from_string(const char *s); + +const char *kill_who_to_string(KillWho k); +KillWho kill_who_from_string(const char *s); + +#endif diff --git a/src/login/logind-user-dbus.c b/src/login/logind-user-dbus.c new file mode 100644 index 000000000..a9d680f89 --- /dev/null +++ b/src/login/logind-user-dbus.c @@ -0,0 +1,417 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "logind.h" +#include "logind-user.h" +#include "dbus-common.h" + +#define BUS_USER_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_USER_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.login1.User\0" + +static int bus_user_append_display(DBusMessageIter *i, const char *property, void *data) { + DBusMessageIter sub; + User *u = data; + const char *id, *path; + char *p = NULL; + + assert(i); + assert(property); + assert(u); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub)) + return -ENOMEM; + + if (u->display) { + id = u->display->id; + path = p = session_bus_path(u->display); + + if (!p) + return -ENOMEM; + } else { + id = ""; + path = "/"; + } + + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &id) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &path)) { + free(p); + return -ENOMEM; + } + + free(p); + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_user_append_state(DBusMessageIter *i, const char *property, void *data) { + User *u = data; + const char *state; + + assert(i); + assert(property); + assert(u); + + state = user_state_to_string(user_get_state(u)); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state)) + return -ENOMEM; + + return 0; +} + +static int bus_user_append_sessions(DBusMessageIter *i, const char *property, void *data) { + DBusMessageIter sub, sub2; + User *u = data; + Session *session; + + assert(i); + assert(property); + assert(u); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(so)", &sub)) + return -ENOMEM; + + LIST_FOREACH(sessions_by_user, session, u->sessions) { + char *p; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) + return -ENOMEM; + + p = session_bus_path(session); + if (!p) + return -ENOMEM; + + if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &session->id) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + return -ENOMEM; + } + + free(p); + + if (!dbus_message_iter_close_container(&sub, &sub2)) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_user_append_idle_hint(DBusMessageIter *i, const char *property, void *data) { + User *u = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(u); + + b = user_get_idle_hint(u, NULL) > 0; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_user_append_idle_hint_since(DBusMessageIter *i, const char *property, void *data) { + User *u = data; + dual_timestamp t; + uint64_t k; + + assert(i); + assert(property); + assert(u); + + user_get_idle_hint(u, &t); + k = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &k)) + return -ENOMEM; + + return 0; +} + +static int get_user_for_path(Manager *m, const char *path, User **_u) { + User *u; + unsigned long lu; + int r; + + assert(m); + assert(path); + assert(_u); + + if (!startswith(path, "/org/freedesktop/login1/user/")) + return -EINVAL; + + r = safe_atolu(path + 29, &lu); + if (r < 0) + return r; + + u = hashmap_get(m->users, ULONG_TO_PTR(lu)); + if (!u) + return -ENOENT; + + *_u = u; + return 0; +} + +static const BusProperty bus_login_user_properties[] = { + { "UID", bus_property_append_uid, "u", offsetof(User, uid) }, + { "GID", bus_property_append_gid, "u", offsetof(User, gid) }, + { "Name", bus_property_append_string, "s", offsetof(User, name), true }, + { "Timestamp", bus_property_append_usec, "t", offsetof(User, timestamp.realtime) }, + { "TimestampMonotonic", bus_property_append_usec, "t", offsetof(User, timestamp.monotonic) }, + { "RuntimePath", bus_property_append_string, "s", offsetof(User, runtime_path), true }, + { "ControlGroupPath", bus_property_append_string, "s", offsetof(User, cgroup_path), true }, + { "Service", bus_property_append_string, "s", offsetof(User, service), true }, + { "Display", bus_user_append_display, "(so)", 0 }, + { "State", bus_user_append_state, "s", 0 }, + { "Sessions", bus_user_append_sessions, "a(so)", 0 }, + { "IdleHint", bus_user_append_idle_hint, "b", 0 }, + { "IdleSinceHint", bus_user_append_idle_hint_since, "t", 0 }, + { "IdleSinceHintMonotonic", bus_user_append_idle_hint_since, "t", 0 }, + { NULL, } +}; + +static DBusHandlerResult user_message_dispatch( + User *u, + DBusConnection *connection, + DBusMessage *message) { + + DBusError error; + DBusMessage *reply = NULL; + int r; + + assert(u); + assert(connection); + assert(message); + + if (dbus_message_is_method_call(message, "org.freedesktop.login1.User", "Terminate")) { + + r = user_stop(u); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.User", "Kill")) { + int32_t signo; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_INT32, &signo, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (signo <= 0 || signo >= _NSIG) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + r = user_kill(u, signo); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else { + const BusBoundProperties bps[] = { + { "org.freedesktop.login1.User", bus_login_user_properties, u }, + { NULL, } + }; + + return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); + } + + if (reply) { + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static DBusHandlerResult user_message_handler( + DBusConnection *connection, + DBusMessage *message, + void *userdata) { + + Manager *m = userdata; + User *u; + int r; + + r = get_user_for_path(m, dbus_message_get_path(message), &u); + if (r < 0) { + + if (r == -ENOMEM) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (r == -ENOENT) { + DBusError e; + + dbus_error_init(&e); + dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown user"); + return bus_send_error_reply(connection, message, &e, r); + } + + return bus_send_error_reply(connection, message, NULL, r); + } + + return user_message_dispatch(u, connection, message); +} + +const DBusObjectPathVTable bus_user_vtable = { + .message_function = user_message_handler +}; + +char *user_bus_path(User *u) { + char *s; + + assert(u); + + if (asprintf(&s, "/org/freedesktop/login1/user/%llu", (unsigned long long) u->uid) < 0) + return NULL; + + return s; +} + +int user_send_signal(User *u, bool new_user) { + DBusMessage *m; + int r = -ENOMEM; + char *p = NULL; + uint32_t uid; + + assert(u); + + m = dbus_message_new_signal("/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + new_user ? "UserNew" : "UserRemoved"); + + if (!m) + return -ENOMEM; + + p = user_bus_path(u); + if (!p) + goto finish; + + uid = u->uid; + + if (!dbus_message_append_args( + m, + DBUS_TYPE_UINT32, &uid, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID)) + goto finish; + + if (!dbus_connection_send(u->manager->bus, m, NULL)) + goto finish; + + r = 0; + +finish: + dbus_message_unref(m); + free(p); + + return r; +} + +int user_send_changed(User *u, const char *properties) { + DBusMessage *m; + int r = -ENOMEM; + char *p = NULL; + + assert(u); + + if (!u->started) + return 0; + + p = user_bus_path(u); + if (!p) + return -ENOMEM; + + m = bus_properties_changed_new(p, "org.freedesktop.login1.User", properties); + if (!m) + goto finish; + + if (!dbus_connection_send(u->manager->bus, m, NULL)) + goto finish; + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + free(p); + + return r; +} diff --git a/src/login/logind-user.c b/src/login/logind-user.c new file mode 100644 index 000000000..717f0e20a --- /dev/null +++ b/src/login/logind-user.c @@ -0,0 +1,589 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "logind-user.h" +#include "util.h" +#include "cgroup-util.h" +#include "hashmap.h" +#include "strv.h" + +User* user_new(Manager *m, uid_t uid, gid_t gid, const char *name) { + User *u; + + assert(m); + assert(name); + + u = new0(User, 1); + if (!u) + return NULL; + + u->name = strdup(name); + if (!u->name) { + free(u); + return NULL; + } + + if (asprintf(&u->state_file, "/run/systemd/users/%lu", (unsigned long) uid) < 0) { + free(u->name); + free(u); + return NULL; + } + + if (hashmap_put(m->users, ULONG_TO_PTR((unsigned long) uid), u) < 0) { + free(u->state_file); + free(u->name); + free(u); + return NULL; + } + + u->manager = m; + u->uid = uid; + u->gid = gid; + + return u; +} + +void user_free(User *u) { + assert(u); + + if (u->in_gc_queue) + LIST_REMOVE(User, gc_queue, u->manager->user_gc_queue, u); + + while (u->sessions) + session_free(u->sessions); + + free(u->cgroup_path); + + free(u->service); + free(u->runtime_path); + + hashmap_remove(u->manager->users, ULONG_TO_PTR((unsigned long) u->uid)); + + free(u->name); + free(u->state_file); + free(u); +} + +int user_save(User *u) { + FILE *f; + int r; + char *temp_path; + + assert(u); + assert(u->state_file); + + if (!u->started) + return 0; + + r = safe_mkdir("/run/systemd/users", 0755, 0, 0); + if (r < 0) + goto finish; + + r = fopen_temporary(u->state_file, &f, &temp_path); + if (r < 0) + goto finish; + + fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n" + "NAME=%s\n" + "STATE=%s\n", + u->name, + user_state_to_string(user_get_state(u))); + + if (u->cgroup_path) + fprintf(f, + "CGROUP=%s\n", + u->cgroup_path); + + if (u->runtime_path) + fprintf(f, + "RUNTIME=%s\n", + u->runtime_path); + + if (u->service) + fprintf(f, + "SERVICE=%s\n", + u->service); + + if (u->display) + fprintf(f, + "DISPLAY=%s\n", + u->display->id); + + if (u->sessions) { + Session *i; + + fputs("SESSIONS=", f); + LIST_FOREACH(sessions_by_user, i, u->sessions) { + fprintf(f, + "%s%c", + i->id, + i->sessions_by_user_next ? ' ' : '\n'); + } + + fputs("SEATS=", f); + LIST_FOREACH(sessions_by_user, i, u->sessions) { + if (i->seat) + fprintf(f, + "%s%c", + i->seat->id, + i->sessions_by_user_next ? ' ' : '\n'); + } + + fputs("ACTIVE_SESSIONS=", f); + LIST_FOREACH(sessions_by_user, i, u->sessions) + if (session_is_active(i)) + fprintf(f, + "%lu%c", + (unsigned long) i->user->uid, + i->sessions_by_user_next ? ' ' : '\n'); + + fputs("ACTIVE_SEATS=", f); + LIST_FOREACH(sessions_by_user, i, u->sessions) { + if (session_is_active(i) && i->seat) + fprintf(f, + "%s%c", + i->seat->id, + i->sessions_by_user_next ? ' ' : '\n'); + } + } + + fflush(f); + + if (ferror(f) || rename(temp_path, u->state_file) < 0) { + r = -errno; + unlink(u->state_file); + unlink(temp_path); + } + + fclose(f); + free(temp_path); + +finish: + if (r < 0) + log_error("Failed to save user data for %s: %s", u->name, strerror(-r)); + + return r; +} + +int user_load(User *u) { + int r; + char *display = NULL; + Session *s = NULL; + + assert(u); + + r = parse_env_file(u->state_file, NEWLINE, + "CGROUP", &u->cgroup_path, + "RUNTIME", &u->runtime_path, + "SERVICE", &u->service, + "DISPLAY", &display, + NULL); + if (r < 0) { + free(display); + + if (r == -ENOENT) + return 0; + + log_error("Failed to read %s: %s", u->state_file, strerror(-r)); + return r; + } + + if (display) { + s = hashmap_get(u->manager->sessions, display); + free(display); + } + + if (s && s->display && display_is_local(s->display)) + u->display = s; + + return r; +} + +static int user_mkdir_runtime_path(User *u) { + char *p; + int r; + + assert(u); + + r = safe_mkdir("/run/user", 0755, 0, 0); + if (r < 0) { + log_error("Failed to create /run/user: %s", strerror(-r)); + return r; + } + + if (!u->runtime_path) { + p = strappend("/run/user/", u->name); + + if (!p) { + log_error("Out of memory"); + return -ENOMEM; + } + } else + p = u->runtime_path; + + r = safe_mkdir(p, 0700, u->uid, u->gid); + if (r < 0) { + log_error("Failed to create runtime directory %s: %s", p, strerror(-r)); + free(p); + u->runtime_path = NULL; + return r; + } + + u->runtime_path = p; + return 0; +} + +static int user_create_cgroup(User *u) { + char **k; + char *p; + int r; + + assert(u); + + if (!u->cgroup_path) { + if (asprintf(&p, "%s/%s", u->manager->cgroup_path, u->name) < 0) { + log_error("Out of memory"); + return -ENOMEM; + } + } else + p = u->cgroup_path; + + r = cg_create(SYSTEMD_CGROUP_CONTROLLER, p); + if (r < 0) { + log_error("Failed to create cgroup "SYSTEMD_CGROUP_CONTROLLER":%s: %s", p, strerror(-r)); + free(p); + u->cgroup_path = NULL; + return r; + } + + u->cgroup_path = p; + + STRV_FOREACH(k, u->manager->controllers) { + + if (strv_contains(u->manager->reset_controllers, *k)) + continue; + + r = cg_create(*k, p); + if (r < 0) + log_warning("Failed to create cgroup %s:%s: %s", *k, p, strerror(-r)); + } + + return 0; +} + +static int user_start_service(User *u) { + assert(u); + + /* FIXME: Fill me in later ... */ + + return 0; +} + +int user_start(User *u) { + int r; + + assert(u); + + if (u->started) + return 0; + + log_debug("New user %s logged in.", u->name); + + /* Make XDG_RUNTIME_DIR */ + r = user_mkdir_runtime_path(u); + if (r < 0) + return r; + + /* Create cgroup */ + r = user_create_cgroup(u); + if (r < 0) + return r; + + /* Spawn user systemd */ + r = user_start_service(u); + if (r < 0) + return r; + + dual_timestamp_get(&u->timestamp); + + u->started = true; + + /* Save new user data */ + user_save(u); + + user_send_signal(u, true); + + return 0; +} + +static int user_stop_service(User *u) { + assert(u); + + if (!u->service) + return 0; + + return 0; +} + +static int user_shall_kill(User *u) { + assert(u); + + if (!u->manager->kill_user_processes) + return false; + + if (strv_contains(u->manager->kill_exclude_users, u->name)) + return false; + + if (strv_isempty(u->manager->kill_only_users)) + return true; + + return strv_contains(u->manager->kill_only_users, u->name); +} + +static int user_terminate_cgroup(User *u) { + int r; + char **k; + + assert(u); + + if (!u->cgroup_path) + return 0; + + cg_trim(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, false); + + if (user_shall_kill(u)) { + + r = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, true); + if (r < 0) + log_error("Failed to kill user cgroup: %s", strerror(-r)); + } else { + + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, true); + if (r < 0) + log_error("Failed to check user cgroup: %s", strerror(-r)); + else if (r > 0) { + r = cg_delete(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path); + if (r < 0) + log_error("Failed to delete user cgroup: %s", strerror(-r)); + } else + r = -EBUSY; + } + + STRV_FOREACH(k, u->manager->controllers) + cg_trim(*k, u->cgroup_path, true); + + free(u->cgroup_path); + u->cgroup_path = NULL; + + return r; +} + +static int user_remove_runtime_path(User *u) { + int r; + + assert(u); + + if (!u->runtime_path) + return 0; + + r = rm_rf(u->runtime_path, false, true, false); + if (r < 0) + log_error("Failed to remove runtime directory %s: %s", u->runtime_path, strerror(-r)); + + free(u->runtime_path); + u->runtime_path = NULL; + + return r; +} + +int user_stop(User *u) { + Session *s; + int r = 0, k; + assert(u); + + if (u->started) + log_debug("User %s logged out.", u->name); + + LIST_FOREACH(sessions_by_user, s, u->sessions) { + k = session_stop(s); + if (k < 0) + r = k; + } + + /* Kill systemd */ + k = user_stop_service(u); + if (k < 0) + r = k; + + /* Kill cgroup */ + k = user_terminate_cgroup(u); + if (k < 0) + r = k; + + /* Kill XDG_RUNTIME_DIR */ + k = user_remove_runtime_path(u); + if (k < 0) + r = k; + + unlink(u->state_file); + user_add_to_gc_queue(u); + + if (u->started) + user_send_signal(u, false); + + u->started = false; + + return r; +} + +int user_get_idle_hint(User *u, dual_timestamp *t) { + Session *s; + bool idle_hint = true; + dual_timestamp ts = { 0, 0 }; + + assert(u); + + LIST_FOREACH(sessions_by_user, s, u->sessions) { + dual_timestamp k; + int ih; + + ih = session_get_idle_hint(s, &k); + if (ih < 0) + return ih; + + if (!ih) { + if (!idle_hint) { + if (k.monotonic < ts.monotonic) + ts = k; + } else { + idle_hint = false; + ts = k; + } + } else if (idle_hint) { + + if (k.monotonic > ts.monotonic) + ts = k; + } + } + + if (t) + *t = ts; + + return idle_hint; +} + +int user_check_gc(User *u, bool drop_not_started) { + int r; + char *p; + + assert(u); + + if (drop_not_started && !u->started) + return 0; + + if (u->sessions) + return 1; + + if (asprintf(&p, "/var/lib/systemd/linger/%s", u->name) < 0) + return -ENOMEM; + + r = access(p, F_OK) >= 0; + free(p); + + if (r > 0) + return 1; + + if (u->cgroup_path) { + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, false); + if (r < 0) + return r; + + if (r <= 0) + return 1; + } + + return 0; +} + +void user_add_to_gc_queue(User *u) { + assert(u); + + if (u->in_gc_queue) + return; + + LIST_PREPEND(User, gc_queue, u->manager->user_gc_queue, u); + u->in_gc_queue = true; +} + +UserState user_get_state(User *u) { + Session *i; + + assert(u); + + if (!u->sessions) + return USER_LINGERING; + + LIST_FOREACH(sessions_by_user, i, u->sessions) + if (session_is_active(i)) + return USER_ACTIVE; + + return USER_ONLINE; +} + +int user_kill(User *u, int signo) { + int r = 0, q; + Set *pid_set = NULL; + + assert(u); + + if (!u->cgroup_path) + return -ESRCH; + + pid_set = set_new(trivial_hash_func, trivial_compare_func); + if (!pid_set) + return -ENOMEM; + + q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, true, false, pid_set); + if (q < 0) + if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + + if (pid_set) + set_free(pid_set); + + return r; +} + +static const char* const user_state_table[_USER_STATE_MAX] = { + [USER_OFFLINE] = "offline", + [USER_LINGERING] = "lingering", + [USER_ONLINE] = "online", + [USER_ACTIVE] = "active" +}; + +DEFINE_STRING_TABLE_LOOKUP(user_state, UserState); diff --git a/src/login/logind-user.h b/src/login/logind-user.h new file mode 100644 index 000000000..db9a5f6a3 --- /dev/null +++ b/src/login/logind-user.h @@ -0,0 +1,86 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foologinduserhfoo +#define foologinduserhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct User User; + +#include "list.h" +#include "util.h" +#include "logind.h" +#include "logind-session.h" + +typedef enum UserState { + USER_OFFLINE, + USER_LINGERING, + USER_ONLINE, + USER_ACTIVE, + _USER_STATE_MAX, + _USER_STATE_INVALID = -1 +} UserState; + +struct User { + Manager *manager; + + uid_t uid; + gid_t gid; + char *name; + + char *state_file; + char *runtime_path; + char *service; + char *cgroup_path; + + Session *display; + + dual_timestamp timestamp; + + bool in_gc_queue:1; + bool started:1; + + LIST_HEAD(Session, sessions); + LIST_FIELDS(User, gc_queue); +}; + +User* user_new(Manager *m, uid_t uid, gid_t gid, const char *name); +void user_free(User *u); +int user_check_gc(User *u, bool drop_not_started); +void user_add_to_gc_queue(User *u); +int user_start(User *u); +int user_stop(User *u); +UserState user_get_state(User *u); +int user_get_idle_hint(User *u, dual_timestamp *t); +int user_save(User *u); +int user_load(User *u); +int user_kill(User *u, int signo); + +char *user_bus_path(User *s); + +extern const DBusObjectPathVTable bus_user_vtable; + +int user_send_signal(User *u, bool new_user); +int user_send_changed(User *u, const char *properties); + +const char* user_state_to_string(UserState s); +UserState user_state_from_string(const char *s); + +#endif diff --git a/src/login/logind.c b/src/login/logind.c new file mode 100644 index 000000000..a54195ceb --- /dev/null +++ b/src/login/logind.c @@ -0,0 +1,1288 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "logind.h" +#include "dbus-common.h" +#include "dbus-loop.h" +#include "strv.h" +#include "conf-parser.h" + +Manager *manager_new(void) { + Manager *m; + + m = new0(Manager, 1); + if (!m) + return NULL; + + m->console_active_fd = -1; + m->bus_fd = -1; + m->udev_seat_fd = -1; + m->udev_vcsa_fd = -1; + m->epoll_fd = -1; + m->n_autovts = 6; + + m->devices = hashmap_new(string_hash_func, string_compare_func); + m->seats = hashmap_new(string_hash_func, string_compare_func); + m->sessions = hashmap_new(string_hash_func, string_compare_func); + m->users = hashmap_new(trivial_hash_func, trivial_compare_func); + m->cgroups = hashmap_new(string_hash_func, string_compare_func); + m->fifo_fds = hashmap_new(trivial_hash_func, trivial_compare_func); + + if (!m->devices || !m->seats || !m->sessions || !m->users || !m->cgroups || !m->fifo_fds) { + manager_free(m); + return NULL; + } + + m->reset_controllers = strv_new("cpu", NULL); + m->kill_exclude_users = strv_new("root", NULL); + if (!m->reset_controllers || !m->kill_exclude_users) { + manager_free(m); + return NULL; + } + + m->udev = udev_new(); + if (!m->udev) { + manager_free(m); + return NULL; + } + + if (cg_get_user_path(&m->cgroup_path) < 0) { + manager_free(m); + return NULL; + } + + return m; +} + +void manager_free(Manager *m) { + Session *session; + User *u; + Device *d; + Seat *s; + + assert(m); + + while ((session = hashmap_first(m->sessions))) + session_free(session); + + while ((u = hashmap_first(m->users))) + user_free(u); + + while ((d = hashmap_first(m->devices))) + device_free(d); + + while ((s = hashmap_first(m->seats))) + seat_free(s); + + hashmap_free(m->sessions); + hashmap_free(m->users); + hashmap_free(m->devices); + hashmap_free(m->seats); + hashmap_free(m->cgroups); + hashmap_free(m->fifo_fds); + + if (m->console_active_fd >= 0) + close_nointr_nofail(m->console_active_fd); + + if (m->udev_seat_monitor) + udev_monitor_unref(m->udev_seat_monitor); + + if (m->udev_vcsa_monitor) + udev_monitor_unref(m->udev_vcsa_monitor); + + if (m->udev) + udev_unref(m->udev); + + if (m->bus) { + dbus_connection_flush(m->bus); + dbus_connection_close(m->bus); + dbus_connection_unref(m->bus); + } + + if (m->bus_fd >= 0) + close_nointr_nofail(m->bus_fd); + + if (m->epoll_fd >= 0) + close_nointr_nofail(m->epoll_fd); + + strv_free(m->controllers); + strv_free(m->reset_controllers); + strv_free(m->kill_only_users); + strv_free(m->kill_exclude_users); + + free(m->cgroup_path); + free(m); +} + +int manager_add_device(Manager *m, const char *sysfs, Device **_device) { + Device *d; + + assert(m); + assert(sysfs); + + d = hashmap_get(m->devices, sysfs); + if (d) { + if (_device) + *_device = d; + + return 0; + } + + d = device_new(m, sysfs); + if (!d) + return -ENOMEM; + + if (_device) + *_device = d; + + return 0; +} + +int manager_add_seat(Manager *m, const char *id, Seat **_seat) { + Seat *s; + + assert(m); + assert(id); + + s = hashmap_get(m->seats, id); + if (s) { + if (_seat) + *_seat = s; + + return 0; + } + + s = seat_new(m, id); + if (!s) + return -ENOMEM; + + if (_seat) + *_seat = s; + + return 0; +} + +int manager_add_session(Manager *m, User *u, const char *id, Session **_session) { + Session *s; + + assert(m); + assert(id); + + s = hashmap_get(m->sessions, id); + if (s) { + if (_session) + *_session = s; + + return 0; + } + + s = session_new(m, u, id); + if (!s) + return -ENOMEM; + + if (_session) + *_session = s; + + return 0; +} + +int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **_user) { + User *u; + + assert(m); + assert(name); + + u = hashmap_get(m->users, ULONG_TO_PTR((unsigned long) uid)); + if (u) { + if (_user) + *_user = u; + + return 0; + } + + u = user_new(m, uid, gid, name); + if (!u) + return -ENOMEM; + + if (_user) + *_user = u; + + return 0; +} + +int manager_add_user_by_name(Manager *m, const char *name, User **_user) { + uid_t uid; + gid_t gid; + int r; + + assert(m); + assert(name); + + r = get_user_creds(&name, &uid, &gid, NULL); + if (r < 0) + return r; + + return manager_add_user(m, uid, gid, name, _user); +} + +int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user) { + struct passwd *p; + + assert(m); + + errno = 0; + p = getpwuid(uid); + if (!p) + return errno ? -errno : -ENOENT; + + return manager_add_user(m, uid, p->pw_gid, p->pw_name, _user); +} + +int manager_process_seat_device(Manager *m, struct udev_device *d) { + Device *device; + int r; + + assert(m); + + if (streq_ptr(udev_device_get_action(d), "remove")) { + + device = hashmap_get(m->devices, udev_device_get_syspath(d)); + if (!device) + return 0; + + seat_add_to_gc_queue(device->seat); + device_free(device); + + } else { + const char *sn; + Seat *seat; + + sn = udev_device_get_property_value(d, "ID_SEAT"); + if (isempty(sn)) + sn = "seat0"; + + if (!seat_name_is_valid(sn)) { + log_warning("Device with invalid seat name %s found, ignoring.", sn); + return 0; + } + + r = manager_add_device(m, udev_device_get_syspath(d), &device); + if (r < 0) + return r; + + r = manager_add_seat(m, sn, &seat); + if (r < 0) { + if (!device->seat) + device_free(device); + + return r; + } + + device_attach(device, seat); + seat_start(seat); + } + + return 0; +} + +int manager_enumerate_devices(Manager *m) { + struct udev_list_entry *item = NULL, *first = NULL; + struct udev_enumerate *e; + int r; + + assert(m); + + /* Loads devices from udev and creates seats for them as + * necessary */ + + e = udev_enumerate_new(m->udev); + if (!e) { + r = -ENOMEM; + goto finish; + } + + r = udev_enumerate_add_match_subsystem(e, "graphics"); + if (r < 0) + goto finish; + + r = udev_enumerate_add_match_tag(e, "seat"); + if (r < 0) + goto finish; + + r = udev_enumerate_scan_devices(e); + if (r < 0) + goto finish; + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + struct udev_device *d; + int k; + + d = udev_device_new_from_syspath(m->udev, udev_list_entry_get_name(item)); + if (!d) { + r = -ENOMEM; + goto finish; + } + + k = manager_process_seat_device(m, d); + udev_device_unref(d); + + if (k < 0) + r = k; + } + +finish: + if (e) + udev_enumerate_unref(e); + + return r; +} + +int manager_enumerate_seats(Manager *m) { + DIR *d; + struct dirent *de; + int r = 0; + + assert(m); + + /* This loads data about seats stored on disk, but does not + * actually create any seats. Removes data of seats that no + * longer exist. */ + + d = opendir("/run/systemd/seats"); + if (!d) { + if (errno == ENOENT) + return 0; + + log_error("Failed to open /run/systemd/seats: %m"); + return -errno; + } + + while ((de = readdir(d))) { + Seat *s; + int k; + + if (!dirent_is_file(de)) + continue; + + s = hashmap_get(m->seats, de->d_name); + if (!s) { + unlinkat(dirfd(d), de->d_name, 0); + continue; + } + + k = seat_load(s); + if (k < 0) + r = k; + } + + closedir(d); + + return r; +} + +static int manager_enumerate_users_from_cgroup(Manager *m) { + int r = 0; + char *name; + DIR *d; + int k; + + r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_path, &d); + if (r < 0) { + if (r == -ENOENT) + return 0; + + log_error("Failed to open %s: %s", m->cgroup_path, strerror(-r)); + return r; + } + + while ((k = cg_read_subgroup(d, &name)) > 0) { + User *user; + + k = manager_add_user_by_name(m, name, &user); + if (k < 0) { + free(name); + r = k; + continue; + } + + user_add_to_gc_queue(user); + + if (!user->cgroup_path) + if (asprintf(&user->cgroup_path, "%s/%s", m->cgroup_path, name) < 0) { + r = -ENOMEM; + free(name); + break; + } + + free(name); + } + + if (r >= 0 && k < 0) + r = k; + + closedir(d); + + return r; +} + +static int manager_enumerate_linger_users(Manager *m) { + DIR *d; + struct dirent *de; + int r = 0; + + d = opendir("/var/lib/systemd/linger"); + if (!d) { + if (errno == ENOENT) + return 0; + + log_error("Failed to open /var/lib/systemd/linger/: %m"); + return -errno; + } + + while ((de = readdir(d))) { + int k; + + if (!dirent_is_file(de)) + continue; + + k = manager_add_user_by_name(m, de->d_name, NULL); + if (k < 0) { + log_notice("Couldn't add lingering user %s: %s", de->d_name, strerror(-k)); + r = k; + } + } + + closedir(d); + + return r; +} + +int manager_enumerate_users(Manager *m) { + DIR *d; + struct dirent *de; + int r, k; + + assert(m); + + /* First, enumerate user cgroups */ + r = manager_enumerate_users_from_cgroup(m); + + /* Second, add lingering users on top */ + k = manager_enumerate_linger_users(m); + if (k < 0) + r = k; + + /* Third, read in user data stored on disk */ + d = opendir("/run/systemd/users"); + if (!d) { + if (errno == ENOENT) + return 0; + + log_error("Failed to open /run/systemd/users: %m"); + return -errno; + } + + while ((de = readdir(d))) { + uid_t uid; + User *u; + + if (!dirent_is_file(de)) + continue; + + k = parse_uid(de->d_name, &uid); + if (k < 0) { + log_error("Failed to parse file name %s: %s", de->d_name, strerror(-k)); + continue; + } + + u = hashmap_get(m->users, ULONG_TO_PTR(uid)); + if (!u) { + unlinkat(dirfd(d), de->d_name, 0); + continue; + } + + k = user_load(u); + if (k < 0) + r = k; + } + + closedir(d); + + return r; +} + +static int manager_enumerate_sessions_from_cgroup(Manager *m) { + User *u; + Iterator i; + int r = 0; + + HASHMAP_FOREACH(u, m->users, i) { + DIR *d; + char *name; + int k; + + if (!u->cgroup_path) + continue; + + k = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, &d); + if (k < 0) { + if (k == -ENOENT) + continue; + + log_error("Failed to open %s: %s", u->cgroup_path, strerror(-k)); + r = k; + continue; + } + + while ((k = cg_read_subgroup(d, &name)) > 0) { + Session *session; + + if (streq(name, "shared")) + continue; + + k = manager_add_session(m, u, name, &session); + if (k < 0) { + free(name); + break; + } + + session_add_to_gc_queue(session); + + if (!session->cgroup_path) + if (asprintf(&session->cgroup_path, "%s/%s", u->cgroup_path, name) < 0) { + k = -ENOMEM; + free(name); + break; + } + + free(name); + } + + closedir(d); + + if (k < 0) + r = k; + } + + return r; +} + +int manager_enumerate_sessions(Manager *m) { + DIR *d; + struct dirent *de; + int r = 0; + + assert(m); + + /* First enumerate session cgroups */ + r = manager_enumerate_sessions_from_cgroup(m); + + /* Second, read in session data stored on disk */ + d = opendir("/run/systemd/sessions"); + if (!d) { + if (errno == ENOENT) + return 0; + + log_error("Failed to open /run/systemd/sessions: %m"); + return -errno; + } + + while ((de = readdir(d))) { + struct Session *s; + int k; + + if (!dirent_is_file(de)) + continue; + + s = hashmap_get(m->sessions, de->d_name); + if (!s) { + unlinkat(dirfd(d), de->d_name, 0); + continue; + } + + k = session_load(s); + if (k < 0) + r = k; + } + + closedir(d); + + return r; +} + +int manager_dispatch_seat_udev(Manager *m) { + struct udev_device *d; + int r; + + assert(m); + + d = udev_monitor_receive_device(m->udev_seat_monitor); + if (!d) + return -ENOMEM; + + r = manager_process_seat_device(m, d); + udev_device_unref(d); + + return r; +} + +int manager_dispatch_vcsa_udev(Manager *m) { + struct udev_device *d; + int r = 0; + const char *name; + + assert(m); + + d = udev_monitor_receive_device(m->udev_vcsa_monitor); + if (!d) + return -ENOMEM; + + name = udev_device_get_sysname(d); + + /* Whenever a VCSA device is removed try to reallocate our + * VTs, to make sure our auto VTs never go away. */ + + if (name && startswith(name, "vcsa") && streq_ptr(udev_device_get_action(d), "remove")) + r = seat_preallocate_vts(m->vtconsole); + + udev_device_unref(d); + + return r; +} + +int manager_dispatch_console(Manager *m) { + assert(m); + + if (m->vtconsole) + seat_read_active_vt(m->vtconsole); + + return 0; +} + +static int vt_is_busy(int vtnr) { + struct vt_stat vt_stat; + int r = 0, fd; + + assert(vtnr >= 1); + + /* We explicitly open /dev/tty1 here instead of /dev/tty0. If + * we'd open the latter we'd open the foreground tty which + * hence would be unconditionally busy. By opening /dev/tty1 + * we avoid this. Since tty1 is special and needs to be an + * explicitly loaded getty or DM this is safe. */ + + fd = open_terminal("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0) + r = -errno; + else + r = !!(vt_stat.v_state & (1 << vtnr)); + + close_nointr_nofail(fd); + + return r; +} + +int manager_spawn_autovt(Manager *m, int vtnr) { + int r; + DBusMessage *message = NULL, *reply = NULL; + char *name = NULL; + const char *mode = "fail"; + DBusError error; + + assert(m); + assert(vtnr >= 1); + + dbus_error_init(&error); + + if ((unsigned) vtnr > m->n_autovts) + return 0; + + r = vt_is_busy(vtnr); + if (r < 0) + return r; + else if (r > 0) + return -EBUSY; + + message = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartUnit"); + if (!message) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (asprintf(&name, "autovt@tty%i.service", vtnr) < 0) { + log_error("Could not allocate service name."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID)) { + log_error("Could not attach target and flag information to message."); + r = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(m->bus, message, -1, &error); + if (!reply) { + log_error("Failed to start unit: %s", bus_error_message(&error)); + goto finish; + } + + r = 0; + +finish: + free(name); + + if (message) + dbus_message_unref(message); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +int manager_get_session_by_cgroup(Manager *m, const char *cgroup, Session **session) { + Session *s; + char *p; + + assert(m); + assert(cgroup); + assert(session); + + s = hashmap_get(m->cgroups, cgroup); + if (s) { + *session = s; + return 1; + } + + p = strdup(cgroup); + if (!p) { + log_error("Out of memory."); + return -ENOMEM; + } + + for (;;) { + char *e; + + e = strrchr(p, '/'); + if (!e || e == p) { + free(p); + *session = NULL; + return 0; + } + + *e = 0; + + s = hashmap_get(m->cgroups, p); + if (s) { + free(p); + *session = s; + return 1; + } + } +} + +int manager_get_session_by_pid(Manager *m, pid_t pid, Session **session) { + char *p; + int r; + + assert(m); + assert(pid >= 1); + assert(session); + + r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, pid, &p); + if (r < 0) + return r; + + r = manager_get_session_by_cgroup(m, p, session); + free(p); + + return r; +} + +void manager_cgroup_notify_empty(Manager *m, const char *cgroup) { + Session *s; + int r; + + r = manager_get_session_by_cgroup(m, cgroup, &s); + if (r <= 0) + return; + + session_add_to_gc_queue(s); +} + +static void manager_pipe_notify_eof(Manager *m, int fd) { + Session *s; + + assert_se(m); + assert_se(fd >= 0); + + assert_se(s = hashmap_get(m->fifo_fds, INT_TO_PTR(fd + 1))); + assert(s->fifo_fd == fd); + session_remove_fifo(s); + + session_stop(s); +} + +static int manager_connect_bus(Manager *m) { + DBusError error; + int r; + struct epoll_event ev; + + assert(m); + assert(!m->bus); + assert(m->bus_fd < 0); + + dbus_error_init(&error); + + m->bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + if (!m->bus) { + log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error)); + r = -ECONNREFUSED; + goto fail; + } + + if (!dbus_connection_register_object_path(m->bus, "/org/freedesktop/login1", &bus_manager_vtable, m) || + !dbus_connection_register_fallback(m->bus, "/org/freedesktop/login1/seat", &bus_seat_vtable, m) || + !dbus_connection_register_fallback(m->bus, "/org/freedesktop/login1/session", &bus_session_vtable, m) || + !dbus_connection_register_fallback(m->bus, "/org/freedesktop/login1/user", &bus_user_vtable, m) || + !dbus_connection_add_filter(m->bus, bus_message_filter, m, NULL)) { + log_error("Not enough memory"); + r = -ENOMEM; + goto fail; + } + + dbus_bus_add_match(m->bus, + "type='signal'," + "interface='org.freedesktop.systemd1.Agent'," + "member='Released'," + "path='/org/freedesktop/systemd1/agent'", + &error); + + if (dbus_error_is_set(&error)) { + log_error("Failed to register match: %s", bus_error_message(&error)); + r = -EIO; + goto fail; + } + + r = dbus_bus_request_name(m->bus, "org.freedesktop.login1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to register name on bus: %s", bus_error_message(&error)); + r = -EIO; + goto fail; + } + + if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + log_error("Failed to acquire name."); + r = -EEXIST; + goto fail; + } + + m->bus_fd = bus_loop_open(m->bus); + if (m->bus_fd < 0) { + r = m->bus_fd; + goto fail; + } + + zero(ev); + ev.events = EPOLLIN; + ev.data.u32 = FD_BUS; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->bus_fd, &ev) < 0) + goto fail; + + return 0; + +fail: + dbus_error_free(&error); + + return r; +} + +static int manager_connect_console(Manager *m) { + struct epoll_event ev; + + assert(m); + assert(m->console_active_fd < 0); + + m->console_active_fd = open("/sys/class/tty/tty0/active", O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (m->console_active_fd < 0) { + + /* On certain architectures (S390 and Xen), /dev/tty0 + does not exist, so don't fail if we can't open it.*/ + if (errno == ENOENT) + return 0; + + log_error("Failed to open /sys/class/tty/tty0/active: %m"); + return -errno; + } + + zero(ev); + ev.events = 0; + ev.data.u32 = FD_CONSOLE; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->console_active_fd, &ev) < 0) + return -errno; + + return 0; +} + +static int manager_connect_udev(Manager *m) { + struct epoll_event ev; + int r; + + assert(m); + assert(!m->udev_seat_monitor); + assert(!m->udev_vcsa_monitor); + + m->udev_seat_monitor = udev_monitor_new_from_netlink(m->udev, "udev"); + if (!m->udev_seat_monitor) + return -ENOMEM; + + r = udev_monitor_filter_add_match_tag(m->udev_seat_monitor, "seat"); + if (r < 0) + return r; + + r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_seat_monitor, "graphics", NULL); + if (r < 0) + return r; + + r = udev_monitor_enable_receiving(m->udev_seat_monitor); + if (r < 0) + return r; + + m->udev_seat_fd = udev_monitor_get_fd(m->udev_seat_monitor); + + zero(ev); + ev.events = EPOLLIN; + ev.data.u32 = FD_SEAT_UDEV; + + /* Don't bother watching VCSA devices, if nobody cares */ + if (m->n_autovts <= 0 || m->console_active_fd < 0) + return 0; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->udev_seat_fd, &ev) < 0) + return -errno; + + m->udev_vcsa_monitor = udev_monitor_new_from_netlink(m->udev, "udev"); + if (!m->udev_vcsa_monitor) + return -ENOMEM; + + r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_vcsa_monitor, "vc", NULL); + if (r < 0) + return r; + + r = udev_monitor_enable_receiving(m->udev_vcsa_monitor); + if (r < 0) + return r; + + m->udev_vcsa_fd = udev_monitor_get_fd(m->udev_vcsa_monitor); + + zero(ev); + ev.events = EPOLLIN; + ev.data.u32 = FD_VCSA_UDEV; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->udev_vcsa_fd, &ev) < 0) + return -errno; + + return 0; +} + +void manager_gc(Manager *m, bool drop_not_started) { + Seat *seat; + Session *session; + User *user; + + assert(m); + + while ((seat = m->seat_gc_queue)) { + LIST_REMOVE(Seat, gc_queue, m->seat_gc_queue, seat); + seat->in_gc_queue = false; + + if (seat_check_gc(seat, drop_not_started) == 0) { + seat_stop(seat); + seat_free(seat); + } + } + + while ((session = m->session_gc_queue)) { + LIST_REMOVE(Session, gc_queue, m->session_gc_queue, session); + session->in_gc_queue = false; + + if (session_check_gc(session, drop_not_started) == 0) { + session_stop(session); + session_free(session); + } + } + + while ((user = m->user_gc_queue)) { + LIST_REMOVE(User, gc_queue, m->user_gc_queue, user); + user->in_gc_queue = false; + + if (user_check_gc(user, drop_not_started) == 0) { + user_stop(user); + user_free(user); + } + } +} + +int manager_get_idle_hint(Manager *m, dual_timestamp *t) { + Session *s; + bool idle_hint = true; + dual_timestamp ts = { 0, 0 }; + Iterator i; + + assert(m); + + HASHMAP_FOREACH(s, m->sessions, i) { + dual_timestamp k; + int ih; + + ih = session_get_idle_hint(s, &k); + if (ih < 0) + return ih; + + if (!ih) { + if (!idle_hint) { + if (k.monotonic < ts.monotonic) + ts = k; + } else { + idle_hint = false; + ts = k; + } + } else if (idle_hint) { + + if (k.monotonic > ts.monotonic) + ts = k; + } + } + + if (t) + *t = ts; + + return idle_hint; +} + +int manager_startup(Manager *m) { + int r; + Seat *seat; + Session *session; + User *user; + Iterator i; + + assert(m); + assert(m->epoll_fd <= 0); + + m->epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (m->epoll_fd < 0) + return -errno; + + /* Connect to console */ + r = manager_connect_console(m); + if (r < 0) + return r; + + /* Connect to udev */ + r = manager_connect_udev(m); + if (r < 0) + return r; + + /* Connect to the bus */ + r = manager_connect_bus(m); + if (r < 0) + return r; + + /* Instantiate magic seat 0 */ + r = manager_add_seat(m, "seat0", &m->vtconsole); + if (r < 0) + return r; + + /* Deserialize state */ + manager_enumerate_devices(m); + manager_enumerate_seats(m); + manager_enumerate_users(m); + manager_enumerate_sessions(m); + + /* Remove stale objects before we start them */ + manager_gc(m, false); + + /* And start everything */ + HASHMAP_FOREACH(seat, m->seats, i) + seat_start(seat); + + HASHMAP_FOREACH(user, m->users, i) + user_start(user); + + HASHMAP_FOREACH(session, m->sessions, i) + session_start(session); + + return 0; +} + +int manager_run(Manager *m) { + assert(m); + + for (;;) { + struct epoll_event event; + int n; + + manager_gc(m, true); + + if (dbus_connection_dispatch(m->bus) != DBUS_DISPATCH_COMPLETE) + continue; + + manager_gc(m, true); + + n = epoll_wait(m->epoll_fd, &event, 1, -1); + if (n < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + log_error("epoll() failed: %m"); + return -errno; + } + + switch (event.data.u32) { + + case FD_SEAT_UDEV: + manager_dispatch_seat_udev(m); + break; + + case FD_VCSA_UDEV: + manager_dispatch_vcsa_udev(m); + break; + + case FD_CONSOLE: + manager_dispatch_console(m); + break; + + case FD_BUS: + bus_loop_dispatch(m->bus_fd); + break; + + default: + if (event.data.u32 >= FD_FIFO_BASE) + manager_pipe_notify_eof(m, event.data.u32 - FD_FIFO_BASE); + } + } + + return 0; +} + +static int manager_parse_config_file(Manager *m) { + FILE *f; + const char *fn; + int r; + + assert(m); + + fn = "/etc/systemd/logind.conf"; + f = fopen(fn, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + log_warning("Failed to open configuration file %s: %m", fn); + return -errno; + } + + r = config_parse(fn, f, "Login\0", config_item_perf_lookup, (void*) logind_gperf_lookup, false, m); + if (r < 0) + log_warning("Failed to parse configuration file: %s", strerror(-r)); + + fclose(f); + + return r; +} + +int main(int argc, char *argv[]) { + Manager *m = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_set_facility(LOG_AUTH); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + m = manager_new(); + if (!m) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + manager_parse_config_file(m); + + r = manager_startup(m); + if (r < 0) { + log_error("Failed to fully start up daemon: %s", strerror(-r)); + goto finish; + } + + log_debug("systemd-logind running as pid %lu", (unsigned long) getpid()); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + r = manager_run(m); + + log_debug("systemd-logind stopped as pid %lu", (unsigned long) getpid()); + +finish: + sd_notify(false, + "STATUS=Shutting down..."); + + if (m) + manager_free(m); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/login/logind.conf b/src/login/logind.conf new file mode 100644 index 000000000..24b9d77a6 --- /dev/null +++ b/src/login/logind.conf @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# See logind.conf(5) for details + +[Login] +#NAutoVTs=6 +#KillUserProcesses=no +#KillOnlyUsers= +#KillExcludeUsers=root +#Controllers= +#ResetControllers=cpu diff --git a/src/login/logind.h b/src/login/logind.h new file mode 100644 index 000000000..a4b460267 --- /dev/null +++ b/src/login/logind.h @@ -0,0 +1,131 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foologindhfoo +#define foologindhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "util.h" +#include "list.h" +#include "hashmap.h" +#include "cgroup-util.h" + +typedef struct Manager Manager; + +#include "logind-device.h" +#include "logind-seat.h" +#include "logind-session.h" +#include "logind-user.h" + +struct Manager { + DBusConnection *bus; + + Hashmap *devices; + Hashmap *seats; + Hashmap *sessions; + Hashmap *users; + + LIST_HEAD(Seat, seat_gc_queue); + LIST_HEAD(Session, session_gc_queue); + LIST_HEAD(User, user_gc_queue); + + struct udev *udev; + struct udev_monitor *udev_seat_monitor, *udev_vcsa_monitor; + + int udev_seat_fd; + int udev_vcsa_fd; + + int console_active_fd; + int bus_fd; + int epoll_fd; + + unsigned n_autovts; + + Seat *vtconsole; + + char *cgroup_path; + char **controllers, **reset_controllers; + + char **kill_only_users, **kill_exclude_users; + + bool kill_user_processes; + + unsigned long session_counter; + + Hashmap *cgroups; + Hashmap *fifo_fds; +}; + +enum { + FD_SEAT_UDEV, + FD_VCSA_UDEV, + FD_CONSOLE, + FD_BUS, + FD_FIFO_BASE +}; + +Manager *manager_new(void); +void manager_free(Manager *m); + +int manager_add_device(Manager *m, const char *sysfs, Device **_device); +int manager_add_seat(Manager *m, const char *id, Seat **_seat); +int manager_add_session(Manager *m, User *u, const char *id, Session **_session); +int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **_user); +int manager_add_user_by_name(Manager *m, const char *name, User **_user); +int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user); + +int manager_process_seat_device(Manager *m, struct udev_device *d); +int manager_dispatch_seat_udev(Manager *m); +int manager_dispatch_vcsa_udev(Manager *m); +int manager_dispatch_console(Manager *m); + +int manager_enumerate_devices(Manager *m); +int manager_enumerate_seats(Manager *m); +int manager_enumerate_sessions(Manager *m); +int manager_enumerate_users(Manager *m); + +int manager_startup(Manager *m); +int manager_run(Manager *m); +int manager_spawn_autovt(Manager *m, int vtnr); + +void manager_cgroup_notify_empty(Manager *m, const char *cgroup); + +void manager_gc(Manager *m, bool drop_not_started); + +int manager_get_idle_hint(Manager *m, dual_timestamp *t); + +int manager_get_session_by_cgroup(Manager *m, const char *cgroup, Session **session); +int manager_get_session_by_pid(Manager *m, pid_t pid, Session **session); + +extern const DBusObjectPathVTable bus_manager_vtable; + +DBusHandlerResult bus_message_filter(DBusConnection *c, DBusMessage *message, void *userdata); + +int manager_send_changed(Manager *manager, const char *properties); + +/* gperf lookup function */ +const struct ConfigPerfItem* logind_gperf_lookup(const char *key, unsigned length); + +#endif diff --git a/src/login/multi-seat-x.c b/src/login/multi-seat-x.c new file mode 100644 index 000000000..7133e026d --- /dev/null +++ b/src/login/multi-seat-x.c @@ -0,0 +1,195 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include + +#include "util.h" + +int main(int argc, char *argv[]) { + + struct udev *udev = NULL; + struct udev_enumerate *enumerator = NULL; + struct udev_list_entry *first, *item; + int i; + const char *seat = NULL; + char **new_argv; + char *path = NULL, *device_node = NULL; + int r; + FILE *f = NULL; + + /* This binary will go away as soon as X natively supports + * display enumeration with udev in a way that covers both PCI + * and USB. */ + + /* This will simply determine the fb device id of the graphics + * device assigned to a seat and write a configuration file + * from it and then spawn the real X server. */ + + /* If this file is removed, don't forget to remove the code + * that invokes this in gdm and other display managers. */ + + for (i = 1; i < argc; i++) + if (streq(argv[i], "-seat")) + seat = argv[i+1]; + + if (isempty(seat) || streq(seat, "seat0")) { + argv[0] = (char*) X_SERVER; + execv(X_SERVER, argv); + log_error("Failed to execute real X server: %m"); + goto fail; + } + + udev = udev_new(); + if (!udev) { + log_error("Failed to allocate udev environment."); + goto fail; + } + + enumerator = udev_enumerate_new(udev); + if (!enumerator) { + log_error("Failed to allocate udev enumerator."); + goto fail; + } + + udev_enumerate_add_match_subsystem(enumerator, "graphics"); + udev_enumerate_add_match_tag(enumerator, seat); + + r = udev_enumerate_scan_devices(enumerator); + if (r < 0) { + log_error("Failed to enumerate devices."); + goto fail; + } + + first = udev_enumerate_get_list_entry(enumerator); + udev_list_entry_foreach(item, first) { + struct udev_device *d; + const char *dn; + + d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!d) + continue; + + dn = udev_device_get_devnode(d); + + if (dn) { + device_node = strdup(dn); + if (!device_node) { + udev_device_unref(d); + log_error("Out of memory."); + goto fail; + } + } + + udev_device_unref(d); + + if (device_node) + break; + } + + if (!device_node) { + log_error("Failed to find device node for seat %s.", seat); + goto fail; + } + + r = safe_mkdir("/run/systemd/multi-session-x", 0755, 0, 0); + if (r < 0) { + log_error("Failed to create directory: %s", strerror(-r)); + goto fail; + } + + path = strappend("/run/systemd/multi-session-x/", seat); + if (!path) { + log_error("Out of memory"); + goto fail; + } + + f = fopen(path, "we"); + if (!f) { + log_error("Failed to write configuration file: %m"); + goto fail; + } + + fprintf(f, + "Section \"Device\"\n" + " Identifier \"udev\"\n" + " Driver \"fbdev\"\n" + " Option \"fbdev\" \"%s\"\n" + "EndSection\n" + "Section \"ServerFlags\"\n" + " Option \"AutoAddDevices\" \"True\"\n" + " Option \"AllowEmptyInput\" \"True\"\n" + " Option \"DontVTSwitch\" \"True\"\n" + "EndSection\n" + "Section \"InputClass\"\n" + " Identifier \"Force Input Devices to Seat\"\n" + " Option \"GrabDevice\" \"True\"\n" + "EndSection\n", + device_node); + + fflush(f); + + if (ferror(f)) { + log_error("Failed to write configuration file: %m"); + goto fail; + } + + fclose(f); + f = NULL; + + new_argv = alloca(sizeof(char*) * (argc + 3 + 1)); + memcpy(new_argv, argv, sizeof(char*) * (argc + 2 + 1)); + + new_argv[0] = (char*) X_SERVER; + new_argv[argc+0] = (char*) "-config"; + new_argv[argc+1] = path; + new_argv[argc+2] = (char*) "-sharevts"; + new_argv[argc+3] = NULL; + + udev_enumerate_unref(enumerator); + enumerator = NULL; + + udev_unref(udev); + udev = NULL; + + free(device_node); + device_node = NULL; + + execv(X_SERVER, new_argv); + log_error("Failed to execute real X server: %m"); + +fail: + if (enumerator) + udev_enumerate_unref(enumerator); + + if (udev) + udev_unref(udev); + + free(path); + free(device_node); + + if (f) + fclose(f); + + return EXIT_FAILURE; +} diff --git a/src/login/org.freedesktop.login1.conf b/src/login/org.freedesktop.login1.conf new file mode 100644 index 000000000..9ef852bb7 --- /dev/null +++ b/src/login/org.freedesktop.login1.conf @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/login/org.freedesktop.login1.policy.in b/src/login/org.freedesktop.login1.policy.in new file mode 100644 index 000000000..adc904886 --- /dev/null +++ b/src/login/org.freedesktop.login1.policy.in @@ -0,0 +1,89 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Allow non-logged-in users to run programs + <_message>Authentication is required to allow a non-logged-in user to run programs + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Allow attaching devices to seats + <_message>Authentication is required to allow attaching a device to a seat + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Flush device to seat attachments + <_message>Authentication is required to allow resetting how devices are attached to seats + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Power off the system + <_message>Authentication is required to allow powering off the system + + auth_admin_keep + auth_admin_keep + yes + + + + + <_description>Power off the system when other users are logged in + <_message>Authentication is required to allow powering off the system while other users are logged in + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Reboot the system + <_message>Authentication is required to allow rebooting the system + + auth_admin_keep + auth_admin_keep + yes + + + + + <_description>Reboot the system when other users are logged in + <_message>Authentication is required to allow rebooting the system while other users are logged in + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + diff --git a/src/login/org.freedesktop.login1.service b/src/login/org.freedesktop.login1.service new file mode 100644 index 000000000..4a64177e3 --- /dev/null +++ b/src/login/org.freedesktop.login1.service @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[D-BUS Service] +Name=org.freedesktop.login1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.login1.service diff --git a/src/login/pam-module.c b/src/login/pam-module.c new file mode 100644 index 000000000..4106d2ba4 --- /dev/null +++ b/src/login/pam-module.c @@ -0,0 +1,695 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "util.h" +#include "macro.h" +#include "strv.h" +#include "dbus-common.h" +#include "def.h" +#include "socket-util.h" + +static int parse_argv(pam_handle_t *handle, + int argc, const char **argv, + char ***controllers, + char ***reset_controllers, + bool *kill_processes, + char ***kill_only_users, + char ***kill_exclude_users, + bool *debug) { + + unsigned i; + + assert(argc >= 0); + assert(argc == 0 || argv); + + for (i = 0; i < (unsigned) argc; i++) { + int k; + + if (startswith(argv[i], "kill-session-processes=")) { + if ((k = parse_boolean(argv[i] + 23)) < 0) { + pam_syslog(handle, LOG_ERR, "Failed to parse kill-session-processes= argument."); + return k; + } + + if (kill_processes) + *kill_processes = k; + + } else if (startswith(argv[i], "kill-session=")) { + /* As compatibility for old versions */ + + if ((k = parse_boolean(argv[i] + 13)) < 0) { + pam_syslog(handle, LOG_ERR, "Failed to parse kill-session= argument."); + return k; + } + + if (kill_processes) + *kill_processes = k; + + } else if (startswith(argv[i], "controllers=")) { + + if (controllers) { + char **l; + + if (!(l = strv_split(argv[i] + 12, ","))) { + pam_syslog(handle, LOG_ERR, "Out of memory."); + return -ENOMEM; + } + + strv_free(*controllers); + *controllers = l; + } + + } else if (startswith(argv[i], "reset-controllers=")) { + + if (reset_controllers) { + char **l; + + if (!(l = strv_split(argv[i] + 18, ","))) { + pam_syslog(handle, LOG_ERR, "Out of memory."); + return -ENOMEM; + } + + strv_free(*reset_controllers); + *reset_controllers = l; + } + + } else if (startswith(argv[i], "kill-only-users=")) { + + if (kill_only_users) { + char **l; + + if (!(l = strv_split(argv[i] + 16, ","))) { + pam_syslog(handle, LOG_ERR, "Out of memory."); + return -ENOMEM; + } + + strv_free(*kill_only_users); + *kill_only_users = l; + } + + } else if (startswith(argv[i], "kill-exclude-users=")) { + + if (kill_exclude_users) { + char **l; + + if (!(l = strv_split(argv[i] + 19, ","))) { + pam_syslog(handle, LOG_ERR, "Out of memory."); + return -ENOMEM; + } + + strv_free(*kill_exclude_users); + *kill_exclude_users = l; + } + + } else if (startswith(argv[i], "debug=")) { + if ((k = parse_boolean(argv[i] + 6)) < 0) { + pam_syslog(handle, LOG_ERR, "Failed to parse debug= argument."); + return k; + } + + if (debug) + *debug = k; + + } else if (startswith(argv[i], "create-session=") || + startswith(argv[i], "kill-user=")) { + + pam_syslog(handle, LOG_WARNING, "Option %s not supported anymore, ignoring.", argv[i]); + + } else { + pam_syslog(handle, LOG_ERR, "Unknown parameter '%s'.", argv[i]); + return -EINVAL; + } + } + + return 0; +} + +static int get_user_data( + pam_handle_t *handle, + const char **ret_username, + struct passwd **ret_pw) { + + const char *username = NULL; + struct passwd *pw = NULL; + uid_t uid; + int r; + + assert(handle); + assert(ret_username); + assert(ret_pw); + + r = audit_loginuid_from_pid(0, &uid); + if (r >= 0) + pw = pam_modutil_getpwuid(handle, uid); + else { + r = pam_get_user(handle, &username, NULL); + if (r != PAM_SUCCESS) { + pam_syslog(handle, LOG_ERR, "Failed to get user name."); + return r; + } + + if (isempty(username)) { + pam_syslog(handle, LOG_ERR, "User name not valid."); + return PAM_AUTH_ERR; + } + + pw = pam_modutil_getpwnam(handle, username); + } + + if (!pw) { + pam_syslog(handle, LOG_ERR, "Failed to get user data."); + return PAM_USER_UNKNOWN; + } + + *ret_pw = pw; + *ret_username = username ? username : pw->pw_name; + + return PAM_SUCCESS; +} + +static bool check_user_lists( + pam_handle_t *handle, + uid_t uid, + char **kill_only_users, + char **kill_exclude_users) { + + const char *name = NULL; + char **l; + + assert(handle); + + if (uid == 0) + name = "root"; /* Avoid obvious NSS requests, to suppress network traffic */ + else { + struct passwd *pw; + + pw = pam_modutil_getpwuid(handle, uid); + if (pw) + name = pw->pw_name; + } + + STRV_FOREACH(l, kill_exclude_users) { + uid_t u; + + if (parse_uid(*l, &u) >= 0) + if (u == uid) + return false; + + if (name && streq(name, *l)) + return false; + } + + if (strv_isempty(kill_only_users)) + return true; + + STRV_FOREACH(l, kill_only_users) { + uid_t u; + + if (parse_uid(*l, &u) >= 0) + if (u == uid) + return true; + + if (name && streq(name, *l)) + return true; + } + + return false; +} + +static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) { + char *p = NULL; + int r; + int fd; + union sockaddr_union sa; + struct ucred ucred; + socklen_t l; + char *tty; + int v; + + assert(display); + assert(vtnr); + + /* We deduce the X11 socket from the display name, then use + * SO_PEERCRED to determine the X11 server process, ask for + * the controlling tty of that and if it's a VC then we know + * the seat and the virtual terminal. Sounds ugly, is only + * semi-ugly. */ + + r = socket_from_display(display, &p); + if (r < 0) + return r; + + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (fd < 0) { + free(p); + return -errno; + } + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1); + free(p); + + if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + l = sizeof(ucred); + r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l); + close_nointr_nofail(fd); + + if (r < 0) + return -errno; + + r = get_ctty(ucred.pid, NULL, &tty); + if (r < 0) + return r; + + v = vtnr_from_tty(tty); + free(tty); + + if (v < 0) + return v; + else if (v == 0) + return -ENOENT; + + if (seat) + *seat = "seat0"; + *vtnr = (uint32_t) v; + + return 0; +} + +_public_ PAM_EXTERN int pam_sm_open_session( + pam_handle_t *handle, + int flags, + int argc, const char **argv) { + + struct passwd *pw; + bool kill_processes = false, debug = false; + const char *username, *id, *object_path, *runtime_path, *service = NULL, *tty = NULL, *display = NULL, *remote_user = NULL, *remote_host = NULL, *seat = NULL, *type, *class, *cvtnr = NULL; + char **controllers = NULL, **reset_controllers = NULL, **kill_only_users = NULL, **kill_exclude_users = NULL; + DBusError error; + uint32_t uid, pid; + DBusMessageIter iter; + dbus_bool_t kp; + int session_fd = -1; + DBusConnection *bus = NULL; + DBusMessage *m = NULL, *reply = NULL; + dbus_bool_t remote; + int r; + uint32_t vtnr = 0; + + assert(handle); + + dbus_error_init(&error); + + /* pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); */ + + /* Make this a NOP on non-systemd systems */ + if (sd_booted() <= 0) + return PAM_SUCCESS; + + if (parse_argv(handle, + argc, argv, + &controllers, &reset_controllers, + &kill_processes, &kill_only_users, &kill_exclude_users, + &debug) < 0) { + r = PAM_SESSION_ERR; + goto finish; + } + + r = get_user_data(handle, &username, &pw); + if (r != PAM_SUCCESS) + goto finish; + + /* Make sure we don't enter a loop by talking to + * systemd-logind when it is actually waiting for the + * background to finish start-up. If the service is + * "systemd-shared" we simply set XDG_RUNTIME_DIR and + * leave. */ + + pam_get_item(handle, PAM_SERVICE, (const void**) &service); + if (streq_ptr(service, "systemd-shared")) { + char *p, *rt = NULL; + + if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) { + r = PAM_BUF_ERR; + goto finish; + } + + r = parse_env_file(p, NEWLINE, + "RUNTIME", &rt, + NULL); + free(p); + + if (r < 0 && r != -ENOENT) { + r = PAM_SESSION_ERR; + free(rt); + goto finish; + } + + if (rt) { + r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0); + free(rt); + + if (r != PAM_SUCCESS) { + pam_syslog(handle, LOG_ERR, "Failed to set runtime dir."); + goto finish; + } + } + + r = PAM_SUCCESS; + goto finish; + } + + if (kill_processes) + kill_processes = check_user_lists(handle, pw->pw_uid, kill_only_users, kill_exclude_users); + + dbus_connection_set_change_sigpipe(FALSE); + + bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + if (!bus) { + pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error)); + r = PAM_SESSION_ERR; + goto finish; + } + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "CreateSession"); + if (!m) { + pam_syslog(handle, LOG_ERR, "Could not allocate create session message."); + r = PAM_BUF_ERR; + goto finish; + } + + uid = pw->pw_uid; + pid = getpid(); + + pam_get_item(handle, PAM_XDISPLAY, (const void**) &display); + pam_get_item(handle, PAM_TTY, (const void**) &tty); + pam_get_item(handle, PAM_RUSER, (const void**) &remote_user); + pam_get_item(handle, PAM_RHOST, (const void**) &remote_host); + seat = pam_getenv(handle, "XDG_SEAT"); + cvtnr = pam_getenv(handle, "XDG_VTNR"); + + service = strempty(service); + tty = strempty(tty); + display = strempty(display); + remote_user = strempty(remote_user); + remote_host = strempty(remote_host); + seat = strempty(seat); + + if (strchr(tty, ':')) { + /* A tty with a colon is usually an X11 display, place + * there to show up in utmp. We rearrange things and + * don't pretend that an X display was a tty */ + + if (isempty(display)) + display = tty; + tty = ""; + } else if (streq(tty, "cron")) { + /* cron has been setting PAM_TTY to "cron" for a very long time + * and it cannot stop doing that for compatibility reasons. */ + tty = ""; + } + + if (!isempty(cvtnr)) + safe_atou32(cvtnr, &vtnr); + + if (!isempty(display) && vtnr <= 0) { + if (isempty(seat)) + get_seat_from_display(display, &seat, &vtnr); + else if (streq(seat, "seat0")) + get_seat_from_display(display, NULL, &vtnr); + } + + type = !isempty(display) ? "x11" : + !isempty(tty) ? "tty" : "unspecified"; + + class = pam_getenv(handle, "XDG_SESSION_CLASS"); + if (isempty(class)) + class = "user"; + + remote = !isempty(remote_host) && + !streq(remote_host, "localhost") && + !streq(remote_host, "localhost.localdomain"); + + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &uid, + DBUS_TYPE_UINT32, &pid, + DBUS_TYPE_STRING, &service, + DBUS_TYPE_STRING, &type, + DBUS_TYPE_STRING, &class, + DBUS_TYPE_STRING, &seat, + DBUS_TYPE_UINT32, &vtnr, + DBUS_TYPE_STRING, &tty, + DBUS_TYPE_STRING, &display, + DBUS_TYPE_BOOLEAN, &remote, + DBUS_TYPE_STRING, &remote_user, + DBUS_TYPE_STRING, &remote_host, + DBUS_TYPE_INVALID)) { + pam_syslog(handle, LOG_ERR, "Could not attach parameters to message."); + r = PAM_BUF_ERR; + goto finish; + } + + dbus_message_iter_init_append(m, &iter); + + r = bus_append_strv_iter(&iter, controllers); + if (r < 0) { + pam_syslog(handle, LOG_ERR, "Could not attach parameter to message."); + r = PAM_BUF_ERR; + goto finish; + } + + r = bus_append_strv_iter(&iter, reset_controllers); + if (r < 0) { + pam_syslog(handle, LOG_ERR, "Could not attach parameter to message."); + r = PAM_BUF_ERR; + goto finish; + } + + kp = kill_processes; + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &kp)) { + pam_syslog(handle, LOG_ERR, "Could not attach parameter to message."); + r = PAM_BUF_ERR; + goto finish; + } + + if (debug) + pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: " + "uid=%u pid=%u service=%s type=%s seat=%s vtnr=%u tty=%s display=%s remote=%s remote_user=%s remote_host=%s", + uid, pid, service, type, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host); + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error)); + r = PAM_SESSION_ERR; + goto finish; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_STRING, &id, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_STRING, &runtime_path, + DBUS_TYPE_UNIX_FD, &session_fd, + DBUS_TYPE_STRING, &seat, + DBUS_TYPE_UINT32, &vtnr, + DBUS_TYPE_INVALID)) { + pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error)); + r = PAM_SESSION_ERR; + goto finish; + } + + if (debug) + pam_syslog(handle, LOG_DEBUG, "Reply from logind: " + "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u", + id, object_path, runtime_path, session_fd, seat, vtnr); + + r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0); + if (r != PAM_SUCCESS) { + pam_syslog(handle, LOG_ERR, "Failed to set session id."); + goto finish; + } + + r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0); + if (r != PAM_SUCCESS) { + pam_syslog(handle, LOG_ERR, "Failed to set runtime dir."); + goto finish; + } + + if (!isempty(seat)) { + r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0); + if (r != PAM_SUCCESS) { + pam_syslog(handle, LOG_ERR, "Failed to set seat."); + goto finish; + } + } + + if (vtnr > 0) { + char buf[11]; + snprintf(buf, sizeof(buf), "%u", vtnr); + char_array_0(buf); + + r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0); + if (r != PAM_SUCCESS) { + pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number."); + goto finish; + } + } + + if (session_fd >= 0) { + r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL); + if (r != PAM_SUCCESS) { + pam_syslog(handle, LOG_ERR, "Failed to install session fd."); + return r; + } + } + + session_fd = -1; + + r = PAM_SUCCESS; + +finish: + strv_free(controllers); + strv_free(reset_controllers); + strv_free(kill_only_users); + strv_free(kill_exclude_users); + + dbus_error_free(&error); + + if (bus) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + if (session_fd >= 0) + close_nointr_nofail(session_fd); + + return r; +} + +_public_ PAM_EXTERN int pam_sm_close_session( + pam_handle_t *handle, + int flags, + int argc, const char **argv) { + + const void *p = NULL; + const char *id; + DBusConnection *bus = NULL; + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + + assert(handle); + + dbus_error_init(&error); + + id = pam_getenv(handle, "XDG_SESSION_ID"); + if (id) { + + /* Before we go and close the FIFO we need to tell + * logind that this is a clean session shutdown, so + * that it doesn't just go and slaughter us + * immediately after closing the fd */ + + bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + if (!bus) { + pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error)); + r = PAM_SESSION_ERR; + goto finish; + } + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "ReleaseSession"); + if (!m) { + pam_syslog(handle, LOG_ERR, "Could not allocate release session message."); + r = PAM_BUF_ERR; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &id, + DBUS_TYPE_INVALID)) { + pam_syslog(handle, LOG_ERR, "Could not attach parameters to message."); + r = PAM_BUF_ERR; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error)); + r = PAM_SESSION_ERR; + goto finish; + } + } + + r = PAM_SUCCESS; + +finish: + pam_get_data(handle, "systemd.session-fd", &p); + if (p) + close_nointr(PTR_TO_INT(p) - 1); + + dbus_error_free(&error); + + if (bus) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return r; +} diff --git a/src/login/sd-login.c b/src/login/sd-login.c new file mode 100644 index 000000000..887c42100 --- /dev/null +++ b/src/login/sd-login.c @@ -0,0 +1,835 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "util.h" +#include "cgroup-util.h" +#include "macro.h" +#include "sd-login.h" +#include "strv.h" + +static int pid_get_cgroup(pid_t pid, char **root, char **cgroup) { + char *cg_process, *cg_init, *p; + int r; + + if (pid == 0) + pid = getpid(); + + if (pid <= 0) + return -EINVAL; + + r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, pid, &cg_process); + if (r < 0) + return r; + + r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 1, &cg_init); + if (r < 0) { + free(cg_process); + return r; + } + + if (endswith(cg_init, "/system")) + cg_init[strlen(cg_init)-7] = 0; + else if (streq(cg_init, "/")) + cg_init[0] = 0; + + if (startswith(cg_process, cg_init)) + p = cg_process + strlen(cg_init); + else + p = cg_process; + + free(cg_init); + + if (cgroup) { + char* c; + + c = strdup(p); + if (!c) { + free(cg_process); + return -ENOMEM; + } + + *cgroup = c; + } + + if (root) { + cg_process[p-cg_process] = 0; + *root = cg_process; + } else + free(cg_process); + + return 0; +} + +_public_ int sd_pid_get_session(pid_t pid, char **session) { + int r; + char *cgroup, *p; + + if (!session) + return -EINVAL; + + r = pid_get_cgroup(pid, NULL, &cgroup); + if (r < 0) + return r; + + if (!startswith(cgroup, "/user/")) { + free(cgroup); + return -ENOENT; + } + + p = strchr(cgroup + 6, '/'); + if (!p) { + free(cgroup); + return -ENOENT; + } + + p++; + if (startswith(p, "shared/") || streq(p, "shared")) { + free(cgroup); + return -ENOENT; + } + + p = strndup(p, strcspn(p, "/")); + free(cgroup); + + if (!p) + return -ENOMEM; + + *session = p; + return 0; +} + +_public_ int sd_pid_get_unit(pid_t pid, char **unit) { + int r; + char *cgroup, *p, *at, *b; + size_t k; + + if (!unit) + return -EINVAL; + + r = pid_get_cgroup(pid, NULL, &cgroup); + if (r < 0) + return r; + + if (!startswith(cgroup, "/system/")) { + free(cgroup); + return -ENOENT; + } + + p = cgroup + 8; + k = strcspn(p, "/"); + + at = memchr(p, '@', k); + if (at && at[1] == '.') { + size_t j; + + /* This is a templated service */ + if (p[k] != '/') { + free(cgroup); + return -EIO; + } + + j = strcspn(p+k+1, "/"); + + b = malloc(k + j + 1); + + if (b) { + memcpy(b, p, at - p + 1); + memcpy(b + (at - p) + 1, p + k + 1, j); + memcpy(b + (at - p) + 1 + j, at + 1, k - (at - p) - 1); + b[k+j] = 0; + } + } else + b = strndup(p, k); + + free(cgroup); + + if (!b) + return -ENOMEM; + + *unit = b; + return 0; +} + +_public_ int sd_pid_get_owner_uid(pid_t pid, uid_t *uid) { + int r; + char *root, *cgroup, *p, *cc; + struct stat st; + + if (!uid) + return -EINVAL; + + r = pid_get_cgroup(pid, &root, &cgroup); + if (r < 0) + return r; + + if (!startswith(cgroup, "/user/")) { + free(cgroup); + free(root); + return -ENOENT; + } + + p = strchr(cgroup + 6, '/'); + if (!p) { + free(cgroup); + return -ENOENT; + } + + p++; + p += strcspn(p, "/"); + *p = 0; + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, root, cgroup, &cc); + free(root); + free(cgroup); + + if (r < 0) + return -ENOMEM; + + r = lstat(cc, &st); + free(cc); + + if (r < 0) + return -errno; + + if (!S_ISDIR(st.st_mode)) + return -ENOTDIR; + + *uid = st.st_uid; + return 0; +} + +_public_ int sd_uid_get_state(uid_t uid, char**state) { + char *p, *s = NULL; + int r; + + if (!state) + return -EINVAL; + + if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) uid) < 0) + return -ENOMEM; + + r = parse_env_file(p, NEWLINE, "STATE", &s, NULL); + free(p); + + if (r == -ENOENT) { + free(s); + s = strdup("offline"); + if (!s) + return -ENOMEM; + + *state = s; + return 0; + } else if (r < 0) { + free(s); + return r; + } else if (!s) + return -EIO; + + *state = s; + return 0; +} + +_public_ int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat) { + char *p, *w, *t, *state, *s = NULL; + size_t l; + int r; + const char *variable; + + if (!seat) + return -EINVAL; + + variable = require_active ? "ACTIVE_UID" : "UIDS"; + + p = strappend("/run/systemd/seats/", seat); + if (!p) + return -ENOMEM; + + r = parse_env_file(p, NEWLINE, variable, &s, NULL); + free(p); + + if (r < 0) { + free(s); + return r; + } + + if (!s) + return -EIO; + + if (asprintf(&t, "%lu", (unsigned long) uid) < 0) { + free(s); + return -ENOMEM; + } + + FOREACH_WORD(w, l, s, state) { + if (strncmp(t, w, l) == 0) { + free(s); + free(t); + + return 1; + } + } + + free(s); + free(t); + + return 0; +} + +static int uid_get_array(uid_t uid, const char *variable, char ***array) { + char *p, *s = NULL; + char **a; + int r; + + if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) uid) < 0) + return -ENOMEM; + + r = parse_env_file(p, NEWLINE, + variable, &s, + NULL); + free(p); + + if (r < 0) { + free(s); + + if (r == -ENOENT) { + if (array) + *array = NULL; + return 0; + } + + return r; + } + + if (!s) { + if (array) + *array = NULL; + return 0; + } + + a = strv_split(s, " "); + free(s); + + if (!a) + return -ENOMEM; + + strv_uniq(a); + r = strv_length(a); + + if (array) + *array = a; + else + strv_free(a); + + return r; +} + +_public_ int sd_uid_get_sessions(uid_t uid, int require_active, char ***sessions) { + return uid_get_array(uid, require_active ? "ACTIVE_SESSIONS" : "SESSIONS", sessions); +} + +_public_ int sd_uid_get_seats(uid_t uid, int require_active, char ***seats) { + return uid_get_array(uid, require_active ? "ACTIVE_SEATS" : "SEATS", seats); +} + +static int file_of_session(const char *session, char **_p) { + char *p; + int r; + + assert(_p); + + if (session) + p = strappend("/run/systemd/sessions/", session); + else { + char *buf; + + r = sd_pid_get_session(0, &buf); + if (r < 0) + return r; + + p = strappend("/run/systemd/sessions/", buf); + free(buf); + } + + if (!p) + return -ENOMEM; + + *_p = p; + return 0; +} + +_public_ int sd_session_is_active(const char *session) { + int r; + char *p, *s = NULL; + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "ACTIVE", &s, NULL); + free(p); + + if (r < 0) { + free(s); + return r; + } + + if (!s) + return -EIO; + + r = parse_boolean(s); + free(s); + + return r; +} + +_public_ int sd_session_get_uid(const char *session, uid_t *uid) { + int r; + char *p, *s = NULL; + + if (!uid) + return -EINVAL; + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "UID", &s, NULL); + free(p); + + if (r < 0) { + free(s); + return r; + } + + if (!s) + return -EIO; + + r = parse_uid(s, uid); + free(s); + + return r; +} + +static int session_get_string(const char *session, const char *field, char **value) { + char *p, *s = NULL; + int r; + + if (!value) + return -EINVAL; + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, field, &s, NULL); + free(p); + + if (r < 0) { + free(s); + return r; + } + + if (isempty(s)) + return -ENOENT; + + *value = s; + return 0; +} + +_public_ int sd_session_get_seat(const char *session, char **seat) { + return session_get_string(session, "SEAT", seat); +} + +_public_ int sd_session_get_service(const char *session, char **service) { + return session_get_string(session, "SERVICE", service); +} + +_public_ int sd_session_get_type(const char *session, char **type) { + return session_get_string(session, "TYPE", type); +} + +_public_ int sd_session_get_class(const char *session, char **class) { + return session_get_string(session, "CLASS", class); +} + +_public_ int sd_session_get_display(const char *session, char **display) { + return session_get_string(session, "DISPLAY", display); +} + +static int file_of_seat(const char *seat, char **_p) { + char *p; + int r; + + assert(_p); + + if (seat) + p = strappend("/run/systemd/seats/", seat); + else { + char *buf; + + r = sd_session_get_seat(NULL, &buf); + if (r < 0) + return r; + + p = strappend("/run/systemd/seats/", buf); + free(buf); + } + + if (!p) + return -ENOMEM; + + *_p = p; + return 0; +} + +_public_ int sd_seat_get_active(const char *seat, char **session, uid_t *uid) { + char *p, *s = NULL, *t = NULL; + int r; + + if (!session && !uid) + return -EINVAL; + + r = file_of_seat(seat, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, + "ACTIVE", &s, + "ACTIVE_UID", &t, + NULL); + free(p); + + if (r < 0) { + free(s); + free(t); + return r; + } + + if (session && !s) { + free(t); + return -ENOENT; + } + + if (uid && !t) { + free(s); + return -ENOENT; + } + + if (uid && t) { + r = parse_uid(t, uid); + if (r < 0) { + free(t); + free(s); + return r; + } + } + + free(t); + + if (session && s) + *session = s; + else + free(s); + + return 0; +} + +_public_ int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **uids, unsigned *n_uids) { + char *p, *s = NULL, *t = NULL, **a = NULL; + uid_t *b = NULL; + unsigned n = 0; + int r; + + r = file_of_seat(seat, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, + "SESSIONS", &s, + "ACTIVE_SESSIONS", &t, + NULL); + free(p); + + if (r < 0) { + free(s); + free(t); + return r; + } + + if (s) { + a = strv_split(s, " "); + if (!a) { + free(s); + free(t); + return -ENOMEM; + } + } + + free(s); + + if (uids && t) { + char *w, *state; + size_t l; + + FOREACH_WORD(w, l, t, state) + n++; + + if (n == 0) + b = NULL; + else { + unsigned i = 0; + + b = new(uid_t, n); + if (!b) { + strv_free(a); + return -ENOMEM; + } + + FOREACH_WORD(w, l, t, state) { + char *k; + + k = strndup(w, l); + if (!k) { + free(t); + free(b); + strv_free(a); + return -ENOMEM; + } + + r = parse_uid(k, b + i); + free(k); + if (r < 0) + continue; + + i++; + } + } + } + + free(t); + + r = strv_length(a); + + if (sessions) + *sessions = a; + else + strv_free(a); + + if (uids) + *uids = b; + + if (n_uids) + *n_uids = n; + + return r; +} + +_public_ int sd_seat_can_multi_session(const char *seat) { + char *p, *s = NULL; + int r; + + r = file_of_seat(seat, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, + "CAN_MULTI_SESSION", &s, + NULL); + free(p); + + if (r < 0) { + free(s); + return r; + } + + if (s) { + r = parse_boolean(s); + free(s); + } else + r = 0; + + return r; +} + +_public_ int sd_get_seats(char ***seats) { + return get_files_in_directory("/run/systemd/seats/", seats); +} + +_public_ int sd_get_sessions(char ***sessions) { + return get_files_in_directory("/run/systemd/sessions/", sessions); +} + +_public_ int sd_get_uids(uid_t **users) { + DIR *d; + int r = 0; + unsigned n = 0; + uid_t *l = NULL; + + d = opendir("/run/systemd/users/"); + if (!d) + return -errno; + + for (;;) { + struct dirent buffer, *de; + int k; + uid_t uid; + + k = readdir_r(d, &buffer, &de); + if (k != 0) { + r = -k; + goto finish; + } + + if (!de) + break; + + dirent_ensure_type(d, de); + + if (!dirent_is_file(de)) + continue; + + k = parse_uid(de->d_name, &uid); + if (k < 0) + continue; + + if (users) { + if ((unsigned) r >= n) { + uid_t *t; + + n = MAX(16, 2*r); + t = realloc(l, sizeof(uid_t) * n); + if (!t) { + r = -ENOMEM; + goto finish; + } + + l = t; + } + + assert((unsigned) r < n); + l[r++] = uid; + } else + r++; + } + +finish: + if (d) + closedir(d); + + if (r >= 0) { + if (users) + *users = l; + } else + free(l); + + return r; +} + +static inline int MONITOR_TO_FD(sd_login_monitor *m) { + return (int) (unsigned long) m - 1; +} + +static inline sd_login_monitor* FD_TO_MONITOR(int fd) { + return (sd_login_monitor*) (unsigned long) (fd + 1); +} + +_public_ int sd_login_monitor_new(const char *category, sd_login_monitor **m) { + int fd, k; + bool good = false; + + if (!m) + return -EINVAL; + + fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (fd < 0) + return errno; + + if (!category || streq(category, "seat")) { + k = inotify_add_watch(fd, "/run/systemd/seats/", IN_MOVED_TO|IN_DELETE); + if (k < 0) { + close_nointr_nofail(fd); + return -errno; + } + + good = true; + } + + if (!category || streq(category, "session")) { + k = inotify_add_watch(fd, "/run/systemd/sessions/", IN_MOVED_TO|IN_DELETE); + if (k < 0) { + close_nointr_nofail(fd); + return -errno; + } + + good = true; + } + + if (!category || streq(category, "uid")) { + k = inotify_add_watch(fd, "/run/systemd/users/", IN_MOVED_TO|IN_DELETE); + if (k < 0) { + close_nointr_nofail(fd); + return -errno; + } + + good = true; + } + + if (!good) { + close_nointr(fd); + return -EINVAL; + } + + *m = FD_TO_MONITOR(fd); + return 0; +} + +_public_ sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m) { + int fd; + + if (!m) + return NULL; + + fd = MONITOR_TO_FD(m); + close_nointr(fd); + + return NULL; +} + +_public_ int sd_login_monitor_flush(sd_login_monitor *m) { + + if (!m) + return -EINVAL; + + return flush_fd(MONITOR_TO_FD(m)); +} + +_public_ int sd_login_monitor_get_fd(sd_login_monitor *m) { + + if (!m) + return -EINVAL; + + return MONITOR_TO_FD(m); +} diff --git a/src/login/sysfs-show.c b/src/login/sysfs-show.c new file mode 100644 index 000000000..b8b356d77 --- /dev/null +++ b/src/login/sysfs-show.c @@ -0,0 +1,187 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "util.h" +#include "sysfs-show.h" + +static int show_sysfs_one( + struct udev *udev, + const char *seat, + struct udev_list_entry **item, + const char *sub, + const char *prefix, + unsigned n_columns) { + + assert(udev); + assert(seat); + assert(item); + assert(prefix); + + while (*item) { + struct udev_list_entry *next, *lookahead; + struct udev_device *d; + const char *sn, *name, *sysfs, *subsystem, *sysname; + char *l, *k; + + sysfs = udev_list_entry_get_name(*item); + if (!path_startswith(sysfs, sub)) + return 0; + + d = udev_device_new_from_syspath(udev, sysfs); + if (!d) { + *item = udev_list_entry_get_next(*item); + continue; + } + + sn = udev_device_get_property_value(d, "ID_SEAT"); + if (isempty(sn)) + sn = "seat0"; + + /* fixme, also check for tag 'seat' here */ + if (!streq(seat, sn) || !udev_device_has_tag(d, "seat")) { + udev_device_unref(d); + *item = udev_list_entry_get_next(*item); + continue; + } + + name = udev_device_get_sysattr_value(d, "name"); + if (!name) + name = udev_device_get_sysattr_value(d, "id"); + subsystem = udev_device_get_subsystem(d); + sysname = udev_device_get_sysname(d); + + /* Look if there's more coming after this */ + lookahead = next = udev_list_entry_get_next(*item); + while (lookahead) { + const char *lookahead_sysfs; + + lookahead_sysfs = udev_list_entry_get_name(lookahead); + + if (path_startswith(lookahead_sysfs, sub) && + !path_startswith(lookahead_sysfs, sysfs)) { + struct udev_device *lookahead_d; + + lookahead_d = udev_device_new_from_syspath(udev, lookahead_sysfs); + if (lookahead_d) { + const char *lookahead_sn; + bool found; + + lookahead_sn = udev_device_get_property_value(d, "ID_SEAT"); + if (isempty(lookahead_sn)) + lookahead_sn = "seat0"; + + found = streq(seat, lookahead_sn) && udev_device_has_tag(lookahead_d, "seat"); + udev_device_unref(lookahead_d); + + if (found) + break; + } + } + + lookahead = udev_list_entry_get_next(lookahead); + } + + k = ellipsize(sysfs, n_columns, 20); + printf("%s%s %s\n", prefix, lookahead ? "\342\224\234" : "\342\224\224", k ? k : sysfs); + free(k); + + if (asprintf(&l, + "(%s:%s)%s%s%s", + subsystem, sysname, + name ? " \"" : "", name ? name : "", name ? "\"" : "") < 0) { + udev_device_unref(d); + return -ENOMEM; + } + + k = ellipsize(l, n_columns, 70); + printf("%s%s %s\n", prefix, lookahead ? "\342\224\202" : " ", k ? k : l); + free(k); + free(l); + + *item = next; + if (*item) { + char *p; + + p = strappend(prefix, lookahead ? "\342\224\202 " : " "); + show_sysfs_one(udev, seat, item, sysfs, p ? p : prefix, n_columns - 2); + free(p); + } + + udev_device_unref(d); + } + + return 0; +} + +int show_sysfs(const char *seat, const char *prefix, unsigned n_columns) { + struct udev *udev; + struct udev_list_entry *first = NULL; + struct udev_enumerate *e; + int r; + + if (n_columns <= 0) + n_columns = columns(); + + if (!prefix) + prefix = ""; + + if (isempty(seat)) + seat = "seat0"; + + udev = udev_new(); + if (!udev) + return -ENOMEM; + + e = udev_enumerate_new(udev); + if (!e) { + r = -ENOMEM; + goto finish; + } + + if (!streq(seat, "seat0")) + r = udev_enumerate_add_match_tag(e, seat); + else + r = udev_enumerate_add_match_tag(e, "seat"); + + if (r < 0) + goto finish; + + r = udev_enumerate_scan_devices(e); + if (r < 0) + goto finish; + + first = udev_enumerate_get_list_entry(e); + if (first) + show_sysfs_one(udev, seat, &first, "/", prefix, n_columns); + +finish: + if (e) + udev_enumerate_unref(e); + + if (udev) + udev_unref(udev); + + return r; +} diff --git a/src/login/test-login.c b/src/login/test-login.c new file mode 100644 index 000000000..dd8404285 --- /dev/null +++ b/src/login/test-login.c @@ -0,0 +1,188 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include + +#include "util.h" +#include "strv.h" + +int main(int argc, char* argv[]) { + int r, k; + uid_t u, u2; + char *seat, *type, *class, *display; + char *session; + char *state; + char *session2; + char *t; + char **seats, **sessions; + uid_t *uids; + unsigned n; + struct pollfd pollfd; + sd_login_monitor *m; + + assert_se(sd_pid_get_session(0, &session) == 0); + printf("session = %s\n", session); + + assert_se(sd_pid_get_owner_uid(0, &u2) == 0); + printf("user = %lu\n", (unsigned long) u2); + + r = sd_uid_get_sessions(u2, false, &sessions); + assert_se(r >= 0); + assert_se(r == (int) strv_length(sessions)); + assert_se(t = strv_join(sessions, ", ")); + strv_free(sessions); + printf("sessions = %s\n", t); + free(t); + + assert_se(r == sd_uid_get_sessions(u2, false, NULL)); + + r = sd_uid_get_seats(u2, false, &seats); + assert_se(r >= 0); + assert_se(r == (int) strv_length(seats)); + assert_se(t = strv_join(seats, ", ")); + strv_free(seats); + printf("seats = %s\n", t); + free(t); + + assert_se(r == sd_uid_get_seats(u2, false, NULL)); + + r = sd_session_is_active(session); + assert_se(r >= 0); + printf("active = %s\n", yes_no(r)); + + assert_se(sd_session_get_uid(session, &u) >= 0); + printf("uid = %lu\n", (unsigned long) u); + assert_se(u == u2); + + assert_se(sd_session_get_type(session, &type) >= 0); + printf("type = %s\n", type); + free(type); + + assert_se(sd_session_get_class(session, &class) >= 0); + printf("class = %s\n", class); + free(class); + + assert_se(sd_session_get_display(session, &display) >= 0); + printf("display = %s\n", display); + free(display); + + assert_se(sd_session_get_seat(session, &seat) >= 0); + printf("seat = %s\n", seat); + + r = sd_seat_can_multi_session(seat); + assert_se(r >= 0); + printf("can do multi session = %s\n", yes_no(r)); + + assert_se(sd_uid_get_state(u, &state) >= 0); + printf("state = %s\n", state); + + assert_se(sd_uid_is_on_seat(u, 0, seat) > 0); + + k = sd_uid_is_on_seat(u, 1, seat); + assert_se(k >= 0); + assert_se(!!r == !!r); + + assert_se(sd_seat_get_active(seat, &session2, &u2) >= 0); + printf("session2 = %s\n", session2); + printf("uid2 = %lu\n", (unsigned long) u2); + + r = sd_seat_get_sessions(seat, &sessions, &uids, &n); + assert_se(r >= 0); + printf("n_sessions = %i\n", r); + assert_se(r == (int) strv_length(sessions)); + assert_se(t = strv_join(sessions, ", ")); + strv_free(sessions); + printf("sessions = %s\n", t); + free(t); + printf("uids ="); + for (k = 0; k < (int) n; k++) + printf(" %lu", (unsigned long) uids[k]); + printf("\n"); + free(uids); + + assert_se(sd_seat_get_sessions(seat, NULL, NULL, NULL) == r); + + free(session); + free(state); + free(session2); + free(seat); + + r = sd_get_seats(&seats); + assert_se(r >= 0); + assert_se(r == (int) strv_length(seats)); + assert_se(t = strv_join(seats, ", ")); + strv_free(seats); + printf("n_seats = %i\n", r); + printf("seats = %s\n", t); + free(t); + + assert_se(sd_get_seats(NULL) == r); + + r = sd_seat_get_active(NULL, &t, NULL); + assert_se(r >= 0); + printf("active session on current seat = %s\n", t); + free(t); + + r = sd_get_sessions(&sessions); + assert_se(r >= 0); + assert_se(r == (int) strv_length(sessions)); + assert_se(t = strv_join(sessions, ", ")); + strv_free(sessions); + printf("n_sessions = %i\n", r); + printf("sessions = %s\n", t); + free(t); + + assert_se(sd_get_sessions(NULL) == r); + + r = sd_get_uids(&uids); + assert_se(r >= 0); + + printf("uids ="); + for (k = 0; k < r; k++) + printf(" %lu", (unsigned long) uids[k]); + printf("\n"); + free(uids); + + printf("n_uids = %i\n", r); + assert_se(sd_get_uids(NULL) == r); + + r = sd_login_monitor_new("session", &m); + assert_se(r >= 0); + + zero(pollfd); + pollfd.fd = sd_login_monitor_get_fd(m); + pollfd.events = POLLIN; + + for (n = 0; n < 5; n++) { + r = poll(&pollfd, 1, -1); + assert_se(r >= 0); + + sd_login_monitor_flush(m); + printf("Wake!\n"); + } + + sd_login_monitor_unref(m); + + return 0; +} diff --git a/src/login/uaccess.c b/src/login/uaccess.c new file mode 100644 index 000000000..e1af5bf43 --- /dev/null +++ b/src/login/uaccess.c @@ -0,0 +1,91 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include +#include + +#include "logind-acl.h" +#include "util.h" +#include "log.h" + +int main(int argc, char *argv[]) { + int r; + const char *path = NULL, *seat; + bool changed_acl = false; + uid_t uid; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc < 2 || argc > 3) { + log_error("This program expects one or two arguments."); + r = -EINVAL; + goto finish; + } + + /* Make sure we don't muck around with ACLs the system is not + * running systemd. */ + if (!sd_booted()) + return 0; + + path = argv[1]; + seat = argc < 3 || isempty(argv[2]) ? "seat0" : argv[2]; + + r = sd_seat_get_active(seat, NULL, &uid); + if (r == -ENOENT) { + /* No active session on this seat */ + r = 0; + goto finish; + } else if (r < 0) { + log_error("Failed to determine active user on seat %s.", seat); + goto finish; + } + + r = devnode_acl(path, true, false, 0, true, uid); + if (r < 0) { + log_error("Failed to apply ACL on %s: %s", path, strerror(-r)); + goto finish; + } + + changed_acl = true; + r = 0; + +finish: + if (path && !changed_acl) { + int k; + /* Better be safe that sorry and reset ACL */ + + k = devnode_acl(path, true, false, 0, false, 0); + if (k < 0) { + log_error("Failed to apply ACL on %s: %s", path, strerror(-k)); + if (r >= 0) + r = k; + } + } + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/login/user-sessions.c b/src/login/user-sessions.c new file mode 100644 index 000000000..64aa3bb1c --- /dev/null +++ b/src/login/user-sessions.c @@ -0,0 +1,100 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "log.h" +#include "util.h" +#include "cgroup-util.h" + +int main(int argc, char*argv[]) { + int ret = EXIT_FAILURE; + + if (argc != 2) { + log_error("This program requires one argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (streq(argv[1], "start")) { + int q = 0, r = 0; + + if (unlink("/run/nologin") < 0 && errno != ENOENT) { + log_error("Failed to remove /run/nologin file: %m"); + r = -errno; + } + + if (unlink("/etc/nologin") < 0 && errno != ENOENT) { + + /* If the file doesn't exist and /etc simply + * was read-only (in which case unlink() + * returns EROFS even if the file doesn't + * exist), don't complain */ + + if (errno != EROFS || access("/etc/nologin", F_OK) >= 0) { + log_error("Failed to remove /etc/nologin file: %m"); + q = -errno; + } + } + + if (r < 0 || q < 0) + goto finish; + + } else if (streq(argv[1], "stop")) { + int r, q; + char *cgroup_user_tree = NULL; + + if ((r = write_one_line_file_atomic("/run/nologin", "System is going down.")) < 0) + log_error("Failed to create /run/nologin: %s", strerror(-r)); + + if ((q = cg_get_user_path(&cgroup_user_tree)) < 0) { + log_error("Failed to determine use path: %s", strerror(-q)); + goto finish; + } + + q = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, cgroup_user_tree, true); + free(cgroup_user_tree); + + if (q < 0) { + log_error("Failed to kill sessions: %s", strerror(-q)); + goto finish; + } + + if (r < 0) + goto finish; + + } else { + log_error("Unknown verb %s.", argv[1]); + goto finish; + } + + ret = EXIT_SUCCESS; + +finish: + return ret; +} diff --git a/src/logs-show.c b/src/logs-show.c new file mode 100644 index 000000000..0a07a77be --- /dev/null +++ b/src/logs-show.c @@ -0,0 +1,664 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "logs-show.h" +#include "log.h" +#include "util.h" + +#define PRINT_THRESHOLD 128 + +static bool contains_unprintable(const void *p, size_t l) { + const char *j; + + for (j = p; j < (const char *) p + l; j++) + if (*j < ' ' || *j >= 127) + return true; + + return false; +} + +static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) { + size_t fl, nl; + void *buf; + + assert(data); + assert(field); + assert(target); + assert(target_size); + + fl = strlen(field); + if (length < fl) + return 0; + + if (memcmp(data, field, fl)) + return 0; + + nl = length - fl; + buf = malloc(nl+1); + memcpy(buf, (const char*) data + fl, nl); + ((char*)buf)[nl] = 0; + if (!buf) { + log_error("Out of memory"); + return -ENOMEM; + } + + free(*target); + *target = buf; + *target_size = nl; + + return 1; +} + +static bool shall_print(bool show_all, char *p, size_t l) { + if (show_all) + return true; + + if (l > PRINT_THRESHOLD) + return false; + + if (contains_unprintable(p, l)) + return false; + + return true; +} + +static int output_short(sd_journal *j, unsigned line, unsigned n_columns, bool show_all, bool monotonic_mode) { + int r; + const void *data; + size_t length; + size_t n = 0; + char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL; + size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, realtime_len = 0, monotonic_len = 0; + + assert(j); + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + + r = parse_field(data, length, "_HOSTNAME=", &hostname, &hostname_len); + if (r < 0) + goto finish; + else if (r > 0) + continue; + + r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &identifier_len); + if (r < 0) + goto finish; + else if (r > 0) + continue; + + r = parse_field(data, length, "_COMM=", &comm, &comm_len); + if (r < 0) + goto finish; + else if (r > 0) + continue; + + r = parse_field(data, length, "_PID=", &pid, &pid_len); + if (r < 0) + goto finish; + else if (r > 0) + continue; + + r = parse_field(data, length, "SYSLOG_PID=", &fake_pid, &fake_pid_len); + if (r < 0) + goto finish; + else if (r > 0) + continue; + + r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len); + if (r < 0) + goto finish; + else if (r > 0) + continue; + + r = parse_field(data, length, "_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len); + if (r < 0) + goto finish; + else if (r > 0) + continue; + + r = parse_field(data, length, "MESSAGE=", &message, &message_len); + if (r < 0) + goto finish; + } + + if (!message) { + r = 0; + goto finish; + } + + if (monotonic_mode) { + 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) { + log_error("Failed to get monotonic: %s", strerror(-r)); + goto finish; + } + + printf("[%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; + + r = -ENOENT; + + if (realtime) + r = safe_atou64(realtime, &x); + + if (r < 0) + r = sd_journal_get_realtime_usec(j, &x); + + if (r < 0) { + log_error("Failed to get realtime: %s", strerror(-r)); + goto finish; + } + + t = (time_t) (x / USEC_PER_SEC); + if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm)) <= 0) { + log_error("Failed to format time."); + goto finish; + } + + fputs(buf, stdout); + n += strlen(buf); + } + + if (hostname && shall_print(show_all, hostname, hostname_len)) { + printf(" %.*s", (int) hostname_len, hostname); + n += hostname_len + 1; + } + + if (identifier && shall_print(show_all, identifier, identifier_len)) { + printf(" %.*s", (int) identifier_len, identifier); + n += identifier_len + 1; + } else if (comm && shall_print(show_all, comm, comm_len)) { + printf(" %.*s", (int) comm_len, comm); + n += comm_len + 1; + } + + if (pid && shall_print(show_all, pid, pid_len)) { + printf("[%.*s]", (int) pid_len, pid); + n += pid_len + 2; + } else if (fake_pid && shall_print(show_all, fake_pid, fake_pid_len)) { + printf("[%.*s]", (int) fake_pid_len, fake_pid); + n += fake_pid_len + 2; + } + + if (show_all) + printf(": %.*s\n", (int) message_len, message); + else if (contains_unprintable(message, message_len)) { + char bytes[FORMAT_BYTES_MAX]; + printf(": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len)); + } else if (message_len + n < n_columns) + printf(": %.*s\n", (int) message_len, message); + else if (n < n_columns) { + char *e; + + e = ellipsize_mem(message, message_len, n_columns - n - 2, 90); + + if (!e) + printf(": %.*s\n", (int) message_len, message); + else + printf(": %s\n", e); + + free(e); + } else + fputs("\n", stdout); + + r = 0; + +finish: + free(hostname); + free(identifier); + free(comm); + free(pid); + free(fake_pid); + free(message); + free(monotonic); + free(realtime); + + return r; +} + +static int output_short_realtime(sd_journal *j, unsigned line, unsigned n_columns, bool show_all) { + return output_short(j, line, n_columns, show_all, false); +} + +static int output_short_monotonic(sd_journal *j, unsigned line, unsigned n_columns, bool show_all) { + return output_short(j, line, n_columns, show_all, true); +} + +static int output_verbose(sd_journal *j, unsigned line, unsigned n_columns, bool show_all) { + const void *data; + size_t length; + char *cursor; + uint64_t realtime; + char ts[FORMAT_TIMESTAMP_MAX]; + int r; + + assert(j); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) { + log_error("Failed to get realtime timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) { + log_error("Failed to get cursor: %s", strerror(-r)); + return r; + } + + printf("%s [%s]\n", + format_timestamp(ts, sizeof(ts), realtime), + cursor); + + free(cursor); + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + if (!show_all && (length > PRINT_THRESHOLD || + contains_unprintable(data, length))) { + const char *c; + char bytes[FORMAT_BYTES_MAX]; + + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + + printf("\t%.*s=[%s blob data]\n", + (int) (c - (const char*) data), + (const char*) data, + format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1)); + } else + printf("\t%.*s\n", (int) length, (const char*) data); + } + + return 0; +} + +static int output_export(sd_journal *j, unsigned line, unsigned n_columns, bool show_all) { + sd_id128_t boot_id; + char sid[33]; + int r; + usec_t realtime, monotonic; + char *cursor; + const void *data; + size_t length; + + assert(j); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) { + log_error("Failed to get realtime timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); + if (r < 0) { + log_error("Failed to get monotonic timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) { + log_error("Failed to get cursor: %s", strerror(-r)); + return r; + } + + printf("__CURSOR=%s\n" + "__REALTIME=%llu\n" + "__MONOTONIC=%llu\n" + "__BOOT_ID=%s\n", + cursor, + (unsigned long long) realtime, + (unsigned long long) monotonic, + sd_id128_to_string(boot_id, sid)); + + free(cursor); + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + + if (contains_unprintable(data, length)) { + const char *c; + uint64_t le64; + + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + + fwrite(data, c - (const char*) data, 1, stdout); + fputc('\n', stdout); + le64 = htole64(length - (c - (const char*) data) - 1); + fwrite(&le64, sizeof(le64), 1, stdout); + fwrite(c + 1, length - (c - (const char*) data) - 1, 1, stdout); + } else + fwrite(data, length, 1, stdout); + + fputc('\n', stdout); + } + + fputc('\n', stdout); + + return 0; +} + +static void json_escape(const char* p, size_t l) { + + if (contains_unprintable(p, l)) { + bool not_first = false; + + fputs("[ ", stdout); + + while (l > 0) { + if (not_first) + printf(", %u", (uint8_t) *p); + else { + not_first = true; + printf("%u", (uint8_t) *p); + } + + p++; + l--; + } + + fputs(" ]", stdout); + } else { + fputc('\"', stdout); + + while (l > 0) { + if (*p == '"' || *p == '\\') { + fputc('\\', stdout); + fputc(*p, stdout); + } else + fputc(*p, stdout); + + p++; + l--; + } + + fputc('\"', stdout); + } +} + +static int output_json(sd_journal *j, unsigned line, unsigned n_columns, bool show_all) { + uint64_t realtime, monotonic; + char *cursor; + const void *data; + size_t length; + sd_id128_t boot_id; + char sid[33]; + int r; + + assert(j); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) { + log_error("Failed to get realtime timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); + if (r < 0) { + log_error("Failed to get monotonic timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) { + log_error("Failed to get cursor: %s", strerror(-r)); + return r; + } + + if (line == 1) + fputc('\n', stdout); + else + fputs(",\n", stdout); + + printf("{\n" + "\t\"__CURSOR\" : \"%s\",\n" + "\t\"__REALTIME\" : \"%llu\",\n" + "\t\"__MONOTONIC\" : \"%llu\",\n" + "\t\"__BOOT_ID\" : \"%s\"", + cursor, + (unsigned long long) realtime, + (unsigned long long) monotonic, + sd_id128_to_string(boot_id, sid)); + + free(cursor); + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + const char *c; + + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + + fputs(",\n\t", stdout); + json_escape(data, c - (const char*) data); + fputs(" : ", stdout); + json_escape(c + 1, length - (c - (const char*) data) - 1); + } + + fputs("\n}", stdout); + fflush(stdout); + + return 0; +} + +static int output_cat(sd_journal *j, unsigned line, unsigned n_columns, bool show_all) { + const void *data; + size_t l; + int r; + + assert(j); + + r = sd_journal_get_data(j, "MESSAGE", &data, &l); + if (r < 0) { + log_error("Failed to get data: %s", strerror(-r)); + return r; + } + + assert(l >= 8); + + fwrite((const char*) data + 8, 1, l - 8, stdout); + putchar('\n'); + + return 0; +} + +static int (*output_funcs[_OUTPUT_MODE_MAX])(sd_journal*j, unsigned line, unsigned n_columns, bool show_all) = { + [OUTPUT_SHORT] = output_short_realtime, + [OUTPUT_SHORT_MONOTONIC] = output_short_monotonic, + [OUTPUT_VERBOSE] = output_verbose, + [OUTPUT_EXPORT] = output_export, + [OUTPUT_JSON] = output_json, + [OUTPUT_CAT] = output_cat +}; + +int output_journal(sd_journal *j, OutputMode mode, unsigned line, unsigned n_columns, bool show_all) { + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + + if (n_columns <= 0) + n_columns = columns(); + + return output_funcs[mode](j, line, n_columns, show_all); +} + +int show_journal_by_unit( + const char *unit, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + bool show_all, + bool follow) { + + char *m = NULL; + sd_journal *j; + int r; + int fd; + unsigned line = 0; + bool need_seek = false; + + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + assert(unit); + + if (!endswith(unit, ".service") && + !endswith(unit, ".socket") && + !endswith(unit, ".mount") && + !endswith(unit, ".swap")) + return 0; + + if (how_many <= 0) + return 0; + + if (asprintf(&m, "_SYSTEMD_UNIT=%s", unit) < 0) { + r = -ENOMEM; + goto finish; + } + + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY); + if (r < 0) + goto finish; + + fd = sd_journal_get_fd(j); + if (fd < 0) + goto finish; + + r = sd_journal_add_match(j, m, strlen(m)); + if (r < 0) + goto finish; + + r = sd_journal_seek_tail(j); + if (r < 0) + goto finish; + + r = sd_journal_previous_skip(j, how_many); + if (r < 0) + goto finish; + + if (mode == OUTPUT_JSON) { + fputc('[', stdout); + fflush(stdout); + } + + for (;;) { + for (;;) { + usec_t usec; + + if (need_seek) { + r = sd_journal_next(j); + if (r < 0) + goto finish; + } + + if (r == 0) + break; + + need_seek = true; + + if (not_before > 0) { + r = sd_journal_get_monotonic_usec(j, &usec, NULL); + + /* -ESTALE is returned if the + timestamp is not from this boot */ + if (r == -ESTALE) + continue; + else if (r < 0) + goto finish; + + if (usec < not_before) + continue; + } + + line ++; + + r = output_journal(j, mode, line, n_columns, show_all); + if (r < 0) + goto finish; + } + + if (!follow) + break; + + r = fd_wait_for_event(fd, POLLIN, (usec_t) -1); + if (r < 0) + goto finish; + + r = sd_journal_process(j); + if (r < 0) + goto finish; + + } + + if (mode == OUTPUT_JSON) + fputs("\n]\n", stdout); + +finish: + if (m) + free(m); + + if (j) + sd_journal_close(j); + + return r; +} + +static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { + [OUTPUT_SHORT] = "short", + [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", + [OUTPUT_VERBOSE] = "verbose", + [OUTPUT_EXPORT] = "export", + [OUTPUT_JSON] = "json", + [OUTPUT_CAT] = "cat" +}; + +DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode); diff --git a/src/logs-show.h b/src/logs-show.h new file mode 100644 index 000000000..db9c7e34a --- /dev/null +++ b/src/logs-show.h @@ -0,0 +1,56 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foologsshowhfoo +#define foologsshowhfoo + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include + +#include "util.h" + +typedef enum OutputMode { + OUTPUT_SHORT, + OUTPUT_SHORT_MONOTONIC, + OUTPUT_VERBOSE, + OUTPUT_EXPORT, + OUTPUT_JSON, + OUTPUT_CAT, + _OUTPUT_MODE_MAX, + _OUTPUT_MODE_INVALID = -1 +} OutputMode; + +int output_journal(sd_journal *j, OutputMode mode, unsigned line, unsigned n_columns, bool show_all); + +int show_journal_by_unit( + const char *unit, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + bool show_all, + bool follow); + +const char* output_mode_to_string(OutputMode m); +OutputMode output_mode_from_string(const char *s); + +#endif diff --git a/src/loopback-setup.c b/src/loopback-setup.c new file mode 100644 index 000000000..b6359def7 --- /dev/null +++ b/src/loopback-setup.c @@ -0,0 +1,274 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "macro.h" +#include "loopback-setup.h" +#include "socket-util.h" + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((uint8_t*) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +static int add_rtattr(struct nlmsghdr *n, size_t max_length, int type, const void *data, size_t data_length) { + size_t length; + struct rtattr *rta; + + length = RTA_LENGTH(data_length); + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(length) > max_length) + return -E2BIG; + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = length; + memcpy(RTA_DATA(rta), data, data_length); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(length); + + return 0; +} + +static ssize_t sendto_loop(int fd, const void *buf, size_t buf_len, int flags, const struct sockaddr *sa, socklen_t sa_len) { + + for (;;) { + ssize_t l; + + if ((l = sendto(fd, buf, buf_len, flags, sa, sa_len)) >= 0) + return l; + + if (errno != EINTR) + return -errno; + } +} + +static ssize_t recvfrom_loop(int fd, void *buf, size_t buf_len, int flags, struct sockaddr *sa, socklen_t *sa_len) { + + for (;;) { + ssize_t l; + + if ((l = recvfrom(fd, buf, buf_len, flags, sa, sa_len)) >= 0) + return l; + + if (errno != EINTR) + return -errno; + } +} + +static int add_adresses(int fd, int if_loopback, unsigned *requests) { + union { + struct sockaddr sa; + struct sockaddr_nl nl; + } sa; + union { + struct nlmsghdr header; + uint8_t buf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + + NLMSG_ALIGN(sizeof(struct ifaddrmsg)) + + RTA_LENGTH(sizeof(struct in6_addr))]; + } request; + + struct ifaddrmsg *ifaddrmsg; + uint32_t ipv4_address = htonl(INADDR_LOOPBACK); + int r; + + zero(request); + + request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + request.header.nlmsg_type = RTM_NEWADDR; + request.header.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_ACK; + request.header.nlmsg_seq = *requests + 1; + + ifaddrmsg = NLMSG_DATA(&request.header); + ifaddrmsg->ifa_family = AF_INET; + ifaddrmsg->ifa_prefixlen = 8; + ifaddrmsg->ifa_flags = IFA_F_PERMANENT; + ifaddrmsg->ifa_scope = RT_SCOPE_HOST; + ifaddrmsg->ifa_index = if_loopback; + + if ((r = add_rtattr(&request.header, sizeof(request), IFA_LOCAL, &ipv4_address, sizeof(ipv4_address))) < 0) + return r; + + zero(sa); + sa.nl.nl_family = AF_NETLINK; + + if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0) + return -errno; + (*requests)++; + + if (!socket_ipv6_is_supported()) + return 0; + + request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + request.header.nlmsg_seq = *requests + 1; + + ifaddrmsg->ifa_family = AF_INET6; + ifaddrmsg->ifa_prefixlen = 128; + + if ((r = add_rtattr(&request.header, sizeof(request), IFA_LOCAL, &in6addr_loopback, sizeof(in6addr_loopback))) < 0) + return r; + + if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0) + return -errno; + (*requests)++; + + return 0; +} + +static int start_interface(int fd, int if_loopback, unsigned *requests) { + union { + struct sockaddr sa; + struct sockaddr_nl nl; + } sa; + union { + struct nlmsghdr header; + uint8_t buf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + + NLMSG_ALIGN(sizeof(struct ifinfomsg))]; + } request; + + struct ifinfomsg *ifinfomsg; + + zero(request); + + request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + request.header.nlmsg_type = RTM_NEWLINK; + request.header.nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK; + request.header.nlmsg_seq = *requests + 1; + + ifinfomsg = NLMSG_DATA(&request.header); + ifinfomsg->ifi_family = AF_UNSPEC; + ifinfomsg->ifi_index = if_loopback; + ifinfomsg->ifi_flags = IFF_UP; + ifinfomsg->ifi_change = IFF_UP; + + zero(sa); + sa.nl.nl_family = AF_NETLINK; + + if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0) + return -errno; + + (*requests)++; + + return 0; +} + +static int read_response(int fd, unsigned requests_max) { + union { + struct sockaddr sa; + struct sockaddr_nl nl; + } sa; + union { + struct nlmsghdr header; + uint8_t buf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + + NLMSG_ALIGN(sizeof(struct nlmsgerr))]; + } response; + + ssize_t l; + socklen_t sa_len = sizeof(sa); + struct nlmsgerr *nlmsgerr; + + if ((l = recvfrom_loop(fd, &response, sizeof(response), 0, &sa.sa, &sa_len)) < 0) + return -errno; + + if (sa_len != sizeof(sa.nl) || + sa.nl.nl_family != AF_NETLINK) + return -EIO; + + if (sa.nl.nl_pid != 0) + return 0; + + if ((size_t) l < sizeof(struct nlmsghdr)) + return -EIO; + + if (response.header.nlmsg_type != NLMSG_ERROR || + (pid_t) response.header.nlmsg_pid != getpid() || + response.header.nlmsg_seq >= requests_max) + return 0; + + if ((size_t) l < NLMSG_LENGTH(sizeof(struct nlmsgerr)) || + response.header.nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) + return -EIO; + + nlmsgerr = NLMSG_DATA(&response.header); + + if (nlmsgerr->error < 0 && nlmsgerr->error != -EEXIST) { + log_warning("Netlink failure for request %i: %s", response.header.nlmsg_seq, strerror(-nlmsgerr->error)); + return nlmsgerr->error; + } + + return response.header.nlmsg_seq; +} + +int loopback_setup(void) { + int r, if_loopback; + union { + struct sockaddr sa; + struct sockaddr_nl nl; + struct sockaddr_storage storage; + } sa; + unsigned requests = 0, i; + int fd; + + errno = 0; + if ((if_loopback = (int) if_nametoindex("lo")) <= 0) + return errno ? -errno : -ENODEV; + + if ((fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) + return -errno; + + zero(sa); + sa.nl.nl_family = AF_NETLINK; + + if (bind(fd, &sa.sa, sizeof(sa)) < 0) { + r = -errno; + goto finish; + } + + if ((r = add_adresses(fd, if_loopback, &requests)) < 0) + goto finish; + + if ((r = start_interface(fd, if_loopback, &requests)) < 0) + goto finish; + + for (i = 0; i < requests; i++) { + if ((r = read_response(fd, requests)) < 0) + goto finish; + } + + r = 0; + +finish: + if (r < 0) + log_warning("Failed to configure loopback device: %s", strerror(-r)); + + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} diff --git a/src/loopback-setup.h b/src/loopback-setup.h new file mode 100644 index 000000000..81f452963 --- /dev/null +++ b/src/loopback-setup.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooloopbacksetuphfoo +#define fooloopbacksetuphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +int loopback_setup(void); + +#endif diff --git a/src/machine-id-main.c b/src/machine-id-main.c new file mode 100644 index 000000000..03970a2b0 --- /dev/null +++ b/src/machine-id-main.c @@ -0,0 +1,35 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "machine-id-setup.h" +#include "log.h" + +int main(int argc, char *argv[]) { + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + return machine_id_setup() < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/machine-id-setup.c b/src/machine-id-setup.c new file mode 100644 index 000000000..0f9743380 --- /dev/null +++ b/src/machine-id-setup.c @@ -0,0 +1,267 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "machine-id-setup.h" +#include "macro.h" +#include "util.h" +#include "log.h" +#include "virt.h" + +static int shorten_uuid(char destination[36], const char *source) { + unsigned i, j; + + for (i = 0, j = 0; i < 36 && j < 32; i++) { + int t; + + t = unhexchar(source[i]); + if (t < 0) + continue; + + destination[j++] = hexchar(t); + } + + if (i == 36 && j == 32) { + destination[32] = '\n'; + destination[33] = 0; + return 0; + } + + return -EINVAL; +} + +static int generate(char id[34]) { + int fd, r; + unsigned char *p; + sd_id128_t buf; + char *q; + ssize_t k; + const char *vm_id; + + assert(id); + + /* First, try reading the D-Bus machine id, unless it is a symlink */ + fd = open("/var/lib/dbus/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd >= 0) { + + k = loop_read(fd, id, 32, false); + close_nointr_nofail(fd); + + if (k >= 32) { + id[32] = '\n'; + id[33] = 0; + + log_info("Initializing machine ID from D-Bus machine ID."); + return 0; + } + } + + /* If that didn't work, see if we are running in qemu/kvm and a + * machine ID was passed in via -uuid on the qemu/kvm command + * line */ + + r = detect_vm(&vm_id); + if (r > 0 && streq(vm_id, "kvm")) { + char uuid[37]; + + fd = open("/sys/class/dmi/id/product_uuid", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd >= 0) { + k = loop_read(fd, uuid, 36, false); + close_nointr_nofail(fd); + + if (k >= 36) { + r = shorten_uuid(id, uuid); + if (r >= 0) { + log_info("Initializing machine ID from KVM UUID"); + return 0; + } + } + } + } + + /* If that didn't work either, see if we are running in a + * container, and a machine ID was passed in via + * $container_uuid the way libvirt/LXC does it */ + + r = detect_container(NULL); + if (r > 0) { + FILE *f; + + f = fopen("/proc/1/environ", "re"); + if (f) { + bool done = false; + + do { + char line[LINE_MAX]; + unsigned i; + + for (i = 0; i < sizeof(line)-1; i++) { + int c; + + c = getc(f); + if (_unlikely_(c == EOF)) { + done = true; + break; + } else if (c == 0) + break; + + line[i] = c; + } + line[i] = 0; + + if (startswith(line, "container_uuid=") && + strlen(line + 15) >= 36) { + r = shorten_uuid(id, line + 15); + if (r >= 0) { + log_info("Initializing machine ID from container UUID"); + return 0; + } + } + + } while (!done); + + fclose(f); + } + } + + /* If that didn't work, generate a random machine id */ + r = sd_id128_randomize(&buf); + if (r < 0) { + log_error("Failed to open /dev/urandom: %s", strerror(-r)); + return r; + } + + for (p = buf.bytes, q = id; p < buf.bytes + sizeof(buf); p++, q += 2) { + q[0] = hexchar(*p >> 4); + q[1] = hexchar(*p & 15); + } + + id[32] = '\n'; + id[33] = 0; + + log_info("Initializing machine ID from random generator."); + + return 0; +} + +int machine_id_setup(void) { + int fd, r; + bool writable; + struct stat st; + char id[34]; /* 32 + \n + \0 */ + mode_t m; + + m = umask(0000); + + /* We create this 0444, to indicate that this isn't really + * something you should ever modify. Of course, since the file + * will be owned by root it doesn't matter much, but maybe + * people look. */ + + fd = open("/etc/machine-id", O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444); + if (fd >= 0) + writable = true; + else { + fd = open("/etc/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + umask(m); + log_error("Cannot open /etc/machine-id: %m"); + return -errno; + } + + writable = false; + } + + umask(m); + + if (fstat(fd, &st) < 0) { + log_error("fstat() failed: %m"); + r = -errno; + goto finish; + } + + if (S_ISREG(st.st_mode)) { + if (loop_read(fd, id, 32, false) >= 32) { + r = 0; + goto finish; + } + } + + /* Hmm, so, the id currently stored is not useful, then let's + * generate one */ + + r = generate(id); + if (r < 0) + goto finish; + + if (S_ISREG(st.st_mode) && writable) { + lseek(fd, 0, SEEK_SET); + + if (loop_write(fd, id, 33, false) == 33) { + r = 0; + goto finish; + } + } + + close_nointr_nofail(fd); + fd = -1; + + /* Hmm, we couldn't write it? So let's write it to + * /run/systemd/machine-id as a replacement */ + + mkdir_p("/run/systemd", 0755); + + m = umask(0022); + r = write_one_line_file("/run/systemd/machine-id", id); + umask(m); + + if (r < 0) { + log_error("Cannot write /run/systemd/machine-id: %s", strerror(-r)); + + unlink("/run/systemd/machine-id"); + goto finish; + } + + /* And now, let's mount it over */ + r = mount("/run/systemd/machine-id", "/etc/machine-id", "bind", MS_BIND|MS_RDONLY, NULL) < 0 ? -errno : 0; + unlink("/run/systemd/machine-id"); + + if (r < 0) + log_error("Failed to mount /etc/machine-id: %s", strerror(-r)); + else + log_info("Installed transient /etc/machine-id file."); + +finish: + + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} diff --git a/src/machine-id-setup.h b/src/machine-id-setup.h new file mode 100644 index 000000000..4d0a9cf33 --- /dev/null +++ b/src/machine-id-setup.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foomachineidsetuphfoo +#define foomachineidsetuphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +int machine_id_setup(void); + +#endif diff --git a/src/macro.h b/src/macro.h new file mode 100644 index 000000000..19f259e4f --- /dev/null +++ b/src/macro.h @@ -0,0 +1,181 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foomacrohfoo +#define foomacrohfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#define _printf_attr_(a,b) __attribute__ ((format (printf, a, b))) +#define _sentinel_ __attribute__ ((sentinel)) +#define _noreturn_ __attribute__((noreturn)) +#define _unused_ __attribute__ ((unused)) +#define _destructor_ __attribute__ ((destructor)) +#define _pure_ __attribute__ ((pure)) +#define _const_ __attribute__ ((const)) +#define _deprecated_ __attribute__ ((deprecated)) +#define _packed_ __attribute__ ((packed)) +#define _malloc_ __attribute__ ((malloc)) +#define _weak_ __attribute__ ((weak)) +#define _likely_(x) (__builtin_expect(!!(x),1)) +#define _unlikely_(x) (__builtin_expect(!!(x),0)) +#define _public_ __attribute__ ((visibility("default"))) +#define _hidden_ __attribute__ ((visibility("hidden"))) +#define _weakref_(x) __attribute__((weakref(#x))) +#define _introspect_(x) __attribute__((section("introspect." x))) + +#define XSTRINGIFY(x) #x +#define STRINGIFY(x) XSTRINGIFY(x) + +/* Rounds up */ +#define ALIGN(l) ALIGN_TO((l), sizeof(void*)) +static inline size_t ALIGN_TO(size_t l, size_t ali) { + return ((l + ali - 1) & ~(ali - 1)); +} + +#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) + +#ifndef MAX +#define MAX(a,b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) +#endif + +#define MAX3(a,b,c) \ + MAX(MAX(a,b),c) + +#ifndef MIN +#define MIN(a,b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a < _b ? _a : _b; \ + }) +#endif + +#define MIN3(a,b,c) \ + MIN(MIN(a,b),c) + +#define CLAMP(x, low, high) \ + __extension__ ({ \ + typeof(x) _x = (x); \ + typeof(low) _low = (low); \ + typeof(high) _high = (high); \ + ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \ + }) + +#define assert_se(expr) \ + do { \ + if (_unlikely_(!(expr))) \ + log_assert_failed(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + } while (false) \ + +/* We override the glibc assert() here. */ +#undef assert +#ifdef NDEBUG +#define assert(expr) do {} while(false) +#else +#define assert(expr) assert_se(expr) +#endif + +#define assert_not_reached(t) \ + do { \ + log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + } while (false) + +#define assert_cc(expr) \ + do { \ + switch (0) { \ + case 0: \ + case !!(expr): \ + ; \ + } \ + } while (false) + +#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) +#define UINT_TO_PTR(u) ((void*) ((uintptr_t) (u))) + +#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p))) +#define UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u))) + +#define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p))) +#define ULONG_TO_PTR(u) ((void*) ((uintptr_t) (u))) + +#define PTR_TO_INT(p) ((int) ((intptr_t) (p))) +#define INT_TO_PTR(u) ((void*) ((intptr_t) (u))) + +#define TO_INT32(p) ((int32_t) ((intptr_t) (p))) +#define INT32_TO_PTR(u) ((void*) ((intptr_t) (u))) + +#define PTR_TO_LONG(p) ((long) ((intptr_t) (p))) +#define LONG_TO_PTR(u) ((void*) ((intptr_t) (u))) + +#define memzero(x,l) (memset((x), 0, (l))) +#define zero(x) (memzero(&(x), sizeof(x))) + +#define char_array_0(x) x[sizeof(x)-1] = 0; + +#define IOVEC_SET_STRING(i, s) \ + do { \ + struct iovec *_i = &(i); \ + char *_s = (char *)(s); \ + _i->iov_base = _s; \ + _i->iov_len = strlen(_s); \ + } while(false) + +static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, unsigned n) { + unsigned j; + size_t r = 0; + + for (j = 0; j < n; j++) + r += i[j].iov_len; + + return r; +} + +static inline size_t IOVEC_INCREMENT(struct iovec *i, unsigned n, size_t k) { + unsigned j; + + for (j = 0; j < n; j++) { + size_t sub; + + if (_unlikely_(k <= 0)) + break; + + sub = MIN(i[j].iov_len, k); + i[j].iov_len -= sub; + i[j].iov_base = (uint8_t*) i[j].iov_base + sub; + k -= sub; + } + + return k; +} + +#include "log.h" + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 000000000..7ae88414a --- /dev/null +++ b/src/main.c @@ -0,0 +1,1632 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "manager.h" +#include "log.h" +#include "mount-setup.h" +#include "hostname-setup.h" +#include "loopback-setup.h" +#include "kmod-setup.h" +#include "locale-setup.h" +#include "selinux-setup.h" +#include "ima-setup.h" +#include "machine-id-setup.h" +#include "load-fragment.h" +#include "fdset.h" +#include "special.h" +#include "conf-parser.h" +#include "bus-errors.h" +#include "missing.h" +#include "label.h" +#include "build.h" +#include "strv.h" +#include "def.h" +#include "virt.h" + +static enum { + ACTION_RUN, + ACTION_HELP, + ACTION_TEST, + ACTION_DUMP_CONFIGURATION_ITEMS, + ACTION_DONE +} arg_action = ACTION_RUN; + +static char *arg_default_unit = NULL; +static ManagerRunningAs arg_running_as = _MANAGER_RUNNING_AS_INVALID; + +static bool arg_dump_core = true; +static bool arg_crash_shell = false; +static int arg_crash_chvt = -1; +static bool arg_confirm_spawn = false; +static bool arg_show_status = true; +#ifdef HAVE_SYSV_COMPAT +static bool arg_sysv_console = true; +#endif +static bool arg_mount_auto = true; +static bool arg_swap_auto = true; +static char **arg_default_controllers = NULL; +static char ***arg_join_controllers = NULL; +static ExecOutput arg_default_std_output = EXEC_OUTPUT_JOURNAL; +static ExecOutput arg_default_std_error = EXEC_OUTPUT_INHERIT; + +static FILE* serialization = NULL; + +static void nop_handler(int sig) { +} + +_noreturn_ static void crash(int sig) { + + if (!arg_dump_core) + log_error("Caught <%s>, not dumping core.", signal_to_string(sig)); + else { + struct sigaction sa; + pid_t pid; + + /* We want to wait for the core process, hence let's enable SIGCHLD */ + zero(sa); + sa.sa_handler = nop_handler; + sa.sa_flags = SA_NOCLDSTOP|SA_RESTART; + assert_se(sigaction(SIGCHLD, &sa, NULL) == 0); + + if ((pid = fork()) < 0) + log_error("Caught <%s>, cannot fork for core dump: %s", signal_to_string(sig), strerror(errno)); + + else if (pid == 0) { + struct rlimit rl; + + /* Enable default signal handler for core dump */ + zero(sa); + sa.sa_handler = SIG_DFL; + assert_se(sigaction(sig, &sa, NULL) == 0); + + /* Don't limit the core dump size */ + zero(rl); + rl.rlim_cur = RLIM_INFINITY; + rl.rlim_max = RLIM_INFINITY; + setrlimit(RLIMIT_CORE, &rl); + + /* Just to be sure... */ + assert_se(chdir("/") == 0); + + /* Raise the signal again */ + raise(sig); + + assert_not_reached("We shouldn't be here..."); + _exit(1); + + } else { + siginfo_t status; + int r; + + /* Order things nicely. */ + if ((r = wait_for_terminate(pid, &status)) < 0) + log_error("Caught <%s>, waitpid() failed: %s", signal_to_string(sig), strerror(-r)); + else if (status.si_code != CLD_DUMPED) + log_error("Caught <%s>, core dump failed.", signal_to_string(sig)); + else + log_error("Caught <%s>, dumped core as pid %lu.", signal_to_string(sig), (unsigned long) pid); + } + } + + if (arg_crash_chvt) + chvt(arg_crash_chvt); + + if (arg_crash_shell) { + struct sigaction sa; + pid_t pid; + + log_info("Executing crash shell in 10s..."); + sleep(10); + + /* Let the kernel reap children for us */ + zero(sa); + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT|SA_RESTART; + assert_se(sigaction(SIGCHLD, &sa, NULL) == 0); + + if ((pid = fork()) < 0) + log_error("Failed to fork off crash shell: %s", strerror(errno)); + else if (pid == 0) { + int fd, r; + + if ((fd = acquire_terminal("/dev/console", false, true, true)) < 0) + log_error("Failed to acquire terminal: %s", strerror(-fd)); + else if ((r = make_stdio(fd)) < 0) + log_error("Failed to duplicate terminal fd: %s", strerror(-r)); + + execl("/bin/sh", "/bin/sh", NULL); + + log_error("execl() failed: %s", strerror(errno)); + _exit(1); + } + + log_info("Successfully spawned crash shell as pid %lu.", (unsigned long) pid); + } + + log_info("Freezing execution."); + freeze(); +} + +static void install_crash_handler(void) { + struct sigaction sa; + + zero(sa); + + sa.sa_handler = crash; + sa.sa_flags = SA_NODEFER; + + sigaction_many(&sa, SIGNALS_CRASH_HANDLER, -1); +} + +static int console_setup(bool do_reset) { + int tty_fd, r; + + /* If we are init, we connect stdin/stdout/stderr to /dev/null + * and make sure we don't have a controlling tty. */ + + release_terminal(); + + if (!do_reset) + return 0; + + tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (tty_fd < 0) { + log_error("Failed to open /dev/console: %s", strerror(-tty_fd)); + return -tty_fd; + } + + /* We don't want to force text mode. + * plymouth may be showing pictures already from initrd. */ + r = reset_terminal_fd(tty_fd, false); + if (r < 0) + log_error("Failed to reset /dev/console: %s", strerror(-r)); + + close_nointr_nofail(tty_fd); + return r; +} + +static int set_default_unit(const char *u) { + char *c; + + assert(u); + + if (!(c = strdup(u))) + return -ENOMEM; + + free(arg_default_unit); + arg_default_unit = c; + return 0; +} + +static int parse_proc_cmdline_word(const char *word) { + + static const char * const rlmap[] = { + "emergency", SPECIAL_EMERGENCY_TARGET, + "-b", SPECIAL_EMERGENCY_TARGET, + "single", SPECIAL_RESCUE_TARGET, + "-s", SPECIAL_RESCUE_TARGET, + "s", SPECIAL_RESCUE_TARGET, + "S", SPECIAL_RESCUE_TARGET, + "1", SPECIAL_RESCUE_TARGET, + "2", SPECIAL_RUNLEVEL2_TARGET, + "3", SPECIAL_RUNLEVEL3_TARGET, + "4", SPECIAL_RUNLEVEL4_TARGET, + "5", SPECIAL_RUNLEVEL5_TARGET, + }; + + assert(word); + + if (startswith(word, "systemd.unit=")) + return set_default_unit(word + 13); + + else if (startswith(word, "systemd.log_target=")) { + + if (log_set_target_from_string(word + 19) < 0) + log_warning("Failed to parse log target %s. Ignoring.", word + 19); + + } else if (startswith(word, "systemd.log_level=")) { + + if (log_set_max_level_from_string(word + 18) < 0) + log_warning("Failed to parse log level %s. Ignoring.", word + 18); + + } else if (startswith(word, "systemd.log_color=")) { + + if (log_show_color_from_string(word + 18) < 0) + log_warning("Failed to parse log color setting %s. Ignoring.", word + 18); + + } else if (startswith(word, "systemd.log_location=")) { + + if (log_show_location_from_string(word + 21) < 0) + log_warning("Failed to parse log location setting %s. Ignoring.", word + 21); + + } else if (startswith(word, "systemd.dump_core=")) { + int r; + + if ((r = parse_boolean(word + 18)) < 0) + log_warning("Failed to parse dump core switch %s. Ignoring.", word + 18); + else + arg_dump_core = r; + + } else if (startswith(word, "systemd.crash_shell=")) { + int r; + + if ((r = parse_boolean(word + 20)) < 0) + log_warning("Failed to parse crash shell switch %s. Ignoring.", word + 20); + else + arg_crash_shell = r; + + } else if (startswith(word, "systemd.confirm_spawn=")) { + int r; + + if ((r = parse_boolean(word + 22)) < 0) + log_warning("Failed to parse confirm spawn switch %s. Ignoring.", word + 22); + else + arg_confirm_spawn = r; + + } else if (startswith(word, "systemd.crash_chvt=")) { + int k; + + if (safe_atoi(word + 19, &k) < 0) + log_warning("Failed to parse crash chvt switch %s. Ignoring.", word + 19); + else + arg_crash_chvt = k; + + } else if (startswith(word, "systemd.show_status=")) { + int r; + + if ((r = parse_boolean(word + 20)) < 0) + log_warning("Failed to parse show status switch %s. Ignoring.", word + 20); + else + arg_show_status = r; + } else if (startswith(word, "systemd.default_standard_output=")) { + int r; + + if ((r = exec_output_from_string(word + 32)) < 0) + log_warning("Failed to parse default standard output switch %s. Ignoring.", word + 32); + else + arg_default_std_output = r; + } else if (startswith(word, "systemd.default_standard_error=")) { + int r; + + if ((r = exec_output_from_string(word + 31)) < 0) + log_warning("Failed to parse default standard error switch %s. Ignoring.", word + 31); + else + arg_default_std_error = r; + } else if (startswith(word, "systemd.setenv=")) { + char *cenv, *eq; + int r; + + cenv = strdup(word + 15); + if (!cenv) + return -ENOMEM; + + eq = strchr(cenv, '='); + if (!eq) { + r = unsetenv(cenv); + if (r < 0) + log_warning("unsetenv failed %s. Ignoring.", strerror(errno)); + } else { + *eq = 0; + r = setenv(cenv, eq + 1, 1); + if (r < 0) + log_warning("setenv failed %s. Ignoring.", strerror(errno)); + } + free(cenv); +#ifdef HAVE_SYSV_COMPAT + } else if (startswith(word, "systemd.sysv_console=")) { + int r; + + if ((r = parse_boolean(word + 21)) < 0) + log_warning("Failed to parse SysV console switch %s. Ignoring.", word + 20); + else + arg_sysv_console = r; +#endif + + } else if (startswith(word, "systemd.")) { + + log_warning("Unknown kernel switch %s. Ignoring.", word); + + log_info("Supported kernel switches:\n" + "systemd.unit=UNIT Default unit to start\n" + "systemd.dump_core=0|1 Dump core on crash\n" + "systemd.crash_shell=0|1 Run shell on crash\n" + "systemd.crash_chvt=N Change to VT #N on crash\n" + "systemd.confirm_spawn=0|1 Confirm every process spawn\n" + "systemd.show_status=0|1 Show status updates on the console during bootup\n" +#ifdef HAVE_SYSV_COMPAT + "systemd.sysv_console=0|1 Connect output of SysV scripts to console\n" +#endif + "systemd.log_target=console|kmsg|journal|journal-or-kmsg|syslog|syslog-or-kmsg|null\n" + " Log target\n" + "systemd.log_level=LEVEL Log level\n" + "systemd.log_color=0|1 Highlight important log messages\n" + "systemd.log_location=0|1 Include code location in log messages\n" + "systemd.default_standard_output=null|tty|syslog|syslog+console|kmsg|kmsg+console|journal|journal+console\n" + " Set default log output for services\n" + "systemd.default_standard_error=null|tty|syslog|syslog+console|kmsg|kmsg+console|journal|journal+console\n" + " Set default log error output for services\n"); + + } else if (streq(word, "quiet")) { + arg_show_status = false; +#ifdef HAVE_SYSV_COMPAT + arg_sysv_console = false; +#endif + } else { + unsigned i; + + /* SysV compatibility */ + for (i = 0; i < ELEMENTSOF(rlmap); i += 2) + if (streq(word, rlmap[i])) + return set_default_unit(rlmap[i+1]); + } + + return 0; +} + +static int config_parse_level2( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + assert(filename); + assert(lvalue); + assert(rvalue); + + log_set_max_level_from_string(rvalue); + return 0; +} + +static int config_parse_target( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + assert(filename); + assert(lvalue); + assert(rvalue); + + log_set_target_from_string(rvalue); + return 0; +} + +static int config_parse_color( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + assert(filename); + assert(lvalue); + assert(rvalue); + + log_show_color_from_string(rvalue); + return 0; +} + +static int config_parse_location( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + assert(filename); + assert(lvalue); + assert(rvalue); + + log_show_location_from_string(rvalue); + return 0; +} + +static int config_parse_cpu_affinity2( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char *w; + size_t l; + char *state; + cpu_set_t *c = NULL; + unsigned ncpus = 0; + + assert(filename); + assert(lvalue); + assert(rvalue); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t; + int r; + unsigned cpu; + + if (!(t = strndup(w, l))) + return -ENOMEM; + + r = safe_atou(t, &cpu); + free(t); + + if (!c) + if (!(c = cpu_set_malloc(&ncpus))) + return -ENOMEM; + + if (r < 0 || cpu >= ncpus) { + log_error("[%s:%u] Failed to parse CPU affinity: %s", filename, line, rvalue); + CPU_FREE(c); + return -EBADMSG; + } + + CPU_SET_S(cpu, CPU_ALLOC_SIZE(ncpus), c); + } + + if (c) { + if (sched_setaffinity(0, CPU_ALLOC_SIZE(ncpus), c) < 0) + log_warning("Failed to set CPU affinity: %m"); + + CPU_FREE(c); + } + + return 0; +} + +static void strv_free_free(char ***l) { + char ***i; + + if (!l) + return; + + for (i = l; *i; i++) + strv_free(*i); + + free(l); +} + +static void free_join_controllers(void) { + if (!arg_join_controllers) + return; + + strv_free_free(arg_join_controllers); + arg_join_controllers = NULL; +} + +static int config_parse_join_controllers( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + unsigned n = 0; + char *state, *w; + size_t length; + + assert(filename); + assert(lvalue); + assert(rvalue); + + free_join_controllers(); + + FOREACH_WORD_QUOTED(w, length, rvalue, state) { + char *s, **l; + + s = strndup(w, length); + if (!s) + return -ENOMEM; + + l = strv_split(s, ","); + free(s); + + strv_uniq(l); + + if (strv_length(l) <= 1) { + strv_free(l); + continue; + } + + if (!arg_join_controllers) { + arg_join_controllers = new(char**, 2); + if (!arg_join_controllers) { + strv_free(l); + return -ENOMEM; + } + + arg_join_controllers[0] = l; + arg_join_controllers[1] = NULL; + + n = 1; + } else { + char ***a; + char ***t; + + t = new0(char**, n+2); + if (!t) { + strv_free(l); + return -ENOMEM; + } + + n = 0; + + for (a = arg_join_controllers; *a; a++) { + + if (strv_overlap(*a, l)) { + char **c; + + c = strv_merge(*a, l); + if (!c) { + strv_free(l); + strv_free_free(t); + return -ENOMEM; + } + + strv_free(l); + l = c; + } else { + char **c; + + c = strv_copy(*a); + if (!c) { + strv_free(l); + strv_free_free(t); + return -ENOMEM; + } + + t[n++] = c; + } + } + + t[n++] = strv_uniq(l); + + strv_free_free(arg_join_controllers); + arg_join_controllers = t; + } + } + + return 0; +} + +static int parse_config_file(void) { + + const ConfigTableItem items[] = { + { "Manager", "LogLevel", config_parse_level2, 0, NULL }, + { "Manager", "LogTarget", config_parse_target, 0, NULL }, + { "Manager", "LogColor", config_parse_color, 0, NULL }, + { "Manager", "LogLocation", config_parse_location, 0, NULL }, + { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, + { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, + { "Manager", "ShowStatus", config_parse_bool, 0, &arg_show_status }, +#ifdef HAVE_SYSV_COMPAT + { "Manager", "SysVConsole", config_parse_bool, 0, &arg_sysv_console }, +#endif + { "Manager", "CrashChVT", config_parse_int, 0, &arg_crash_chvt }, + { "Manager", "CPUAffinity", config_parse_cpu_affinity2, 0, NULL }, + { "Manager", "MountAuto", config_parse_bool, 0, &arg_mount_auto }, + { "Manager", "SwapAuto", config_parse_bool, 0, &arg_swap_auto }, + { "Manager", "DefaultControllers", config_parse_strv, 0, &arg_default_controllers }, + { "Manager", "DefaultStandardOutput", config_parse_output, 0, &arg_default_std_output }, + { "Manager", "DefaultStandardError", config_parse_output, 0, &arg_default_std_error }, + { "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers }, + { NULL, NULL, NULL, 0, NULL } + }; + + FILE *f; + const char *fn; + int r; + + fn = arg_running_as == MANAGER_SYSTEM ? SYSTEM_CONFIG_FILE : USER_CONFIG_FILE; + f = fopen(fn, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + log_warning("Failed to open configuration file '%s': %m", fn); + return 0; + } + + r = config_parse(fn, f, "Manager\0", config_item_table_lookup, (void*) items, false, NULL); + if (r < 0) + log_warning("Failed to parse configuration file: %s", strerror(-r)); + + fclose(f); + + return 0; +} + +static int parse_proc_cmdline(void) { + char *line, *w, *state; + int r; + size_t l; + + /* Don't read /proc/cmdline if we are in a container, since + * that is only relevant for the host system */ + if (detect_container(NULL) > 0) + return 0; + + if ((r = read_one_line_file("/proc/cmdline", &line)) < 0) { + log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r)); + return 0; + } + + FOREACH_WORD_QUOTED(w, l, line, state) { + char *word; + + if (!(word = strndup(w, l))) { + r = -ENOMEM; + goto finish; + } + + r = parse_proc_cmdline_word(word); + free(word); + + if (r < 0) + goto finish; + } + + r = 0; + +finish: + free(line); + return r; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_LOG_LEVEL = 0x100, + ARG_LOG_TARGET, + ARG_LOG_COLOR, + ARG_LOG_LOCATION, + ARG_UNIT, + ARG_SYSTEM, + ARG_USER, + ARG_TEST, + ARG_DUMP_CONFIGURATION_ITEMS, + ARG_DUMP_CORE, + ARG_CRASH_SHELL, + ARG_CONFIRM_SPAWN, + ARG_SHOW_STATUS, + ARG_SYSV_CONSOLE, + ARG_DESERIALIZE, + ARG_INTROSPECT, + ARG_DEFAULT_STD_OUTPUT, + ARG_DEFAULT_STD_ERROR + }; + + static const struct option options[] = { + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-target", required_argument, NULL, ARG_LOG_TARGET }, + { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, + { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, + { "unit", required_argument, NULL, ARG_UNIT }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "test", no_argument, NULL, ARG_TEST }, + { "help", no_argument, NULL, 'h' }, + { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, + { "dump-core", no_argument, NULL, ARG_DUMP_CORE }, + { "crash-shell", no_argument, NULL, ARG_CRASH_SHELL }, + { "confirm-spawn", no_argument, NULL, ARG_CONFIRM_SPAWN }, + { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, +#ifdef HAVE_SYSV_COMPAT + { "sysv-console", optional_argument, NULL, ARG_SYSV_CONSOLE }, +#endif + { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, + { "introspect", optional_argument, NULL, ARG_INTROSPECT }, + { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, + { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, + { NULL, 0, NULL, 0 } + }; + + int c, r; + + assert(argc >= 1); + assert(argv); + + if (getpid() == 1) + opterr = 0; + + while ((c = getopt_long(argc, argv, "hDbsz:", options, NULL)) >= 0) + + switch (c) { + + case ARG_LOG_LEVEL: + if ((r = log_set_max_level_from_string(optarg)) < 0) { + log_error("Failed to parse log level %s.", optarg); + return r; + } + + break; + + case ARG_LOG_TARGET: + + if ((r = log_set_target_from_string(optarg)) < 0) { + log_error("Failed to parse log target %s.", optarg); + return r; + } + + break; + + case ARG_LOG_COLOR: + + if (optarg) { + if ((r = log_show_color_from_string(optarg)) < 0) { + log_error("Failed to parse log color setting %s.", optarg); + return r; + } + } else + log_show_color(true); + + break; + + case ARG_LOG_LOCATION: + + if (optarg) { + if ((r = log_show_location_from_string(optarg)) < 0) { + log_error("Failed to parse log location setting %s.", optarg); + return r; + } + } else + log_show_location(true); + + break; + + case ARG_DEFAULT_STD_OUTPUT: + + if ((r = exec_output_from_string(optarg)) < 0) { + log_error("Failed to parse default standard output setting %s.", optarg); + return r; + } else + arg_default_std_output = r; + break; + + case ARG_DEFAULT_STD_ERROR: + + if ((r = exec_output_from_string(optarg)) < 0) { + log_error("Failed to parse default standard error output setting %s.", optarg); + return r; + } else + arg_default_std_error = r; + break; + + case ARG_UNIT: + + if ((r = set_default_unit(optarg)) < 0) { + log_error("Failed to set default unit %s: %s", optarg, strerror(-r)); + return r; + } + + break; + + case ARG_SYSTEM: + arg_running_as = MANAGER_SYSTEM; + break; + + case ARG_USER: + arg_running_as = MANAGER_USER; + break; + + case ARG_TEST: + arg_action = ACTION_TEST; + break; + + case ARG_DUMP_CONFIGURATION_ITEMS: + arg_action = ACTION_DUMP_CONFIGURATION_ITEMS; + break; + + case ARG_DUMP_CORE: + arg_dump_core = true; + break; + + case ARG_CRASH_SHELL: + arg_crash_shell = true; + break; + + case ARG_CONFIRM_SPAWN: + arg_confirm_spawn = true; + break; + + case ARG_SHOW_STATUS: + + if (optarg) { + if ((r = parse_boolean(optarg)) < 0) { + log_error("Failed to show status boolean %s.", optarg); + return r; + } + arg_show_status = r; + } else + arg_show_status = true; + break; +#ifdef HAVE_SYSV_COMPAT + case ARG_SYSV_CONSOLE: + + if (optarg) { + if ((r = parse_boolean(optarg)) < 0) { + log_error("Failed to SysV console boolean %s.", optarg); + return r; + } + arg_sysv_console = r; + } else + arg_sysv_console = true; + break; +#endif + + case ARG_DESERIALIZE: { + int fd; + FILE *f; + + if ((r = safe_atoi(optarg, &fd)) < 0 || fd < 0) { + log_error("Failed to parse deserialize option %s.", optarg); + return r; + } + + if (!(f = fdopen(fd, "r"))) { + log_error("Failed to open serialization fd: %m"); + return r; + } + + if (serialization) + fclose(serialization); + + serialization = f; + + break; + } + + case ARG_INTROSPECT: { + const char * const * i = NULL; + + for (i = bus_interface_table; *i; i += 2) + if (!optarg || streq(i[0], optarg)) { + fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "\n", stdout); + fputs(i[1], stdout); + fputs("\n", stdout); + + if (optarg) + break; + } + + if (!i[0] && optarg) + log_error("Unknown interface %s.", optarg); + + arg_action = ACTION_DONE; + break; + } + + case 'h': + arg_action = ACTION_HELP; + break; + + case 'D': + log_set_max_level(LOG_DEBUG); + break; + + case 'b': + case 's': + case 'z': + /* Just to eat away the sysvinit kernel + * cmdline args without getopt() error + * messages that we'll parse in + * parse_proc_cmdline_word() or ignore. */ + + case '?': + default: + if (getpid() != 1) { + log_error("Unknown option code %c", c); + return -EINVAL; + } + + break; + } + + if (optind < argc && getpid() != 1) { + /* Hmm, when we aren't run as init system + * let's complain about excess arguments */ + + log_error("Excess arguments."); + return -EINVAL; + } + + if (detect_container(NULL) > 0) { + char **a; + + /* All /proc/cmdline arguments the kernel didn't + * understand it passed to us. We're not really + * interested in that usually since /proc/cmdline is + * more interesting and complete. With one exception: + * if we are run in a container /proc/cmdline is not + * relevant for the container, hence we rely on argv[] + * instead. */ + + for (a = argv; a < argv + argc; a++) + if ((r = parse_proc_cmdline_word(*a)) < 0) + return r; + } + + return 0; +} + +static int help(void) { + + printf("%s [OPTIONS...]\n\n" + "Starts up and maintains the system or user services.\n\n" + " -h --help Show this help\n" + " --test Determine startup sequence, dump it and exit\n" + " --dump-configuration-items Dump understood unit configuration items\n" + " --introspect[=INTERFACE] Extract D-Bus interface data\n" + " --unit=UNIT Set default unit\n" + " --system Run a system instance, even if PID != 1\n" + " --user Run a user instance\n" + " --dump-core Dump core on crash\n" + " --crash-shell Run shell on crash\n" + " --confirm-spawn Ask for confirmation when spawning processes\n" + " --show-status[=0|1] Show status updates on the console during bootup\n" +#ifdef HAVE_SYSV_COMPAT + " --sysv-console[=0|1] Connect output of SysV scripts to console\n" +#endif + " --log-target=TARGET Set log target (console, journal, syslog, kmsg, journal-or-kmsg, syslog-or-kmsg, null)\n" + " --log-level=LEVEL Set log level (debug, info, notice, warning, err, crit, alert, emerg)\n" + " --log-color[=0|1] Highlight important log messages\n" + " --log-location[=0|1] Include code location in log messages\n" + " --default-standard-output= Set default standard output for services\n" + " --default-standard-error= Set default standard error output for services\n", + program_invocation_short_name); + + return 0; +} + +static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds) { + FILE *f = NULL; + FDSet *fds = NULL; + int r; + + assert(m); + assert(_f); + assert(_fds); + + /* Make sure nothing is really destructed when we shut down */ + m->n_reloading ++; + + if ((r = manager_open_serialization(m, &f)) < 0) { + log_error("Failed to create serialization file: %s", strerror(-r)); + goto fail; + } + + if (!(fds = fdset_new())) { + r = -ENOMEM; + log_error("Failed to allocate fd set: %s", strerror(-r)); + goto fail; + } + + if ((r = manager_serialize(m, f, fds)) < 0) { + log_error("Failed to serialize state: %s", strerror(-r)); + goto fail; + } + + if (fseeko(f, 0, SEEK_SET) < 0) { + log_error("Failed to rewind serialization fd: %m"); + goto fail; + } + + if ((r = fd_cloexec(fileno(f), false)) < 0) { + log_error("Failed to disable O_CLOEXEC for serialization: %s", strerror(-r)); + goto fail; + } + + if ((r = fdset_cloexec(fds, false)) < 0) { + log_error("Failed to disable O_CLOEXEC for serialization fds: %s", strerror(-r)); + goto fail; + } + + *_f = f; + *_fds = fds; + + return 0; + +fail: + fdset_free(fds); + + if (f) + fclose(f); + + return r; +} + +static struct dual_timestamp* parse_initrd_timestamp(struct dual_timestamp *t) { + const char *e; + unsigned long long a, b; + + assert(t); + + if (!(e = getenv("RD_TIMESTAMP"))) + return NULL; + + if (sscanf(e, "%llu %llu", &a, &b) != 2) + return NULL; + + t->realtime = (usec_t) a; + t->monotonic = (usec_t) b; + + return t; +} + +static void test_mtab(void) { + char *p; + + /* Check that /etc/mtab is a symlink */ + + if (readlink_malloc("/etc/mtab", &p) >= 0) { + bool b; + + b = streq(p, "/proc/self/mounts") || streq(p, "/proc/mounts"); + free(p); + + if (b) + return; + } + + log_warning("/etc/mtab is not a symlink or not pointing to /proc/self/mounts. " + "This is not supported anymore. " + "Please make sure to replace this file by a symlink to avoid incorrect or misleading mount(8) output."); +} + +static void test_usr(void) { + + /* Check that /usr is not a separate fs */ + + if (dir_is_empty("/usr") <= 0) + return; + + log_warning("/usr appears to be on its own filesytem and is not already mounted. This is not a supported setup. " + "Some things will probably break (sometimes even silently) in mysterious ways. " + "Consult http://freedesktop.org/wiki/Software/systemd/separate-usr-is-broken for more information."); +} + +static void test_cgroups(void) { + + if (access("/proc/cgroups", F_OK) >= 0) + return; + + log_warning("CONFIG_CGROUPS was not set when your kernel was compiled. " + "Systems without control groups are not supported. " + "We will now sleep for 10s, and then continue boot-up. " + "Expect breakage and please do not file bugs. " + "Instead fix your kernel and enable CONFIG_CGROUPS." ); + + sleep(10); +} + +int main(int argc, char *argv[]) { + Manager *m = NULL; + int r, retval = EXIT_FAILURE; + usec_t before_startup, after_startup; + char timespan[FORMAT_TIMESPAN_MAX]; + FDSet *fds = NULL; + bool reexecute = false; + const char *shutdown_verb = NULL; + dual_timestamp initrd_timestamp = { 0ULL, 0ULL }; + static char systemd[] = "systemd"; + bool is_reexec = false; + int j; + bool loaded_policy = false; + +#ifdef HAVE_SYSV_COMPAT + if (getpid() != 1 && strstr(program_invocation_short_name, "init")) { + /* This is compatibility support for SysV, where + * calling init as a user is identical to telinit. */ + + errno = -ENOENT; + execv(SYSTEMCTL_BINARY_PATH, argv); + log_error("Failed to exec " SYSTEMCTL_BINARY_PATH ": %m"); + return 1; + } +#endif + + /* Determine if this is a reexecution or normal bootup. We do + * the full command line parsing much later, so let's just + * have a quick peek here. */ + + for (j = 1; j < argc; j++) + if (streq(argv[j], "--deserialize")) { + is_reexec = true; + break; + } + + /* If we get started via the /sbin/init symlink then we are + called 'init'. After a subsequent reexecution we are then + called 'systemd'. That is confusing, hence let's call us + systemd right-away. */ + program_invocation_short_name = systemd; + prctl(PR_SET_NAME, systemd); + + saved_argv = argv; + saved_argc = argc; + + log_show_color(isatty(STDERR_FILENO) > 0); + log_show_location(false); + log_set_max_level(LOG_INFO); + + if (getpid() == 1) { + arg_running_as = MANAGER_SYSTEM; + log_set_target(detect_container(NULL) > 0 ? LOG_TARGET_CONSOLE : LOG_TARGET_JOURNAL_OR_KMSG); + + if (!is_reexec) { + if (selinux_setup(&loaded_policy) < 0) + goto finish; + if (ima_setup() < 0) + goto finish; + } + + log_open(); + + if (label_init() < 0) + goto finish; + + if (!is_reexec) + if (hwclock_is_localtime() > 0) { + int min; + + r = hwclock_apply_localtime_delta(&min); + if (r < 0) + log_error("Failed to apply local time delta, ignoring: %s", strerror(-r)); + else + log_info("RTC configured in localtime, applying delta of %i minutes to system time.", min); + } + + } else { + arg_running_as = MANAGER_USER; + log_set_target(LOG_TARGET_AUTO); + log_open(); + } + + /* Initialize default unit */ + if (set_default_unit(SPECIAL_DEFAULT_TARGET) < 0) + goto finish; + + /* By default, mount "cpu" and "cpuacct" together */ + arg_join_controllers = new(char**, 2); + if (!arg_join_controllers) + goto finish; + + arg_join_controllers[0] = strv_new("cpu", "cpuacct", NULL); + arg_join_controllers[1] = NULL; + + if (!arg_join_controllers[0]) + goto finish; + + /* Mount /proc, /sys and friends, so that /proc/cmdline and + * /proc/$PID/fd is available. */ + if (geteuid() == 0 && !getenv("SYSTEMD_SKIP_API_MOUNTS")) { + r = mount_setup(loaded_policy); + if (r < 0) + goto finish; + } + + /* Reset all signal handlers. */ + assert_se(reset_all_signal_handlers() == 0); + + /* If we are init, we can block sigkill. Yay. */ + ignore_signals(SIGNALS_IGNORE, -1); + + if (parse_config_file() < 0) + goto finish; + + if (arg_running_as == MANAGER_SYSTEM) + if (parse_proc_cmdline() < 0) + goto finish; + + log_parse_environment(); + + if (parse_argv(argc, argv) < 0) + goto finish; + + if (arg_action == ACTION_TEST && geteuid() == 0) { + log_error("Don't run test mode as root."); + goto finish; + } + + if (arg_running_as == MANAGER_SYSTEM && + arg_action == ACTION_RUN && + running_in_chroot() > 0) { + log_error("Cannot be run in a chroot() environment."); + goto finish; + } + + if (arg_action == ACTION_HELP) { + retval = help(); + goto finish; + } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) { + unit_dump_config_items(stdout); + retval = EXIT_SUCCESS; + goto finish; + } else if (arg_action == ACTION_DONE) { + retval = EXIT_SUCCESS; + goto finish; + } + + assert_se(arg_action == ACTION_RUN || arg_action == ACTION_TEST); + + /* Close logging fds, in order not to confuse fdset below */ + log_close(); + + /* Remember open file descriptors for later deserialization */ + if (serialization) { + if ((r = fdset_new_fill(&fds)) < 0) { + log_error("Failed to allocate fd set: %s", strerror(-r)); + goto finish; + } + + assert_se(fdset_remove(fds, fileno(serialization)) >= 0); + } else + close_all_fds(NULL, 0); + + /* Set up PATH unless it is already set */ + setenv("PATH", +#ifdef HAVE_SPLIT_USR + "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", +#else + "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin", +#endif + arg_running_as == MANAGER_SYSTEM); + + if (arg_running_as == MANAGER_SYSTEM) { + /* Parse the data passed to us by the initrd and unset it */ + parse_initrd_timestamp(&initrd_timestamp); + filter_environ("RD_"); + + /* Unset some environment variables passed in from the + * kernel that don't really make sense for us. */ + unsetenv("HOME"); + unsetenv("TERM"); + + /* All other variables are left as is, so that clients + * can still read them via /proc/1/environ */ + } + + /* Move out of the way, so that we won't block unmounts */ + assert_se(chdir("/") == 0); + + if (arg_running_as == MANAGER_SYSTEM) { + /* Become a session leader if we aren't one yet. */ + setsid(); + + /* Disable the umask logic */ + umask(0); + } + + /* Make sure D-Bus doesn't fiddle with the SIGPIPE handlers */ + dbus_connection_set_change_sigpipe(FALSE); + + /* Reset the console, but only if this is really init and we + * are freshly booted */ + if (arg_running_as == MANAGER_SYSTEM && arg_action == ACTION_RUN) { + console_setup(getpid() == 1 && !is_reexec); + make_null_stdio(); + } + + /* Open the logging devices, if possible and necessary */ + log_open(); + + /* Make sure we leave a core dump without panicing the + * kernel. */ + if (getpid() == 1) + install_crash_handler(); + + if (geteuid() == 0 && !getenv("SYSTEMD_SKIP_API_MOUNTS")) { + r = mount_cgroup_controllers(arg_join_controllers); + if (r < 0) + goto finish; + } + + log_full(arg_running_as == MANAGER_SYSTEM ? LOG_INFO : LOG_DEBUG, + PACKAGE_STRING " running in %s mode. (" SYSTEMD_FEATURES "; " DISTRIBUTION ")", manager_running_as_to_string(arg_running_as)); + + if (arg_running_as == MANAGER_SYSTEM && !is_reexec) { + locale_setup(); + + if (arg_show_status || plymouth_running()) + status_welcome(); + + kmod_setup(); + hostname_setup(); + machine_id_setup(); + loopback_setup(); + + test_mtab(); + test_usr(); + test_cgroups(); + } + + if ((r = manager_new(arg_running_as, &m)) < 0) { + log_error("Failed to allocate manager object: %s", strerror(-r)); + goto finish; + } + + m->confirm_spawn = arg_confirm_spawn; +#ifdef HAVE_SYSV_COMPAT + m->sysv_console = arg_sysv_console; +#endif + m->mount_auto = arg_mount_auto; + m->swap_auto = arg_swap_auto; + m->default_std_output = arg_default_std_output; + m->default_std_error = arg_default_std_error; + + if (dual_timestamp_is_set(&initrd_timestamp)) + m->initrd_timestamp = initrd_timestamp; + + if (arg_default_controllers) + manager_set_default_controllers(m, arg_default_controllers); + + manager_set_show_status(m, arg_show_status); + + before_startup = now(CLOCK_MONOTONIC); + + if ((r = manager_startup(m, serialization, fds)) < 0) + log_error("Failed to fully start up daemon: %s", strerror(-r)); + + if (fds) { + /* This will close all file descriptors that were opened, but + * not claimed by any unit. */ + + fdset_free(fds); + fds = NULL; + } + + if (serialization) { + fclose(serialization); + serialization = NULL; + } else { + DBusError error; + Unit *target = NULL; + Job *default_unit_job; + + dbus_error_init(&error); + + log_debug("Activating default unit: %s", arg_default_unit); + + if ((r = manager_load_unit(m, arg_default_unit, NULL, &error, &target)) < 0) { + log_error("Failed to load default target: %s", bus_error(&error, r)); + dbus_error_free(&error); + } else if (target->load_state == UNIT_ERROR) + log_error("Failed to load default target: %s", strerror(-target->load_error)); + else if (target->load_state == UNIT_MASKED) + log_error("Default target masked."); + + if (!target || target->load_state != UNIT_LOADED) { + log_info("Trying to load rescue target..."); + + if ((r = manager_load_unit(m, SPECIAL_RESCUE_TARGET, NULL, &error, &target)) < 0) { + log_error("Failed to load rescue target: %s", bus_error(&error, r)); + dbus_error_free(&error); + goto finish; + } else if (target->load_state == UNIT_ERROR) { + log_error("Failed to load rescue target: %s", strerror(-target->load_error)); + goto finish; + } else if (target->load_state == UNIT_MASKED) { + log_error("Rescue target masked."); + goto finish; + } + } + + assert(target->load_state == UNIT_LOADED); + + if (arg_action == ACTION_TEST) { + printf("-> By units:\n"); + manager_dump_units(m, stdout, "\t"); + } + + r = manager_add_job(m, JOB_START, target, JOB_REPLACE, false, &error, &default_unit_job); + if (r < 0) { + log_error("Failed to start default target: %s", bus_error(&error, r)); + dbus_error_free(&error); + goto finish; + } + m->default_unit_job_id = default_unit_job->id; + + after_startup = now(CLOCK_MONOTONIC); + log_full(arg_action == ACTION_TEST ? LOG_INFO : LOG_DEBUG, + "Loaded units and determined initial transaction in %s.", + format_timespan(timespan, sizeof(timespan), after_startup - before_startup)); + + if (arg_action == ACTION_TEST) { + printf("-> By jobs:\n"); + manager_dump_jobs(m, stdout, "\t"); + retval = EXIT_SUCCESS; + goto finish; + } + } + + for (;;) { + if ((r = manager_loop(m)) < 0) { + log_error("Failed to run mainloop: %s", strerror(-r)); + goto finish; + } + + switch (m->exit_code) { + + case MANAGER_EXIT: + retval = EXIT_SUCCESS; + log_debug("Exit."); + goto finish; + + case MANAGER_RELOAD: + log_info("Reloading."); + if ((r = manager_reload(m)) < 0) + log_error("Failed to reload: %s", strerror(-r)); + break; + + case MANAGER_REEXECUTE: + if (prepare_reexecute(m, &serialization, &fds) < 0) + goto finish; + + reexecute = true; + log_notice("Reexecuting."); + goto finish; + + case MANAGER_REBOOT: + case MANAGER_POWEROFF: + case MANAGER_HALT: + case MANAGER_KEXEC: { + static const char * const table[_MANAGER_EXIT_CODE_MAX] = { + [MANAGER_REBOOT] = "reboot", + [MANAGER_POWEROFF] = "poweroff", + [MANAGER_HALT] = "halt", + [MANAGER_KEXEC] = "kexec" + }; + + assert_se(shutdown_verb = table[m->exit_code]); + + log_notice("Shutting down."); + goto finish; + } + + default: + assert_not_reached("Unknown exit code."); + } + } + +finish: + if (m) + manager_free(m); + + free(arg_default_unit); + strv_free(arg_default_controllers); + free_join_controllers(); + + dbus_shutdown(); + + label_finish(); + + if (reexecute) { + const char *args[15]; + unsigned i = 0; + char sfd[16]; + + assert(serialization); + assert(fds); + + args[i++] = SYSTEMD_BINARY_PATH; + + args[i++] = "--log-level"; + args[i++] = log_level_to_string(log_get_max_level()); + + args[i++] = "--log-target"; + args[i++] = log_target_to_string(log_get_target()); + + if (arg_running_as == MANAGER_SYSTEM) + args[i++] = "--system"; + else + args[i++] = "--user"; + + if (arg_dump_core) + args[i++] = "--dump-core"; + + if (arg_crash_shell) + args[i++] = "--crash-shell"; + + if (arg_confirm_spawn) + args[i++] = "--confirm-spawn"; + + if (arg_show_status) + args[i++] = "--show-status=1"; + else + args[i++] = "--show-status=0"; + +#ifdef HAVE_SYSV_COMPAT + if (arg_sysv_console) + args[i++] = "--sysv-console=1"; + else + args[i++] = "--sysv-console=0"; +#endif + + snprintf(sfd, sizeof(sfd), "%i", fileno(serialization)); + char_array_0(sfd); + + args[i++] = "--deserialize"; + args[i++] = sfd; + + args[i++] = NULL; + + assert(i <= ELEMENTSOF(args)); + + execv(args[0], (char* const*) args); + + log_error("Failed to reexecute: %m"); + } + + if (serialization) + fclose(serialization); + + if (fds) + fdset_free(fds); + + if (shutdown_verb) { + const char * command_line[] = { + SYSTEMD_SHUTDOWN_BINARY_PATH, + shutdown_verb, + NULL + }; + + execv(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line); + log_error("Failed to execute shutdown binary, freezing: %m"); + } + + if (getpid() == 1) + freeze(); + + return retval; +} diff --git a/src/manager.c b/src/manager.c new file mode 100644 index 000000000..74bd74074 --- /dev/null +++ b/src/manager.c @@ -0,0 +1,3199 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_AUDIT +#include +#endif + +#include + +#include "manager.h" +#include "hashmap.h" +#include "macro.h" +#include "strv.h" +#include "log.h" +#include "util.h" +#include "ratelimit.h" +#include "cgroup.h" +#include "mount-setup.h" +#include "unit-name.h" +#include "dbus-unit.h" +#include "dbus-job.h" +#include "missing.h" +#include "path-lookup.h" +#include "special.h" +#include "bus-errors.h" +#include "exit-status.h" +#include "virt.h" + +/* As soon as 16 units are in our GC queue, make sure to run a gc sweep */ +#define GC_QUEUE_ENTRIES_MAX 16 + +/* As soon as 5s passed since a unit was added to our GC queue, make sure to run a gc sweep */ +#define GC_QUEUE_USEC_MAX (10*USEC_PER_SEC) + +/* Where clients shall send notification messages to */ +#define NOTIFY_SOCKET_SYSTEM "/run/systemd/notify" +#define NOTIFY_SOCKET_USER "@/org/freedesktop/systemd1/notify" + +static int manager_setup_notify(Manager *m) { + union { + struct sockaddr sa; + struct sockaddr_un un; + } sa; + struct epoll_event ev; + int one = 1, r; + mode_t u; + + assert(m); + + m->notify_watch.type = WATCH_NOTIFY; + if ((m->notify_watch.fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) { + log_error("Failed to allocate notification socket: %m"); + return -errno; + } + + zero(sa); + sa.sa.sa_family = AF_UNIX; + + if (getpid() != 1) + snprintf(sa.un.sun_path, sizeof(sa.un.sun_path), NOTIFY_SOCKET_USER "/%llu", random_ull()); + else { + unlink(NOTIFY_SOCKET_SYSTEM); + strncpy(sa.un.sun_path, NOTIFY_SOCKET_SYSTEM, sizeof(sa.un.sun_path)); + } + + if (sa.un.sun_path[0] == '@') + sa.un.sun_path[0] = 0; + + u = umask(0111); + r = bind(m->notify_watch.fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)); + umask(u); + + if (r < 0) { + log_error("bind() failed: %m"); + return -errno; + } + + if (setsockopt(m->notify_watch.fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) { + log_error("SO_PASSCRED failed: %m"); + return -errno; + } + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = &m->notify_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->notify_watch.fd, &ev) < 0) + return -errno; + + if (sa.un.sun_path[0] == 0) + sa.un.sun_path[0] = '@'; + + if (!(m->notify_socket = strdup(sa.un.sun_path))) + return -ENOMEM; + + log_debug("Using notification socket %s", m->notify_socket); + + return 0; +} + +static int enable_special_signals(Manager *m) { + int fd; + + assert(m); + + /* Enable that we get SIGINT on control-alt-del */ + if (reboot(RB_DISABLE_CAD) < 0) + log_warning("Failed to enable ctrl-alt-del handling: %m"); + + if ((fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0) + log_warning("Failed to open /dev/tty0: %m"); + else { + /* Enable that we get SIGWINCH on kbrequest */ + if (ioctl(fd, KDSIGACCEPT, SIGWINCH) < 0) + log_warning("Failed to enable kbrequest handling: %s", strerror(errno)); + + close_nointr_nofail(fd); + } + + return 0; +} + +static int manager_setup_signals(Manager *m) { + sigset_t mask; + struct epoll_event ev; + struct sigaction sa; + + assert(m); + + /* We are not interested in SIGSTOP and friends. */ + zero(sa); + sa.sa_handler = SIG_DFL; + sa.sa_flags = SA_NOCLDSTOP|SA_RESTART; + assert_se(sigaction(SIGCHLD, &sa, NULL) == 0); + + assert_se(sigemptyset(&mask) == 0); + + sigset_add_many(&mask, + SIGCHLD, /* Child died */ + SIGTERM, /* Reexecute daemon */ + SIGHUP, /* Reload configuration */ + SIGUSR1, /* systemd/upstart: reconnect to D-Bus */ + SIGUSR2, /* systemd: dump status */ + SIGINT, /* Kernel sends us this on control-alt-del */ + SIGWINCH, /* Kernel sends us this on kbrequest (alt-arrowup) */ + SIGPWR, /* Some kernel drivers and upsd send us this on power failure */ + SIGRTMIN+0, /* systemd: start default.target */ + SIGRTMIN+1, /* systemd: isolate rescue.target */ + SIGRTMIN+2, /* systemd: isolate emergency.target */ + SIGRTMIN+3, /* systemd: start halt.target */ + SIGRTMIN+4, /* systemd: start poweroff.target */ + SIGRTMIN+5, /* systemd: start reboot.target */ + SIGRTMIN+6, /* systemd: start kexec.target */ + SIGRTMIN+13, /* systemd: Immediate halt */ + SIGRTMIN+14, /* systemd: Immediate poweroff */ + SIGRTMIN+15, /* systemd: Immediate reboot */ + SIGRTMIN+16, /* systemd: Immediate kexec */ + SIGRTMIN+20, /* systemd: enable status messages */ + SIGRTMIN+21, /* systemd: disable status messages */ + SIGRTMIN+22, /* systemd: set log level to LOG_DEBUG */ + SIGRTMIN+23, /* systemd: set log level to LOG_INFO */ + SIGRTMIN+26, /* systemd: set log target to journal-or-kmsg */ + SIGRTMIN+27, /* systemd: set log target to console */ + SIGRTMIN+28, /* systemd: set log target to kmsg */ + SIGRTMIN+29, /* systemd: set log target to syslog-or-kmsg */ + -1); + assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); + + m->signal_watch.type = WATCH_SIGNAL; + if ((m->signal_watch.fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) + return -errno; + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = &m->signal_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->signal_watch.fd, &ev) < 0) + return -errno; + + if (m->running_as == MANAGER_SYSTEM) + return enable_special_signals(m); + + return 0; +} + +int manager_new(ManagerRunningAs running_as, Manager **_m) { + Manager *m; + int r = -ENOMEM; + + assert(_m); + assert(running_as >= 0); + assert(running_as < _MANAGER_RUNNING_AS_MAX); + + if (!(m = new0(Manager, 1))) + return -ENOMEM; + + dual_timestamp_get(&m->startup_timestamp); + + m->running_as = running_as; + m->name_data_slot = m->conn_data_slot = m->subscribed_data_slot = -1; + m->exit_code = _MANAGER_EXIT_CODE_INVALID; + m->pin_cgroupfs_fd = -1; + +#ifdef HAVE_AUDIT + m->audit_fd = -1; +#endif + + m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = m->dev_autofs_fd = m->swap_watch.fd = -1; + m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ + + if (!(m->environment = strv_copy(environ))) + goto fail; + + if (running_as == MANAGER_SYSTEM) { + m->default_controllers = strv_new("cpu", NULL); + if (!m->default_controllers) + goto fail; + } + + if (!(m->units = hashmap_new(string_hash_func, string_compare_func))) + goto fail; + + if (!(m->jobs = hashmap_new(trivial_hash_func, trivial_compare_func))) + goto fail; + + if (!(m->transaction_jobs = hashmap_new(trivial_hash_func, trivial_compare_func))) + goto fail; + + if (!(m->watch_pids = hashmap_new(trivial_hash_func, trivial_compare_func))) + goto fail; + + if (!(m->cgroup_bondings = hashmap_new(string_hash_func, string_compare_func))) + goto fail; + + if (!(m->watch_bus = hashmap_new(string_hash_func, string_compare_func))) + goto fail; + + if ((m->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) + goto fail; + + if ((r = lookup_paths_init(&m->lookup_paths, m->running_as, true)) < 0) + goto fail; + + if ((r = manager_setup_signals(m)) < 0) + goto fail; + + if ((r = manager_setup_cgroup(m)) < 0) + goto fail; + + if ((r = manager_setup_notify(m)) < 0) + goto fail; + + /* Try to connect to the busses, if possible. */ + if ((r = bus_init(m, running_as != MANAGER_SYSTEM)) < 0) + goto fail; + +#ifdef HAVE_AUDIT + if ((m->audit_fd = audit_open()) < 0 && + /* If the kernel lacks netlink or audit support, + * don't worry about it. */ + errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT) + log_error("Failed to connect to audit log: %m"); +#endif + + m->taint_usr = dir_is_empty("/usr") > 0; + + *_m = m; + return 0; + +fail: + manager_free(m); + return r; +} + +static unsigned manager_dispatch_cleanup_queue(Manager *m) { + Unit *u; + unsigned n = 0; + + assert(m); + + while ((u = m->cleanup_queue)) { + assert(u->in_cleanup_queue); + + unit_free(u); + n++; + } + + return n; +} + +enum { + GC_OFFSET_IN_PATH, /* This one is on the path we were traveling */ + GC_OFFSET_UNSURE, /* No clue */ + GC_OFFSET_GOOD, /* We still need this unit */ + GC_OFFSET_BAD, /* We don't need this unit anymore */ + _GC_OFFSET_MAX +}; + +static void unit_gc_sweep(Unit *u, unsigned gc_marker) { + Iterator i; + Unit *other; + bool is_bad; + + assert(u); + + if (u->gc_marker == gc_marker + GC_OFFSET_GOOD || + u->gc_marker == gc_marker + GC_OFFSET_BAD || + u->gc_marker == gc_marker + GC_OFFSET_IN_PATH) + return; + + if (u->in_cleanup_queue) + goto bad; + + if (unit_check_gc(u)) + goto good; + + u->gc_marker = gc_marker + GC_OFFSET_IN_PATH; + + is_bad = true; + + SET_FOREACH(other, u->dependencies[UNIT_REFERENCED_BY], i) { + unit_gc_sweep(other, gc_marker); + + if (other->gc_marker == gc_marker + GC_OFFSET_GOOD) + goto good; + + if (other->gc_marker != gc_marker + GC_OFFSET_BAD) + is_bad = false; + } + + if (is_bad) + goto bad; + + /* We were unable to find anything out about this entry, so + * let's investigate it later */ + u->gc_marker = gc_marker + GC_OFFSET_UNSURE; + unit_add_to_gc_queue(u); + return; + +bad: + /* We definitely know that this one is not useful anymore, so + * let's mark it for deletion */ + u->gc_marker = gc_marker + GC_OFFSET_BAD; + unit_add_to_cleanup_queue(u); + return; + +good: + u->gc_marker = gc_marker + GC_OFFSET_GOOD; +} + +static unsigned manager_dispatch_gc_queue(Manager *m) { + Unit *u; + unsigned n = 0; + unsigned gc_marker; + + assert(m); + + if ((m->n_in_gc_queue < GC_QUEUE_ENTRIES_MAX) && + (m->gc_queue_timestamp <= 0 || + (m->gc_queue_timestamp + GC_QUEUE_USEC_MAX) > now(CLOCK_MONOTONIC))) + return 0; + + log_debug("Running GC..."); + + m->gc_marker += _GC_OFFSET_MAX; + if (m->gc_marker + _GC_OFFSET_MAX <= _GC_OFFSET_MAX) + m->gc_marker = 1; + + gc_marker = m->gc_marker; + + while ((u = m->gc_queue)) { + assert(u->in_gc_queue); + + unit_gc_sweep(u, gc_marker); + + LIST_REMOVE(Unit, gc_queue, m->gc_queue, u); + u->in_gc_queue = false; + + n++; + + if (u->gc_marker == gc_marker + GC_OFFSET_BAD || + u->gc_marker == gc_marker + GC_OFFSET_UNSURE) { + log_debug("Collecting %s", u->id); + u->gc_marker = gc_marker + GC_OFFSET_BAD; + unit_add_to_cleanup_queue(u); + } + } + + m->n_in_gc_queue = 0; + m->gc_queue_timestamp = 0; + + return n; +} + +static void manager_clear_jobs_and_units(Manager *m) { + Job *j; + Unit *u; + + assert(m); + + while ((j = hashmap_first(m->transaction_jobs))) + job_free(j); + + while ((u = hashmap_first(m->units))) + unit_free(u); + + manager_dispatch_cleanup_queue(m); + + assert(!m->load_queue); + assert(!m->run_queue); + assert(!m->dbus_unit_queue); + assert(!m->dbus_job_queue); + assert(!m->cleanup_queue); + assert(!m->gc_queue); + + assert(hashmap_isempty(m->transaction_jobs)); + assert(hashmap_isempty(m->jobs)); + assert(hashmap_isempty(m->units)); +} + +void manager_free(Manager *m) { + UnitType c; + + assert(m); + + manager_clear_jobs_and_units(m); + + for (c = 0; c < _UNIT_TYPE_MAX; c++) + if (unit_vtable[c]->shutdown) + unit_vtable[c]->shutdown(m); + + /* If we reexecute ourselves, we keep the root cgroup + * around */ + manager_shutdown_cgroup(m, m->exit_code != MANAGER_REEXECUTE); + + manager_undo_generators(m); + + bus_done(m); + + hashmap_free(m->units); + hashmap_free(m->jobs); + hashmap_free(m->transaction_jobs); + hashmap_free(m->watch_pids); + hashmap_free(m->watch_bus); + + if (m->epoll_fd >= 0) + close_nointr_nofail(m->epoll_fd); + if (m->signal_watch.fd >= 0) + close_nointr_nofail(m->signal_watch.fd); + if (m->notify_watch.fd >= 0) + close_nointr_nofail(m->notify_watch.fd); + +#ifdef HAVE_AUDIT + if (m->audit_fd >= 0) + audit_close(m->audit_fd); +#endif + + free(m->notify_socket); + + lookup_paths_free(&m->lookup_paths); + strv_free(m->environment); + + strv_free(m->default_controllers); + + hashmap_free(m->cgroup_bondings); + set_free_free(m->unit_path_cache); + + free(m); +} + +int manager_enumerate(Manager *m) { + int r = 0, q; + UnitType c; + + assert(m); + + /* Let's ask every type to load all units from disk/kernel + * that it might know */ + for (c = 0; c < _UNIT_TYPE_MAX; c++) + if (unit_vtable[c]->enumerate) + if ((q = unit_vtable[c]->enumerate(m)) < 0) + r = q; + + manager_dispatch_load_queue(m); + return r; +} + +int manager_coldplug(Manager *m) { + int r = 0, q; + Iterator i; + Unit *u; + char *k; + + assert(m); + + /* Then, let's set up their initial state. */ + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + + /* ignore aliases */ + if (u->id != k) + continue; + + if ((q = unit_coldplug(u)) < 0) + r = q; + } + + return r; +} + +static void manager_build_unit_path_cache(Manager *m) { + char **i; + DIR *d = NULL; + int r; + + assert(m); + + set_free_free(m->unit_path_cache); + + if (!(m->unit_path_cache = set_new(string_hash_func, string_compare_func))) { + log_error("Failed to allocate unit path cache."); + return; + } + + /* This simply builds a list of files we know exist, so that + * we don't always have to go to disk */ + + STRV_FOREACH(i, m->lookup_paths.unit_path) { + struct dirent *de; + + if (!(d = opendir(*i))) { + log_error("Failed to open directory: %m"); + continue; + } + + while ((de = readdir(d))) { + char *p; + + if (ignore_file(de->d_name)) + continue; + + p = join(streq(*i, "/") ? "" : *i, "/", de->d_name, NULL); + if (!p) { + r = -ENOMEM; + goto fail; + } + + if ((r = set_put(m->unit_path_cache, p)) < 0) { + free(p); + goto fail; + } + } + + closedir(d); + d = NULL; + } + + return; + +fail: + log_error("Failed to build unit path cache: %s", strerror(-r)); + + set_free_free(m->unit_path_cache); + m->unit_path_cache = NULL; + + if (d) + closedir(d); +} + +int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { + int r, q; + + assert(m); + + manager_run_generators(m); + + manager_build_unit_path_cache(m); + + /* If we will deserialize make sure that during enumeration + * this is already known, so we increase the counter here + * already */ + if (serialization) + m->n_reloading ++; + + /* First, enumerate what we can from all config files */ + r = manager_enumerate(m); + + /* Second, deserialize if there is something to deserialize */ + if (serialization) + if ((q = manager_deserialize(m, serialization, fds)) < 0) + r = q; + + /* Third, fire things up! */ + if ((q = manager_coldplug(m)) < 0) + r = q; + + if (serialization) { + assert(m->n_reloading > 0); + m->n_reloading --; + } + + return r; +} + +static void transaction_delete_job(Manager *m, Job *j, bool delete_dependencies) { + assert(m); + assert(j); + + /* Deletes one job from the transaction */ + + manager_transaction_unlink_job(m, j, delete_dependencies); + + if (!j->installed) + job_free(j); +} + +static void transaction_delete_unit(Manager *m, Unit *u) { + Job *j; + + /* Deletes all jobs associated with a certain unit from the + * transaction */ + + while ((j = hashmap_get(m->transaction_jobs, u))) + transaction_delete_job(m, j, true); +} + +static void transaction_clean_dependencies(Manager *m) { + Iterator i; + Job *j; + + assert(m); + + /* Drops all dependencies of all installed jobs */ + + HASHMAP_FOREACH(j, m->jobs, i) { + while (j->subject_list) + job_dependency_free(j->subject_list); + while (j->object_list) + job_dependency_free(j->object_list); + } + + assert(!m->transaction_anchor); +} + +static void transaction_abort(Manager *m) { + Job *j; + + assert(m); + + while ((j = hashmap_first(m->transaction_jobs))) + if (j->installed) + transaction_delete_job(m, j, true); + else + job_free(j); + + assert(hashmap_isempty(m->transaction_jobs)); + + transaction_clean_dependencies(m); +} + +static void transaction_find_jobs_that_matter_to_anchor(Manager *m, Job *j, unsigned generation) { + JobDependency *l; + + assert(m); + + /* A recursive sweep through the graph that marks all units + * that matter to the anchor job, i.e. are directly or + * indirectly a dependency of the anchor job via paths that + * are fully marked as mattering. */ + + if (j) + l = j->subject_list; + else + l = m->transaction_anchor; + + LIST_FOREACH(subject, l, l) { + + /* This link does not matter */ + if (!l->matters) + continue; + + /* This unit has already been marked */ + if (l->object->generation == generation) + continue; + + l->object->matters_to_anchor = true; + l->object->generation = generation; + + transaction_find_jobs_that_matter_to_anchor(m, l->object, generation); + } +} + +static void transaction_merge_and_delete_job(Manager *m, Job *j, Job *other, JobType t) { + JobDependency *l, *last; + + assert(j); + assert(other); + assert(j->unit == other->unit); + assert(!j->installed); + + /* Merges 'other' into 'j' and then deletes j. */ + + j->type = t; + j->state = JOB_WAITING; + j->override = j->override || other->override; + + j->matters_to_anchor = j->matters_to_anchor || other->matters_to_anchor; + + /* Patch us in as new owner of the JobDependency objects */ + last = NULL; + LIST_FOREACH(subject, l, other->subject_list) { + assert(l->subject == other); + l->subject = j; + last = l; + } + + /* Merge both lists */ + if (last) { + last->subject_next = j->subject_list; + if (j->subject_list) + j->subject_list->subject_prev = last; + j->subject_list = other->subject_list; + } + + /* Patch us in as new owner of the JobDependency objects */ + last = NULL; + LIST_FOREACH(object, l, other->object_list) { + assert(l->object == other); + l->object = j; + last = l; + } + + /* Merge both lists */ + if (last) { + last->object_next = j->object_list; + if (j->object_list) + j->object_list->object_prev = last; + j->object_list = other->object_list; + } + + /* Kill the other job */ + other->subject_list = NULL; + other->object_list = NULL; + transaction_delete_job(m, other, true); +} +static bool job_is_conflicted_by(Job *j) { + JobDependency *l; + + assert(j); + + /* Returns true if this job is pulled in by a least one + * ConflictedBy dependency. */ + + LIST_FOREACH(object, l, j->object_list) + if (l->conflicts) + return true; + + return false; +} + +static int delete_one_unmergeable_job(Manager *m, Job *j) { + Job *k; + + assert(j); + + /* Tries to delete one item in the linked list + * j->transaction_next->transaction_next->... that conflicts + * with another one, in an attempt to make an inconsistent + * transaction work. */ + + /* We rely here on the fact that if a merged with b does not + * merge with c, either a or b merge with c neither */ + LIST_FOREACH(transaction, j, j) + LIST_FOREACH(transaction, k, j->transaction_next) { + Job *d; + + /* Is this one mergeable? Then skip it */ + if (job_type_is_mergeable(j->type, k->type)) + continue; + + /* Ok, we found two that conflict, let's see if we can + * drop one of them */ + if (!j->matters_to_anchor && !k->matters_to_anchor) { + + /* Both jobs don't matter, so let's + * find the one that is smarter to + * remove. Let's think positive and + * rather remove stops then starts -- + * except if something is being + * stopped because it is conflicted by + * another unit in which case we + * rather remove the start. */ + + log_debug("Looking at job %s/%s conflicted_by=%s", j->unit->id, job_type_to_string(j->type), yes_no(j->type == JOB_STOP && job_is_conflicted_by(j))); + log_debug("Looking at job %s/%s conflicted_by=%s", k->unit->id, job_type_to_string(k->type), yes_no(k->type == JOB_STOP && job_is_conflicted_by(k))); + + if (j->type == JOB_STOP) { + + if (job_is_conflicted_by(j)) + d = k; + else + d = j; + + } else if (k->type == JOB_STOP) { + + if (job_is_conflicted_by(k)) + d = j; + else + d = k; + } else + d = j; + + } else if (!j->matters_to_anchor) + d = j; + else if (!k->matters_to_anchor) + d = k; + else + return -ENOEXEC; + + /* Ok, we can drop one, so let's do so. */ + log_debug("Fixing conflicting jobs by deleting job %s/%s", d->unit->id, job_type_to_string(d->type)); + transaction_delete_job(m, d, true); + return 0; + } + + return -EINVAL; +} + +static int transaction_merge_jobs(Manager *m, DBusError *e) { + Job *j; + Iterator i; + int r; + + assert(m); + + /* First step, check whether any of the jobs for one specific + * task conflict. If so, try to drop one of them. */ + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + JobType t; + Job *k; + + t = j->type; + LIST_FOREACH(transaction, k, j->transaction_next) { + if (job_type_merge(&t, k->type) >= 0) + continue; + + /* OK, we could not merge all jobs for this + * action. Let's see if we can get rid of one + * of them */ + + if ((r = delete_one_unmergeable_job(m, j)) >= 0) + /* Ok, we managed to drop one, now + * let's ask our callers to call us + * again after garbage collecting */ + return -EAGAIN; + + /* We couldn't merge anything. Failure */ + dbus_set_error(e, BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, "Transaction contains conflicting jobs '%s' and '%s' for %s. Probably contradicting requirement dependencies configured.", + job_type_to_string(t), job_type_to_string(k->type), k->unit->id); + return r; + } + } + + /* Second step, merge the jobs. */ + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + JobType t = j->type; + Job *k; + + /* Merge all transactions */ + LIST_FOREACH(transaction, k, j->transaction_next) + assert_se(job_type_merge(&t, k->type) == 0); + + /* If an active job is mergeable, merge it too */ + if (j->unit->job) + job_type_merge(&t, j->unit->job->type); /* Might fail. Which is OK */ + + while ((k = j->transaction_next)) { + if (j->installed) { + transaction_merge_and_delete_job(m, k, j, t); + j = k; + } else + transaction_merge_and_delete_job(m, j, k, t); + } + + if (j->unit->job && !j->installed) + transaction_merge_and_delete_job(m, j, j->unit->job, t); + + assert(!j->transaction_next); + assert(!j->transaction_prev); + } + + return 0; +} + +static void transaction_drop_redundant(Manager *m) { + bool again; + + assert(m); + + /* Goes through the transaction and removes all jobs that are + * a noop */ + + do { + Job *j; + Iterator i; + + again = false; + + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + bool changes_something = false; + Job *k; + + LIST_FOREACH(transaction, k, j) { + + if (!job_is_anchor(k) && + (k->installed || job_type_is_redundant(k->type, unit_active_state(k->unit))) && + (!k->unit->job || !job_type_is_conflicting(k->type, k->unit->job->type))) + continue; + + changes_something = true; + break; + } + + if (changes_something) + continue; + + /* log_debug("Found redundant job %s/%s, dropping.", j->unit->id, job_type_to_string(j->type)); */ + transaction_delete_job(m, j, false); + again = true; + break; + } + + } while (again); +} + +static bool unit_matters_to_anchor(Unit *u, Job *j) { + assert(u); + assert(!j->transaction_prev); + + /* Checks whether at least one of the jobs for this unit + * matters to the anchor. */ + + LIST_FOREACH(transaction, j, j) + if (j->matters_to_anchor) + return true; + + return false; +} + +static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned generation, DBusError *e) { + Iterator i; + Unit *u; + int r; + + assert(m); + assert(j); + assert(!j->transaction_prev); + + /* Does a recursive sweep through the ordering graph, looking + * for a cycle. If we find cycle we try to break it. */ + + /* Have we seen this before? */ + if (j->generation == generation) { + Job *k, *delete; + + /* If the marker is NULL we have been here already and + * decided the job was loop-free from here. Hence + * shortcut things and return right-away. */ + if (!j->marker) + return 0; + + /* So, the marker is not NULL and we already have been + * here. We have a cycle. Let's try to break it. We go + * backwards in our path and try to find a suitable + * job to remove. We use the marker to find our way + * back, since smart how we are we stored our way back + * in there. */ + log_warning("Found ordering cycle on %s/%s", j->unit->id, job_type_to_string(j->type)); + + delete = NULL; + for (k = from; k; k = ((k->generation == generation && k->marker != k) ? k->marker : NULL)) { + + log_info("Walked on cycle path to %s/%s", k->unit->id, job_type_to_string(k->type)); + + if (!delete && + !k->installed && + !unit_matters_to_anchor(k->unit, k)) { + /* Ok, we can drop this one, so let's + * do so. */ + delete = k; + } + + /* Check if this in fact was the beginning of + * the cycle */ + if (k == j) + break; + } + + + if (delete) { + log_warning("Breaking ordering cycle by deleting job %s/%s", delete->unit->id, job_type_to_string(delete->type)); + transaction_delete_unit(m, delete->unit); + return -EAGAIN; + } + + log_error("Unable to break cycle"); + + dbus_set_error(e, BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, "Transaction order is cyclic. See system logs for details."); + return -ENOEXEC; + } + + /* Make the marker point to where we come from, so that we can + * find our way backwards if we want to break a cycle. We use + * a special marker for the beginning: we point to + * ourselves. */ + j->marker = from ? from : j; + j->generation = generation; + + /* We assume that the the dependencies are bidirectional, and + * hence can ignore UNIT_AFTER */ + SET_FOREACH(u, j->unit->dependencies[UNIT_BEFORE], i) { + Job *o; + + /* Is there a job for this unit? */ + if (!(o = hashmap_get(m->transaction_jobs, u))) + + /* Ok, there is no job for this in the + * transaction, but maybe there is already one + * running? */ + if (!(o = u->job)) + continue; + + if ((r = transaction_verify_order_one(m, o, j, generation, e)) < 0) + return r; + } + + /* Ok, let's backtrack, and remember that this entry is not on + * our path anymore. */ + j->marker = NULL; + + return 0; +} + +static int transaction_verify_order(Manager *m, unsigned *generation, DBusError *e) { + Job *j; + int r; + Iterator i; + unsigned g; + + assert(m); + assert(generation); + + /* Check if the ordering graph is cyclic. If it is, try to fix + * that up by dropping one of the jobs. */ + + g = (*generation)++; + + HASHMAP_FOREACH(j, m->transaction_jobs, i) + if ((r = transaction_verify_order_one(m, j, NULL, g, e)) < 0) + return r; + + return 0; +} + +static void transaction_collect_garbage(Manager *m) { + bool again; + + assert(m); + + /* Drop jobs that are not required by any other job */ + + do { + Iterator i; + Job *j; + + again = false; + + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + if (j->object_list) { + /* log_debug("Keeping job %s/%s because of %s/%s", */ + /* j->unit->id, job_type_to_string(j->type), */ + /* j->object_list->subject ? j->object_list->subject->unit->id : "root", */ + /* j->object_list->subject ? job_type_to_string(j->object_list->subject->type) : "root"); */ + continue; + } + + /* log_debug("Garbage collecting job %s/%s", j->unit->id, job_type_to_string(j->type)); */ + transaction_delete_job(m, j, true); + again = true; + break; + } + + } while (again); +} + +static int transaction_is_destructive(Manager *m, DBusError *e) { + Iterator i; + Job *j; + + assert(m); + + /* Checks whether applying this transaction means that + * existing jobs would be replaced */ + + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + + /* Assume merged */ + assert(!j->transaction_prev); + assert(!j->transaction_next); + + if (j->unit->job && + j->unit->job != j && + !job_type_is_superset(j->type, j->unit->job->type)) { + + dbus_set_error(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, "Transaction is destructive."); + return -EEXIST; + } + } + + return 0; +} + +static void transaction_minimize_impact(Manager *m) { + bool again; + assert(m); + + /* Drops all unnecessary jobs that reverse already active jobs + * or that stop a running service. */ + + do { + Job *j; + Iterator i; + + again = false; + + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + LIST_FOREACH(transaction, j, j) { + bool stops_running_service, changes_existing_job; + + /* If it matters, we shouldn't drop it */ + if (j->matters_to_anchor) + continue; + + /* Would this stop a running service? + * Would this change an existing job? + * If so, let's drop this entry */ + + stops_running_service = + j->type == JOB_STOP && UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(j->unit)); + + changes_existing_job = + j->unit->job && + job_type_is_conflicting(j->type, j->unit->job->type); + + if (!stops_running_service && !changes_existing_job) + continue; + + if (stops_running_service) + log_debug("%s/%s would stop a running service.", j->unit->id, job_type_to_string(j->type)); + + if (changes_existing_job) + log_debug("%s/%s would change existing job.", j->unit->id, job_type_to_string(j->type)); + + /* Ok, let's get rid of this */ + log_debug("Deleting %s/%s to minimize impact.", j->unit->id, job_type_to_string(j->type)); + + transaction_delete_job(m, j, true); + again = true; + break; + } + + if (again) + break; + } + + } while (again); +} + +static int transaction_apply(Manager *m, JobMode mode) { + Iterator i; + Job *j; + int r; + + /* Moves the transaction jobs to the set of active jobs */ + + if (mode == JOB_ISOLATE) { + + /* When isolating first kill all installed jobs which + * aren't part of the new transaction */ + rescan: + HASHMAP_FOREACH(j, m->jobs, i) { + assert(j->installed); + + if (hashmap_get(m->transaction_jobs, j->unit)) + continue; + + /* 'j' itself is safe to remove, but if other jobs + are invalidated recursively, our iterator may become + invalid and we need to start over. */ + if (job_finish_and_invalidate(j, JOB_CANCELED) > 0) + goto rescan; + } + } + + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + /* Assume merged */ + assert(!j->transaction_prev); + assert(!j->transaction_next); + + if (j->installed) + continue; + + if ((r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j)) < 0) + goto rollback; + } + + while ((j = hashmap_steal_first(m->transaction_jobs))) { + if (j->installed) { + /* log_debug("Skipping already installed job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); */ + continue; + } + + if (j->unit->job) + job_free(j->unit->job); + + j->unit->job = j; + j->installed = true; + m->n_installed_jobs ++; + + /* We're fully installed. Now let's free data we don't + * need anymore. */ + + assert(!j->transaction_next); + assert(!j->transaction_prev); + + job_add_to_run_queue(j); + job_add_to_dbus_queue(j); + job_start_timer(j); + + log_debug("Installed new job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); + } + + /* As last step, kill all remaining job dependencies. */ + transaction_clean_dependencies(m); + + return 0; + +rollback: + + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + if (j->installed) + continue; + + hashmap_remove(m->jobs, UINT32_TO_PTR(j->id)); + } + + return r; +} + +static int transaction_activate(Manager *m, JobMode mode, DBusError *e) { + int r; + unsigned generation = 1; + + assert(m); + + /* This applies the changes recorded in transaction_jobs to + * the actual list of jobs, if possible. */ + + /* First step: figure out which jobs matter */ + transaction_find_jobs_that_matter_to_anchor(m, NULL, generation++); + + /* Second step: Try not to stop any running services if + * we don't have to. Don't try to reverse running + * jobs if we don't have to. */ + if (mode == JOB_FAIL) + transaction_minimize_impact(m); + + /* Third step: Drop redundant jobs */ + transaction_drop_redundant(m); + + for (;;) { + /* Fourth step: Let's remove unneeded jobs that might + * be lurking. */ + if (mode != JOB_ISOLATE) + transaction_collect_garbage(m); + + /* Fifth step: verify order makes sense and correct + * cycles if necessary and possible */ + if ((r = transaction_verify_order(m, &generation, e)) >= 0) + break; + + if (r != -EAGAIN) { + log_warning("Requested transaction contains an unfixable cyclic ordering dependency: %s", bus_error(e, r)); + goto rollback; + } + + /* Let's see if the resulting transaction ordering + * graph is still cyclic... */ + } + + for (;;) { + /* Sixth step: let's drop unmergeable entries if + * necessary and possible, merge entries we can + * merge */ + if ((r = transaction_merge_jobs(m, e)) >= 0) + break; + + if (r != -EAGAIN) { + log_warning("Requested transaction contains unmergeable jobs: %s", bus_error(e, r)); + goto rollback; + } + + /* Seventh step: an entry got dropped, let's garbage + * collect its dependencies. */ + if (mode != JOB_ISOLATE) + transaction_collect_garbage(m); + + /* Let's see if the resulting transaction still has + * unmergeable entries ... */ + } + + /* Eights step: Drop redundant jobs again, if the merging now allows us to drop more. */ + transaction_drop_redundant(m); + + /* Ninth step: check whether we can actually apply this */ + if (mode == JOB_FAIL) + if ((r = transaction_is_destructive(m, e)) < 0) { + log_notice("Requested transaction contradicts existing jobs: %s", bus_error(e, r)); + goto rollback; + } + + /* Tenth step: apply changes */ + if ((r = transaction_apply(m, mode)) < 0) { + log_warning("Failed to apply transaction: %s", strerror(-r)); + goto rollback; + } + + assert(hashmap_isempty(m->transaction_jobs)); + assert(!m->transaction_anchor); + + return 0; + +rollback: + transaction_abort(m); + return r; +} + +static Job* transaction_add_one_job(Manager *m, JobType type, Unit *unit, bool override, bool *is_new) { + Job *j, *f; + + assert(m); + assert(unit); + + /* Looks for an existing prospective job and returns that. If + * it doesn't exist it is created and added to the prospective + * jobs list. */ + + f = hashmap_get(m->transaction_jobs, unit); + + LIST_FOREACH(transaction, j, f) { + assert(j->unit == unit); + + if (j->type == type) { + if (is_new) + *is_new = false; + return j; + } + } + + if (unit->job && unit->job->type == type) + j = unit->job; + else if (!(j = job_new(m, type, unit))) + return NULL; + + j->generation = 0; + j->marker = NULL; + j->matters_to_anchor = false; + j->override = override; + + LIST_PREPEND(Job, transaction, f, j); + + if (hashmap_replace(m->transaction_jobs, unit, f) < 0) { + job_free(j); + return NULL; + } + + if (is_new) + *is_new = true; + + /* log_debug("Added job %s/%s to transaction.", unit->id, job_type_to_string(type)); */ + + return j; +} + +void manager_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies) { + assert(m); + assert(j); + + if (j->transaction_prev) + j->transaction_prev->transaction_next = j->transaction_next; + else if (j->transaction_next) + hashmap_replace(m->transaction_jobs, j->unit, j->transaction_next); + else + hashmap_remove_value(m->transaction_jobs, j->unit, j); + + if (j->transaction_next) + j->transaction_next->transaction_prev = j->transaction_prev; + + j->transaction_prev = j->transaction_next = NULL; + + while (j->subject_list) + job_dependency_free(j->subject_list); + + while (j->object_list) { + Job *other = j->object_list->matters ? j->object_list->subject : NULL; + + job_dependency_free(j->object_list); + + if (other && delete_dependencies) { + log_debug("Deleting job %s/%s as dependency of job %s/%s", + other->unit->id, job_type_to_string(other->type), + j->unit->id, job_type_to_string(j->type)); + transaction_delete_job(m, other, delete_dependencies); + } + } +} + +static int transaction_add_job_and_dependencies( + Manager *m, + JobType type, + Unit *unit, + Job *by, + bool matters, + bool override, + bool conflicts, + bool ignore_requirements, + bool ignore_order, + DBusError *e, + Job **_ret) { + Job *ret; + Iterator i; + Unit *dep; + int r; + bool is_new; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(unit); + + /* log_debug("Pulling in %s/%s from %s/%s", */ + /* unit->id, job_type_to_string(type), */ + /* by ? by->unit->id : "NA", */ + /* by ? job_type_to_string(by->type) : "NA"); */ + + if (unit->load_state != UNIT_LOADED && + unit->load_state != UNIT_ERROR && + unit->load_state != UNIT_MASKED) { + dbus_set_error(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id); + return -EINVAL; + } + + if (type != JOB_STOP && unit->load_state == UNIT_ERROR) { + dbus_set_error(e, BUS_ERROR_LOAD_FAILED, + "Unit %s failed to load: %s. " + "See system logs and 'systemctl status %s' for details.", + unit->id, + strerror(-unit->load_error), + unit->id); + return -EINVAL; + } + + if (type != JOB_STOP && unit->load_state == UNIT_MASKED) { + dbus_set_error(e, BUS_ERROR_MASKED, "Unit %s is masked.", unit->id); + return -EINVAL; + } + + if (!unit_job_is_applicable(unit, type)) { + dbus_set_error(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, "Job type %s is not applicable for unit %s.", job_type_to_string(type), unit->id); + return -EBADR; + } + + /* First add the job. */ + if (!(ret = transaction_add_one_job(m, type, unit, override, &is_new))) + return -ENOMEM; + + ret->ignore_order = ret->ignore_order || ignore_order; + + /* Then, add a link to the job. */ + if (!job_dependency_new(by, ret, matters, conflicts)) + return -ENOMEM; + + if (is_new && !ignore_requirements) { + Set *following; + + /* If we are following some other unit, make sure we + * add all dependencies of everybody following. */ + if (unit_following_set(ret->unit, &following) > 0) { + SET_FOREACH(dep, following, i) + if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, false, override, false, false, ignore_order, e, NULL)) < 0) { + log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); + + if (e) + dbus_error_free(e); + } + + set_free(following); + } + + /* Finally, recursively add in all dependencies. */ + if (type == JOB_START || type == JOB_RELOAD_OR_START) { + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { + if (r != -EBADR) + goto fail; + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_BIND_TO], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { + + if (r != -EBADR) + goto fail; + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES_OVERRIDABLE], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !override, override, false, false, ignore_order, e, NULL)) < 0) { + log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, false, false, false, ignore_order, e, NULL)) < 0) { + log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { + + if (r != -EBADR) + goto fail; + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE_OVERRIDABLE], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, !override, override, false, false, ignore_order, e, NULL)) < 0) { + log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, override, true, false, ignore_order, e, NULL)) < 0) { + + if (r != -EBADR) + goto fail; + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, false, override, false, false, ignore_order, e, NULL)) < 0) { + log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); + + if (e) + dbus_error_free(e); + } + + } + + if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) { + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRED_BY], i) + if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { + + if (r != -EBADR) + goto fail; + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_BOUND_BY], i) + if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { + + if (r != -EBADR) + goto fail; + + if (e) + dbus_error_free(e); + } + } + + if (type == JOB_RELOAD || type == JOB_RELOAD_OR_START) { + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATE_RELOAD_TO], i) { + r = transaction_add_job_and_dependencies(m, JOB_RELOAD, dep, ret, false, override, false, false, ignore_order, e, NULL); + + if (r < 0) { + log_warning("Cannot add dependency reload job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); + + if (e) + dbus_error_free(e); + } + } + } + + /* JOB_VERIFY_STARTED, JOB_RELOAD require no dependency handling */ + } + + if (_ret) + *_ret = ret; + + return 0; + +fail: + return r; +} + +static int transaction_add_isolate_jobs(Manager *m) { + Iterator i; + Unit *u; + char *k; + int r; + + assert(m); + + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + + /* ignore aliases */ + if (u->id != k) + continue; + + if (u->ignore_on_isolate) + continue; + + /* No need to stop inactive jobs */ + if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u)) && !u->job) + continue; + + /* Is there already something listed for this? */ + if (hashmap_get(m->transaction_jobs, u)) + continue; + + if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, u, NULL, true, false, false, false, false, NULL, NULL)) < 0) + log_warning("Cannot add isolate job for unit %s, ignoring: %s", u->id, strerror(-r)); + } + + return 0; +} + +int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool override, DBusError *e, Job **_ret) { + int r; + Job *ret; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(unit); + assert(mode < _JOB_MODE_MAX); + + if (mode == JOB_ISOLATE && type != JOB_START) { + dbus_set_error(e, BUS_ERROR_INVALID_JOB_MODE, "Isolate is only valid for start."); + return -EINVAL; + } + + if (mode == JOB_ISOLATE && !unit->allow_isolate) { + dbus_set_error(e, BUS_ERROR_NO_ISOLATION, "Operation refused, unit may not be isolated."); + return -EPERM; + } + + log_debug("Trying to enqueue job %s/%s/%s", unit->id, job_type_to_string(type), job_mode_to_string(mode)); + + if ((r = transaction_add_job_and_dependencies(m, type, unit, NULL, true, override, false, + mode == JOB_IGNORE_DEPENDENCIES || mode == JOB_IGNORE_REQUIREMENTS, + mode == JOB_IGNORE_DEPENDENCIES, e, &ret)) < 0) { + transaction_abort(m); + return r; + } + + if (mode == JOB_ISOLATE) + if ((r = transaction_add_isolate_jobs(m)) < 0) { + transaction_abort(m); + return r; + } + + if ((r = transaction_activate(m, mode, e)) < 0) + return r; + + log_debug("Enqueued job %s/%s as %u", unit->id, job_type_to_string(type), (unsigned) ret->id); + + if (_ret) + *_ret = ret; + + return 0; +} + +int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool override, DBusError *e, Job **_ret) { + Unit *unit; + int r; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(name); + assert(mode < _JOB_MODE_MAX); + + if ((r = manager_load_unit(m, name, NULL, NULL, &unit)) < 0) + return r; + + return manager_add_job(m, type, unit, mode, override, e, _ret); +} + +Job *manager_get_job(Manager *m, uint32_t id) { + assert(m); + + return hashmap_get(m->jobs, UINT32_TO_PTR(id)); +} + +Unit *manager_get_unit(Manager *m, const char *name) { + assert(m); + assert(name); + + return hashmap_get(m->units, name); +} + +unsigned manager_dispatch_load_queue(Manager *m) { + Unit *u; + unsigned n = 0; + + assert(m); + + /* Make sure we are not run recursively */ + if (m->dispatching_load_queue) + return 0; + + m->dispatching_load_queue = true; + + /* Dispatches the load queue. Takes a unit from the queue and + * tries to load its data until the queue is empty */ + + while ((u = m->load_queue)) { + assert(u->in_load_queue); + + unit_load(u); + n++; + } + + m->dispatching_load_queue = false; + return n; +} + +int manager_load_unit_prepare(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret) { + Unit *ret; + UnitType t; + int r; + + assert(m); + assert(name || path); + + /* This will prepare the unit for loading, but not actually + * load anything from disk. */ + + if (path && !is_path(path)) { + dbus_set_error(e, BUS_ERROR_INVALID_PATH, "Path %s is not absolute.", path); + return -EINVAL; + } + + if (!name) + name = file_name_from_path(path); + + t = unit_name_to_type(name); + + if (t == _UNIT_TYPE_INVALID || !unit_name_is_valid_no_type(name, false)) { + dbus_set_error(e, BUS_ERROR_INVALID_NAME, "Unit name %s is not valid.", name); + return -EINVAL; + } + + ret = manager_get_unit(m, name); + if (ret) { + *_ret = ret; + return 1; + } + + ret = unit_new(m, unit_vtable[t]->object_size); + if (!ret) + return -ENOMEM; + + if (path) { + ret->fragment_path = strdup(path); + if (!ret->fragment_path) { + unit_free(ret); + return -ENOMEM; + } + } + + if ((r = unit_add_name(ret, name)) < 0) { + unit_free(ret); + return r; + } + + unit_add_to_load_queue(ret); + unit_add_to_dbus_queue(ret); + unit_add_to_gc_queue(ret); + + if (_ret) + *_ret = ret; + + return 0; +} + +int manager_load_unit(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret) { + int r; + + assert(m); + + /* This will load the service information files, but not actually + * start any services or anything. */ + + if ((r = manager_load_unit_prepare(m, name, path, e, _ret)) != 0) + return r; + + manager_dispatch_load_queue(m); + + if (_ret) + *_ret = unit_follow_merge(*_ret); + + return 0; +} + +void manager_dump_jobs(Manager *s, FILE *f, const char *prefix) { + Iterator i; + Job *j; + + assert(s); + assert(f); + + HASHMAP_FOREACH(j, s->jobs, i) + job_dump(j, f, prefix); +} + +void manager_dump_units(Manager *s, FILE *f, const char *prefix) { + Iterator i; + Unit *u; + const char *t; + + assert(s); + assert(f); + + HASHMAP_FOREACH_KEY(u, t, s->units, i) + if (u->id == t) + unit_dump(u, f, prefix); +} + +void manager_clear_jobs(Manager *m) { + Job *j; + + assert(m); + + transaction_abort(m); + + while ((j = hashmap_first(m->jobs))) + job_finish_and_invalidate(j, JOB_CANCELED); +} + +unsigned manager_dispatch_run_queue(Manager *m) { + Job *j; + unsigned n = 0; + + if (m->dispatching_run_queue) + return 0; + + m->dispatching_run_queue = true; + + while ((j = m->run_queue)) { + assert(j->installed); + assert(j->in_run_queue); + + job_run_and_invalidate(j); + n++; + } + + m->dispatching_run_queue = false; + return n; +} + +unsigned manager_dispatch_dbus_queue(Manager *m) { + Job *j; + Unit *u; + unsigned n = 0; + + assert(m); + + if (m->dispatching_dbus_queue) + return 0; + + m->dispatching_dbus_queue = true; + + while ((u = m->dbus_unit_queue)) { + assert(u->in_dbus_queue); + + bus_unit_send_change_signal(u); + n++; + } + + while ((j = m->dbus_job_queue)) { + assert(j->in_dbus_queue); + + bus_job_send_change_signal(j); + n++; + } + + m->dispatching_dbus_queue = false; + return n; +} + +static int manager_process_notify_fd(Manager *m) { + ssize_t n; + + assert(m); + + for (;;) { + char buf[4096]; + struct msghdr msghdr; + struct iovec iovec; + struct ucred *ucred; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; + } control; + Unit *u; + char **tags; + + zero(iovec); + iovec.iov_base = buf; + iovec.iov_len = sizeof(buf)-1; + + zero(control); + zero(msghdr); + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + if ((n = recvmsg(m->notify_watch.fd, &msghdr, MSG_DONTWAIT)) <= 0) { + if (n >= 0) + return -EIO; + + if (errno == EAGAIN || errno == EINTR) + break; + + return -errno; + } + + if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || + control.cmsghdr.cmsg_level != SOL_SOCKET || + control.cmsghdr.cmsg_type != SCM_CREDENTIALS || + control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { + log_warning("Received notify message without credentials. Ignoring."); + continue; + } + + ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); + + if (!(u = hashmap_get(m->watch_pids, LONG_TO_PTR(ucred->pid)))) + if (!(u = cgroup_unit_by_pid(m, ucred->pid))) { + log_warning("Cannot find unit for notify message of PID %lu.", (unsigned long) ucred->pid); + continue; + } + + assert((size_t) n < sizeof(buf)); + buf[n] = 0; + if (!(tags = strv_split(buf, "\n\r"))) + return -ENOMEM; + + log_debug("Got notification message for unit %s", u->id); + + if (UNIT_VTABLE(u)->notify_message) + UNIT_VTABLE(u)->notify_message(u, ucred->pid, tags); + + strv_free(tags); + } + + return 0; +} + +static int manager_dispatch_sigchld(Manager *m) { + assert(m); + + for (;;) { + siginfo_t si; + Unit *u; + int r; + + zero(si); + + /* First we call waitd() for a PID and do not reap the + * zombie. That way we can still access /proc/$PID for + * it while it is a zombie. */ + if (waitid(P_ALL, 0, &si, WEXITED|WNOHANG|WNOWAIT) < 0) { + + if (errno == ECHILD) + break; + + if (errno == EINTR) + continue; + + return -errno; + } + + if (si.si_pid <= 0) + break; + + if (si.si_code == CLD_EXITED || si.si_code == CLD_KILLED || si.si_code == CLD_DUMPED) { + char *name = NULL; + + get_process_comm(si.si_pid, &name); + log_debug("Got SIGCHLD for process %lu (%s)", (unsigned long) si.si_pid, strna(name)); + free(name); + } + + /* Let's flush any message the dying child might still + * have queued for us. This ensures that the process + * still exists in /proc so that we can figure out + * which cgroup and hence unit it belongs to. */ + if ((r = manager_process_notify_fd(m)) < 0) + return r; + + /* And now figure out the unit this belongs to */ + if (!(u = hashmap_get(m->watch_pids, LONG_TO_PTR(si.si_pid)))) + u = cgroup_unit_by_pid(m, si.si_pid); + + /* And now, we actually reap the zombie. */ + if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) { + if (errno == EINTR) + continue; + + return -errno; + } + + if (si.si_code != CLD_EXITED && si.si_code != CLD_KILLED && si.si_code != CLD_DUMPED) + continue; + + log_debug("Child %lu died (code=%s, status=%i/%s)", + (long unsigned) si.si_pid, + sigchld_code_to_string(si.si_code), + si.si_status, + strna(si.si_code == CLD_EXITED + ? exit_status_to_string(si.si_status, EXIT_STATUS_FULL) + : signal_to_string(si.si_status))); + + if (!u) + continue; + + log_debug("Child %lu belongs to %s", (long unsigned) si.si_pid, u->id); + + hashmap_remove(m->watch_pids, LONG_TO_PTR(si.si_pid)); + UNIT_VTABLE(u)->sigchld_event(u, si.si_pid, si.si_code, si.si_status); + } + + return 0; +} + +static int manager_start_target(Manager *m, const char *name, JobMode mode) { + int r; + DBusError error; + + dbus_error_init(&error); + + log_debug("Activating special unit %s", name); + + if ((r = manager_add_job_by_name(m, JOB_START, name, mode, true, &error, NULL)) < 0) + log_error("Failed to enqueue %s job: %s", name, bus_error(&error, r)); + + dbus_error_free(&error); + + return r; +} + +static int manager_process_signal_fd(Manager *m) { + ssize_t n; + struct signalfd_siginfo sfsi; + bool sigchld = false; + + assert(m); + + for (;;) { + if ((n = read(m->signal_watch.fd, &sfsi, sizeof(sfsi))) != sizeof(sfsi)) { + + if (n >= 0) + return -EIO; + + if (errno == EINTR || errno == EAGAIN) + break; + + return -errno; + } + + if (sfsi.ssi_pid > 0) { + char *p = NULL; + + get_process_comm(sfsi.ssi_pid, &p); + + log_debug("Received SIG%s from PID %lu (%s).", + signal_to_string(sfsi.ssi_signo), + (unsigned long) sfsi.ssi_pid, strna(p)); + free(p); + } else + log_debug("Received SIG%s.", signal_to_string(sfsi.ssi_signo)); + + switch (sfsi.ssi_signo) { + + case SIGCHLD: + sigchld = true; + break; + + case SIGTERM: + if (m->running_as == MANAGER_SYSTEM) { + /* This is for compatibility with the + * original sysvinit */ + m->exit_code = MANAGER_REEXECUTE; + break; + } + + /* Fall through */ + + case SIGINT: + if (m->running_as == MANAGER_SYSTEM) { + manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE); + break; + } + + /* Run the exit target if there is one, if not, just exit. */ + if (manager_start_target(m, SPECIAL_EXIT_TARGET, JOB_REPLACE) < 0) { + m->exit_code = MANAGER_EXIT; + return 0; + } + + break; + + case SIGWINCH: + if (m->running_as == MANAGER_SYSTEM) + manager_start_target(m, SPECIAL_KBREQUEST_TARGET, JOB_REPLACE); + + /* This is a nop on non-init */ + break; + + case SIGPWR: + if (m->running_as == MANAGER_SYSTEM) + manager_start_target(m, SPECIAL_SIGPWR_TARGET, JOB_REPLACE); + + /* This is a nop on non-init */ + break; + + case SIGUSR1: { + Unit *u; + + u = manager_get_unit(m, SPECIAL_DBUS_SERVICE); + + if (!u || UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) { + log_info("Trying to reconnect to bus..."); + bus_init(m, true); + } + + if (!u || !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) { + log_info("Loading D-Bus service..."); + manager_start_target(m, SPECIAL_DBUS_SERVICE, JOB_REPLACE); + } + + break; + } + + case SIGUSR2: { + FILE *f; + char *dump = NULL; + size_t size; + + if (!(f = open_memstream(&dump, &size))) { + log_warning("Failed to allocate memory stream."); + break; + } + + manager_dump_units(m, f, "\t"); + manager_dump_jobs(m, f, "\t"); + + if (ferror(f)) { + fclose(f); + free(dump); + log_warning("Failed to write status stream"); + break; + } + + fclose(f); + log_dump(LOG_INFO, dump); + free(dump); + + break; + } + + case SIGHUP: + m->exit_code = MANAGER_RELOAD; + break; + + default: { + + /* Starting SIGRTMIN+0 */ + static const char * const target_table[] = { + [0] = SPECIAL_DEFAULT_TARGET, + [1] = SPECIAL_RESCUE_TARGET, + [2] = SPECIAL_EMERGENCY_TARGET, + [3] = SPECIAL_HALT_TARGET, + [4] = SPECIAL_POWEROFF_TARGET, + [5] = SPECIAL_REBOOT_TARGET, + [6] = SPECIAL_KEXEC_TARGET + }; + + /* Starting SIGRTMIN+13, so that target halt and system halt are 10 apart */ + static const ManagerExitCode code_table[] = { + [0] = MANAGER_HALT, + [1] = MANAGER_POWEROFF, + [2] = MANAGER_REBOOT, + [3] = MANAGER_KEXEC + }; + + if ((int) sfsi.ssi_signo >= SIGRTMIN+0 && + (int) sfsi.ssi_signo < SIGRTMIN+(int) ELEMENTSOF(target_table)) { + int idx = (int) sfsi.ssi_signo - SIGRTMIN; + manager_start_target(m, target_table[idx], + (idx == 1 || idx == 2) ? JOB_ISOLATE : JOB_REPLACE); + break; + } + + if ((int) sfsi.ssi_signo >= SIGRTMIN+13 && + (int) sfsi.ssi_signo < SIGRTMIN+13+(int) ELEMENTSOF(code_table)) { + m->exit_code = code_table[sfsi.ssi_signo - SIGRTMIN - 13]; + break; + } + + switch (sfsi.ssi_signo - SIGRTMIN) { + + case 20: + log_debug("Enabling showing of status."); + manager_set_show_status(m, true); + break; + + case 21: + log_debug("Disabling showing of status."); + manager_set_show_status(m, false); + break; + + case 22: + log_set_max_level(LOG_DEBUG); + log_notice("Setting log level to debug."); + break; + + case 23: + log_set_max_level(LOG_INFO); + log_notice("Setting log level to info."); + break; + + case 26: + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + log_notice("Setting log target to journal-or-kmsg."); + break; + + case 27: + log_set_target(LOG_TARGET_CONSOLE); + log_notice("Setting log target to console."); + break; + + case 28: + log_set_target(LOG_TARGET_KMSG); + log_notice("Setting log target to kmsg."); + break; + + case 29: + log_set_target(LOG_TARGET_SYSLOG_OR_KMSG); + log_notice("Setting log target to syslog-or-kmsg."); + break; + + default: + log_warning("Got unhandled signal <%s>.", signal_to_string(sfsi.ssi_signo)); + } + } + } + } + + if (sigchld) + return manager_dispatch_sigchld(m); + + return 0; +} + +static int process_event(Manager *m, struct epoll_event *ev) { + int r; + Watch *w; + + assert(m); + assert(ev); + + assert_se(w = ev->data.ptr); + + if (w->type == WATCH_INVALID) + return 0; + + switch (w->type) { + + case WATCH_SIGNAL: + + /* An incoming signal? */ + if (ev->events != EPOLLIN) + return -EINVAL; + + if ((r = manager_process_signal_fd(m)) < 0) + return r; + + break; + + case WATCH_NOTIFY: + + /* An incoming daemon notification event? */ + if (ev->events != EPOLLIN) + return -EINVAL; + + if ((r = manager_process_notify_fd(m)) < 0) + return r; + + break; + + case WATCH_FD: + + /* Some fd event, to be dispatched to the units */ + UNIT_VTABLE(w->data.unit)->fd_event(w->data.unit, w->fd, ev->events, w); + break; + + case WATCH_UNIT_TIMER: + case WATCH_JOB_TIMER: { + uint64_t v; + ssize_t k; + + /* Some timer event, to be dispatched to the units */ + if ((k = read(w->fd, &v, sizeof(v))) != sizeof(v)) { + + if (k < 0 && (errno == EINTR || errno == EAGAIN)) + break; + + return k < 0 ? -errno : -EIO; + } + + if (w->type == WATCH_UNIT_TIMER) + UNIT_VTABLE(w->data.unit)->timer_event(w->data.unit, v, w); + else + job_timer_event(w->data.job, v, w); + break; + } + + case WATCH_MOUNT: + /* Some mount table change, intended for the mount subsystem */ + mount_fd_event(m, ev->events); + break; + + case WATCH_SWAP: + /* Some swap table change, intended for the swap subsystem */ + swap_fd_event(m, ev->events); + break; + + case WATCH_UDEV: + /* Some notification from udev, intended for the device subsystem */ + device_fd_event(m, ev->events); + break; + + case WATCH_DBUS_WATCH: + bus_watch_event(m, w, ev->events); + break; + + case WATCH_DBUS_TIMEOUT: + bus_timeout_event(m, w, ev->events); + break; + + default: + log_error("event type=%i", w->type); + assert_not_reached("Unknown epoll event type."); + } + + return 0; +} + +int manager_loop(Manager *m) { + int r; + + RATELIMIT_DEFINE(rl, 1*USEC_PER_SEC, 50000); + + assert(m); + m->exit_code = MANAGER_RUNNING; + + /* Release the path cache */ + set_free_free(m->unit_path_cache); + m->unit_path_cache = NULL; + + manager_check_finished(m); + + /* There might still be some zombies hanging around from + * before we were exec()'ed. Leat's reap them */ + if ((r = manager_dispatch_sigchld(m)) < 0) + return r; + + while (m->exit_code == MANAGER_RUNNING) { + struct epoll_event event; + int n; + + if (!ratelimit_test(&rl)) { + /* Yay, something is going seriously wrong, pause a little */ + log_warning("Looping too fast. Throttling execution a little."); + sleep(1); + } + + if (manager_dispatch_load_queue(m) > 0) + continue; + + if (manager_dispatch_run_queue(m) > 0) + continue; + + if (bus_dispatch(m) > 0) + continue; + + if (manager_dispatch_cleanup_queue(m) > 0) + continue; + + if (manager_dispatch_gc_queue(m) > 0) + continue; + + if (manager_dispatch_dbus_queue(m) > 0) + continue; + + if (swap_dispatch_reload(m) > 0) + continue; + + if ((n = epoll_wait(m->epoll_fd, &event, 1, -1)) < 0) { + + if (errno == EINTR) + continue; + + return -errno; + } + + assert(n == 1); + + if ((r = process_event(m, &event)) < 0) + return r; + } + + return m->exit_code; +} + +int manager_get_unit_from_dbus_path(Manager *m, const char *s, Unit **_u) { + char *n; + Unit *u; + + assert(m); + assert(s); + assert(_u); + + if (!startswith(s, "/org/freedesktop/systemd1/unit/")) + return -EINVAL; + + if (!(n = bus_path_unescape(s+31))) + return -ENOMEM; + + u = manager_get_unit(m, n); + free(n); + + if (!u) + return -ENOENT; + + *_u = u; + + return 0; +} + +int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j) { + Job *j; + unsigned id; + int r; + + assert(m); + assert(s); + assert(_j); + + if (!startswith(s, "/org/freedesktop/systemd1/job/")) + return -EINVAL; + + if ((r = safe_atou(s + 30, &id)) < 0) + return r; + + if (!(j = manager_get_job(m, id))) + return -ENOENT; + + *_j = j; + + return 0; +} + +void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) { + +#ifdef HAVE_AUDIT + char *p; + + if (m->audit_fd < 0) + return; + + /* Don't generate audit events if the service was already + * started and we're just deserializing */ + if (m->n_reloading > 0) + return; + + if (m->running_as != MANAGER_SYSTEM) + return; + + if (u->type != UNIT_SERVICE) + return; + + if (!(p = unit_name_to_prefix_and_instance(u->id))) { + log_error("Failed to allocate unit name for audit message: %s", strerror(ENOMEM)); + return; + } + + if (audit_log_user_comm_message(m->audit_fd, type, "", p, NULL, NULL, NULL, success) < 0) { + log_warning("Failed to send audit message: %m"); + + if (errno == EPERM) { + /* We aren't allowed to send audit messages? + * Then let's not retry again, to avoid + * spamming the user with the same and same + * messages over and over. */ + + audit_close(m->audit_fd); + m->audit_fd = -1; + } + } + + free(p); +#endif + +} + +void manager_send_unit_plymouth(Manager *m, Unit *u) { + int fd = -1; + union sockaddr_union sa; + int n = 0; + char *message = NULL; + + /* Don't generate plymouth events if the service was already + * started and we're just deserializing */ + if (m->n_reloading > 0) + return; + + if (m->running_as != MANAGER_SYSTEM) + return; + + if (u->type != UNIT_SERVICE && + u->type != UNIT_MOUNT && + u->type != UNIT_SWAP) + return; + + /* We set SOCK_NONBLOCK here so that we rather drop the + * message then wait for plymouth */ + if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) { + log_error("socket() failed: %m"); + return; + } + + zero(sa); + sa.sa.sa_family = AF_UNIX; + strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1); + if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) { + + if (errno != EPIPE && + errno != EAGAIN && + errno != ENOENT && + errno != ECONNREFUSED && + errno != ECONNRESET && + errno != ECONNABORTED) + log_error("connect() failed: %m"); + + goto finish; + } + + if (asprintf(&message, "U\002%c%s%n", (int) (strlen(u->id) + 1), u->id, &n) < 0) { + log_error("Out of memory"); + goto finish; + } + + errno = 0; + if (write(fd, message, n + 1) != n + 1) { + + if (errno != EPIPE && + errno != EAGAIN && + errno != ENOENT && + errno != ECONNREFUSED && + errno != ECONNRESET && + errno != ECONNABORTED) + log_error("Failed to write Plymouth message: %m"); + + goto finish; + } + +finish: + if (fd >= 0) + close_nointr_nofail(fd); + + free(message); +} + +void manager_dispatch_bus_name_owner_changed( + Manager *m, + const char *name, + const char* old_owner, + const char *new_owner) { + + Unit *u; + + assert(m); + assert(name); + + if (!(u = hashmap_get(m->watch_bus, name))) + return; + + UNIT_VTABLE(u)->bus_name_owner_change(u, name, old_owner, new_owner); +} + +void manager_dispatch_bus_query_pid_done( + Manager *m, + const char *name, + pid_t pid) { + + Unit *u; + + assert(m); + assert(name); + assert(pid >= 1); + + if (!(u = hashmap_get(m->watch_bus, name))) + return; + + UNIT_VTABLE(u)->bus_query_pid_done(u, name, pid); +} + +int manager_open_serialization(Manager *m, FILE **_f) { + char *path = NULL; + mode_t saved_umask; + int fd; + FILE *f; + + assert(_f); + + if (m->running_as == MANAGER_SYSTEM) + asprintf(&path, "/run/systemd/dump-%lu-XXXXXX", (unsigned long) getpid()); + else + asprintf(&path, "/tmp/systemd-dump-%lu-XXXXXX", (unsigned long) getpid()); + + if (!path) + return -ENOMEM; + + saved_umask = umask(0077); + fd = mkostemp(path, O_RDWR|O_CLOEXEC); + umask(saved_umask); + + if (fd < 0) { + free(path); + return -errno; + } + + unlink(path); + + log_debug("Serializing state to %s", path); + free(path); + + if (!(f = fdopen(fd, "w+"))) + return -errno; + + *_f = f; + + return 0; +} + +int manager_serialize(Manager *m, FILE *f, FDSet *fds) { + Iterator i; + Unit *u; + const char *t; + int r; + + assert(m); + assert(f); + assert(fds); + + m->n_reloading ++; + + fprintf(f, "current-job-id=%i\n", m->current_job_id); + fprintf(f, "taint-usr=%s\n", yes_no(m->taint_usr)); + + dual_timestamp_serialize(f, "initrd-timestamp", &m->initrd_timestamp); + dual_timestamp_serialize(f, "startup-timestamp", &m->startup_timestamp); + dual_timestamp_serialize(f, "finish-timestamp", &m->finish_timestamp); + + fputc('\n', f); + + HASHMAP_FOREACH_KEY(u, t, m->units, i) { + if (u->id != t) + continue; + + if (!unit_can_serialize(u)) + continue; + + /* Start marker */ + fputs(u->id, f); + fputc('\n', f); + + if ((r = unit_serialize(u, f, fds)) < 0) { + m->n_reloading --; + return r; + } + } + + assert(m->n_reloading > 0); + m->n_reloading --; + + if (ferror(f)) + return -EIO; + + r = bus_fdset_add_all(m, fds); + if (r < 0) + return r; + + return 0; +} + +int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { + int r = 0; + + assert(m); + assert(f); + + log_debug("Deserializing state..."); + + m->n_reloading ++; + + for (;;) { + char line[LINE_MAX], *l; + + if (!fgets(line, sizeof(line), f)) { + if (feof(f)) + r = 0; + else + r = -errno; + + goto finish; + } + + char_array_0(line); + l = strstrip(line); + + if (l[0] == 0) + break; + + if (startswith(l, "current-job-id=")) { + uint32_t id; + + if (safe_atou32(l+15, &id) < 0) + log_debug("Failed to parse current job id value %s", l+15); + else + m->current_job_id = MAX(m->current_job_id, id); + } else if (startswith(l, "taint-usr=")) { + int b; + + if ((b = parse_boolean(l+10)) < 0) + log_debug("Failed to parse taint /usr flag %s", l+10); + else + m->taint_usr = m->taint_usr || b; + } else if (startswith(l, "initrd-timestamp=")) + dual_timestamp_deserialize(l+17, &m->initrd_timestamp); + else if (startswith(l, "startup-timestamp=")) + dual_timestamp_deserialize(l+18, &m->startup_timestamp); + else if (startswith(l, "finish-timestamp=")) + dual_timestamp_deserialize(l+17, &m->finish_timestamp); + else + log_debug("Unknown serialization item '%s'", l); + } + + for (;;) { + Unit *u; + char name[UNIT_NAME_MAX+2]; + + /* Start marker */ + if (!fgets(name, sizeof(name), f)) { + if (feof(f)) + r = 0; + else + r = -errno; + + goto finish; + } + + char_array_0(name); + + if ((r = manager_load_unit(m, strstrip(name), NULL, NULL, &u)) < 0) + goto finish; + + if ((r = unit_deserialize(u, f, fds)) < 0) + goto finish; + } + +finish: + if (ferror(f)) { + r = -EIO; + goto finish; + } + + assert(m->n_reloading > 0); + m->n_reloading --; + + return r; +} + +int manager_reload(Manager *m) { + int r, q; + FILE *f; + FDSet *fds; + + assert(m); + + if ((r = manager_open_serialization(m, &f)) < 0) + return r; + + m->n_reloading ++; + + if (!(fds = fdset_new())) { + m->n_reloading --; + r = -ENOMEM; + goto finish; + } + + if ((r = manager_serialize(m, f, fds)) < 0) { + m->n_reloading --; + goto finish; + } + + if (fseeko(f, 0, SEEK_SET) < 0) { + m->n_reloading --; + r = -errno; + goto finish; + } + + /* From here on there is no way back. */ + manager_clear_jobs_and_units(m); + manager_undo_generators(m); + + /* Find new unit paths */ + lookup_paths_free(&m->lookup_paths); + if ((q = lookup_paths_init(&m->lookup_paths, m->running_as, true)) < 0) + r = q; + + manager_run_generators(m); + + manager_build_unit_path_cache(m); + + /* First, enumerate what we can from all config files */ + if ((q = manager_enumerate(m)) < 0) + r = q; + + /* Second, deserialize our stored data */ + if ((q = manager_deserialize(m, f, fds)) < 0) + r = q; + + fclose(f); + f = NULL; + + /* Third, fire things up! */ + if ((q = manager_coldplug(m)) < 0) + r = q; + + assert(m->n_reloading > 0); + m->n_reloading--; + +finish: + if (f) + fclose(f); + + if (fds) + fdset_free(fds); + + return r; +} + +bool manager_is_booting_or_shutting_down(Manager *m) { + Unit *u; + + assert(m); + + /* Is the initial job still around? */ + if (manager_get_job(m, m->default_unit_job_id)) + return true; + + /* Is there a job for the shutdown target? */ + u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET); + if (u) + return !!u->job; + + return false; +} + +void manager_reset_failed(Manager *m) { + Unit *u; + Iterator i; + + assert(m); + + HASHMAP_FOREACH(u, m->units, i) + unit_reset_failed(u); +} + +bool manager_unit_pending_inactive(Manager *m, const char *name) { + Unit *u; + + assert(m); + assert(name); + + /* Returns true if the unit is inactive or going down */ + if (!(u = manager_get_unit(m, name))) + return true; + + return unit_pending_inactive(u); +} + +void manager_check_finished(Manager *m) { + char userspace[FORMAT_TIMESPAN_MAX], initrd[FORMAT_TIMESPAN_MAX], kernel[FORMAT_TIMESPAN_MAX], sum[FORMAT_TIMESPAN_MAX]; + usec_t kernel_usec = 0, initrd_usec = 0, userspace_usec = 0, total_usec = 0; + + assert(m); + + if (dual_timestamp_is_set(&m->finish_timestamp)) + return; + + if (hashmap_size(m->jobs) > 0) + return; + + dual_timestamp_get(&m->finish_timestamp); + + if (m->running_as == MANAGER_SYSTEM && detect_container(NULL) <= 0) { + + userspace_usec = m->finish_timestamp.monotonic - m->startup_timestamp.monotonic; + total_usec = m->finish_timestamp.monotonic; + + if (dual_timestamp_is_set(&m->initrd_timestamp)) { + + kernel_usec = m->initrd_timestamp.monotonic; + initrd_usec = m->startup_timestamp.monotonic - m->initrd_timestamp.monotonic; + + log_info("Startup finished in %s (kernel) + %s (initrd) + %s (userspace) = %s.", + format_timespan(kernel, sizeof(kernel), kernel_usec), + format_timespan(initrd, sizeof(initrd), initrd_usec), + format_timespan(userspace, sizeof(userspace), userspace_usec), + format_timespan(sum, sizeof(sum), total_usec)); + } else { + kernel_usec = m->startup_timestamp.monotonic; + initrd_usec = 0; + + log_info("Startup finished in %s (kernel) + %s (userspace) = %s.", + format_timespan(kernel, sizeof(kernel), kernel_usec), + format_timespan(userspace, sizeof(userspace), userspace_usec), + format_timespan(sum, sizeof(sum), total_usec)); + } + } else { + userspace_usec = initrd_usec = kernel_usec = 0; + total_usec = m->finish_timestamp.monotonic - m->startup_timestamp.monotonic; + + log_debug("Startup finished in %s.", + format_timespan(sum, sizeof(sum), total_usec)); + } + + bus_broadcast_finished(m, kernel_usec, initrd_usec, userspace_usec, total_usec); + + sd_notifyf(false, + "READY=1\nSTATUS=Startup finished in %s.", + format_timespan(sum, sizeof(sum), total_usec)); +} + +void manager_run_generators(Manager *m) { + DIR *d = NULL; + const char *generator_path; + const char *argv[3]; + mode_t u; + + assert(m); + + generator_path = m->running_as == MANAGER_SYSTEM ? SYSTEM_GENERATOR_PATH : USER_GENERATOR_PATH; + if (!(d = opendir(generator_path))) { + + if (errno == ENOENT) + return; + + log_error("Failed to enumerate generator directory: %m"); + return; + } + + if (!m->generator_unit_path) { + const char *p; + char user_path[] = "/tmp/systemd-generator-XXXXXX"; + + if (m->running_as == MANAGER_SYSTEM && getpid() == 1) { + p = "/run/systemd/generator"; + + if (mkdir_p(p, 0755) < 0) { + log_error("Failed to create generator directory: %m"); + goto finish; + } + + } else { + if (!(p = mkdtemp(user_path))) { + log_error("Failed to create generator directory: %m"); + goto finish; + } + } + + if (!(m->generator_unit_path = strdup(p))) { + log_error("Failed to allocate generator unit path."); + goto finish; + } + } + + argv[0] = NULL; /* Leave this empty, execute_directory() will fill something in */ + argv[1] = m->generator_unit_path; + argv[2] = NULL; + + u = umask(0022); + execute_directory(generator_path, d, (char**) argv); + umask(u); + + if (rmdir(m->generator_unit_path) >= 0) { + /* Uh? we were able to remove this dir? I guess that + * means the directory was empty, hence let's shortcut + * this */ + + free(m->generator_unit_path); + m->generator_unit_path = NULL; + goto finish; + } + + if (!strv_find(m->lookup_paths.unit_path, m->generator_unit_path)) { + char **l; + + if (!(l = strv_append(m->lookup_paths.unit_path, m->generator_unit_path))) { + log_error("Failed to add generator directory to unit search path: %m"); + goto finish; + } + + strv_free(m->lookup_paths.unit_path); + m->lookup_paths.unit_path = l; + + log_debug("Added generator unit path %s to search path.", m->generator_unit_path); + } + +finish: + if (d) + closedir(d); +} + +void manager_undo_generators(Manager *m) { + assert(m); + + if (!m->generator_unit_path) + return; + + strv_remove(m->lookup_paths.unit_path, m->generator_unit_path); + rm_rf(m->generator_unit_path, false, true, false); + + free(m->generator_unit_path); + m->generator_unit_path = NULL; +} + +int manager_set_default_controllers(Manager *m, char **controllers) { + char **l; + + assert(m); + + if (!(l = strv_copy(controllers))) + return -ENOMEM; + + strv_free(m->default_controllers); + m->default_controllers = l; + + return 0; +} + +void manager_recheck_journal(Manager *m) { + Unit *u; + + assert(m); + + if (m->running_as != MANAGER_SYSTEM) + return; + + u = manager_get_unit(m, SPECIAL_JOURNALD_SOCKET); + if (u && SOCKET(u)->state != SOCKET_RUNNING) { + log_close_journal(); + return; + } + + u = manager_get_unit(m, SPECIAL_JOURNALD_SERVICE); + if (u && SERVICE(u)->state != SERVICE_RUNNING) { + log_close_journal(); + return; + } + + /* Hmm, OK, so the socket is fully up and the service is up + * too, then let's make use of the thing. */ + log_open(); +} + +void manager_set_show_status(Manager *m, bool b) { + assert(m); + + if (m->running_as != MANAGER_SYSTEM) + return; + + m->show_status = b; + + if (b) + touch("/run/systemd/show-status"); + else + unlink("/run/systemd/show-status"); +} + +bool manager_get_show_status(Manager *m) { + assert(m); + + if (m->running_as != MANAGER_SYSTEM) + return false; + + if (m->show_status) + return true; + + /* If Plymouth is running make sure we show the status, so + * that there's something nice to see when people press Esc */ + + return plymouth_running(); +} + +static const char* const manager_running_as_table[_MANAGER_RUNNING_AS_MAX] = { + [MANAGER_SYSTEM] = "system", + [MANAGER_USER] = "user" +}; + +DEFINE_STRING_TABLE_LOOKUP(manager_running_as, ManagerRunningAs); diff --git a/src/manager.h b/src/manager.h new file mode 100644 index 000000000..a9d08f0a2 --- /dev/null +++ b/src/manager.h @@ -0,0 +1,301 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foomanagerhfoo +#define foomanagerhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "fdset.h" + +/* Enforce upper limit how many names we allow */ +#define MANAGER_MAX_NAMES 131072 /* 128K */ + +typedef struct Manager Manager; +typedef enum WatchType WatchType; +typedef struct Watch Watch; + +typedef enum ManagerExitCode { + MANAGER_RUNNING, + MANAGER_EXIT, + MANAGER_RELOAD, + MANAGER_REEXECUTE, + MANAGER_REBOOT, + MANAGER_POWEROFF, + MANAGER_HALT, + MANAGER_KEXEC, + _MANAGER_EXIT_CODE_MAX, + _MANAGER_EXIT_CODE_INVALID = -1 +} ManagerExitCode; + +typedef enum ManagerRunningAs { + MANAGER_SYSTEM, + MANAGER_USER, + _MANAGER_RUNNING_AS_MAX, + _MANAGER_RUNNING_AS_INVALID = -1 +} ManagerRunningAs; + +enum WatchType { + WATCH_INVALID, + WATCH_SIGNAL, + WATCH_NOTIFY, + WATCH_FD, + WATCH_UNIT_TIMER, + WATCH_JOB_TIMER, + WATCH_MOUNT, + WATCH_SWAP, + WATCH_UDEV, + WATCH_DBUS_WATCH, + WATCH_DBUS_TIMEOUT +}; + +struct Watch { + int fd; + WatchType type; + union { + struct Unit *unit; + struct Job *job; + DBusWatch *bus_watch; + DBusTimeout *bus_timeout; + } data; + bool fd_is_dupped:1; + bool socket_accept:1; +}; + +#include "unit.h" +#include "job.h" +#include "hashmap.h" +#include "list.h" +#include "set.h" +#include "dbus.h" +#include "path-lookup.h" + +struct Manager { + /* Note that the set of units we know of is allowed to be + * inconsistent. However the subset of it that is loaded may + * not, and the list of jobs may neither. */ + + /* Active jobs and units */ + Hashmap *units; /* name string => Unit object n:1 */ + Hashmap *jobs; /* job id => Job object 1:1 */ + + /* To make it easy to iterate through the units of a specific + * type we maintain a per type linked list */ + LIST_HEAD(Unit, units_by_type[_UNIT_TYPE_MAX]); + + /* Units that need to be loaded */ + LIST_HEAD(Unit, load_queue); /* this is actually more a stack than a queue, but uh. */ + + /* Jobs that need to be run */ + LIST_HEAD(Job, run_queue); /* more a stack than a queue, too */ + + /* Units and jobs that have not yet been announced via + * D-Bus. When something about a job changes it is added here + * if it is not in there yet. This allows easy coalescing of + * D-Bus change signals. */ + LIST_HEAD(Unit, dbus_unit_queue); + LIST_HEAD(Job, dbus_job_queue); + + /* Units to remove */ + LIST_HEAD(Unit, cleanup_queue); + + /* Units to check when doing GC */ + LIST_HEAD(Unit, gc_queue); + + /* Jobs to be added */ + Hashmap *transaction_jobs; /* Unit object => Job object list 1:1 */ + JobDependency *transaction_anchor; + + Hashmap *watch_pids; /* pid => Unit object n:1 */ + + char *notify_socket; + + Watch notify_watch; + Watch signal_watch; + + int epoll_fd; + + unsigned n_snapshots; + + LookupPaths lookup_paths; + Set *unit_path_cache; + + char **environment; + char **default_controllers; + + dual_timestamp initrd_timestamp; + dual_timestamp startup_timestamp; + dual_timestamp finish_timestamp; + + char *generator_unit_path; + + /* Data specific to the device subsystem */ + struct udev* udev; + struct udev_monitor* udev_monitor; + Watch udev_watch; + Hashmap *devices_by_sysfs; + + /* Data specific to the mount subsystem */ + FILE *proc_self_mountinfo; + Watch mount_watch; + + /* Data specific to the swap filesystem */ + FILE *proc_swaps; + Hashmap *swaps_by_proc_swaps; + bool request_reload; + Watch swap_watch; + + /* Data specific to the D-Bus subsystem */ + DBusConnection *api_bus, *system_bus; + DBusServer *private_bus; + Set *bus_connections, *bus_connections_for_dispatch; + + DBusMessage *queued_message; /* This is used during reloading: + * before the reload we queue the + * reply message here, and + * afterwards we send it */ + DBusConnection *queued_message_connection; /* The connection to send the queued message on */ + + Hashmap *watch_bus; /* D-Bus names => Unit object n:1 */ + int32_t name_data_slot; + int32_t conn_data_slot; + int32_t subscribed_data_slot; + + uint32_t current_job_id; + uint32_t default_unit_job_id; + + /* Data specific to the Automount subsystem */ + int dev_autofs_fd; + + /* Data specific to the cgroup subsystem */ + Hashmap *cgroup_bondings; /* path string => CGroupBonding object 1:n */ + char *cgroup_hierarchy; + + usec_t gc_queue_timestamp; + int gc_marker; + unsigned n_in_gc_queue; + + /* Make sure the user cannot accidentally unmount our cgroup + * file system */ + int pin_cgroupfs_fd; + + /* Audit fd */ +#ifdef HAVE_AUDIT + int audit_fd; +#endif + + /* Flags */ + ManagerRunningAs running_as; + ManagerExitCode exit_code:5; + + bool dispatching_load_queue:1; + bool dispatching_run_queue:1; + bool dispatching_dbus_queue:1; + + bool taint_usr:1; + + bool show_status; + bool confirm_spawn; +#ifdef HAVE_SYSV_COMPAT + bool sysv_console; +#endif + bool mount_auto; + bool swap_auto; + + ExecOutput default_std_output, default_std_error; + + /* non-zero if we are reloading or reexecuting, */ + int n_reloading; + + unsigned n_installed_jobs; + unsigned n_failed_jobs; +}; + +int manager_new(ManagerRunningAs running_as, Manager **m); +void manager_free(Manager *m); + +int manager_enumerate(Manager *m); +int manager_coldplug(Manager *m); +int manager_startup(Manager *m, FILE *serialization, FDSet *fds); + +Job *manager_get_job(Manager *m, uint32_t id); +Unit *manager_get_unit(Manager *m, const char *name); + +int manager_get_unit_from_dbus_path(Manager *m, const char *s, Unit **_u); +int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j); + +int manager_load_unit_prepare(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret); +int manager_load_unit(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret); + +int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool force, DBusError *e, Job **_ret); +int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool force, DBusError *e, Job **_ret); + +void manager_dump_units(Manager *s, FILE *f, const char *prefix); +void manager_dump_jobs(Manager *s, FILE *f, const char *prefix); + +void manager_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies); + +void manager_clear_jobs(Manager *m); + +unsigned manager_dispatch_load_queue(Manager *m); +unsigned manager_dispatch_run_queue(Manager *m); +unsigned manager_dispatch_dbus_queue(Manager *m); + +int manager_set_default_controllers(Manager *m, char **controllers); + +int manager_loop(Manager *m); + +void manager_dispatch_bus_name_owner_changed(Manager *m, const char *name, const char* old_owner, const char *new_owner); +void manager_dispatch_bus_query_pid_done(Manager *m, const char *name, pid_t pid); + +int manager_open_serialization(Manager *m, FILE **_f); + +int manager_serialize(Manager *m, FILE *f, FDSet *fds); +int manager_deserialize(Manager *m, FILE *f, FDSet *fds); + +int manager_reload(Manager *m); + +bool manager_is_booting_or_shutting_down(Manager *m); + +void manager_reset_failed(Manager *m); + +void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success); +void manager_send_unit_plymouth(Manager *m, Unit *u); + +bool manager_unit_pending_inactive(Manager *m, const char *name); + +void manager_check_finished(Manager *m); + +void manager_run_generators(Manager *m); +void manager_undo_generators(Manager *m); + +void manager_recheck_journal(Manager *m); + +void manager_set_show_status(Manager *m, bool b); +bool manager_get_show_status(Manager *m); + +const char *manager_running_as_to_string(ManagerRunningAs i); +ManagerRunningAs manager_running_as_from_string(const char *s); + +#endif diff --git a/src/missing.h b/src/missing.h new file mode 100644 index 000000000..095bf1fe0 --- /dev/null +++ b/src/missing.h @@ -0,0 +1,187 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foomissinghfoo +#define foomissinghfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +/* Missing glibc definitions to access certain kernel APIs */ + +#include +#include +#include +#include +#include + +#ifdef HAVE_AUDIT +#include +#endif + +#include "macro.h" + +#ifdef ARCH_MIPS +#include +#endif + +#ifndef RLIMIT_RTTIME +#define RLIMIT_RTTIME 15 +#endif + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_SETPIPE_SZ +#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7) +#endif + +#ifndef F_GETPIPE_SZ +#define F_GETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 8) +#endif + +#ifndef IP_FREEBIND +#define IP_FREEBIND 15 +#endif + +#ifndef OOM_SCORE_ADJ_MIN +#define OOM_SCORE_ADJ_MIN (-1000) +#endif + +#ifndef OOM_SCORE_ADJ_MAX +#define OOM_SCORE_ADJ_MAX 1000 +#endif + +#ifndef AUDIT_SERVICE_START +#define AUDIT_SERVICE_START 1130 /* Service (daemon) start */ +#endif + +#ifndef AUDIT_SERVICE_STOP +#define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */ +#endif + +#ifndef TIOCVHANGUP +#define TIOCVHANGUP 0x5437 +#endif + +#ifndef IP_TRANSPARENT +#define IP_TRANSPARENT 19 +#endif + +static inline int pivot_root(const char *new_root, const char *put_old) { + return syscall(SYS_pivot_root, new_root, put_old); +} + +#ifdef __x86_64__ +# ifndef __NR_fanotify_init +# define __NR_fanotify_init 300 +# endif +# ifndef __NR_fanotify_mark +# define __NR_fanotify_mark 301 +# endif +#elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# ifndef __NR_fanotify_init +# define __NR_fanotify_init 4336 +# endif +# ifndef __NR_fanotify_mark +# define __NR_fanotify_mark 4337 +# endif +# elif _MIPS_SIM == _MIPS_SIM_NABI32 +# ifndef __NR_fanotify_init +# define __NR_fanotify_init 6300 +# endif +# ifndef __NR_fanotify_mark +# define __NR_fanotify_mark 6301 +# endif +# elif _MIPS_SIM == _MIPS_SIM_ABI64 +# ifndef __NR_fanotify_init +# define __NR_fanotify_init 5295 +# endif +# ifndef __NR_fanotify_mark +# define __NR_fanotify_mark 5296 +# endif +# endif +#else +# ifndef __NR_fanotify_init +# define __NR_fanotify_init 338 +# endif +# ifndef __NR_fanotify_mark +# define __NR_fanotify_mark 339 +# endif +#endif + +static inline int fanotify_init(unsigned int flags, unsigned int event_f_flags) { + return syscall(__NR_fanotify_init, flags, event_f_flags); +} + +static inline int fanotify_mark(int fanotify_fd, unsigned int flags, uint64_t mask, + int dfd, const char *pathname) { +#if defined _MIPS_SIM && _MIPS_SIM == _MIPS_SIM_ABI32 + union { + uint64_t _64; + uint32_t _32[2]; + } _mask; + _mask._64 = mask; + + return syscall(__NR_fanotify_mark, fanotify_fd, flags, + _mask._32[0], _mask._32[1], dfd, pathname); +#else + return syscall(__NR_fanotify_mark, fanotify_fd, flags, mask, dfd, pathname); +#endif +} + +#ifndef BTRFS_IOCTL_MAGIC +#define BTRFS_IOCTL_MAGIC 0x94 +#endif + +#ifndef BTRFS_PATH_NAME_MAX +#define BTRFS_PATH_NAME_MAX 4087 +#endif + +struct btrfs_ioctl_vol_args { + int64_t fd; + char name[BTRFS_PATH_NAME_MAX + 1]; +}; + +#ifndef BTRFS_IOC_DEFRAG +#define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_SUPER_MAGIC +#define BTRFS_SUPER_MAGIC 0x9123683E +#endif + +#ifndef MS_MOVE +#define MS_MOVE 8192 +#endif + +#ifndef MS_PRIVATE +#define MS_PRIVATE (1 << 18) +#endif + +static inline pid_t gettid(void) { + return (pid_t) syscall(SYS_gettid); +} + +#ifndef SCM_SECURITY +#define SCM_SECURITY 0x03 +#endif + +#endif diff --git a/src/modules-load.c b/src/modules-load.c new file mode 100644 index 000000000..ff1f690aa --- /dev/null +++ b/src/modules-load.c @@ -0,0 +1,155 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "util.h" +#include "strv.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +static void systemd_kmod_log(void *data, int priority, const char *file, int line, + const char *fn, const char *format, va_list args) +{ + log_meta(priority, file, line, fn, format, args); +} +#pragma GCC diagnostic pop + +int main(int argc, char *argv[]) { + int r = EXIT_FAILURE; + char **files, **fn; + struct kmod_ctx *ctx; + const int probe_flags = KMOD_PROBE_APPLY_BLACKLIST|KMOD_PROBE_IGNORE_LOADED; + + if (argc > 1) { + log_error("This program takes no argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + ctx = kmod_new(NULL, NULL); + if (!ctx) { + log_error("Failed to allocate memory for kmod."); + goto finish; + } + + kmod_load_resources(ctx); + + kmod_set_log_fn(ctx, systemd_kmod_log, NULL); + + if (conf_files_list(&files, ".conf", + "/etc/modules-load.d", + "/run/modules-load.d", + "/usr/local/lib/modules-load.d", + "/usr/lib/modules-load.d", +#ifdef HAVE_SPLIT_USR + "/lib/modules-load.d", +#endif + NULL) < 0) { + log_error("Failed to enumerate modules-load.d files: %s", strerror(-r)); + goto finish; + } + + r = EXIT_SUCCESS; + + STRV_FOREACH(fn, files) { + FILE *f; + + f = fopen(*fn, "re"); + if (!f) { + if (errno == ENOENT) + continue; + + log_error("Failed to open %s: %m", *fn); + r = EXIT_FAILURE; + continue; + } + + log_debug("apply: %s\n", *fn); + for (;;) { + char line[LINE_MAX], *l; + struct kmod_list *itr, *modlist = NULL; + int err; + + if (!fgets(line, sizeof(line), f)) + break; + + l = strstrip(line); + if (*l == '#' || *l == 0) + continue; + + err = kmod_module_new_from_lookup(ctx, l, &modlist); + if (err < 0) { + log_error("Failed to lookup alias '%s'", l); + r = EXIT_FAILURE; + continue; + } + + kmod_list_foreach(itr, modlist) { + struct kmod_module *mod; + + mod = kmod_module_get_module(itr); + err = kmod_module_probe_insert_module(mod, probe_flags, + NULL, NULL, NULL, NULL); + + if (err == 0) + log_info("Inserted module '%s'", kmod_module_get_name(mod)); + else if (err == KMOD_PROBE_APPLY_BLACKLIST) + log_info("Module '%s' is blacklisted", kmod_module_get_name(mod)); + else { + log_error("Failed to insert '%s': %s", kmod_module_get_name(mod), + strerror(-err)); + r = EXIT_FAILURE; + } + + kmod_module_unref(mod); + } + + kmod_module_unref_list(modlist); + } + + if (ferror(f)) { + log_error("Failed to read from file: %m"); + r = EXIT_FAILURE; + } + + fclose(f); + } + +finish: + strv_free(files); + kmod_unref(ctx); + + return r; +} diff --git a/src/mount-setup.c b/src/mount-setup.c new file mode 100644 index 000000000..aaffb655e --- /dev/null +++ b/src/mount-setup.c @@ -0,0 +1,422 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mount-setup.h" +#include "log.h" +#include "macro.h" +#include "util.h" +#include "label.h" +#include "set.h" +#include "strv.h" + +#ifndef TTY_GID +#define TTY_GID 5 +#endif + +typedef struct MountPoint { + const char *what; + const char *where; + const char *type; + const char *options; + unsigned long flags; + bool fatal; +} MountPoint; + +/* The first three entries we might need before SELinux is up. The + * fourth (securityfs) is needed by IMA to load a custom policy. The + * other ones we can delay until SELinux and IMA are loaded. */ +#define N_EARLY_MOUNT 4 + +static const MountPoint mount_table[] = { + { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true }, + { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true }, + { "devtmpfs", "/dev", "devtmpfs", "mode=755", MS_NOSUID, true }, + { "securityfs", "/sys/kernel/security", "securityfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, false }, + { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV, true }, + { "devpts", "/dev/pts", "devpts", "mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, false }, + { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV, true }, + { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV, false }, + { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, false }, +}; + +/* These are API file systems that might be mounted by other software, + * we just list them here so that we know that we should ignore them */ + +static const char * const ignore_paths[] = { + "/sys/fs/selinux", + "/selinux", + "/proc/bus/usb" +}; + +bool mount_point_is_api(const char *path) { + unsigned i; + + /* Checks if this mount point is considered "API", and hence + * should be ignored */ + + for (i = 0; i < ELEMENTSOF(mount_table); i ++) + if (path_equal(path, mount_table[i].where)) + return true; + + return path_startswith(path, "/sys/fs/cgroup/"); +} + +bool mount_point_ignore(const char *path) { + unsigned i; + + for (i = 0; i < ELEMENTSOF(ignore_paths); i++) + if (path_equal(path, ignore_paths[i])) + return true; + + return false; +} + +static int mount_one(const MountPoint *p, bool relabel) { + int r; + + assert(p); + + /* Relabel first, just in case */ + if (relabel) + label_fix(p->where, true); + + if ((r = path_is_mount_point(p->where, true)) < 0) + return r; + + if (r > 0) + return 0; + + /* The access mode here doesn't really matter too much, since + * the mounted file system will take precedence anyway. */ + mkdir_p(p->where, 0755); + + log_debug("Mounting %s to %s of type %s with options %s.", + p->what, + p->where, + p->type, + strna(p->options)); + + if (mount(p->what, + p->where, + p->type, + p->flags, + p->options) < 0) { + log_error("Failed to mount %s: %s", p->where, strerror(errno)); + return p->fatal ? -errno : 0; + } + + /* Relabel again, since we now mounted something fresh here */ + if (relabel) + label_fix(p->where, false); + + return 1; +} + +int mount_setup_early(void) { + unsigned i; + int r = 0; + + assert_cc(N_EARLY_MOUNT <= ELEMENTSOF(mount_table)); + + /* Do a minimal mount of /proc and friends to enable the most + * basic stuff, such as SELinux */ + for (i = 0; i < N_EARLY_MOUNT; i ++) { + int j; + + j = mount_one(mount_table + i, false); + if (r == 0) + r = j; + } + + return r; +} + +int mount_cgroup_controllers(char ***join_controllers) { + int r; + FILE *f; + char buf[LINE_MAX]; + Set *controllers; + + /* Mount all available cgroup controllers that are built into the kernel. */ + + f = fopen("/proc/cgroups", "re"); + if (!f) { + log_error("Failed to enumerate cgroup controllers: %m"); + return 0; + } + + controllers = set_new(string_hash_func, string_compare_func); + if (!controllers) { + r = -ENOMEM; + log_error("Failed to allocate controller set."); + goto finish; + } + + /* Ignore the header line */ + (void) fgets(buf, sizeof(buf), f); + + for (;;) { + char *controller; + int enabled = 0; + + if (fscanf(f, "%ms %*i %*i %i", &controller, &enabled) != 2) { + + if (feof(f)) + break; + + log_error("Failed to parse /proc/cgroups."); + r = -EIO; + goto finish; + } + + if (!enabled) { + free(controller); + continue; + } + + r = set_put(controllers, controller); + if (r < 0) { + log_error("Failed to add controller to set."); + free(controller); + goto finish; + } + } + + for (;;) { + MountPoint p; + char *controller, *where, *options; + char ***k = NULL; + + controller = set_steal_first(controllers); + if (!controller) + break; + + if (join_controllers) + for (k = join_controllers; *k; k++) + if (strv_find(*k, controller)) + break; + + if (k && *k) { + char **i, **j; + + for (i = *k, j = *k; *i; i++) { + + if (!streq(*i, controller)) { + char *t; + + t = set_remove(controllers, *i); + if (!t) { + free(*i); + continue; + } + free(t); + } + + *(j++) = *i; + } + + *j = NULL; + + options = strv_join(*k, ","); + if (!options) { + log_error("Failed to join options"); + free(controller); + r = -ENOMEM; + goto finish; + } + + } else { + options = controller; + controller = NULL; + } + + where = strappend("/sys/fs/cgroup/", options); + if (!where) { + log_error("Failed to build path"); + free(options); + r = -ENOMEM; + goto finish; + } + + zero(p); + p.what = "cgroup"; + p.where = where; + p.type = "cgroup"; + p.options = options; + p.flags = MS_NOSUID|MS_NOEXEC|MS_NODEV; + p.fatal = false; + + r = mount_one(&p, true); + free(controller); + free(where); + + if (r < 0) { + free(options); + goto finish; + } + + if (r > 0 && k && *k) { + char **i; + + for (i = *k; *i; i++) { + char *t; + + t = strappend("/sys/fs/cgroup/", *i); + if (!t) { + log_error("Failed to build path"); + r = -ENOMEM; + free(options); + goto finish; + } + + r = symlink(options, t); + free(t); + + if (r < 0 && errno != EEXIST) { + log_error("Failed to create symlink: %m"); + r = -errno; + free(options); + goto finish; + } + } + } + + free(options); + } + + r = 0; + +finish: + set_free_free(controllers); + + fclose(f); + + return r; +} + +static int symlink_and_label(const char *old_path, const char *new_path) { + int r; + + assert(old_path); + assert(new_path); + + if ((r = label_symlinkfile_set(new_path)) < 0) + return r; + + if (symlink(old_path, new_path) < 0) + r = -errno; + + label_file_clear(); + + return r; +} + +static int nftw_cb( + const char *fpath, + const struct stat *sb, + int tflag, + struct FTW *ftwbuf) { + + /* No need to label /dev twice in a row... */ + if (_unlikely_(ftwbuf->level == 0)) + return FTW_CONTINUE; + + label_fix(fpath, true); + + /* /run/initramfs is static data and big, no need to + * dynamically relabel its contents at boot... */ + if (_unlikely_(ftwbuf->level == 1 && + tflag == FTW_D && + streq(fpath, "/run/initramfs"))) + return FTW_SKIP_SUBTREE; + + return FTW_CONTINUE; +}; + +int mount_setup(bool loaded_policy) { + + static const char symlinks[] = + "/proc/kcore\0" "/dev/core\0" + "/proc/self/fd\0" "/dev/fd\0" + "/proc/self/fd/0\0" "/dev/stdin\0" + "/proc/self/fd/1\0" "/dev/stdout\0" + "/proc/self/fd/2\0" "/dev/stderr\0"; + + static const char relabel[] = + "/run/initramfs/root-fsck\0" + "/run/initramfs/shutdown\0"; + + int r; + unsigned i; + const char *j, *k; + + for (i = 0; i < ELEMENTSOF(mount_table); i ++) { + r = mount_one(mount_table + i, true); + + if (r < 0) + return r; + } + + /* Nodes in devtmpfs and /run need to be manually updated for + * the appropriate labels, after mounting. The other virtual + * API file systems like /sys and /proc do not need that, they + * use the same label for all their files. */ + if (loaded_policy) { + usec_t before_relabel, after_relabel; + char timespan[FORMAT_TIMESPAN_MAX]; + + before_relabel = now(CLOCK_MONOTONIC); + + nftw("/dev", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); + nftw("/run", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); + + /* Explicitly relabel these */ + NULSTR_FOREACH(j, relabel) + label_fix(j, true); + + after_relabel = now(CLOCK_MONOTONIC); + + log_info("Relabelled /dev and /run in %s.", + format_timespan(timespan, sizeof(timespan), after_relabel - before_relabel)); + } + + /* Create a few default symlinks, which are normally created + * by udevd, but some scripts might need them before we start + * udevd. */ + NULSTR_FOREACH_PAIR(j, k, symlinks) + symlink_and_label(j, k); + + /* Create a few directories we always want around */ + label_mkdir("/run/systemd", 0755); + label_mkdir("/run/systemd/system", 0755); + + return 0; +} diff --git a/src/mount-setup.h b/src/mount-setup.h new file mode 100644 index 000000000..c1a27ba6b --- /dev/null +++ b/src/mount-setup.h @@ -0,0 +1,36 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foomountsetuphfoo +#define foomountsetuphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +int mount_setup_early(void); + +int mount_setup(bool loaded_policy); + +int mount_cgroup_controllers(char ***join_controllers); + +bool mount_point_is_api(const char *path); +bool mount_point_ignore(const char *path); + +#endif diff --git a/src/mount.c b/src/mount.c new file mode 100644 index 000000000..ed0f819c7 --- /dev/null +++ b/src/mount.c @@ -0,0 +1,1929 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "unit.h" +#include "mount.h" +#include "load-fragment.h" +#include "load-dropin.h" +#include "log.h" +#include "strv.h" +#include "mount-setup.h" +#include "unit-name.h" +#include "dbus-mount.h" +#include "special.h" +#include "bus-errors.h" +#include "exit-status.h" +#include "def.h" + +static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = { + [MOUNT_DEAD] = UNIT_INACTIVE, + [MOUNT_MOUNTING] = UNIT_ACTIVATING, + [MOUNT_MOUNTING_DONE] = UNIT_ACTIVE, + [MOUNT_MOUNTED] = UNIT_ACTIVE, + [MOUNT_REMOUNTING] = UNIT_RELOADING, + [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING, + [MOUNT_MOUNTING_SIGTERM] = UNIT_DEACTIVATING, + [MOUNT_MOUNTING_SIGKILL] = UNIT_DEACTIVATING, + [MOUNT_REMOUNTING_SIGTERM] = UNIT_RELOADING, + [MOUNT_REMOUNTING_SIGKILL] = UNIT_RELOADING, + [MOUNT_UNMOUNTING_SIGTERM] = UNIT_DEACTIVATING, + [MOUNT_UNMOUNTING_SIGKILL] = UNIT_DEACTIVATING, + [MOUNT_FAILED] = UNIT_FAILED +}; + +static void mount_init(Unit *u) { + Mount *m = MOUNT(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + m->timeout_usec = DEFAULT_TIMEOUT_USEC; + m->directory_mode = 0755; + + exec_context_init(&m->exec_context); + + /* The stdio/kmsg bridge socket is on /, in order to avoid a + * dep loop, don't use kmsg logging for -.mount */ + if (!unit_has_name(u, "-.mount")) { + m->exec_context.std_output = u->manager->default_std_output; + m->exec_context.std_error = u->manager->default_std_error; + } + + /* We need to make sure that /bin/mount is always called in + * the same process group as us, so that the autofs kernel + * side doesn't send us another mount request while we are + * already trying to comply its last one. */ + m->exec_context.same_pgrp = true; + + m->timer_watch.type = WATCH_INVALID; + + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + + UNIT(m)->ignore_on_isolate = true; +} + +static void mount_unwatch_control_pid(Mount *m) { + assert(m); + + if (m->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(m), m->control_pid); + m->control_pid = 0; +} + +static void mount_parameters_done(MountParameters *p) { + assert(p); + + free(p->what); + free(p->options); + free(p->fstype); + + p->what = p->options = p->fstype = NULL; +} + +static void mount_done(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + free(m->where); + m->where = NULL; + + mount_parameters_done(&m->parameters_etc_fstab); + mount_parameters_done(&m->parameters_proc_self_mountinfo); + mount_parameters_done(&m->parameters_fragment); + + exec_context_done(&m->exec_context); + exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); + m->control_command = NULL; + + mount_unwatch_control_pid(m); + + unit_unwatch_timer(u, &m->timer_watch); +} + +static MountParameters* get_mount_parameters_configured(Mount *m) { + assert(m); + + if (m->from_fragment) + return &m->parameters_fragment; + else if (m->from_etc_fstab) + return &m->parameters_etc_fstab; + + return NULL; +} + +static MountParameters* get_mount_parameters(Mount *m) { + assert(m); + + if (m->from_proc_self_mountinfo) + return &m->parameters_proc_self_mountinfo; + + return get_mount_parameters_configured(m); +} + +static int mount_add_mount_links(Mount *m) { + Unit *other; + int r; + MountParameters *pm; + + assert(m); + + pm = get_mount_parameters_configured(m); + + /* Adds in links to other mount points that might lie below or + * above us in the hierarchy */ + + LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_MOUNT]) { + Mount *n = MOUNT(other); + MountParameters *pn; + + if (n == m) + continue; + + if (UNIT(n)->load_state != UNIT_LOADED) + continue; + + pn = get_mount_parameters_configured(n); + + if (path_startswith(m->where, n->where)) { + + if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(n), true)) < 0) + return r; + + if (pn) + if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(n), true)) < 0) + return r; + + } else if (path_startswith(n->where, m->where)) { + + if ((r = unit_add_dependency(UNIT(n), UNIT_AFTER, UNIT(m), true)) < 0) + return r; + + if (pm) + if ((r = unit_add_dependency(UNIT(n), UNIT_REQUIRES, UNIT(m), true)) < 0) + return r; + + } else if (pm && pm->what && path_startswith(pm->what, n->where)) { + + if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(n), true)) < 0) + return r; + + if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(n), true)) < 0) + return r; + + } else if (pn && pn->what && path_startswith(pn->what, m->where)) { + + if ((r = unit_add_dependency(UNIT(n), UNIT_AFTER, UNIT(m), true)) < 0) + return r; + + if ((r = unit_add_dependency(UNIT(n), UNIT_REQUIRES, UNIT(m), true)) < 0) + return r; + } + } + + return 0; +} + +static int mount_add_swap_links(Mount *m) { + Unit *other; + int r; + + assert(m); + + LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_SWAP]) + if ((r = swap_add_one_mount_link(SWAP(other), m)) < 0) + return r; + + return 0; +} + +static int mount_add_path_links(Mount *m) { + Unit *other; + int r; + + assert(m); + + LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_PATH]) + if ((r = path_add_one_mount_link(PATH(other), m)) < 0) + return r; + + return 0; +} + +static int mount_add_automount_links(Mount *m) { + Unit *other; + int r; + + assert(m); + + LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_AUTOMOUNT]) + if ((r = automount_add_one_mount_link(AUTOMOUNT(other), m)) < 0) + return r; + + return 0; +} + +static int mount_add_socket_links(Mount *m) { + Unit *other; + int r; + + assert(m); + + LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_SOCKET]) + if ((r = socket_add_one_mount_link(SOCKET(other), m)) < 0) + return r; + + return 0; +} + +static char* mount_test_option(const char *haystack, const char *needle) { + struct mntent me; + + assert(needle); + + /* Like glibc's hasmntopt(), but works on a string, not a + * struct mntent */ + + if (!haystack) + return NULL; + + zero(me); + me.mnt_opts = (char*) haystack; + + return hasmntopt(&me, needle); +} + +static bool mount_is_network(MountParameters *p) { + assert(p); + + if (mount_test_option(p->options, "_netdev")) + return true; + + if (p->fstype && fstype_is_network(p->fstype)) + return true; + + return false; +} + +static bool mount_is_bind(MountParameters *p) { + assert(p); + + if (mount_test_option(p->options, "bind")) + return true; + + if (p->fstype && streq(p->fstype, "bind")) + return true; + + return false; +} + +static bool needs_quota(MountParameters *p) { + assert(p); + + if (mount_is_network(p)) + return false; + + if (mount_is_bind(p)) + return false; + + return mount_test_option(p->options, "usrquota") || + mount_test_option(p->options, "grpquota") || + mount_test_option(p->options, "quota") || + mount_test_option(p->options, "usrjquota") || + mount_test_option(p->options, "grpjquota"); +} + +static int mount_add_fstab_links(Mount *m) { + const char *target, *after, *tu_wants = NULL; + MountParameters *p; + Unit *tu; + int r; + bool noauto, nofail, handle, automount; + + assert(m); + + if (UNIT(m)->manager->running_as != MANAGER_SYSTEM) + return 0; + + if (!(p = get_mount_parameters_configured(m))) + return 0; + + if (p != &m->parameters_etc_fstab) + return 0; + + noauto = !!mount_test_option(p->options, "noauto"); + nofail = !!mount_test_option(p->options, "nofail"); + automount = + mount_test_option(p->options, "comment=systemd.automount") || + mount_test_option(p->options, "x-systemd-automount"); + handle = + automount || + mount_test_option(p->options, "comment=systemd.mount") || + mount_test_option(p->options, "x-systemd-mount") || + UNIT(m)->manager->mount_auto; + + if (mount_is_network(p)) { + target = SPECIAL_REMOTE_FS_TARGET; + after = tu_wants = SPECIAL_REMOTE_FS_PRE_TARGET; + } else { + target = SPECIAL_LOCAL_FS_TARGET; + after = SPECIAL_LOCAL_FS_PRE_TARGET; + } + + r = manager_load_unit(UNIT(m)->manager, target, NULL, NULL, &tu); + if (r < 0) + return r; + + if (tu_wants) { + r = unit_add_dependency_by_name(tu, UNIT_WANTS, tu_wants, NULL, true); + if (r < 0) + return r; + } + + if (after) { + r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, after, NULL, true); + if (r < 0) + return r; + } + + if (automount) { + Unit *am; + + if ((r = unit_load_related_unit(UNIT(m), ".automount", &am)) < 0) + return r; + + /* If auto is configured as well also pull in the + * mount right-away, but don't rely on it. */ + if (!noauto) /* automount + auto */ + if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(m), true)) < 0) + return r; + + /* Install automount unit */ + if (!nofail) /* automount + fail */ + return unit_add_two_dependencies(tu, UNIT_AFTER, UNIT_REQUIRES, am, true); + else /* automount + nofail */ + return unit_add_two_dependencies(tu, UNIT_AFTER, UNIT_WANTS, am, true); + + } else if (handle && !noauto) { + + /* Automatically add mount points that aren't natively + * configured to local-fs.target */ + + if (!nofail) /* auto + fail */ + return unit_add_two_dependencies(tu, UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true); + else /* auto + nofail */ + return unit_add_dependency(tu, UNIT_WANTS, UNIT(m), true); + } + + return 0; +} + +static int mount_add_device_links(Mount *m) { + MountParameters *p; + int r; + + assert(m); + + if (!(p = get_mount_parameters_configured(m))) + return 0; + + if (!p->what) + return 0; + + if (!mount_is_bind(p) && + !path_equal(m->where, "/") && + p == &m->parameters_etc_fstab) { + bool nofail, noauto; + + noauto = !!mount_test_option(p->options, "noauto"); + nofail = !!mount_test_option(p->options, "nofail"); + + if ((r = unit_add_node_link(UNIT(m), p->what, + !noauto && nofail && + UNIT(m)->manager->running_as == MANAGER_SYSTEM)) < 0) + return r; + } + + if (p->passno > 0 && + !mount_is_bind(p) && + UNIT(m)->manager->running_as == MANAGER_SYSTEM && + !path_equal(m->where, "/")) { + char *name; + Unit *fsck; + /* Let's add in the fsck service */ + + /* aka SPECIAL_FSCK_SERVICE */ + if (!(name = unit_name_from_path_instance("fsck", p->what, ".service"))) + return -ENOMEM; + + if ((r = manager_load_unit_prepare(UNIT(m)->manager, name, NULL, NULL, &fsck)) < 0) { + log_warning("Failed to prepare unit %s: %s", name, strerror(-r)); + free(name); + return r; + } + + free(name); + + SERVICE(fsck)->fsck_passno = p->passno; + + if ((r = unit_add_two_dependencies(UNIT(m), UNIT_AFTER, UNIT_REQUIRES, fsck, true)) < 0) + return r; + } + + return 0; +} + +static int mount_add_default_dependencies(Mount *m) { + int r; + MountParameters *p; + + assert(m); + + if (UNIT(m)->manager->running_as != MANAGER_SYSTEM) + return 0; + + p = get_mount_parameters_configured(m); + if (p && needs_quota(p)) { + if ((r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTACHECK_SERVICE, NULL, true)) < 0 || + (r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTAON_SERVICE, NULL, true)) < 0) + return r; + } + + if (!path_equal(m->where, "/")) + if ((r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0) + return r; + + return 0; +} + +static int mount_fix_timeouts(Mount *m) { + MountParameters *p; + const char *timeout = NULL; + Unit *other; + Iterator i; + usec_t u; + char *t; + int r; + + assert(m); + + if (!(p = get_mount_parameters_configured(m))) + return 0; + + /* Allow configuration how long we wait for a device that + * backs a mount point to show up. This is useful to support + * endless device timeouts for devices that show up only after + * user input, like crypto devices. */ + + if ((timeout = mount_test_option(p->options, "comment=systemd.device-timeout"))) + timeout += 31; + else if ((timeout = mount_test_option(p->options, "x-systemd-device-timeout"))) + timeout += 25; + else + return 0; + + t = strndup(timeout, strcspn(timeout, ",;" WHITESPACE)); + if (!t) + return -ENOMEM; + + r = parse_usec(t, &u); + free(t); + + if (r < 0) { + log_warning("Failed to parse timeout for %s, ignoring: %s", m->where, timeout); + return r; + } + + SET_FOREACH(other, UNIT(m)->dependencies[UNIT_AFTER], i) { + if (other->type != UNIT_DEVICE) + continue; + + other->job_timeout = u; + } + + return 0; +} + +static int mount_verify(Mount *m) { + bool b; + char *e; + assert(m); + + if (UNIT(m)->load_state != UNIT_LOADED) + return 0; + + if (!m->from_etc_fstab && !m->from_fragment && !m->from_proc_self_mountinfo) + return -ENOENT; + + if (!(e = unit_name_from_path(m->where, ".mount"))) + return -ENOMEM; + + b = unit_has_name(UNIT(m), e); + free(e); + + if (!b) { + log_error("%s's Where setting doesn't match unit name. Refusing.", UNIT(m)->id); + return -EINVAL; + } + + if (mount_point_is_api(m->where) || mount_point_ignore(m->where)) { + log_error("Cannot create mount unit for API file system %s. Refusing.", m->where); + return -EINVAL; + } + + if (UNIT(m)->fragment_path && !m->parameters_fragment.what) { + log_error("%s's What setting is missing. Refusing.", UNIT(m)->id); + return -EBADMSG; + } + + if (m->exec_context.pam_name && m->exec_context.kill_mode != KILL_CONTROL_GROUP) { + log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(m)->id); + return -EINVAL; + } + + return 0; +} + +static int mount_load(Unit *u) { + Mount *m = MOUNT(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + if ((r = unit_load_fragment_and_dropin_optional(u)) < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + if ((r = unit_add_exec_dependencies(u, &m->exec_context)) < 0) + return r; + + if (UNIT(m)->fragment_path) + m->from_fragment = true; + else if (m->from_etc_fstab) + /* We always add several default dependencies to fstab mounts, + * but we do not want the implicit complementing of Wants= with After= + * in the target unit that this mount unit will be hooked into. */ + UNIT(m)->default_dependencies = false; + + if (!m->where) + if (!(m->where = unit_name_to_path(u->id))) + return -ENOMEM; + + path_kill_slashes(m->where); + + if (!UNIT(m)->description) + if ((r = unit_set_description(u, m->where)) < 0) + return r; + + if ((r = mount_add_device_links(m)) < 0) + return r; + + if ((r = mount_add_mount_links(m)) < 0) + return r; + + if ((r = mount_add_socket_links(m)) < 0) + return r; + + if ((r = mount_add_swap_links(m)) < 0) + return r; + + if ((r = mount_add_path_links(m)) < 0) + return r; + + if ((r = mount_add_automount_links(m)) < 0) + return r; + + if ((r = mount_add_fstab_links(m)) < 0) + return r; + + if (UNIT(m)->default_dependencies || m->from_etc_fstab) + if ((r = mount_add_default_dependencies(m)) < 0) + return r; + + if ((r = unit_add_default_cgroups(u)) < 0) + return r; + + mount_fix_timeouts(m); + } + + return mount_verify(m); +} + +static int mount_notify_automount(Mount *m, int status) { + Unit *p; + int r; + Iterator i; + + assert(m); + + SET_FOREACH(p, UNIT(m)->dependencies[UNIT_TRIGGERED_BY], i) + if (p->type == UNIT_AUTOMOUNT) { + r = automount_send_ready(AUTOMOUNT(p), status); + if (r < 0) + return r; + } + + return 0; +} + +static void mount_set_state(Mount *m, MountState state) { + MountState old_state; + assert(m); + + old_state = m->state; + m->state = state; + + if (state != MOUNT_MOUNTING && + state != MOUNT_MOUNTING_DONE && + state != MOUNT_REMOUNTING && + state != MOUNT_UNMOUNTING && + state != MOUNT_MOUNTING_SIGTERM && + state != MOUNT_MOUNTING_SIGKILL && + state != MOUNT_UNMOUNTING_SIGTERM && + state != MOUNT_UNMOUNTING_SIGKILL && + state != MOUNT_REMOUNTING_SIGTERM && + state != MOUNT_REMOUNTING_SIGKILL) { + unit_unwatch_timer(UNIT(m), &m->timer_watch); + mount_unwatch_control_pid(m); + m->control_command = NULL; + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + } + + if (state == MOUNT_MOUNTED || + state == MOUNT_REMOUNTING) + mount_notify_automount(m, 0); + else if (state == MOUNT_DEAD || + state == MOUNT_UNMOUNTING || + state == MOUNT_MOUNTING_SIGTERM || + state == MOUNT_MOUNTING_SIGKILL || + state == MOUNT_REMOUNTING_SIGTERM || + state == MOUNT_REMOUNTING_SIGKILL || + state == MOUNT_UNMOUNTING_SIGTERM || + state == MOUNT_UNMOUNTING_SIGKILL || + state == MOUNT_FAILED) + mount_notify_automount(m, -ENODEV); + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(m)->id, + mount_state_to_string(old_state), + mount_state_to_string(state)); + + unit_notify(UNIT(m), state_translation_table[old_state], state_translation_table[state], m->reload_result == MOUNT_SUCCESS); + m->reload_result = MOUNT_SUCCESS; +} + +static int mount_coldplug(Unit *u) { + Mount *m = MOUNT(u); + MountState new_state = MOUNT_DEAD; + int r; + + assert(m); + assert(m->state == MOUNT_DEAD); + + if (m->deserialized_state != m->state) + new_state = m->deserialized_state; + else if (m->from_proc_self_mountinfo) + new_state = MOUNT_MOUNTED; + + if (new_state != m->state) { + + if (new_state == MOUNT_MOUNTING || + new_state == MOUNT_MOUNTING_DONE || + new_state == MOUNT_REMOUNTING || + new_state == MOUNT_UNMOUNTING || + new_state == MOUNT_MOUNTING_SIGTERM || + new_state == MOUNT_MOUNTING_SIGKILL || + new_state == MOUNT_UNMOUNTING_SIGTERM || + new_state == MOUNT_UNMOUNTING_SIGKILL || + new_state == MOUNT_REMOUNTING_SIGTERM || + new_state == MOUNT_REMOUNTING_SIGKILL) { + + if (m->control_pid <= 0) + return -EBADMSG; + + if ((r = unit_watch_pid(UNIT(m), m->control_pid)) < 0) + return r; + + if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 0) + return r; + } + + mount_set_state(m, new_state); + } + + return 0; +} + +static void mount_dump(Unit *u, FILE *f, const char *prefix) { + Mount *m = MOUNT(u); + MountParameters *p; + + assert(m); + assert(f); + + p = get_mount_parameters(m); + + fprintf(f, + "%sMount State: %s\n" + "%sResult: %s\n" + "%sWhere: %s\n" + "%sWhat: %s\n" + "%sFile System Type: %s\n" + "%sOptions: %s\n" + "%sFrom /etc/fstab: %s\n" + "%sFrom /proc/self/mountinfo: %s\n" + "%sFrom fragment: %s\n" + "%sDirectoryMode: %04o\n", + prefix, mount_state_to_string(m->state), + prefix, mount_result_to_string(m->result), + prefix, m->where, + prefix, strna(p->what), + prefix, strna(p->fstype), + prefix, strna(p->options), + prefix, yes_no(m->from_etc_fstab), + prefix, yes_no(m->from_proc_self_mountinfo), + prefix, yes_no(m->from_fragment), + prefix, m->directory_mode); + + if (m->control_pid > 0) + fprintf(f, + "%sControl PID: %lu\n", + prefix, (unsigned long) m->control_pid); + + exec_context_dump(&m->exec_context, f, prefix); +} + +static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { + pid_t pid; + int r; + + assert(m); + assert(c); + assert(_pid); + + if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 0) + goto fail; + + if ((r = exec_spawn(c, + NULL, + &m->exec_context, + NULL, 0, + UNIT(m)->manager->environment, + true, + true, + true, + UNIT(m)->manager->confirm_spawn, + UNIT(m)->cgroup_bondings, + UNIT(m)->cgroup_attributes, + &pid)) < 0) + goto fail; + + if ((r = unit_watch_pid(UNIT(m), pid)) < 0) + /* FIXME: we need to do something here */ + goto fail; + + *_pid = pid; + + return 0; + +fail: + unit_unwatch_timer(UNIT(m), &m->timer_watch); + + return r; +} + +static void mount_enter_dead(Mount *m, MountResult f) { + assert(m); + + if (f != MOUNT_SUCCESS) + m->result = f; + + mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD); +} + +static void mount_enter_mounted(Mount *m, MountResult f) { + assert(m); + + if (f != MOUNT_SUCCESS) + m->result = f; + + mount_set_state(m, MOUNT_MOUNTED); +} + +static void mount_enter_signal(Mount *m, MountState state, MountResult f) { + int r; + Set *pid_set = NULL; + bool wait_for_exit = false; + + assert(m); + + if (f != MOUNT_SUCCESS) + m->result = f; + + if (m->exec_context.kill_mode != KILL_NONE) { + int sig = (state == MOUNT_MOUNTING_SIGTERM || + state == MOUNT_UNMOUNTING_SIGTERM || + state == MOUNT_REMOUNTING_SIGTERM) ? m->exec_context.kill_signal : SIGKILL; + + if (m->control_pid > 0) { + if (kill_and_sigcont(m->control_pid, sig) < 0 && errno != ESRCH) + + log_warning("Failed to kill control process %li: %m", (long) m->control_pid); + else + wait_for_exit = true; + } + + if (m->exec_context.kill_mode == KILL_CONTROL_GROUP) { + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) { + r = -ENOMEM; + goto fail; + } + + /* Exclude the control pid from being killed via the cgroup */ + if (m->control_pid > 0) + if ((r = set_put(pid_set, LONG_TO_PTR(m->control_pid))) < 0) + goto fail; + + if ((r = cgroup_bonding_kill_list(UNIT(m)->cgroup_bondings, sig, true, pid_set)) < 0) { + if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) + log_warning("Failed to kill control group: %s", strerror(-r)); + } else if (r > 0) + wait_for_exit = true; + + set_free(pid_set); + pid_set = NULL; + } + } + + if (wait_for_exit) { + if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 0) + goto fail; + + mount_set_state(m, state); + } else if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL) + mount_enter_mounted(m, MOUNT_SUCCESS); + else + mount_enter_dead(m, MOUNT_SUCCESS); + + return; + +fail: + log_warning("%s failed to kill processes: %s", UNIT(m)->id, strerror(-r)); + + if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL) + mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); + else + mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); + + if (pid_set) + set_free(pid_set); +} + +static void mount_enter_unmounting(Mount *m) { + int r; + + assert(m); + + m->control_command_id = MOUNT_EXEC_UNMOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_UNMOUNT; + + if ((r = exec_command_set( + m->control_command, + "/bin/umount", + m->where, + NULL)) < 0) + goto fail; + + mount_unwatch_control_pid(m); + + if ((r = mount_spawn(m, m->control_command, &m->control_pid)) < 0) + goto fail; + + mount_set_state(m, MOUNT_UNMOUNTING); + + return; + +fail: + log_warning("%s failed to run 'umount' task: %s", UNIT(m)->id, strerror(-r)); + mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); +} + +static void mount_enter_mounting(Mount *m) { + int r; + MountParameters *p; + + assert(m); + + m->control_command_id = MOUNT_EXEC_MOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_MOUNT; + + mkdir_p(m->where, m->directory_mode); + + /* Create the source directory for bind-mounts if needed */ + p = get_mount_parameters_configured(m); + if (p && mount_is_bind(p)) + mkdir_p(p->what, m->directory_mode); + + if (m->from_fragment) + r = exec_command_set( + m->control_command, + "/bin/mount", + m->parameters_fragment.what, + m->where, + "-t", m->parameters_fragment.fstype ? m->parameters_fragment.fstype : "auto", + m->parameters_fragment.options ? "-o" : NULL, m->parameters_fragment.options, + NULL); + else if (m->from_etc_fstab) + r = exec_command_set( + m->control_command, + "/bin/mount", + m->where, + NULL); + else + r = -ENOENT; + + if (r < 0) + goto fail; + + mount_unwatch_control_pid(m); + + if ((r = mount_spawn(m, m->control_command, &m->control_pid)) < 0) + goto fail; + + mount_set_state(m, MOUNT_MOUNTING); + + return; + +fail: + log_warning("%s failed to run 'mount' task: %s", UNIT(m)->id, strerror(-r)); + mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); +} + +static void mount_enter_mounting_done(Mount *m) { + assert(m); + + mount_set_state(m, MOUNT_MOUNTING_DONE); +} + +static void mount_enter_remounting(Mount *m) { + int r; + + assert(m); + + m->control_command_id = MOUNT_EXEC_REMOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_REMOUNT; + + if (m->from_fragment) { + char *buf = NULL; + const char *o; + + if (m->parameters_fragment.options) { + if (!(buf = strappend("remount,", m->parameters_fragment.options))) { + r = -ENOMEM; + goto fail; + } + + o = buf; + } else + o = "remount"; + + r = exec_command_set( + m->control_command, + "/bin/mount", + m->parameters_fragment.what, + m->where, + "-t", m->parameters_fragment.fstype ? m->parameters_fragment.fstype : "auto", + "-o", o, + NULL); + + free(buf); + } else if (m->from_etc_fstab) + r = exec_command_set( + m->control_command, + "/bin/mount", + m->where, + "-o", "remount", + NULL); + else + r = -ENOENT; + + if (r < 0) + goto fail; + + mount_unwatch_control_pid(m); + + if ((r = mount_spawn(m, m->control_command, &m->control_pid)) < 0) + goto fail; + + mount_set_state(m, MOUNT_REMOUNTING); + + return; + +fail: + log_warning("%s failed to run 'remount' task: %s", UNIT(m)->id, strerror(-r)); + m->reload_result = MOUNT_FAILURE_RESOURCES; + mount_enter_mounted(m, MOUNT_SUCCESS); +} + +static int mount_start(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (m->state == MOUNT_UNMOUNTING || + m->state == MOUNT_UNMOUNTING_SIGTERM || + m->state == MOUNT_UNMOUNTING_SIGKILL || + m->state == MOUNT_MOUNTING_SIGTERM || + m->state == MOUNT_MOUNTING_SIGKILL) + return -EAGAIN; + + /* Already on it! */ + if (m->state == MOUNT_MOUNTING) + return 0; + + assert(m->state == MOUNT_DEAD || m->state == MOUNT_FAILED); + + m->result = MOUNT_SUCCESS; + m->reload_result = MOUNT_SUCCESS; + + mount_enter_mounting(m); + return 0; +} + +static int mount_stop(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + /* Already on it */ + if (m->state == MOUNT_UNMOUNTING || + m->state == MOUNT_UNMOUNTING_SIGKILL || + m->state == MOUNT_UNMOUNTING_SIGTERM || + m->state == MOUNT_MOUNTING_SIGTERM || + m->state == MOUNT_MOUNTING_SIGKILL) + return 0; + + assert(m->state == MOUNT_MOUNTING || + m->state == MOUNT_MOUNTING_DONE || + m->state == MOUNT_MOUNTED || + m->state == MOUNT_REMOUNTING || + m->state == MOUNT_REMOUNTING_SIGTERM || + m->state == MOUNT_REMOUNTING_SIGKILL); + + mount_enter_unmounting(m); + return 0; +} + +static int mount_reload(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + if (m->state == MOUNT_MOUNTING_DONE) + return -EAGAIN; + + assert(m->state == MOUNT_MOUNTED); + + mount_enter_remounting(m); + return 0; +} + +static int mount_serialize(Unit *u, FILE *f, FDSet *fds) { + Mount *m = MOUNT(u); + + assert(m); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", mount_state_to_string(m->state)); + unit_serialize_item(u, f, "result", mount_result_to_string(m->result)); + unit_serialize_item(u, f, "reload-result", mount_result_to_string(m->reload_result)); + + if (m->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", "%lu", (unsigned long) m->control_pid); + + if (m->control_command_id >= 0) + unit_serialize_item(u, f, "control-command", mount_exec_command_to_string(m->control_command_id)); + + return 0; +} + +static int mount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Mount *m = MOUNT(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + MountState state; + + if ((state = mount_state_from_string(value)) < 0) + log_debug("Failed to parse state value %s", value); + else + m->deserialized_state = state; + } else if (streq(key, "result")) { + MountResult f; + + f = mount_result_from_string(value); + if (f < 0) + log_debug("Failed to parse result value %s", value); + else if (f != MOUNT_SUCCESS) + m->result = f; + + } else if (streq(key, "reload-result")) { + MountResult f; + + f = mount_result_from_string(value); + if (f < 0) + log_debug("Failed to parse reload result value %s", value); + else if (f != MOUNT_SUCCESS) + m->reload_result = f; + + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_debug("Failed to parse control-pid value %s", value); + else + m->control_pid = pid; + } else if (streq(key, "control-command")) { + MountExecCommand id; + + if ((id = mount_exec_command_from_string(value)) < 0) + log_debug("Failed to parse exec-command value %s", value); + else { + m->control_command_id = id; + m->control_command = m->exec_command + id; + } + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState mount_active_state(Unit *u) { + assert(u); + + return state_translation_table[MOUNT(u)->state]; +} + +static const char *mount_sub_state_to_string(Unit *u) { + assert(u); + + return mount_state_to_string(MOUNT(u)->state); +} + +static bool mount_check_gc(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + return m->from_etc_fstab || m->from_proc_self_mountinfo; +} + +static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Mount *m = MOUNT(u); + MountResult f; + + assert(m); + assert(pid >= 0); + + if (pid != m->control_pid) + return; + + m->control_pid = 0; + + if (is_clean_exit(code, status)) + f = MOUNT_SUCCESS; + else if (code == CLD_EXITED) + f = MOUNT_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = MOUNT_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = MOUNT_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown code"); + + if (f != MOUNT_SUCCESS) + m->result = f; + + if (m->control_command) { + exec_status_exit(&m->control_command->exec_status, &m->exec_context, pid, code, status); + + m->control_command = NULL; + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + } + + log_full(f == MOUNT_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + "%s mount process exited, code=%s status=%i", u->id, sigchld_code_to_string(code), status); + + /* Note that mount(8) returning and the kernel sending us a + * mount table change event might happen out-of-order. If an + * operation succeed we assume the kernel will follow soon too + * and already change into the resulting state. If it fails + * we check if the kernel still knows about the mount. and + * change state accordingly. */ + + switch (m->state) { + + case MOUNT_MOUNTING: + case MOUNT_MOUNTING_DONE: + case MOUNT_MOUNTING_SIGKILL: + case MOUNT_MOUNTING_SIGTERM: + + if (f == MOUNT_SUCCESS) + mount_enter_mounted(m, f); + else if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, f); + else + mount_enter_dead(m, f); + break; + + case MOUNT_REMOUNTING: + case MOUNT_REMOUNTING_SIGKILL: + case MOUNT_REMOUNTING_SIGTERM: + + m->reload_result = f; + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_SUCCESS); + else + mount_enter_dead(m, MOUNT_SUCCESS); + + break; + + case MOUNT_UNMOUNTING: + case MOUNT_UNMOUNTING_SIGKILL: + case MOUNT_UNMOUNTING_SIGTERM: + + if (f == MOUNT_SUCCESS) + mount_enter_dead(m, f); + else if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, f); + else + mount_enter_dead(m, f); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); +} + +static void mount_timer_event(Unit *u, uint64_t elapsed, Watch *w) { + Mount *m = MOUNT(u); + + assert(m); + assert(elapsed == 1); + assert(w == &m->timer_watch); + + switch (m->state) { + + case MOUNT_MOUNTING: + case MOUNT_MOUNTING_DONE: + log_warning("%s mounting timed out. Stopping.", u->id); + mount_enter_signal(m, MOUNT_MOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); + break; + + case MOUNT_REMOUNTING: + log_warning("%s remounting timed out. Stopping.", u->id); + m->reload_result = MOUNT_FAILURE_TIMEOUT; + mount_enter_mounted(m, MOUNT_SUCCESS); + break; + + case MOUNT_UNMOUNTING: + log_warning("%s unmounting timed out. Stopping.", u->id); + mount_enter_signal(m, MOUNT_UNMOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); + break; + + case MOUNT_MOUNTING_SIGTERM: + if (m->exec_context.send_sigkill) { + log_warning("%s mounting timed out. Killing.", u->id); + mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); + } else { + log_warning("%s mounting timed out. Skipping SIGKILL. Ignoring.", u->id); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); + else + mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); + } + break; + + case MOUNT_REMOUNTING_SIGTERM: + if (m->exec_context.send_sigkill) { + log_warning("%s remounting timed out. Killing.", u->id); + mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); + } else { + log_warning("%s remounting timed out. Skipping SIGKILL. Ignoring.", u->id); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); + else + mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); + } + break; + + case MOUNT_UNMOUNTING_SIGTERM: + if (m->exec_context.send_sigkill) { + log_warning("%s unmounting timed out. Killing.", u->id); + mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); + } else { + log_warning("%s unmounting timed out. Skipping SIGKILL. Ignoring.", u->id); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); + else + mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); + } + break; + + case MOUNT_MOUNTING_SIGKILL: + case MOUNT_REMOUNTING_SIGKILL: + case MOUNT_UNMOUNTING_SIGKILL: + log_warning("%s mount process still around after SIGKILL. Ignoring.", u->id); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); + else + mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } +} + +static int mount_add_one( + Manager *m, + const char *what, + const char *where, + const char *options, + const char *fstype, + int passno, + bool from_proc_self_mountinfo, + bool set_flags) { + int r; + Unit *u; + bool delete; + char *e, *w = NULL, *o = NULL, *f = NULL; + MountParameters *p; + + assert(m); + assert(what); + assert(where); + assert(options); + assert(fstype); + + assert(!set_flags || from_proc_self_mountinfo); + + /* Ignore API mount points. They should never be referenced in + * dependencies ever. */ + if (mount_point_is_api(where) || mount_point_ignore(where)) + return 0; + + if (streq(fstype, "autofs")) + return 0; + + /* probably some kind of swap, ignore */ + if (!is_path(where)) + return 0; + + e = unit_name_from_path(where, ".mount"); + if (!e) + return -ENOMEM; + + u = manager_get_unit(m, e); + if (!u) { + delete = true; + + u = unit_new(m, sizeof(Mount)); + if (!u) { + free(e); + return -ENOMEM; + } + + r = unit_add_name(u, e); + free(e); + + if (r < 0) + goto fail; + + MOUNT(u)->where = strdup(where); + if (!MOUNT(u)->where) { + r = -ENOMEM; + goto fail; + } + + unit_add_to_load_queue(u); + } else { + delete = false; + free(e); + } + + if (!(w = strdup(what)) || + !(o = strdup(options)) || + !(f = strdup(fstype))) { + r = -ENOMEM; + goto fail; + } + + if (from_proc_self_mountinfo) { + p = &MOUNT(u)->parameters_proc_self_mountinfo; + + if (set_flags) { + MOUNT(u)->is_mounted = true; + MOUNT(u)->just_mounted = !MOUNT(u)->from_proc_self_mountinfo; + MOUNT(u)->just_changed = !streq_ptr(p->options, o); + } + + MOUNT(u)->from_proc_self_mountinfo = true; + } else { + p = &MOUNT(u)->parameters_etc_fstab; + MOUNT(u)->from_etc_fstab = true; + } + + free(p->what); + p->what = w; + + free(p->options); + p->options = o; + + free(p->fstype); + p->fstype = f; + + p->passno = passno; + + unit_add_to_dbus_queue(u); + + return 0; + +fail: + free(w); + free(o); + free(f); + + if (delete && u) + unit_free(u); + + return r; +} + +static int mount_find_pri(char *options) { + char *end, *pri; + unsigned long r; + + if (!(pri = mount_test_option(options, "pri"))) + return 0; + + pri += 4; + + errno = 0; + r = strtoul(pri, &end, 10); + + if (errno != 0) + return -errno; + + if (end == pri || (*end != ',' && *end != 0)) + return -EINVAL; + + return (int) r; +} + +static int mount_load_etc_fstab(Manager *m) { + FILE *f; + int r = 0; + struct mntent* me; + + assert(m); + + errno = 0; + if (!(f = setmntent("/etc/fstab", "r"))) + return -errno; + + while ((me = getmntent(f))) { + char *where, *what; + int k; + + if (!(what = fstab_node_to_udev_node(me->mnt_fsname))) { + r = -ENOMEM; + goto finish; + } + + if (!(where = strdup(me->mnt_dir))) { + free(what); + r = -ENOMEM; + goto finish; + } + + if (what[0] == '/') + path_kill_slashes(what); + + if (where[0] == '/') + path_kill_slashes(where); + + if (streq(me->mnt_type, "swap")) { + int pri; + + if ((pri = mount_find_pri(me->mnt_opts)) < 0) + k = pri; + else + k = swap_add_one(m, + what, + NULL, + pri, + !!mount_test_option(me->mnt_opts, "noauto"), + !!mount_test_option(me->mnt_opts, "nofail"), + !!mount_test_option(me->mnt_opts, "comment=systemd.swapon"), + false); + } else + k = mount_add_one(m, what, where, me->mnt_opts, me->mnt_type, me->mnt_passno, false, false); + + free(what); + free(where); + + if (k < 0) + r = k; + } + +finish: + + endmntent(f); + return r; +} + +static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) { + int r = 0; + unsigned i; + char *device, *path, *options, *options2, *fstype, *d, *p, *o; + + assert(m); + + rewind(m->proc_self_mountinfo); + + for (i = 1;; i++) { + int k; + + device = path = options = options2 = fstype = d = p = o = NULL; + + if ((k = fscanf(m->proc_self_mountinfo, + "%*s " /* (1) mount id */ + "%*s " /* (2) parent id */ + "%*s " /* (3) major:minor */ + "%*s " /* (4) root */ + "%ms " /* (5) mount point */ + "%ms" /* (6) mount options */ + "%*[^-]" /* (7) optional fields */ + "- " /* (8) separator */ + "%ms " /* (9) file system type */ + "%ms" /* (10) mount source */ + "%ms" /* (11) mount options 2 */ + "%*[^\n]", /* some rubbish at the end */ + &path, + &options, + &fstype, + &device, + &options2)) != 5) { + + if (k == EOF) + break; + + log_warning("Failed to parse /proc/self/mountinfo:%u.", i); + goto clean_up; + } + + if (asprintf(&o, "%s,%s", options, options2) < 0) { + r = -ENOMEM; + goto finish; + } + + if (!(d = cunescape(device)) || + !(p = cunescape(path))) { + r = -ENOMEM; + goto finish; + } + + if ((k = mount_add_one(m, d, p, o, fstype, 0, true, set_flags)) < 0) + r = k; + +clean_up: + free(device); + free(path); + free(options); + free(options2); + free(fstype); + free(d); + free(p); + free(o); + } + +finish: + free(device); + free(path); + free(options); + free(options2); + free(fstype); + free(d); + free(p); + free(o); + + return r; +} + +static void mount_shutdown(Manager *m) { + assert(m); + + if (m->proc_self_mountinfo) { + fclose(m->proc_self_mountinfo); + m->proc_self_mountinfo = NULL; + } +} + +static int mount_enumerate(Manager *m) { + int r; + struct epoll_event ev; + assert(m); + + if (!m->proc_self_mountinfo) { + if (!(m->proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"))) + return -errno; + + m->mount_watch.type = WATCH_MOUNT; + m->mount_watch.fd = fileno(m->proc_self_mountinfo); + + zero(ev); + ev.events = EPOLLPRI; + ev.data.ptr = &m->mount_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->mount_watch.fd, &ev) < 0) + return -errno; + } + + if ((r = mount_load_etc_fstab(m)) < 0) + goto fail; + + if ((r = mount_load_proc_self_mountinfo(m, false)) < 0) + goto fail; + + return 0; + +fail: + mount_shutdown(m); + return r; +} + +void mount_fd_event(Manager *m, int events) { + Unit *u; + int r; + + assert(m); + assert(events & EPOLLPRI); + + /* The manager calls this for every fd event happening on the + * /proc/self/mountinfo file, which informs us about mounting + * table changes */ + + if ((r = mount_load_proc_self_mountinfo(m, true)) < 0) { + log_error("Failed to reread /proc/self/mountinfo: %s", strerror(-r)); + + /* Reset flags, just in case, for later calls */ + LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) { + Mount *mount = MOUNT(u); + + mount->is_mounted = mount->just_mounted = mount->just_changed = false; + } + + return; + } + + manager_dispatch_load_queue(m); + + LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) { + Mount *mount = MOUNT(u); + + if (!mount->is_mounted) { + /* This has just been unmounted. */ + + mount->from_proc_self_mountinfo = false; + + switch (mount->state) { + + case MOUNT_MOUNTED: + mount_enter_dead(mount, MOUNT_SUCCESS); + break; + + default: + mount_set_state(mount, mount->state); + break; + + } + + } else if (mount->just_mounted || mount->just_changed) { + + /* New or changed mount entry */ + + switch (mount->state) { + + case MOUNT_DEAD: + case MOUNT_FAILED: + mount_enter_mounted(mount, MOUNT_SUCCESS); + break; + + case MOUNT_MOUNTING: + mount_enter_mounting_done(mount); + break; + + default: + /* Nothing really changed, but let's + * issue an notification call + * nonetheless, in case somebody is + * waiting for this. (e.g. file system + * ro/rw remounts.) */ + mount_set_state(mount, mount->state); + break; + } + } + + /* Reset the flags for later calls */ + mount->is_mounted = mount->just_mounted = mount->just_changed = false; + } +} + +static void mount_reset_failed(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + if (m->state == MOUNT_FAILED) + mount_set_state(m, MOUNT_DEAD); + + m->result = MOUNT_SUCCESS; + m->reload_result = MOUNT_SUCCESS; +} + +static int mount_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) { + Mount *m = MOUNT(u); + int r = 0; + Set *pid_set = NULL; + + assert(m); + + if (who == KILL_MAIN) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Mount units have no main processes"); + return -ESRCH; + } + + if (m->control_pid <= 0 && who == KILL_CONTROL) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); + return -ESRCH; + } + + if (who == KILL_CONTROL || who == KILL_ALL) + if (m->control_pid > 0) + if (kill(m->control_pid, signo) < 0) + r = -errno; + + if (who == KILL_ALL && mode == KILL_CONTROL_GROUP) { + int q; + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + /* Exclude the control pid from being killed via the cgroup */ + if (m->control_pid > 0) + if ((q = set_put(pid_set, LONG_TO_PTR(m->control_pid))) < 0) { + r = q; + goto finish; + } + + if ((q = cgroup_bonding_kill_list(UNIT(m)->cgroup_bondings, signo, false, pid_set)) < 0) + if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + } + +finish: + if (pid_set) + set_free(pid_set); + + return r; +} + +static const char* const mount_state_table[_MOUNT_STATE_MAX] = { + [MOUNT_DEAD] = "dead", + [MOUNT_MOUNTING] = "mounting", + [MOUNT_MOUNTING_DONE] = "mounting-done", + [MOUNT_MOUNTED] = "mounted", + [MOUNT_REMOUNTING] = "remounting", + [MOUNT_UNMOUNTING] = "unmounting", + [MOUNT_MOUNTING_SIGTERM] = "mounting-sigterm", + [MOUNT_MOUNTING_SIGKILL] = "mounting-sigkill", + [MOUNT_REMOUNTING_SIGTERM] = "remounting-sigterm", + [MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill", + [MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm", + [MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill", + [MOUNT_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(mount_state, MountState); + +static const char* const mount_exec_command_table[_MOUNT_EXEC_COMMAND_MAX] = { + [MOUNT_EXEC_MOUNT] = "ExecMount", + [MOUNT_EXEC_UNMOUNT] = "ExecUnmount", + [MOUNT_EXEC_REMOUNT] = "ExecRemount", +}; + +DEFINE_STRING_TABLE_LOOKUP(mount_exec_command, MountExecCommand); + +static const char* const mount_result_table[_MOUNT_RESULT_MAX] = { + [MOUNT_SUCCESS] = "success", + [MOUNT_FAILURE_RESOURCES] = "resources", + [MOUNT_FAILURE_TIMEOUT] = "timeout", + [MOUNT_FAILURE_EXIT_CODE] = "exit-code", + [MOUNT_FAILURE_SIGNAL] = "signal", + [MOUNT_FAILURE_CORE_DUMP] = "core-dump" +}; + +DEFINE_STRING_TABLE_LOOKUP(mount_result, MountResult); + +const UnitVTable mount_vtable = { + .suffix = ".mount", + .object_size = sizeof(Mount), + .sections = + "Unit\0" + "Mount\0" + "Install\0", + + .no_alias = true, + .no_instances = true, + .show_status = true, + + .init = mount_init, + .load = mount_load, + .done = mount_done, + + .coldplug = mount_coldplug, + + .dump = mount_dump, + + .start = mount_start, + .stop = mount_stop, + .reload = mount_reload, + + .kill = mount_kill, + + .serialize = mount_serialize, + .deserialize_item = mount_deserialize_item, + + .active_state = mount_active_state, + .sub_state_to_string = mount_sub_state_to_string, + + .check_gc = mount_check_gc, + + .sigchld_event = mount_sigchld_event, + .timer_event = mount_timer_event, + + .reset_failed = mount_reset_failed, + + .bus_interface = "org.freedesktop.systemd1.Mount", + .bus_message_handler = bus_mount_message_handler, + .bus_invalidating_properties = bus_mount_invalidating_properties, + + .enumerate = mount_enumerate, + .shutdown = mount_shutdown +}; diff --git a/src/mount.h b/src/mount.h new file mode 100644 index 000000000..931844424 --- /dev/null +++ b/src/mount.h @@ -0,0 +1,124 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foomounthfoo +#define foomounthfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Mount Mount; + +#include "unit.h" + +typedef enum MountState { + MOUNT_DEAD, + MOUNT_MOUNTING, /* /bin/mount is running, but the mount is not done yet. */ + MOUNT_MOUNTING_DONE, /* /bin/mount is running, and the mount is done. */ + MOUNT_MOUNTED, + MOUNT_REMOUNTING, + MOUNT_UNMOUNTING, + MOUNT_MOUNTING_SIGTERM, + MOUNT_MOUNTING_SIGKILL, + MOUNT_REMOUNTING_SIGTERM, + MOUNT_REMOUNTING_SIGKILL, + MOUNT_UNMOUNTING_SIGTERM, + MOUNT_UNMOUNTING_SIGKILL, + MOUNT_FAILED, + _MOUNT_STATE_MAX, + _MOUNT_STATE_INVALID = -1 +} MountState; + +typedef enum MountExecCommand { + MOUNT_EXEC_MOUNT, + MOUNT_EXEC_UNMOUNT, + MOUNT_EXEC_REMOUNT, + _MOUNT_EXEC_COMMAND_MAX, + _MOUNT_EXEC_COMMAND_INVALID = -1 +} MountExecCommand; + +typedef struct MountParameters { + char *what; + char *options; + char *fstype; + int passno; +} MountParameters; + +typedef enum MountResult { + MOUNT_SUCCESS, + MOUNT_FAILURE_RESOURCES, + MOUNT_FAILURE_TIMEOUT, + MOUNT_FAILURE_EXIT_CODE, + MOUNT_FAILURE_SIGNAL, + MOUNT_FAILURE_CORE_DUMP, + _MOUNT_RESULT_MAX, + _MOUNT_RESULT_INVALID = -1 +} MountResult; + +struct Mount { + Unit meta; + + char *where; + + MountParameters parameters_etc_fstab; + MountParameters parameters_proc_self_mountinfo; + MountParameters parameters_fragment; + + bool from_etc_fstab:1; + bool from_proc_self_mountinfo:1; + bool from_fragment:1; + + /* Used while looking for mount points that vanished or got + * added from/to /proc/self/mountinfo */ + bool is_mounted:1; + bool just_mounted:1; + bool just_changed:1; + + MountResult result; + MountResult reload_result; + + mode_t directory_mode; + + usec_t timeout_usec; + + ExecCommand exec_command[_MOUNT_EXEC_COMMAND_MAX]; + ExecContext exec_context; + + MountState state, deserialized_state; + + ExecCommand* control_command; + MountExecCommand control_command_id; + pid_t control_pid; + + Watch timer_watch; +}; + +extern const UnitVTable mount_vtable; + +void mount_fd_event(Manager *m, int events); + +const char* mount_state_to_string(MountState i); +MountState mount_state_from_string(const char *s); + +const char* mount_exec_command_to_string(MountExecCommand i); +MountExecCommand mount_exec_command_from_string(const char *s); + +const char* mount_result_to_string(MountResult i); +MountResult mount_result_from_string(const char *s); + +#endif diff --git a/src/namespace.c b/src/namespace.c new file mode 100644 index 000000000..09bc82909 --- /dev/null +++ b/src/namespace.c @@ -0,0 +1,346 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "strv.h" +#include "util.h" +#include "namespace.h" +#include "missing.h" + +typedef enum PathMode { + /* This is ordered by priority! */ + INACCESSIBLE, + READONLY, + PRIVATE, + READWRITE +} PathMode; + +typedef struct Path { + const char *path; + PathMode mode; +} Path; + +static int append_paths(Path **p, char **strv, PathMode mode) { + char **i; + + STRV_FOREACH(i, strv) { + + if (!path_is_absolute(*i)) + return -EINVAL; + + (*p)->path = *i; + (*p)->mode = mode; + (*p)++; + } + + return 0; +} + +static int path_compare(const void *a, const void *b) { + const Path *p = a, *q = b; + + if (path_equal(p->path, q->path)) { + + /* If the paths are equal, check the mode */ + if (p->mode < q->mode) + return -1; + + if (p->mode > q->mode) + return 1; + + return 0; + } + + /* If the paths are not equal, then order prefixes first */ + if (path_startswith(p->path, q->path)) + return 1; + + if (path_startswith(q->path, p->path)) + return -1; + + return 0; +} + +static void drop_duplicates(Path *p, unsigned *n, bool *need_inaccessible, bool *need_private) { + Path *f, *t, *previous; + + assert(p); + assert(n); + assert(need_inaccessible); + assert(need_private); + + for (f = p, t = p, previous = NULL; f < p+*n; f++) { + + if (previous && path_equal(f->path, previous->path)) + continue; + + t->path = f->path; + t->mode = f->mode; + + if (t->mode == PRIVATE) + *need_private = true; + + if (t->mode == INACCESSIBLE) + *need_inaccessible = true; + + previous = t; + + t++; + } + + *n = t - p; +} + +static int apply_mount(Path *p, const char *root_dir, const char *inaccessible_dir, const char *private_dir, unsigned long flags) { + const char *what; + char *where; + int r; + + assert(p); + assert(root_dir); + assert(inaccessible_dir); + assert(private_dir); + + if (!(where = strappend(root_dir, p->path))) + return -ENOMEM; + + switch (p->mode) { + + case INACCESSIBLE: + what = inaccessible_dir; + flags |= MS_RDONLY; + break; + + case READONLY: + flags |= MS_RDONLY; + /* Fall through */ + + case READWRITE: + what = p->path; + break; + + case PRIVATE: + what = private_dir; + break; + + default: + assert_not_reached("Unknown mode"); + } + + if ((r = mount(what, where, NULL, MS_BIND|MS_REC, NULL)) >= 0) { + log_debug("Successfully mounted %s to %s", what, where); + + /* The bind mount will always inherit the original + * flags. If we want to set any flag we need + * to do so in a second independent step. */ + if (flags) + r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_REC|flags, NULL); + + /* Avoid exponential growth of trees */ + if (r >= 0 && path_equal(p->path, "/")) + r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_UNBINDABLE|flags, NULL); + + if (r < 0) { + r = -errno; + umount2(where, MNT_DETACH); + } + } + + free(where); + return r; +} + +int setup_namespace( + char **writable, + char **readable, + char **inaccessible, + bool private_tmp, + unsigned long flags) { + + char + tmp_dir[] = "/tmp/systemd-namespace-XXXXXX", + root_dir[] = "/tmp/systemd-namespace-XXXXXX/root", + old_root_dir[] = "/tmp/systemd-namespace-XXXXXX/root/tmp/old-root-XXXXXX", + inaccessible_dir[] = "/tmp/systemd-namespace-XXXXXX/inaccessible", + private_dir[] = "/tmp/systemd-namespace-XXXXXX/private"; + + Path *paths, *p; + unsigned n; + bool need_private = false, need_inaccessible = false; + bool remove_tmp = false, remove_root = false, remove_old_root = false, remove_inaccessible = false, remove_private = false; + int r; + const char *t; + + n = + strv_length(writable) + + strv_length(readable) + + strv_length(inaccessible) + + (private_tmp ? 2 : 1); + + if (!(paths = new(Path, n))) + return -ENOMEM; + + p = paths; + if ((r = append_paths(&p, writable, READWRITE)) < 0 || + (r = append_paths(&p, readable, READONLY)) < 0 || + (r = append_paths(&p, inaccessible, INACCESSIBLE)) < 0) + goto fail; + + if (private_tmp) { + p->path = "/tmp"; + p->mode = PRIVATE; + p++; + } + + p->path = "/"; + p->mode = READWRITE; + p++; + + assert(paths + n == p); + + qsort(paths, n, sizeof(Path), path_compare); + drop_duplicates(paths, &n, &need_inaccessible, &need_private); + + if (!mkdtemp(tmp_dir)) { + r = -errno; + goto fail; + } + remove_tmp = true; + + memcpy(root_dir, tmp_dir, sizeof(tmp_dir)-1); + if (mkdir(root_dir, 0777) < 0) { + r = -errno; + goto fail; + } + remove_root = true; + + if (need_inaccessible) { + memcpy(inaccessible_dir, tmp_dir, sizeof(tmp_dir)-1); + if (mkdir(inaccessible_dir, 0) < 0) { + r = -errno; + goto fail; + } + remove_inaccessible = true; + } + + if (need_private) { + mode_t u; + + memcpy(private_dir, tmp_dir, sizeof(tmp_dir)-1); + + u = umask(0000); + if (mkdir(private_dir, 0777 + S_ISVTX) < 0) { + umask(u); + + r = -errno; + goto fail; + } + + umask(u); + remove_private = true; + } + + if (unshare(CLONE_NEWNS) < 0) { + r = -errno; + goto fail; + } + + /* Remount / as SLAVE so that nothing mounted in the namespace + shows up in the parent */ + if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) { + r = -errno; + goto fail; + } + + for (p = paths; p < paths + n; p++) + if ((r = apply_mount(p, root_dir, inaccessible_dir, private_dir, flags)) < 0) + goto undo_mounts; + + memcpy(old_root_dir, tmp_dir, sizeof(tmp_dir)-1); + if (!mkdtemp(old_root_dir)) { + r = -errno; + goto undo_mounts; + } + remove_old_root = true; + + if (chdir(root_dir) < 0) { + r = -errno; + goto undo_mounts; + } + + if (pivot_root(root_dir, old_root_dir) < 0) { + r = -errno; + goto undo_mounts; + } + + t = old_root_dir + sizeof(root_dir) - 1; + if (umount2(t, MNT_DETACH) < 0) + /* At this point it's too late to turn anything back, + * since we are already in the new root. */ + return -errno; + + if (rmdir(t) < 0) + return -errno; + + return 0; + +undo_mounts: + + for (p--; p >= paths; p--) { + char full_path[PATH_MAX]; + + snprintf(full_path, sizeof(full_path), "%s%s", root_dir, p->path); + char_array_0(full_path); + + umount2(full_path, MNT_DETACH); + } + +fail: + if (remove_old_root) + rmdir(old_root_dir); + + if (remove_inaccessible) + rmdir(inaccessible_dir); + + if (remove_private) + rmdir(private_dir); + + if (remove_root) + rmdir(root_dir); + + if (remove_tmp) + rmdir(tmp_dir); + + free(paths); + + return r; +} diff --git a/src/namespace.h b/src/namespace.h new file mode 100644 index 000000000..7cf1dedd3 --- /dev/null +++ b/src/namespace.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foonamespacehfoo +#define foonamespacehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +int setup_namespace( + char **writable, + char **readable, + char **inaccessible, + bool private_tmp, + unsigned long flags); + +#endif diff --git a/src/notify.c b/src/notify.c new file mode 100644 index 000000000..943808eb0 --- /dev/null +++ b/src/notify.c @@ -0,0 +1,228 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "strv.h" +#include "util.h" +#include "log.h" +#include "sd-readahead.h" +#include "build.h" + +static bool arg_ready = false; +static pid_t arg_pid = 0; +static const char *arg_status = NULL; +static bool arg_booted = false; +static const char *arg_readahead = NULL; + +static int help(void) { + + printf("%s [OPTIONS...] [VARIABLE=VALUE...]\n\n" + "Notify the init system about service status updates.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --ready Inform the init system about service start-up completion\n" + " --pid[=PID] Set main pid of daemon\n" + " --status=TEXT Set status text\n" + " --booted Returns 0 if the system was booted up with systemd, non-zero otherwise\n" + " --readahead=ACTION Controls read-ahead operations\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_READY = 0x100, + ARG_VERSION, + ARG_PID, + ARG_STATUS, + ARG_BOOTED, + ARG_READAHEAD + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "ready", no_argument, NULL, ARG_READY }, + { "pid", optional_argument, NULL, ARG_PID }, + { "status", required_argument, NULL, ARG_STATUS }, + { "booted", no_argument, NULL, ARG_BOOTED }, + { "readahead", required_argument, NULL, ARG_READAHEAD }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(DISTRIBUTION); + puts(SYSTEMD_FEATURES); + return 0; + + case ARG_READY: + arg_ready = true; + break; + + case ARG_PID: + + if (optarg) { + if (parse_pid(optarg, &arg_pid) < 0) { + log_error("Failed to parse PID %s.", optarg); + return -EINVAL; + } + } else + arg_pid = getppid(); + + break; + + case ARG_STATUS: + arg_status = optarg; + break; + + case ARG_BOOTED: + arg_booted = true; + break; + + case ARG_READAHEAD: + arg_readahead = optarg; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (optind >= argc && + !arg_ready && + !arg_status && + !arg_pid && + !arg_booted && + !arg_readahead) { + help(); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char* argv[]) { + char* our_env[4], **final_env = NULL; + unsigned i = 0; + char *status = NULL, *cpid = NULL, *n = NULL; + int r, retval = EXIT_FAILURE; + + log_parse_environment(); + log_open(); + + if ((r = parse_argv(argc, argv)) <= 0) { + retval = r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + goto finish; + } + + if (arg_booted) + return sd_booted() <= 0; + + if (arg_readahead) { + if ((r = sd_readahead(arg_readahead)) < 0) { + log_error("Failed to issue read-ahead control command: %s", strerror(-r)); + goto finish; + } + } + + if (arg_ready) + our_env[i++] = (char*) "READY=1"; + + if (arg_status) { + if (!(status = strappend("STATUS=", arg_status))) { + log_error("Failed to allocate STATUS string."); + goto finish; + } + + our_env[i++] = status; + } + + if (arg_pid > 0) { + if (asprintf(&cpid, "MAINPID=%lu", (unsigned long) arg_pid) < 0) { + log_error("Failed to allocate MAINPID string."); + goto finish; + } + + our_env[i++] = cpid; + } + + our_env[i++] = NULL; + + if (!(final_env = strv_env_merge(2, our_env, argv + optind))) { + log_error("Failed to merge string sets."); + goto finish; + } + + if (strv_length(final_env) <= 0) { + retval = EXIT_SUCCESS; + goto finish; + } + + if (!(n = strv_join(final_env, "\n"))) { + log_error("Failed to concatenate strings."); + goto finish; + } + + if ((r = sd_notify(false, n)) < 0) { + log_error("Failed to notify init system: %s", strerror(-r)); + goto finish; + } + + retval = r <= 0 ? EXIT_FAILURE : EXIT_SUCCESS; + +finish: + free(status); + free(cpid); + free(n); + + strv_free(final_env); + + return retval; +} diff --git a/src/nspawn.c b/src/nspawn.c new file mode 100644 index 000000000..6f5a9d954 --- /dev/null +++ b/src/nspawn.c @@ -0,0 +1,894 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "log.h" +#include "util.h" +#include "missing.h" +#include "cgroup-util.h" +#include "strv.h" +#include "loopback-setup.h" + +static char *arg_directory = NULL; +static char *arg_user = NULL; +static bool arg_private_network = false; + +static int help(void) { + + printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" + "Spawn a minimal namespace container for debugging, testing and building.\n\n" + " -h --help Show this help\n" + " -D --directory=NAME Root directory for the container\n" + " -u --user=USER Run the command under specified user or uid\n" + " --private-network Disable network in container\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_PRIVATE_NETWORK = 0x100 + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "directory", required_argument, NULL, 'D' }, + { "user", required_argument, NULL, 'u' }, + { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+hD:u:", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case 'D': + free(arg_directory); + if (!(arg_directory = strdup(optarg))) { + log_error("Failed to duplicate root directory."); + return -ENOMEM; + } + + break; + + case 'u': + free(arg_user); + if (!(arg_user = strdup(optarg))) { + log_error("Failed to duplicate user name."); + return -ENOMEM; + } + + break; + + case ARG_PRIVATE_NETWORK: + arg_private_network = true; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + return 1; +} + +static int mount_all(const char *dest) { + + typedef struct MountPoint { + const char *what; + const char *where; + const char *type; + const char *options; + unsigned long flags; + bool fatal; + } MountPoint; + + static const MountPoint mount_table[] = { + { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true }, + { "/proc/sys", "/proc/sys", "bind", NULL, MS_BIND, true }, /* Bind mount first */ + { "/proc/sys", "/proc/sys", "bind", NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, true }, /* Then, make it r/o */ + { "/sys", "/sys", "bind", NULL, MS_BIND, true }, /* Bind mount first */ + { "/sys", "/sys", "bind", NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, true }, /* Then, make it r/o */ + { "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID, true }, + { "/dev/pts", "/dev/pts", "bind", NULL, MS_BIND, true }, + { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV, true }, +#ifdef HAVE_SELINUX + { "/sys/fs/selinux", "/sys/fs/selinux", "bind", NULL, MS_BIND, false }, /* Bind mount first */ + { "/sys/fs/selinux", "/sys/fs/selinux", "bind", NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, false }, /* Then, make it r/o */ +#endif + }; + + unsigned k; + int r = 0; + char *where; + + for (k = 0; k < ELEMENTSOF(mount_table); k++) { + int t; + + if (asprintf(&where, "%s/%s", dest, mount_table[k].where) < 0) { + log_error("Out of memory"); + + if (r == 0) + r = -ENOMEM; + + break; + } + + if ((t = path_is_mount_point(where, false)) < 0) { + log_error("Failed to detect whether %s is a mount point: %s", where, strerror(-t)); + free(where); + + if (r == 0) + r = t; + + continue; + } + + mkdir_p(where, 0755); + + if (mount(mount_table[k].what, + where, + mount_table[k].type, + mount_table[k].flags, + mount_table[k].options) < 0 && + mount_table[k].fatal) { + + log_error("mount(%s) failed: %m", where); + + if (r == 0) + r = -errno; + } + + free(where); + } + + /* Fix the timezone, if possible */ + if (asprintf(&where, "%s/etc/localtime", dest) >= 0) { + + if (mount("/etc/localtime", where, "bind", MS_BIND, NULL) >= 0) + mount("/etc/localtime", where, "bind", MS_BIND|MS_REMOUNT|MS_RDONLY, NULL); + + free(where); + } + + if (asprintf(&where, "%s/etc/timezone", dest) >= 0) { + + if (mount("/etc/timezone", where, "bind", MS_BIND, NULL) >= 0) + mount("/etc/timezone", where, "bind", MS_BIND|MS_REMOUNT|MS_RDONLY, NULL); + + free(where); + } + + return r; +} + +static int copy_devnodes(const char *dest, const char *console) { + + static const char devnodes[] = + "null\0" + "zero\0" + "full\0" + "random\0" + "urandom\0" + "tty\0" + "ptmx\0" + "kmsg\0" + "rtc0\0"; + + const char *d; + int r = 0, k; + mode_t u; + struct stat st; + char *from = NULL, *to = NULL; + + assert(dest); + assert(console); + + u = umask(0000); + + NULSTR_FOREACH(d, devnodes) { + from = to = NULL; + + asprintf(&from, "/dev/%s", d); + asprintf(&to, "%s/dev/%s", dest, d); + + if (!from || !to) { + log_error("Failed to allocate devnode path"); + + free(from); + free(to); + + from = to = NULL; + + if (r == 0) + r = -ENOMEM; + + break; + } + + if (stat(from, &st) < 0) { + + if (errno != ENOENT) { + log_error("Failed to stat %s: %m", from); + if (r == 0) + r = -errno; + } + + } else if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { + + log_error("%s is not a char or block device, cannot copy.", from); + if (r == 0) + r = -EIO; + + } else if (mknod(to, st.st_mode, st.st_rdev) < 0) { + + log_error("mknod(%s) failed: %m", dest); + if (r == 0) + r = -errno; + } + + free(from); + free(to); + } + + if (stat(console, &st) < 0) { + + log_error("Failed to stat %s: %m", console); + if (r == 0) + r = -errno; + + goto finish; + + } else if (!S_ISCHR(st.st_mode)) { + + log_error("/dev/console is not a char device."); + if (r == 0) + r = -EIO; + + goto finish; + } + + if (asprintf(&to, "%s/dev/console", dest) < 0) { + + log_error("Out of memory"); + if (r == 0) + r = -ENOMEM; + + goto finish; + } + + /* We need to bind mount the right tty to /dev/console since + * ptys can only exist on pts file systems. To have something + * to bind mount things on we create a device node first, that + * has the right major/minor (note that the major minor + * doesn't actually matter here, since we mount it over + * anyway). */ + + if (mknod(to, (st.st_mode & ~07777) | 0600, st.st_rdev) < 0) + log_error("mknod for /dev/console failed: %m"); + + if (mount(console, to, "bind", MS_BIND, NULL) < 0) { + log_error("bind mount for /dev/console failed: %m"); + + if (r == 0) + r = -errno; + } + + free(to); + + if ((k = chmod_and_chown(console, 0600, 0, 0)) < 0) { + log_error("Failed to correct access mode for TTY: %s", strerror(-k)); + + if (r == 0) + r = k; + } + +finish: + umask(u); + + return r; +} + +static int drop_capabilities(void) { + static const unsigned long retain[] = { + CAP_CHOWN, + CAP_DAC_OVERRIDE, + CAP_DAC_READ_SEARCH, + CAP_FOWNER, + CAP_FSETID, + CAP_IPC_OWNER, + CAP_KILL, + CAP_LEASE, + CAP_LINUX_IMMUTABLE, + CAP_NET_BIND_SERVICE, + CAP_NET_BROADCAST, + CAP_NET_RAW, + CAP_SETGID, + CAP_SETFCAP, + CAP_SETPCAP, + CAP_SETUID, + CAP_SYS_ADMIN, + CAP_SYS_CHROOT, + CAP_SYS_NICE, + CAP_SYS_PTRACE, + CAP_SYS_TTY_CONFIG + }; + + unsigned long l; + + for (l = 0; l <= cap_last_cap(); l++) { + unsigned i; + + for (i = 0; i < ELEMENTSOF(retain); i++) + if (retain[i] == l) + break; + + if (i < ELEMENTSOF(retain)) + continue; + + if (prctl(PR_CAPBSET_DROP, l) < 0) { + log_error("PR_CAPBSET_DROP failed: %m"); + return -errno; + } + } + + return 0; +} + +static int is_os_tree(const char *path) { + int r; + char *p; + /* We use /bin/sh as flag file if something is an OS */ + + if (asprintf(&p, "%s/bin/sh", path) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + return r < 0 ? 0 : 1; +} + +static int process_pty(int master, sigset_t *mask) { + + char in_buffer[LINE_MAX], out_buffer[LINE_MAX]; + size_t in_buffer_full = 0, out_buffer_full = 0; + struct epoll_event stdin_ev, stdout_ev, master_ev, signal_ev; + bool stdin_readable = false, stdout_writable = false, master_readable = false, master_writable = false; + int ep = -1, signal_fd = -1, r; + + fd_nonblock(STDIN_FILENO, 1); + fd_nonblock(STDOUT_FILENO, 1); + fd_nonblock(master, 1); + + if ((signal_fd = signalfd(-1, mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) { + log_error("signalfd(): %m"); + r = -errno; + goto finish; + } + + if ((ep = epoll_create1(EPOLL_CLOEXEC)) < 0) { + log_error("Failed to create epoll: %m"); + r = -errno; + goto finish; + } + + zero(stdin_ev); + stdin_ev.events = EPOLLIN|EPOLLET; + stdin_ev.data.fd = STDIN_FILENO; + + zero(stdout_ev); + stdout_ev.events = EPOLLOUT|EPOLLET; + stdout_ev.data.fd = STDOUT_FILENO; + + zero(master_ev); + master_ev.events = EPOLLIN|EPOLLOUT|EPOLLET; + master_ev.data.fd = master; + + zero(signal_ev); + signal_ev.events = EPOLLIN; + signal_ev.data.fd = signal_fd; + + if (epoll_ctl(ep, EPOLL_CTL_ADD, STDIN_FILENO, &stdin_ev) < 0 || + epoll_ctl(ep, EPOLL_CTL_ADD, STDOUT_FILENO, &stdout_ev) < 0 || + epoll_ctl(ep, EPOLL_CTL_ADD, master, &master_ev) < 0 || + epoll_ctl(ep, EPOLL_CTL_ADD, signal_fd, &signal_ev) < 0) { + log_error("Failed to regiser fds in epoll: %m"); + r = -errno; + goto finish; + } + + for (;;) { + struct epoll_event ev[16]; + ssize_t k; + int i, nfds; + + if ((nfds = epoll_wait(ep, ev, ELEMENTSOF(ev), -1)) < 0) { + + if (errno == EINTR || errno == EAGAIN) + continue; + + log_error("epoll_wait(): %m"); + r = -errno; + goto finish; + } + + assert(nfds >= 1); + + for (i = 0; i < nfds; i++) { + if (ev[i].data.fd == STDIN_FILENO) { + + if (ev[i].events & (EPOLLIN|EPOLLHUP)) + stdin_readable = true; + + } else if (ev[i].data.fd == STDOUT_FILENO) { + + if (ev[i].events & (EPOLLOUT|EPOLLHUP)) + stdout_writable = true; + + } else if (ev[i].data.fd == master) { + + if (ev[i].events & (EPOLLIN|EPOLLHUP)) + master_readable = true; + + if (ev[i].events & (EPOLLOUT|EPOLLHUP)) + master_writable = true; + + } else if (ev[i].data.fd == signal_fd) { + struct signalfd_siginfo sfsi; + ssize_t n; + + if ((n = read(signal_fd, &sfsi, sizeof(sfsi))) != sizeof(sfsi)) { + + if (n >= 0) { + log_error("Failed to read from signalfd: invalid block size"); + r = -EIO; + goto finish; + } + + if (errno != EINTR && errno != EAGAIN) { + log_error("Failed to read from signalfd: %m"); + r = -errno; + goto finish; + } + } else { + + if (sfsi.ssi_signo == SIGWINCH) { + struct winsize ws; + + /* The window size changed, let's forward that. */ + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) >= 0) + ioctl(master, TIOCSWINSZ, &ws); + } else { + r = 0; + goto finish; + } + } + } + } + + while ((stdin_readable && in_buffer_full <= 0) || + (master_writable && in_buffer_full > 0) || + (master_readable && out_buffer_full <= 0) || + (stdout_writable && out_buffer_full > 0)) { + + if (stdin_readable && in_buffer_full < LINE_MAX) { + + if ((k = read(STDIN_FILENO, in_buffer + in_buffer_full, LINE_MAX - in_buffer_full)) < 0) { + + if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO) + stdin_readable = false; + else { + log_error("read(): %m"); + r = -errno; + goto finish; + } + } else + in_buffer_full += (size_t) k; + } + + if (master_writable && in_buffer_full > 0) { + + if ((k = write(master, in_buffer, in_buffer_full)) < 0) { + + if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO) + master_writable = false; + else { + log_error("write(): %m"); + r = -errno; + goto finish; + } + + } else { + assert(in_buffer_full >= (size_t) k); + memmove(in_buffer, in_buffer + k, in_buffer_full - k); + in_buffer_full -= k; + } + } + + if (master_readable && out_buffer_full < LINE_MAX) { + + if ((k = read(master, out_buffer + out_buffer_full, LINE_MAX - out_buffer_full)) < 0) { + + if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO) + master_readable = false; + else { + log_error("read(): %m"); + r = -errno; + goto finish; + } + } else + out_buffer_full += (size_t) k; + } + + if (stdout_writable && out_buffer_full > 0) { + + if ((k = write(STDOUT_FILENO, out_buffer, out_buffer_full)) < 0) { + + if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO) + stdout_writable = false; + else { + log_error("write(): %m"); + r = -errno; + goto finish; + } + + } else { + assert(out_buffer_full >= (size_t) k); + memmove(out_buffer, out_buffer + k, out_buffer_full - k); + out_buffer_full -= k; + } + } + } + } + +finish: + if (ep >= 0) + close_nointr_nofail(ep); + + if (signal_fd >= 0) + close_nointr_nofail(signal_fd); + + return r; +} + +int main(int argc, char *argv[]) { + pid_t pid = 0; + int r = EXIT_FAILURE, k; + char *oldcg = NULL, *newcg = NULL; + int master = -1; + const char *console = NULL; + struct termios saved_attr, raw_attr; + sigset_t mask; + bool saved_attr_valid = false; + struct winsize ws; + + log_parse_environment(); + log_open(); + + if ((r = parse_argv(argc, argv)) <= 0) + goto finish; + + if (arg_directory) { + char *p; + + p = path_make_absolute_cwd(arg_directory); + free(arg_directory); + arg_directory = p; + } else + arg_directory = get_current_dir_name(); + + if (!arg_directory) { + log_error("Failed to determine path"); + goto finish; + } + + path_kill_slashes(arg_directory); + + if (geteuid() != 0) { + log_error("Need to be root."); + goto finish; + } + + if (sd_booted() <= 0) { + log_error("Not running on a systemd system."); + goto finish; + } + + if (path_equal(arg_directory, "/")) { + log_error("Spawning container on root directory not supported."); + goto finish; + } + + if (is_os_tree(arg_directory) <= 0) { + log_error("Directory %s doesn't look like an OS root directory. Refusing.", arg_directory); + goto finish; + } + + if ((k = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 0, &oldcg)) < 0) { + log_error("Failed to determine current cgroup: %s", strerror(-k)); + goto finish; + } + + if (asprintf(&newcg, "%s/nspawn-%lu", oldcg, (unsigned long) getpid()) < 0) { + log_error("Failed to allocate cgroup path."); + goto finish; + } + + if ((k = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, newcg, 0)) < 0) { + log_error("Failed to create cgroup: %s", strerror(-k)); + goto finish; + } + + if ((master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY)) < 0) { + log_error("Failed to acquire pseudo tty: %m"); + goto finish; + } + + if (!(console = ptsname(master))) { + log_error("Failed to determine tty name: %m"); + goto finish; + } + + log_info("Spawning namespace container on %s (console is %s).", arg_directory, console); + + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) >= 0) + ioctl(master, TIOCSWINSZ, &ws); + + if (unlockpt(master) < 0) { + log_error("Failed to unlock tty: %m"); + goto finish; + } + + if (tcgetattr(STDIN_FILENO, &saved_attr) < 0) { + log_error("Failed to get terminal attributes: %m"); + goto finish; + } + + saved_attr_valid = true; + + raw_attr = saved_attr; + cfmakeraw(&raw_attr); + raw_attr.c_lflag &= ~ECHO; + + if (tcsetattr(STDIN_FILENO, TCSANOW, &raw_attr) < 0) { + log_error("Failed to set terminal attributes: %m"); + goto finish; + } + + assert_se(sigemptyset(&mask) == 0); + sigset_add_many(&mask, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, -1); + assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0); + + pid = syscall(__NR_clone, SIGCHLD|CLONE_NEWIPC|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUTS|(arg_private_network ? CLONE_NEWNET : 0), NULL); + if (pid < 0) { + if (errno == EINVAL) + log_error("clone() failed, do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in): %m"); + else + log_error("clone() failed: %m"); + + goto finish; + } + + if (pid == 0) { + /* child */ + + const char *hn; + const char *home = NULL; + uid_t uid = (uid_t) -1; + gid_t gid = (gid_t) -1; + const char *envp[] = { + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "container=systemd-nspawn", /* LXC sets container=lxc, so follow the scheme here */ + NULL, /* TERM */ + NULL, /* HOME */ + NULL, /* USER */ + NULL, /* LOGNAME */ + NULL + }; + + envp[2] = strv_find_prefix(environ, "TERM="); + + close_nointr_nofail(master); + + close_nointr(STDIN_FILENO); + close_nointr(STDOUT_FILENO); + close_nointr(STDERR_FILENO); + + close_all_fds(NULL, 0); + + reset_all_signal_handlers(); + + assert_se(sigemptyset(&mask) == 0); + assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); + + if (setsid() < 0) + goto child_fail; + + if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0) + goto child_fail; + + /* Mark / as private, in case somebody marked it shared */ + if (mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL) < 0) + goto child_fail; + + if (mount_all(arg_directory) < 0) + goto child_fail; + + if (copy_devnodes(arg_directory, console) < 0) + goto child_fail; + + if (chdir(arg_directory) < 0) { + log_error("chdir(%s) failed: %m", arg_directory); + goto child_fail; + } + + if (open_terminal("dev/console", O_RDWR) != STDIN_FILENO || + dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO || + dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO) + goto child_fail; + + if (mount(arg_directory, "/", "bind", MS_BIND|MS_MOVE, NULL) < 0) { + log_error("mount(MS_MOVE) failed: %m"); + goto child_fail; + } + + if (chroot(".") < 0) { + log_error("chroot() failed: %m"); + goto child_fail; + } + + if (chdir("/") < 0) { + log_error("chdir() failed: %m"); + goto child_fail; + } + + umask(0022); + + loopback_setup(); + + if (drop_capabilities() < 0) + goto child_fail; + + if (arg_user) { + + if (get_user_creds((const char**)&arg_user, &uid, &gid, &home) < 0) { + log_error("get_user_creds() failed: %m"); + goto child_fail; + } + + if (mkdir_parents(home, 0775) < 0) { + log_error("mkdir_parents() failed: %m"); + goto child_fail; + } + + if (safe_mkdir(home, 0775, uid, gid) < 0) { + log_error("safe_mkdir() failed: %m"); + goto child_fail; + } + + if (initgroups((const char*)arg_user, gid) < 0) { + log_error("initgroups() failed: %m"); + goto child_fail; + } + + if (setresgid(gid, gid, gid) < 0) { + log_error("setregid() failed: %m"); + goto child_fail; + } + + if (setresuid(uid, uid, uid) < 0) { + log_error("setreuid() failed: %m"); + goto child_fail; + } + } + + if ((asprintf((char**)(envp + 3), "HOME=%s", home? home: "/root") < 0) || + (asprintf((char**)(envp + 4), "USER=%s", arg_user? arg_user : "root") < 0) || + (asprintf((char**)(envp + 5), "LOGNAME=%s", arg_user? arg_user : "root") < 0)) { + log_error("Out of memory"); + goto child_fail; + } + + if ((hn = file_name_from_path(arg_directory))) + sethostname(hn, strlen(hn)); + + if (argc > optind) + execvpe(argv[optind], argv + optind, (char**) envp); + else { + chdir(home ? home : "/root"); + execle("/bin/bash", "-bash", NULL, (char**) envp); + } + + log_error("execv() failed: %m"); + + child_fail: + _exit(EXIT_FAILURE); + } + + if (process_pty(master, &mask) < 0) + goto finish; + + if (saved_attr_valid) { + tcsetattr(STDIN_FILENO, TCSANOW, &saved_attr); + saved_attr_valid = false; + } + + r = wait_for_terminate_and_warn(argc > optind ? argv[optind] : "bash", pid); + + if (r < 0) + r = EXIT_FAILURE; + +finish: + if (saved_attr_valid) + tcsetattr(STDIN_FILENO, TCSANOW, &saved_attr); + + if (master >= 0) + close_nointr_nofail(master); + + if (oldcg) + cg_attach(SYSTEMD_CGROUP_CONTROLLER, oldcg, 0); + + if (newcg) + cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, newcg, true); + + free(arg_directory); + free(oldcg); + free(newcg); + + return r; +} diff --git a/src/org.freedesktop.systemd1.conf b/src/org.freedesktop.systemd1.conf new file mode 100644 index 000000000..201afe65b --- /dev/null +++ b/src/org.freedesktop.systemd1.conf @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/org.freedesktop.systemd1.policy.in.in b/src/org.freedesktop.systemd1.policy.in.in new file mode 100644 index 000000000..1771314e0 --- /dev/null +++ b/src/org.freedesktop.systemd1.policy.in.in @@ -0,0 +1,41 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Send passphrase back to system + <_message>Authentication is required to send the entered passphrase back to the system. + + no + no + auth_admin_keep + + @rootlibexecdir@/systemd-reply-password + + + + <_description>Privileged system and service manager access + <_message>Authentication is required to access the system and service manager. + + no + no + auth_admin_keep + + @bindir@/systemd-stdio-bridge + + + diff --git a/src/org.freedesktop.systemd1.service b/src/org.freedesktop.systemd1.service new file mode 100644 index 000000000..7e1dfd47e --- /dev/null +++ b/src/org.freedesktop.systemd1.service @@ -0,0 +1,11 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[D-BUS Service] +Name=org.freedesktop.systemd1 +Exec=/bin/false +User=root diff --git a/src/pager.c b/src/pager.c new file mode 100644 index 000000000..3fc81820e --- /dev/null +++ b/src/pager.c @@ -0,0 +1,134 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include + +#include "pager.h" +#include "util.h" +#include "macro.h" + +static pid_t pager_pid = 0; + +_noreturn_ static void pager_fallback(void) { + ssize_t n; + do { + n = splice(STDIN_FILENO, NULL, STDOUT_FILENO, NULL, 64*1024, 0); + } while (n > 0); + if (n < 0) { + log_error("Internal pager failed: %m"); + _exit(EXIT_FAILURE); + } + _exit(EXIT_SUCCESS); +} + +void pager_open(void) { + int fd[2]; + const char *pager; + pid_t parent_pid; + + if (pager_pid > 0) + return; + + if ((pager = getenv("SYSTEMD_PAGER")) || (pager = getenv("PAGER"))) + if (!*pager || streq(pager, "cat")) + return; + + if (isatty(STDOUT_FILENO) <= 0) + return; + + /* Determine and cache number of columns before we spawn the + * pager so that we get the value from the actual tty */ + columns(); + + if (pipe(fd) < 0) { + log_error("Failed to create pager pipe: %m"); + return; + } + + parent_pid = getpid(); + + pager_pid = fork(); + if (pager_pid < 0) { + log_error("Failed to fork pager: %m"); + close_pipe(fd); + return; + } + + /* In the child start the pager */ + if (pager_pid == 0) { + + dup2(fd[0], STDIN_FILENO); + close_pipe(fd); + + setenv("LESS", "FRSX", 0); + + /* Make sure the pager goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + /* Check whether our parent died before we were able + * to set the death signal */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + if (pager) { + execlp(pager, pager, NULL); + execl("/bin/sh", "sh", "-c", pager, NULL); + } + + /* Debian's alternatives command for pagers is + * called 'pager'. Note that we do not call + * sensible-pagers here, since that is just a + * shell script that implements a logic that + * is similar to this one anyway, but is + * Debian-specific. */ + execlp("pager", "pager", NULL); + + execlp("less", "less", NULL); + execlp("more", "more", NULL); + + pager_fallback(); + /* not reached */ + } + + /* Return in the parent */ + if (dup2(fd[1], STDOUT_FILENO) < 0) + log_error("Failed to duplicate pager pipe: %m"); + + close_pipe(fd); +} + +void pager_close(void) { + + if (pager_pid <= 0) + return; + + /* Inform pager that we are done */ + fclose(stdout); + kill(pager_pid, SIGCONT); + wait_for_terminate(pager_pid, NULL); + pager_pid = 0; +} diff --git a/src/pager.h b/src/pager.h new file mode 100644 index 000000000..b5b499844 --- /dev/null +++ b/src/pager.h @@ -0,0 +1,28 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foopagerhfoo +#define foopagerhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +void pager_open(void); +void pager_close(void); + +#endif diff --git a/src/path-lookup.c b/src/path-lookup.c new file mode 100644 index 000000000..5464cedbb --- /dev/null +++ b/src/path-lookup.c @@ -0,0 +1,347 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "util.h" +#include "strv.h" + +#include "path-lookup.h" + +int user_config_home(char **config_home) { + const char *e; + + if ((e = getenv("XDG_CONFIG_HOME"))) { + if (asprintf(config_home, "%s/systemd/user", e) < 0) + return -ENOMEM; + + return 1; + } else { + const char *home; + + if ((home = getenv("HOME"))) { + if (asprintf(config_home, "%s/.config/systemd/user", home) < 0) + return -ENOMEM; + + return 1; + } + } + + return 0; +} + +static char** user_dirs(void) { + const char * const config_unit_paths[] = { + USER_CONFIG_UNIT_PATH, + "/etc/systemd/user", + "/run/systemd/user", + NULL + }; + + const char * const data_unit_paths[] = { + "/usr/local/lib/systemd/user", + "/usr/local/share/systemd/user", + USER_DATA_UNIT_PATH, + "/usr/lib/systemd/user", + "/usr/share/systemd/user", + NULL + }; + + const char *home, *e; + char *config_home = NULL, *data_home = NULL; + char **config_dirs = NULL, **data_dirs = NULL; + char **r = NULL, **t; + + /* Implement the mechanisms defined in + * + * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html + * + * We look in both the config and the data dirs because we + * want to encourage that distributors ship their unit files + * as data, and allow overriding as configuration. + */ + + if (user_config_home(&config_home) < 0) + goto fail; + + home = getenv("HOME"); + + if ((e = getenv("XDG_CONFIG_DIRS"))) + if (!(config_dirs = strv_split(e, ":"))) + goto fail; + + /* We don't treat /etc/xdg/systemd here as the spec + * suggests because we assume that that is a link to + * /etc/systemd/ anyway. */ + + if ((e = getenv("XDG_DATA_HOME"))) { + if (asprintf(&data_home, "%s/systemd/user", e) < 0) + goto fail; + + } else if (home) { + if (asprintf(&data_home, "%s/.local/share/systemd/user", home) < 0) + goto fail; + + /* There is really no need for two unit dirs in $HOME, + * except to be fully compliant with the XDG spec. We + * now try to link the two dirs, so that we can + * minimize disk seeks a little. Further down we'll + * then filter out this link, if it is actually is + * one. */ + + mkdir_parents(data_home, 0777); + (void) symlink("../../../.config/systemd/user", data_home); + } + + if ((e = getenv("XDG_DATA_DIRS"))) + data_dirs = strv_split(e, ":"); + else + data_dirs = strv_new("/usr/local/share", + "/usr/share", + NULL); + + if (!data_dirs) + goto fail; + + /* Now merge everything we found. */ + if (config_home) { + if (!(t = strv_append(r, config_home))) + goto fail; + strv_free(r); + r = t; + } + + if (!strv_isempty(config_dirs)) { + if (!(t = strv_merge_concat(r, config_dirs, "/systemd/user"))) + goto finish; + strv_free(r); + r = t; + } + + if (!(t = strv_merge(r, (char**) config_unit_paths))) + goto fail; + strv_free(r); + r = t; + + if (data_home) { + if (!(t = strv_append(r, data_home))) + goto fail; + strv_free(r); + r = t; + } + + if (!strv_isempty(data_dirs)) { + if (!(t = strv_merge_concat(r, data_dirs, "/systemd/user"))) + goto fail; + strv_free(r); + r = t; + } + + if (!(t = strv_merge(r, (char**) data_unit_paths))) + goto fail; + strv_free(r); + r = t; + + if (!strv_path_make_absolute_cwd(r)) + goto fail; + +finish: + free(config_home); + strv_free(config_dirs); + free(data_home); + strv_free(data_dirs); + + return r; + +fail: + strv_free(r); + r = NULL; + goto finish; +} + +int lookup_paths_init(LookupPaths *p, ManagerRunningAs running_as, bool personal) { + const char *e; + char *t; + + assert(p); + + /* First priority is whatever has been passed to us via env + * vars */ + if ((e = getenv("SYSTEMD_UNIT_PATH"))) + if (!(p->unit_path = split_path_and_make_absolute(e))) + return -ENOMEM; + + if (strv_isempty(p->unit_path)) { + + /* Nothing is set, so let's figure something out. */ + strv_free(p->unit_path); + + if (running_as == MANAGER_USER) { + + if (personal) + p->unit_path = user_dirs(); + else + p->unit_path = strv_new( + /* If you modify this you also want to modify + * systemduserunitpath= in systemd.pc.in, and + * the arrays in user_dirs() above! */ + USER_CONFIG_UNIT_PATH, + "/etc/systemd/user", + "/run/systemd/user", + "/usr/local/lib/systemd/user", + "/usr/local/share/systemd/user", + USER_DATA_UNIT_PATH, + "/usr/lib/systemd/user", + "/usr/share/systemd/user", + NULL); + + if (!p->unit_path) + return -ENOMEM; + + } else + if (!(p->unit_path = strv_new( + /* If you modify this you also want to modify + * systemdsystemunitpath= in systemd.pc.in! */ + SYSTEM_CONFIG_UNIT_PATH, + "/etc/systemd/system", + "/run/systemd/system", + "/usr/local/lib/systemd/system", + SYSTEM_DATA_UNIT_PATH, + "/usr/lib/systemd/system", +#ifdef HAVE_SPLIT_USR + "/lib/systemd/system", +#endif + NULL))) + return -ENOMEM; + } + + if (p->unit_path) + if (!strv_path_canonicalize(p->unit_path)) + return -ENOMEM; + + strv_uniq(p->unit_path); + strv_path_remove_empty(p->unit_path); + + if (!strv_isempty(p->unit_path)) { + + if (!(t = strv_join(p->unit_path, "\n\t"))) + return -ENOMEM; + log_debug("Looking for unit files in:\n\t%s", t); + free(t); + } else { + log_debug("Ignoring unit files."); + strv_free(p->unit_path); + p->unit_path = NULL; + } + + if (running_as == MANAGER_SYSTEM) { +#ifdef HAVE_SYSV_COMPAT + /* /etc/init.d/ compatibility does not matter to users */ + + if ((e = getenv("SYSTEMD_SYSVINIT_PATH"))) + if (!(p->sysvinit_path = split_path_and_make_absolute(e))) + return -ENOMEM; + + if (strv_isempty(p->sysvinit_path)) { + strv_free(p->sysvinit_path); + + if (!(p->sysvinit_path = strv_new( + SYSTEM_SYSVINIT_PATH, /* /etc/init.d/ */ + NULL))) + return -ENOMEM; + } + + if ((e = getenv("SYSTEMD_SYSVRCND_PATH"))) + if (!(p->sysvrcnd_path = split_path_and_make_absolute(e))) + return -ENOMEM; + + if (strv_isempty(p->sysvrcnd_path)) { + strv_free(p->sysvrcnd_path); + + if (!(p->sysvrcnd_path = strv_new( + SYSTEM_SYSVRCND_PATH, /* /etc/rcN.d/ */ + NULL))) + return -ENOMEM; + } + + if (p->sysvinit_path) + if (!strv_path_canonicalize(p->sysvinit_path)) + return -ENOMEM; + + if (p->sysvrcnd_path) + if (!strv_path_canonicalize(p->sysvrcnd_path)) + return -ENOMEM; + + strv_uniq(p->sysvinit_path); + strv_uniq(p->sysvrcnd_path); + + strv_path_remove_empty(p->sysvinit_path); + strv_path_remove_empty(p->sysvrcnd_path); + + if (!strv_isempty(p->sysvinit_path)) { + + if (!(t = strv_join(p->sysvinit_path, "\n\t"))) + return -ENOMEM; + + log_debug("Looking for SysV init scripts in:\n\t%s", t); + free(t); + } else { + log_debug("Ignoring SysV init scripts."); + strv_free(p->sysvinit_path); + p->sysvinit_path = NULL; + } + + if (!strv_isempty(p->sysvrcnd_path)) { + + if (!(t = strv_join(p->sysvrcnd_path, "\n\t"))) + return -ENOMEM; + + log_debug("Looking for SysV rcN.d links in:\n\t%s", t); + free(t); + } else { + log_debug("Ignoring SysV rcN.d links."); + strv_free(p->sysvrcnd_path); + p->sysvrcnd_path = NULL; + } +#else + log_debug("Disabled SysV init scripts and rcN.d links support"); +#endif + } + + return 0; +} + +void lookup_paths_free(LookupPaths *p) { + assert(p); + + strv_free(p->unit_path); + p->unit_path = NULL; + +#ifdef HAVE_SYSV_COMPAT + strv_free(p->sysvinit_path); + strv_free(p->sysvrcnd_path); + p->sysvinit_path = p->sysvrcnd_path = NULL; +#endif +} diff --git a/src/path-lookup.h b/src/path-lookup.h new file mode 100644 index 000000000..fc2887d3c --- /dev/null +++ b/src/path-lookup.h @@ -0,0 +1,40 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foopathlookuphfoo +#define foopathlookuphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct LookupPaths { + char **unit_path; +#ifdef HAVE_SYSV_COMPAT + char **sysvinit_path; + char **sysvrcnd_path; +#endif +} LookupPaths; + +#include "manager.h" + +int user_config_home(char **config_home); + +int lookup_paths_init(LookupPaths *p, ManagerRunningAs running_as, bool personal); +void lookup_paths_free(LookupPaths *p); + +#endif diff --git a/src/path.c b/src/path.c new file mode 100644 index 000000000..e97cd0981 --- /dev/null +++ b/src/path.c @@ -0,0 +1,770 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "unit.h" +#include "unit-name.h" +#include "path.h" +#include "dbus-path.h" +#include "special.h" +#include "bus-errors.h" + +static const UnitActiveState state_translation_table[_PATH_STATE_MAX] = { + [PATH_DEAD] = UNIT_INACTIVE, + [PATH_WAITING] = UNIT_ACTIVE, + [PATH_RUNNING] = UNIT_ACTIVE, + [PATH_FAILED] = UNIT_FAILED +}; + +int path_spec_watch(PathSpec *s, Unit *u) { + + static const int flags_table[_PATH_TYPE_MAX] = { + [PATH_EXISTS] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB, + [PATH_EXISTS_GLOB] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB, + [PATH_CHANGED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO, + [PATH_MODIFIED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO|IN_MODIFY, + [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO + }; + + bool exists = false; + char *k, *slash; + int r; + + assert(u); + assert(s); + + path_spec_unwatch(s, u); + + if (!(k = strdup(s->path))) + return -ENOMEM; + + if ((s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC)) < 0) { + r = -errno; + goto fail; + } + + if (unit_watch_fd(u, s->inotify_fd, EPOLLIN, &s->watch) < 0) { + r = -errno; + goto fail; + } + + if ((s->primary_wd = inotify_add_watch(s->inotify_fd, k, flags_table[s->type])) >= 0) + exists = true; + + do { + int flags; + + /* This assumes the path was passed through path_kill_slashes()! */ + if (!(slash = strrchr(k, '/'))) + break; + + /* Trim the path at the last slash. Keep the slash if it's the root dir. */ + slash[slash == k] = 0; + + flags = IN_MOVE_SELF; + if (!exists) + flags |= IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO; + + if (inotify_add_watch(s->inotify_fd, k, flags) >= 0) + exists = true; + } while (slash != k); + + return 0; + +fail: + free(k); + + path_spec_unwatch(s, u); + return r; +} + +void path_spec_unwatch(PathSpec *s, Unit *u) { + + if (s->inotify_fd < 0) + return; + + unit_unwatch_fd(u, &s->watch); + + close_nointr_nofail(s->inotify_fd); + s->inotify_fd = -1; +} + +int path_spec_fd_event(PathSpec *s, uint32_t events) { + uint8_t *buf = NULL; + struct inotify_event *e; + ssize_t k; + int l; + int r = 0; + + if (events != EPOLLIN) { + log_error("Got Invalid poll event on inotify."); + r = -EINVAL; + goto out; + } + + if (ioctl(s->inotify_fd, FIONREAD, &l) < 0) { + log_error("FIONREAD failed: %m"); + r = -errno; + goto out; + } + + assert(l > 0); + + if (!(buf = malloc(l))) { + log_error("Failed to allocate buffer: %m"); + r = -errno; + goto out; + } + + if ((k = read(s->inotify_fd, buf, l)) < 0) { + log_error("Failed to read inotify event: %m"); + r = -errno; + goto out; + } + + e = (struct inotify_event*) buf; + + while (k > 0) { + size_t step; + + if ((s->type == PATH_CHANGED || s->type == PATH_MODIFIED) && + s->primary_wd == e->wd) + r = 1; + + step = sizeof(struct inotify_event) + e->len; + assert(step <= (size_t) k); + + e = (struct inotify_event*) ((uint8_t*) e + step); + k -= step; + } +out: + free(buf); + return r; +} + +static bool path_spec_check_good(PathSpec *s, bool initial) { + bool good = false; + + switch (s->type) { + + case PATH_EXISTS: + good = access(s->path, F_OK) >= 0; + break; + + case PATH_EXISTS_GLOB: + good = glob_exists(s->path) > 0; + break; + + case PATH_DIRECTORY_NOT_EMPTY: { + int k; + + k = dir_is_empty(s->path); + good = !(k == -ENOENT || k > 0); + break; + } + + case PATH_CHANGED: + case PATH_MODIFIED: { + bool b; + + b = access(s->path, F_OK) >= 0; + good = !initial && b != s->previous_exists; + s->previous_exists = b; + break; + } + + default: + ; + } + + return good; +} + +static bool path_spec_startswith(PathSpec *s, const char *what) { + return path_startswith(s->path, what); +} + +static void path_spec_mkdir(PathSpec *s, mode_t mode) { + int r; + + if (s->type == PATH_EXISTS || s->type == PATH_EXISTS_GLOB) + return; + + if ((r = mkdir_p(s->path, mode)) < 0) + log_warning("mkdir(%s) failed: %s", s->path, strerror(-r)); +} + +static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) { + fprintf(f, + "%s%s: %s\n", + prefix, + path_type_to_string(s->type), + s->path); +} + +void path_spec_done(PathSpec *s) { + assert(s); + assert(s->inotify_fd == -1); + + free(s->path); +} + +static void path_init(Unit *u) { + Path *p = PATH(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + p->directory_mode = 0755; +} + +static void path_done(Unit *u) { + Path *p = PATH(u); + PathSpec *s; + + assert(p); + + unit_ref_unset(&p->unit); + + while ((s = p->specs)) { + path_spec_unwatch(s, u); + LIST_REMOVE(PathSpec, spec, p->specs, s); + path_spec_done(s); + free(s); + } +} + +int path_add_one_mount_link(Path *p, Mount *m) { + PathSpec *s; + int r; + + assert(p); + assert(m); + + if (UNIT(p)->load_state != UNIT_LOADED || + UNIT(m)->load_state != UNIT_LOADED) + return 0; + + LIST_FOREACH(spec, s, p->specs) { + + if (!path_spec_startswith(s, m->where)) + continue; + + if ((r = unit_add_two_dependencies(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0) + return r; + } + + return 0; +} + +static int path_add_mount_links(Path *p) { + Unit *other; + int r; + + assert(p); + + LIST_FOREACH(units_by_type, other, UNIT(p)->manager->units_by_type[UNIT_MOUNT]) + if ((r = path_add_one_mount_link(p, MOUNT(other))) < 0) + return r; + + return 0; +} + +static int path_verify(Path *p) { + assert(p); + + if (UNIT(p)->load_state != UNIT_LOADED) + return 0; + + if (!p->specs) { + log_error("%s lacks path setting. Refusing.", UNIT(p)->id); + return -EINVAL; + } + + return 0; +} + +static int path_add_default_dependencies(Path *p) { + int r; + + assert(p); + + if (UNIT(p)->manager->running_as == MANAGER_SYSTEM) { + if ((r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0) + return r; + + if ((r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0) + return r; + } + + return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static int path_load(Unit *u) { + Path *p = PATH(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + if ((r = unit_load_fragment_and_dropin(u)) < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + + if (!UNIT_DEREF(p->unit)) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + unit_ref_set(&p->unit, x); + } + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(p->unit), true); + if (r < 0) + return r; + + if ((r = path_add_mount_links(p)) < 0) + return r; + + if (UNIT(p)->default_dependencies) + if ((r = path_add_default_dependencies(p)) < 0) + return r; + } + + return path_verify(p); +} + +static void path_dump(Unit *u, FILE *f, const char *prefix) { + Path *p = PATH(u); + PathSpec *s; + + assert(p); + assert(f); + + fprintf(f, + "%sPath State: %s\n" + "%sResult: %s\n" + "%sUnit: %s\n" + "%sMakeDirectory: %s\n" + "%sDirectoryMode: %04o\n", + prefix, path_state_to_string(p->state), + prefix, path_result_to_string(p->result), + prefix, UNIT_DEREF(p->unit)->id, + prefix, yes_no(p->make_directory), + prefix, p->directory_mode); + + LIST_FOREACH(spec, s, p->specs) + path_spec_dump(s, f, prefix); +} + +static void path_unwatch(Path *p) { + PathSpec *s; + + assert(p); + + LIST_FOREACH(spec, s, p->specs) + path_spec_unwatch(s, UNIT(p)); +} + +static int path_watch(Path *p) { + int r; + PathSpec *s; + + assert(p); + + LIST_FOREACH(spec, s, p->specs) + if ((r = path_spec_watch(s, UNIT(p))) < 0) + return r; + + return 0; +} + +static void path_set_state(Path *p, PathState state) { + PathState old_state; + assert(p); + + old_state = p->state; + p->state = state; + + if (state != PATH_WAITING && + (state != PATH_RUNNING || p->inotify_triggered)) + path_unwatch(p); + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(p)->id, + path_state_to_string(old_state), + path_state_to_string(state)); + + unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], true); +} + +static void path_enter_waiting(Path *p, bool initial, bool recheck); + +static int path_coldplug(Unit *u) { + Path *p = PATH(u); + + assert(p); + assert(p->state == PATH_DEAD); + + if (p->deserialized_state != p->state) { + + if (p->deserialized_state == PATH_WAITING || + p->deserialized_state == PATH_RUNNING) + path_enter_waiting(p, true, true); + else + path_set_state(p, p->deserialized_state); + } + + return 0; +} + +static void path_enter_dead(Path *p, PathResult f) { + assert(p); + + if (f != PATH_SUCCESS) + p->result = f; + + path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD); +} + +static void path_enter_running(Path *p) { + int r; + DBusError error; + + assert(p); + dbus_error_init(&error); + + /* Don't start job if we are supposed to go down */ + if (UNIT(p)->job && UNIT(p)->job->type == JOB_STOP) + return; + + if ((r = manager_add_job(UNIT(p)->manager, JOB_START, UNIT_DEREF(p->unit), JOB_REPLACE, true, &error, NULL)) < 0) + goto fail; + + p->inotify_triggered = false; + + if ((r = path_watch(p)) < 0) + goto fail; + + path_set_state(p, PATH_RUNNING); + return; + +fail: + log_warning("%s failed to queue unit startup job: %s", UNIT(p)->id, bus_error(&error, r)); + path_enter_dead(p, PATH_FAILURE_RESOURCES); + + dbus_error_free(&error); +} + +static bool path_check_good(Path *p, bool initial) { + PathSpec *s; + bool good = false; + + assert(p); + + LIST_FOREACH(spec, s, p->specs) { + good = path_spec_check_good(s, initial); + + if (good) + break; + } + + return good; +} + +static void path_enter_waiting(Path *p, bool initial, bool recheck) { + int r; + + if (recheck) + if (path_check_good(p, initial)) { + log_debug("%s got triggered.", UNIT(p)->id); + path_enter_running(p); + return; + } + + if ((r = path_watch(p)) < 0) + goto fail; + + /* Hmm, so now we have created inotify watches, but the file + * might have appeared/been removed by now, so we must + * recheck */ + + if (recheck) + if (path_check_good(p, false)) { + log_debug("%s got triggered.", UNIT(p)->id); + path_enter_running(p); + return; + } + + path_set_state(p, PATH_WAITING); + return; + +fail: + log_warning("%s failed to enter waiting state: %s", UNIT(p)->id, strerror(-r)); + path_enter_dead(p, PATH_FAILURE_RESOURCES); +} + +static void path_mkdir(Path *p) { + PathSpec *s; + + assert(p); + + if (!p->make_directory) + return; + + LIST_FOREACH(spec, s, p->specs) + path_spec_mkdir(s, p->directory_mode); +} + +static int path_start(Unit *u) { + Path *p = PATH(u); + + assert(p); + assert(p->state == PATH_DEAD || p->state == PATH_FAILED); + + if (UNIT_DEREF(p->unit)->load_state != UNIT_LOADED) + return -ENOENT; + + path_mkdir(p); + + p->result = PATH_SUCCESS; + path_enter_waiting(p, true, true); + + return 0; +} + +static int path_stop(Unit *u) { + Path *p = PATH(u); + + assert(p); + assert(p->state == PATH_WAITING || p->state == PATH_RUNNING); + + path_enter_dead(p, PATH_SUCCESS); + return 0; +} + +static int path_serialize(Unit *u, FILE *f, FDSet *fds) { + Path *p = PATH(u); + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", path_state_to_string(p->state)); + unit_serialize_item(u, f, "result", path_result_to_string(p->result)); + + return 0; +} + +static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Path *p = PATH(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + PathState state; + + if ((state = path_state_from_string(value)) < 0) + log_debug("Failed to parse state value %s", value); + else + p->deserialized_state = state; + + } else if (streq(key, "result")) { + PathResult f; + + f = path_result_from_string(value); + if (f < 0) + log_debug("Failed to parse result value %s", value); + else if (f != PATH_SUCCESS) + p->result = f; + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState path_active_state(Unit *u) { + assert(u); + + return state_translation_table[PATH(u)->state]; +} + +static const char *path_sub_state_to_string(Unit *u) { + assert(u); + + return path_state_to_string(PATH(u)->state); +} + +static void path_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { + Path *p = PATH(u); + PathSpec *s; + int changed; + + assert(p); + assert(fd >= 0); + + if (p->state != PATH_WAITING && + p->state != PATH_RUNNING) + return; + + /* log_debug("inotify wakeup on %s.", u->id); */ + + LIST_FOREACH(spec, s, p->specs) + if (path_spec_owns_inotify_fd(s, fd)) + break; + + if (!s) { + log_error("Got event on unknown fd."); + goto fail; + } + + changed = path_spec_fd_event(s, events); + if (changed < 0) + goto fail; + + /* If we are already running, then remember that one event was + * dispatched so that we restart the service only if something + * actually changed on disk */ + p->inotify_triggered = true; + + if (changed) + path_enter_running(p); + else + path_enter_waiting(p, false, true); + + return; + +fail: + path_enter_dead(p, PATH_FAILURE_RESOURCES); +} + +void path_unit_notify(Unit *u, UnitActiveState new_state) { + Iterator i; + Unit *k; + + if (u->type == UNIT_PATH) + return; + + SET_FOREACH(k, u->dependencies[UNIT_TRIGGERED_BY], i) { + Path *p; + + if (k->type != UNIT_PATH) + continue; + + if (k->load_state != UNIT_LOADED) + continue; + + p = PATH(k); + + if (p->state == PATH_RUNNING && new_state == UNIT_INACTIVE) { + log_debug("%s got notified about unit deactivation.", UNIT(p)->id); + + /* Hmm, so inotify was triggered since the + * last activation, so I guess we need to + * recheck what is going on. */ + path_enter_waiting(p, false, p->inotify_triggered); + } + } +} + +static void path_reset_failed(Unit *u) { + Path *p = PATH(u); + + assert(p); + + if (p->state == PATH_FAILED) + path_set_state(p, PATH_DEAD); + + p->result = PATH_SUCCESS; +} + +static const char* const path_state_table[_PATH_STATE_MAX] = { + [PATH_DEAD] = "dead", + [PATH_WAITING] = "waiting", + [PATH_RUNNING] = "running", + [PATH_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(path_state, PathState); + +static const char* const path_type_table[_PATH_TYPE_MAX] = { + [PATH_EXISTS] = "PathExists", + [PATH_EXISTS_GLOB] = "PathExistsGlob", + [PATH_CHANGED] = "PathChanged", + [PATH_MODIFIED] = "PathModified", + [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty" +}; + +DEFINE_STRING_TABLE_LOOKUP(path_type, PathType); + +static const char* const path_result_table[_PATH_RESULT_MAX] = { + [PATH_SUCCESS] = "success", + [PATH_FAILURE_RESOURCES] = "resources" +}; + +DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult); + +const UnitVTable path_vtable = { + .suffix = ".path", + .object_size = sizeof(Path), + .sections = + "Unit\0" + "Path\0" + "Install\0", + + .init = path_init, + .done = path_done, + .load = path_load, + + .coldplug = path_coldplug, + + .dump = path_dump, + + .start = path_start, + .stop = path_stop, + + .serialize = path_serialize, + .deserialize_item = path_deserialize_item, + + .active_state = path_active_state, + .sub_state_to_string = path_sub_state_to_string, + + .fd_event = path_fd_event, + + .reset_failed = path_reset_failed, + + .bus_interface = "org.freedesktop.systemd1.Path", + .bus_message_handler = bus_path_message_handler, + .bus_invalidating_properties = bus_path_invalidating_properties +}; diff --git a/src/path.h b/src/path.h new file mode 100644 index 000000000..efb6b5eb4 --- /dev/null +++ b/src/path.h @@ -0,0 +1,113 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foopathhfoo +#define foopathhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Path Path; + +#include "unit.h" +#include "mount.h" + +typedef enum PathState { + PATH_DEAD, + PATH_WAITING, + PATH_RUNNING, + PATH_FAILED, + _PATH_STATE_MAX, + _PATH_STATE_INVALID = -1 +} PathState; + +typedef enum PathType { + PATH_EXISTS, + PATH_EXISTS_GLOB, + PATH_DIRECTORY_NOT_EMPTY, + PATH_CHANGED, + PATH_MODIFIED, + _PATH_TYPE_MAX, + _PATH_TYPE_INVALID = -1 +} PathType; + +typedef struct PathSpec { + char *path; + + Watch watch; + + LIST_FIELDS(struct PathSpec, spec); + + PathType type; + int inotify_fd; + int primary_wd; + + bool previous_exists; +} PathSpec; + +int path_spec_watch(PathSpec *s, Unit *u); +void path_spec_unwatch(PathSpec *s, Unit *u); +int path_spec_fd_event(PathSpec *s, uint32_t events); +void path_spec_done(PathSpec *s); + +static inline bool path_spec_owns_inotify_fd(PathSpec *s, int fd) { + return s->inotify_fd == fd; +} + +typedef enum PathResult { + PATH_SUCCESS, + PATH_FAILURE_RESOURCES, + _PATH_RESULT_MAX, + _PATH_RESULT_INVALID = -1 +} PathResult; + +struct Path { + Unit meta; + + LIST_HEAD(PathSpec, specs); + + UnitRef unit; + + PathState state, deserialized_state; + + bool inotify_triggered; + + bool make_directory; + mode_t directory_mode; + + PathResult result; +}; + +void path_unit_notify(Unit *u, UnitActiveState new_state); + +/* Called from the mount code figure out if a mount is a dependency of + * any of the paths of this path object */ +int path_add_one_mount_link(Path *p, Mount *m); + +extern const UnitVTable path_vtable; + +const char* path_state_to_string(PathState i); +PathState path_state_from_string(const char *s); + +const char* path_type_to_string(PathType i); +PathType path_type_from_string(const char *s); + +const char* path_result_to_string(PathResult i); +PathResult path_result_from_string(const char *s); + +#endif diff --git a/src/polkit.c b/src/polkit.c new file mode 100644 index 000000000..3acbdc613 --- /dev/null +++ b/src/polkit.c @@ -0,0 +1,205 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include + +#include "util.h" +#include "dbus-common.h" +#include "polkit.h" + +/* This mimics dbus_bus_get_unix_user() */ +static pid_t get_unix_process_id( + DBusConnection *connection, + const char *name, + DBusError *error) { + + DBusMessage *m = NULL, *reply = NULL; + uint32_t pid = 0; + + m = dbus_message_new_method_call( + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixProcessID"); + if (!m) { + dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL); + goto finish; + } + + if (!dbus_message_append_args( + m, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL); + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error); + if (!reply) + goto finish; + + if (dbus_set_error_from_message(error, reply)) + goto finish; + + if (!dbus_message_get_args( + reply, error, + DBUS_TYPE_UINT32, &pid, + DBUS_TYPE_INVALID)) + goto finish; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return (pid_t) pid; +} + +int verify_polkit( + DBusConnection *c, + DBusMessage *request, + const char *action, + bool interactive, + bool *_challenge, + DBusError *error) { + + DBusMessage *m = NULL, *reply = NULL; + const char *unix_process = "unix-process", *pid = "pid", *starttime = "start-time", *cancel_id = ""; + const char *sender; + uint32_t flags = interactive ? 1 : 0; + pid_t pid_raw; + uint32_t pid_u32; + unsigned long long starttime_raw; + uint64_t starttime_u64; + DBusMessageIter iter_msg, iter_struct, iter_array, iter_dict, iter_variant; + int r; + dbus_bool_t authorized = FALSE, challenge = FALSE; + + assert(c); + assert(request); + + sender = dbus_message_get_sender(request); + if (!sender) + return -EINVAL; + + pid_raw = get_unix_process_id(c, sender, error); + if (pid_raw == 0) + return -EINVAL; + + r = get_starttime_of_pid(pid_raw, &starttime_raw); + if (r < 0) + return r; + + m = dbus_message_new_method_call( + "org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority", + "org.freedesktop.PolicyKit1.Authority", + "CheckAuthorization"); + if (!m) + return -ENOMEM; + + dbus_message_iter_init_append(m, &iter_msg); + + pid_u32 = (uint32_t) pid_raw; + starttime_u64 = (uint64_t) starttime_raw; + + if (!dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_STRUCT, NULL, &iter_struct) || + !dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_STRING, &unix_process) || + !dbus_message_iter_open_container(&iter_struct, DBUS_TYPE_ARRAY, "{sv}", &iter_array) || + !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) || + !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &pid) || + !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "u", &iter_variant) || + !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT32, &pid_u32) || + !dbus_message_iter_close_container(&iter_dict, &iter_variant) || + !dbus_message_iter_close_container(&iter_array, &iter_dict) || + !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) || + !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &starttime) || + !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "t", &iter_variant) || + !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT64, &starttime_u64) || + !dbus_message_iter_close_container(&iter_dict, &iter_variant) || + !dbus_message_iter_close_container(&iter_array, &iter_dict) || + !dbus_message_iter_close_container(&iter_struct, &iter_array) || + !dbus_message_iter_close_container(&iter_msg, &iter_struct) || + !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_STRING, &action) || + !dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_ARRAY, "{ss}", &iter_array) || + !dbus_message_iter_close_container(&iter_msg, &iter_array) || + !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_UINT32, &flags) || + !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_STRING, &cancel_id)) { + r = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(c, m, -1, error); + if (!reply) { + r = -EIO; + goto finish; + } + + if (dbus_set_error_from_message(error, reply)) { + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter_msg) || + dbus_message_iter_get_arg_type(&iter_msg) != DBUS_TYPE_STRUCT) { + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter_msg, &iter_struct); + + if (dbus_message_iter_get_arg_type(&iter_struct) != DBUS_TYPE_BOOLEAN) { + r = -EIO; + goto finish; + } + + dbus_message_iter_get_basic(&iter_struct, &authorized); + + if (!dbus_message_iter_next(&iter_struct) || + dbus_message_iter_get_arg_type(&iter_struct) != DBUS_TYPE_BOOLEAN) { + r = -EIO; + goto finish; + } + + dbus_message_iter_get_basic(&iter_struct, &challenge); + + if (authorized) + r = 1; + else if (_challenge) { + *_challenge = !!challenge; + r = 0; + } else + r = -EPERM; + +finish: + + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return r; +} diff --git a/src/polkit.h b/src/polkit.h new file mode 100644 index 000000000..0d08194b2 --- /dev/null +++ b/src/polkit.h @@ -0,0 +1,36 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foopolkithfoo +#define foopolkithfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +int verify_polkit( + DBusConnection *c, + DBusMessage *request, + const char *action, + bool interactive, + bool *challenge, + DBusError *error); + +#endif diff --git a/src/quotacheck.c b/src/quotacheck.c new file mode 100644 index 000000000..b6648b836 --- /dev/null +++ b/src/quotacheck.c @@ -0,0 +1,120 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "util.h" +#include "virt.h" + +static bool arg_skip = false; +static bool arg_force = false; + +static int parse_proc_cmdline(void) { + char *line, *w, *state; + int r; + size_t l; + + if (detect_container(NULL) > 0) + return 0; + + if ((r = read_one_line_file("/proc/cmdline", &line)) < 0) { + log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r)); + return 0; + } + + FOREACH_WORD_QUOTED(w, l, line, state) { + + if (strneq(w, "quotacheck.mode=auto", l)) + arg_force = arg_skip = false; + else if (strneq(w, "quotacheck.mode=force", l)) + arg_force = true; + else if (strneq(w, "quotacheck.mode=skip", l)) + arg_skip = true; + else if (startswith(w, "quotacheck.mode")) + log_warning("Invalid quotacheck.mode= parameter. Ignoring."); +#if defined(TARGET_FEDORA) || defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA) + else if (strneq(w, "forcequotacheck", l)) + arg_force = true; +#endif + } + + free(line); + return 0; +} + +static void test_files(void) { +#if defined(TARGET_FEDORA) || defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA) + /* This exists only on Fedora, Mandriva or Mageia */ + if (access("/forcequotacheck", F_OK) >= 0) + arg_force = true; +#endif +} + +int main(int argc, char *argv[]) { + static const char * const cmdline[] = { + "/sbin/quotacheck", + "-anug", + NULL + }; + + int r = EXIT_FAILURE; + pid_t pid; + + if (argc > 1) { + log_error("This program takes no arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + parse_proc_cmdline(); + test_files(); + + if (!arg_force) { + if (arg_skip) + return 0; + + if (access("/run/systemd/quotacheck", F_OK) < 0) + return 0; + } + + if ((pid = fork()) < 0) { + log_error("fork(): %m"); + goto finish; + } else if (pid == 0) { + /* Child */ + execv(cmdline[0], (char**) cmdline); + _exit(1); /* Operational error */ + } + + r = wait_for_terminate_and_warn("quotacheck", pid) == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + +finish: + return r; +} diff --git a/src/random-seed.c b/src/random-seed.c new file mode 100644 index 000000000..8b43bacad --- /dev/null +++ b/src/random-seed.c @@ -0,0 +1,147 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "log.h" +#include "util.h" + +#define POOL_SIZE_MIN 512 + +int main(int argc, char *argv[]) { + int seed_fd = -1, random_fd = -1; + int ret = EXIT_FAILURE; + void* buf; + size_t buf_size = 0; + ssize_t r; + FILE *f; + + if (argc != 2) { + log_error("This program requires one argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + /* Read pool size, if possible */ + if ((f = fopen("/proc/sys/kernel/random/poolsize", "re"))) { + if (fscanf(f, "%zu", &buf_size) > 0) { + /* poolsize is in bits on 2.6, but we want bytes */ + buf_size /= 8; + } + + fclose(f); + } + + if (buf_size <= POOL_SIZE_MIN) + buf_size = POOL_SIZE_MIN; + + if (!(buf = malloc(buf_size))) { + log_error("Failed to allocate buffer."); + goto finish; + } + + if (mkdir_parents(RANDOM_SEED, 0755) < 0) { + log_error("Failed to create directories parents of %s: %m", RANDOM_SEED); + goto finish; + } + + /* When we load the seed we read it and write it to the device + * and then immediately update the saved seed with new data, + * to make sure the next boot gets seeded differently. */ + + if (streq(argv[1], "load")) { + + if ((seed_fd = open(RANDOM_SEED, O_RDWR|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600)) < 0) { + if ((seed_fd = open(RANDOM_SEED, O_RDONLY|O_CLOEXEC|O_NOCTTY)) < 0) { + log_error("Failed to open random seed: %m"); + goto finish; + } + } + + if ((random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY, 0600)) < 0) { + if ((random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY, 0600)) < 0) { + log_error("Failed to open /dev/urandom: %m"); + goto finish; + } + } + + if ((r = loop_read(seed_fd, buf, buf_size, false)) <= 0) { + + if (r != 0) + log_error("Failed to read seed file: %m"); + } else { + lseek(seed_fd, 0, SEEK_SET); + + if ((r = loop_write(random_fd, buf, (size_t) r, false)) <= 0) + log_error("Failed to write seed to /dev/random: %s", r < 0 ? strerror(errno) : "short write"); + } + + } else if (streq(argv[1], "save")) { + + if ((seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600)) < 0) { + log_error("Failed to open random seed: %m"); + goto finish; + } + + if ((random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY)) < 0) { + log_error("Failed to open /dev/urandom: %m"); + goto finish; + } + } else { + log_error("Unknown verb %s.", argv[1]); + goto finish; + } + + /* This is just a safety measure. Given that we are root and + * most likely created the file ourselves the mode and owner + * should be correct anyway. */ + fchmod(seed_fd, 0600); + fchown(seed_fd, 0, 0); + + if ((r = loop_read(random_fd, buf, buf_size, false)) <= 0) + log_error("Failed to read new seed from /dev/urandom: %s", r < 0 ? strerror(errno) : "EOF"); + else { + if ((r = loop_write(seed_fd, buf, (size_t) r, false)) <= 0) + log_error("Failed to write new random seed file: %s", r < 0 ? strerror(errno) : "short write"); + } + + ret = EXIT_SUCCESS; + +finish: + if (random_fd >= 0) + close_nointr_nofail(random_fd); + + if (seed_fd >= 0) + close_nointr_nofail(seed_fd); + + free(buf); + + return ret; +} diff --git a/src/ratelimit.c b/src/ratelimit.c new file mode 100644 index 000000000..93157c7a2 --- /dev/null +++ b/src/ratelimit.c @@ -0,0 +1,57 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "ratelimit.h" +#include "log.h" + +/* Modelled after Linux' lib/ratelimit.c by Dave Young + * , which is licensed GPLv2. */ + +bool ratelimit_test(RateLimit *r) { + usec_t ts; + + assert(r); + + if (r->interval <= 0 || r->burst <= 0) + return true; + + ts = now(CLOCK_MONOTONIC); + + if (r->begin <= 0 || + r->begin + r->interval < ts) { + r->begin = ts; + + /* Reset counter */ + r->num = 0; + goto good; + } + + if (r->num <= r->burst) + goto good; + + return false; + +good: + r->num++; + return true; +} diff --git a/src/ratelimit.h b/src/ratelimit.h new file mode 100644 index 000000000..a6443e7f8 --- /dev/null +++ b/src/ratelimit.h @@ -0,0 +1,53 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooratelimithfoo +#define fooratelimithfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "util.h" + +typedef struct RateLimit { + usec_t interval; + usec_t begin; + unsigned burst; + unsigned num; +} RateLimit; + +#define RATELIMIT_DEFINE(_name, _interval, _burst) \ + RateLimit _name = { \ + .interval = (_interval), \ + .burst = (_burst), \ + .num = 0, \ + .begin = 0 \ + } + +#define RATELIMIT_INIT(v, _interval, _burst) \ + do { \ + RateLimit *_r = &(v); \ + _r->interval = (_interval); \ + _r->burst = (_burst); \ + _r->num = 0; \ + _r->begin = 0; \ + } while (false) + +bool ratelimit_test(RateLimit *r); + +#endif diff --git a/src/rc-local-generator.c b/src/rc-local-generator.c new file mode 100644 index 000000000..56785cf40 --- /dev/null +++ b/src/rc-local-generator.c @@ -0,0 +1,107 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2011 Michal Schmidt + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "log.h" +#include "util.h" + +#if defined(TARGET_FEDORA) || defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA) +#define SCRIPT_PATH "/etc/rc.d/rc.local" +#elif defined(TARGET_SUSE) +#define SCRIPT_PATH "/etc/init.d/boot.local" +#endif + +const char *arg_dest = "/tmp"; + +static int add_symlink(const char *service) { + char *from = NULL, *to = NULL; + int r; + + assert(service); + + asprintf(&from, SYSTEM_DATA_UNIT_PATH "/%s", service); + asprintf(&to, "%s/multi-user.target.wants/%s", arg_dest, service); + + if (!from || !to) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + mkdir_parents(to, 0755); + + r = symlink(from, to); + if (r < 0) { + if (errno == EEXIST) + r = 0; + else { + log_error("Failed to create symlink from %s to %s: %m", from, to); + r = -errno; + } + } + +finish: + + free(from); + free(to); + + return r; +} + +static bool file_is_executable(const char *f) { + struct stat st; + + if (stat(f, &st) < 0) + return false; + + return S_ISREG(st.st_mode) && (st.st_mode & 0111); +} + +int main(int argc, char *argv[]) { + + int r = EXIT_SUCCESS; + + if (argc > 2) { + log_error("This program takes one or no arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + if (argc > 1) + arg_dest = argv[1]; + + if (file_is_executable(SCRIPT_PATH)) { + log_debug("Automatically adding rc-local.service."); + + if (add_symlink("rc-local.service") < 0) + r = EXIT_FAILURE; + + } + + return r; +} diff --git a/src/readahead/Makefile b/src/readahead/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/readahead/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/readahead/readahead-collect.c b/src/readahead/readahead-collect.c new file mode 100644 index 000000000..7e6c243b5 --- /dev/null +++ b/src/readahead/readahead-collect.c @@ -0,0 +1,693 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "missing.h" +#include "util.h" +#include "set.h" +#include "ioprio.h" +#include "readahead-common.h" +#include "virt.h" + +/* fixme: + * + * - detect ssd on btrfs/lvm... + * - read ahead directories + * - gzip? + * - remount rw? + * - handle files where nothing is in mincore + * - does ioprio_set work with fadvise()? + */ + +static unsigned arg_files_max = 16*1024; +static off_t arg_file_size_max = READAHEAD_FILE_SIZE_MAX; +static usec_t arg_timeout = 2*USEC_PER_MINUTE; + +static ReadaheadShared *shared = NULL; + +/* Avoid collisions with the NULL pointer */ +#define SECTOR_TO_PTR(s) ULONG_TO_PTR((s)+1) +#define PTR_TO_SECTOR(p) (PTR_TO_ULONG(p)-1) + +static int btrfs_defrag(int fd) { + struct btrfs_ioctl_vol_args data; + + zero(data); + data.fd = fd; + + return ioctl(fd, BTRFS_IOC_DEFRAG, &data); +} + +static int pack_file(FILE *pack, const char *fn, bool on_btrfs) { + struct stat st; + void *start = MAP_FAILED; + uint8_t *vec; + uint32_t b, c; + size_t l, pages; + bool mapped; + int r = 0, fd = -1, k; + + assert(pack); + assert(fn); + + if ((fd = open(fn, O_RDONLY|O_CLOEXEC|O_NOATIME|O_NOCTTY|O_NOFOLLOW)) < 0) { + + if (errno == ENOENT) + return 0; + + if (errno == EPERM || errno == EACCES) + return 0; + + log_warning("open(%s) failed: %m", fn); + r = -errno; + goto finish; + } + + if ((k = file_verify(fd, fn, arg_file_size_max, &st)) <= 0) { + r = k; + goto finish; + } + + if (on_btrfs) + btrfs_defrag(fd); + + l = PAGE_ALIGN(st.st_size); + if ((start = mmap(NULL, l, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { + log_warning("mmap(%s) failed: %m", fn); + r = -errno; + goto finish; + } + + pages = l / page_size(); + + vec = alloca(pages); + memset(vec, 0, pages); + if (mincore(start, l, vec) < 0) { + log_warning("mincore(%s) failed: %m", fn); + r = -errno; + goto finish; + } + + fputs(fn, pack); + fputc('\n', pack); + + mapped = false; + for (c = 0; c < pages; c++) { + bool new_mapped = !!(vec[c] & 1); + + if (!mapped && new_mapped) + b = c; + else if (mapped && !new_mapped) { + fwrite(&b, sizeof(b), 1, pack); + fwrite(&c, sizeof(c), 1, pack); + + log_debug("%s: page %u to %u", fn, b, c); + } + + mapped = new_mapped; + } + + /* We don't write any range data if we should read the entire file */ + if (mapped && b > 0) { + fwrite(&b, sizeof(b), 1, pack); + fwrite(&c, sizeof(c), 1, pack); + + log_debug("%s: page %u to %u", fn, b, c); + } + + /* End marker */ + b = 0; + fwrite(&b, sizeof(b), 1, pack); + fwrite(&b, sizeof(b), 1, pack); + +finish: + if (start != MAP_FAILED) + munmap(start, l); + + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +static unsigned long fd_first_block(int fd) { + struct { + struct fiemap fiemap; + struct fiemap_extent extent; + } data; + + zero(data); + data.fiemap.fm_length = ~0ULL; + data.fiemap.fm_extent_count = 1; + + if (ioctl(fd, FS_IOC_FIEMAP, &data) < 0) + return 0; + + if (data.fiemap.fm_mapped_extents <= 0) + return 0; + + if (data.fiemap.fm_extents[0].fe_flags & FIEMAP_EXTENT_UNKNOWN) + return 0; + + return (unsigned long) data.fiemap.fm_extents[0].fe_physical; +} + +struct item { + const char *path; + unsigned long block; +}; + +static int qsort_compare(const void *a, const void *b) { + const struct item *i, *j; + + i = a; + j = b; + + if (i->block < j->block) + return -1; + if (i->block > j->block) + return 1; + + return strcmp(i->path, j->path); +} + +static int collect(const char *root) { + enum { + FD_FANOTIFY, /* Get the actual fs events */ + FD_SIGNAL, + FD_INOTIFY, /* We get notifications to quit early via this fd */ + _FD_MAX + }; + struct pollfd pollfd[_FD_MAX]; + int fanotify_fd = -1, signal_fd = -1, inotify_fd = -1, r = 0; + pid_t my_pid; + Hashmap *files = NULL; + Iterator i; + char *p, *q; + sigset_t mask; + FILE *pack = NULL; + char *pack_fn_new = NULL, *pack_fn = NULL; + bool on_ssd, on_btrfs; + struct statfs sfs; + usec_t not_after; + + assert(root); + + write_one_line_file("/proc/self/oom_score_adj", "1000"); + + if (ioprio_set(IOPRIO_WHO_PROCESS, getpid(), IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0)) < 0) + log_warning("Failed to set IDLE IO priority class: %m"); + + assert_se(sigemptyset(&mask) == 0); + sigset_add_many(&mask, SIGINT, SIGTERM, -1); + assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); + + if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) { + log_error("signalfd(): %m"); + r = -errno; + goto finish; + } + + if (!(files = hashmap_new(string_hash_func, string_compare_func))) { + log_error("Failed to allocate set."); + r = -ENOMEM; + goto finish; + } + + if ((fanotify_fd = fanotify_init(FAN_CLOEXEC|FAN_NONBLOCK, O_RDONLY|O_LARGEFILE|O_CLOEXEC|O_NOATIME)) < 0) { + log_error("Failed to create fanotify object: %m"); + r = -errno; + goto finish; + } + + if (fanotify_mark(fanotify_fd, FAN_MARK_ADD|FAN_MARK_MOUNT, FAN_OPEN, AT_FDCWD, root) < 0) { + log_error("Failed to mark %s: %m", root); + r = -errno; + goto finish; + } + + if ((inotify_fd = open_inotify()) < 0) { + r = inotify_fd; + goto finish; + } + + not_after = now(CLOCK_MONOTONIC) + arg_timeout; + + my_pid = getpid(); + + zero(pollfd); + pollfd[FD_FANOTIFY].fd = fanotify_fd; + pollfd[FD_FANOTIFY].events = POLLIN; + pollfd[FD_SIGNAL].fd = signal_fd; + pollfd[FD_SIGNAL].events = POLLIN; + pollfd[FD_INOTIFY].fd = inotify_fd; + pollfd[FD_INOTIFY].events = POLLIN; + + sd_notify(0, + "READY=1\n" + "STATUS=Collecting readahead data"); + + log_debug("Collecting..."); + + if (access("/run/systemd/readahead/cancel", F_OK) >= 0) { + log_debug("Collection canceled"); + r = -ECANCELED; + goto finish; + } + + if (access("/run/systemd/readahead/done", F_OK) >= 0) { + log_debug("Got termination request"); + goto done; + } + + for (;;) { + union { + struct fanotify_event_metadata metadata; + char buffer[4096]; + } data; + ssize_t n; + struct fanotify_event_metadata *m; + usec_t t; + int h; + + if (hashmap_size(files) > arg_files_max) { + log_debug("Reached maximum number of read ahead files, ending collection."); + break; + } + + t = now(CLOCK_MONOTONIC); + if (t >= not_after) { + log_debug("Reached maximum collection time, ending collection."); + break; + } + + if ((h = poll(pollfd, _FD_MAX, (int) ((not_after - t) / USEC_PER_MSEC))) < 0) { + + if (errno == EINTR) + continue; + + log_error("poll(): %m"); + r = -errno; + goto finish; + } + + if (h == 0) { + log_debug("Reached maximum collection time, ending collection."); + break; + } + + if (pollfd[FD_SIGNAL].revents) { + log_debug("Got signal."); + break; + } + + if (pollfd[FD_INOTIFY].revents) { + uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX]; + struct inotify_event *e; + + if ((n = read(inotify_fd, &inotify_buffer, sizeof(inotify_buffer))) < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + log_error("Failed to read inotify event: %m"); + r = -errno; + goto finish; + } + + e = (struct inotify_event*) inotify_buffer; + while (n > 0) { + size_t step; + + if ((e->mask & IN_CREATE) && streq(e->name, "cancel")) { + log_debug("Collection canceled"); + r = -ECANCELED; + goto finish; + } + + if ((e->mask & IN_CREATE) && streq(e->name, "done")) { + log_debug("Got termination request"); + goto done; + } + + step = sizeof(struct inotify_event) + e->len; + assert(step <= (size_t) n); + + e = (struct inotify_event*) ((uint8_t*) e + step); + n -= step; + } + } + + if ((n = read(fanotify_fd, &data, sizeof(data))) < 0) { + + if (errno == EINTR || errno == EAGAIN) + continue; + + /* fanotify sometimes returns EACCES on read() + * where it shouldn't. For now let's just + * ignore it here (which is safe), but + * eventually this should be + * dropped when the kernel is fixed. + * + * https://bugzilla.redhat.com/show_bug.cgi?id=707577 */ + if (errno == EACCES) + continue; + + log_error("Failed to read event: %m"); + r = -errno; + goto finish; + } + + for (m = &data.metadata; FAN_EVENT_OK(m, n); m = FAN_EVENT_NEXT(m, n)) { + char fn[PATH_MAX]; + int k; + + if (m->fd < 0) + goto next_iteration; + + if (m->pid == my_pid) + goto next_iteration; + + __sync_synchronize(); + if (m->pid == shared->replay) + goto next_iteration; + + snprintf(fn, sizeof(fn), "/proc/self/fd/%i", m->fd); + char_array_0(fn); + + if ((k = readlink_malloc(fn, &p)) >= 0) { + if (startswith(p, "/tmp") || + endswith(p, " (deleted)") || + hashmap_get(files, p)) + /* Not interesting, or + * already read */ + free(p); + else { + unsigned long ul; + + ul = fd_first_block(m->fd); + + if ((k = hashmap_put(files, p, SECTOR_TO_PTR(ul))) < 0) { + log_warning("set_put() failed: %s", strerror(-k)); + free(p); + } + } + + } else + log_warning("readlink(%s) failed: %s", fn, strerror(-k)); + + next_iteration: + if (m->fd) + close_nointr_nofail(m->fd); + } + } + +done: + if (fanotify_fd >= 0) { + close_nointr_nofail(fanotify_fd); + fanotify_fd = -1; + } + + log_debug("Writing Pack File..."); + + on_ssd = fs_on_ssd(root) > 0; + log_debug("On SSD: %s", yes_no(on_ssd)); + + on_btrfs = statfs(root, &sfs) >= 0 && (long) sfs.f_type == (long) BTRFS_SUPER_MAGIC; + log_debug("On btrfs: %s", yes_no(on_btrfs)); + + asprintf(&pack_fn, "%s/.readahead", root); + asprintf(&pack_fn_new, "%s/.readahead.new", root); + + if (!pack_fn || !pack_fn_new) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + if (!(pack = fopen(pack_fn_new, "we"))) { + log_error("Failed to open pack file: %m"); + r = -errno; + goto finish; + } + + fputs(CANONICAL_HOST "\n", pack); + putc(on_ssd ? 'S' : 'R', pack); + + if (on_ssd || on_btrfs) { + + /* On SSD or on btrfs, just write things out in the + * order the files were accessed. */ + + HASHMAP_FOREACH_KEY(q, p, files, i) + pack_file(pack, p, on_btrfs); + } else { + struct item *ordered, *j; + unsigned k, n; + + /* On rotating media, order things by the block + * numbers */ + + log_debug("Ordering..."); + + n = hashmap_size(files); + if (!(ordered = new(struct item, n))) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + j = ordered; + HASHMAP_FOREACH_KEY(q, p, files, i) { + j->path = p; + j->block = PTR_TO_SECTOR(q); + j++; + } + + assert(ordered + n == j); + + qsort(ordered, n, sizeof(struct item), qsort_compare); + + for (k = 0; k < n; k++) + pack_file(pack, ordered[k].path, on_btrfs); + + free(ordered); + } + + log_debug("Finalizing..."); + + fflush(pack); + + if (ferror(pack)) { + log_error("Failed to write pack file."); + r = -EIO; + goto finish; + } + + if (rename(pack_fn_new, pack_fn) < 0) { + log_error("Failed to rename readahead file: %m"); + r = -errno; + goto finish; + } + + fclose(pack); + pack = NULL; + + log_debug("Done."); + +finish: + if (fanotify_fd >= 0) + close_nointr_nofail(fanotify_fd); + + if (signal_fd >= 0) + close_nointr_nofail(signal_fd); + + if (inotify_fd >= 0) + close_nointr_nofail(inotify_fd); + + if (pack) { + fclose(pack); + unlink(pack_fn_new); + } + + free(pack_fn_new); + free(pack_fn); + + while ((p = hashmap_steal_first_key(files))) + free(p); + + hashmap_free(files); + + return r; +} + +static int help(void) { + + printf("%s [OPTIONS...] [DIRECTORY]\n\n" + "Collect read-ahead data on early boot.\n\n" + " -h --help Show this help\n" + " --max-files=INT Maximum number of files to read ahead\n" + " --max-file-size=BYTES Maximum size of files to read ahead\n" + " --timeout=USEC Maximum time to spend collecting data\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_FILES_MAX = 0x100, + ARG_FILE_SIZE_MAX, + ARG_TIMEOUT + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "files-max", required_argument, NULL, ARG_FILES_MAX }, + { "file-size-max", required_argument, NULL, ARG_FILE_SIZE_MAX }, + { "timeout", required_argument, NULL, ARG_TIMEOUT }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_FILES_MAX: + if (safe_atou(optarg, &arg_files_max) < 0 || arg_files_max <= 0) { + log_error("Failed to parse maximum number of files %s.", optarg); + return -EINVAL; + } + break; + + case ARG_FILE_SIZE_MAX: { + unsigned long long ull; + + if (safe_atollu(optarg, &ull) < 0 || ull <= 0) { + log_error("Failed to parse maximum file size %s.", optarg); + return -EINVAL; + } + + arg_file_size_max = (off_t) ull; + break; + } + + case ARG_TIMEOUT: + if (parse_usec(optarg, &arg_timeout) < 0 || arg_timeout <= 0) { + log_error("Failed to parse timeout %s.", optarg); + return -EINVAL; + } + + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (optind != argc && + optind != argc-1) { + help(); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r; + const char *root; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if ((r = parse_argv(argc, argv)) <= 0) + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + + root = optind < argc ? argv[optind] : "/"; + + if (fs_on_read_only(root) > 0) { + log_info("Disabling readahead collector due to read-only media."); + return 0; + } + + if (!enough_ram()) { + log_info("Disabling readahead collector due to low memory."); + return 0; + } + + if (detect_virtualization(NULL) > 0) { + log_info("Disabling readahead collector due to execution in virtualized environment."); + return 0; + } + + if (!(shared = shared_get())) + return 1; + + shared->collect = getpid(); + __sync_synchronize(); + + if (collect(root) < 0) + return 1; + + return 0; +} diff --git a/src/readahead/readahead-common.c b/src/readahead/readahead-common.c new file mode 100644 index 000000000..67214ec37 --- /dev/null +++ b/src/readahead/readahead-common.c @@ -0,0 +1,269 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "readahead-common.h" +#include "util.h" + +int file_verify(int fd, const char *fn, off_t file_size_max, struct stat *st) { + assert(fd >= 0); + assert(fn); + assert(st); + + if (fstat(fd, st) < 0) { + log_warning("fstat(%s) failed: %m", fn); + return -errno; + } + + if (!S_ISREG(st->st_mode)) { + log_debug("Not preloading special file %s", fn); + return 0; + } + + if (st->st_size <= 0 || st->st_size > file_size_max) { + log_debug("Not preloading file %s with size out of bounds %llu", fn, (unsigned long long) st->st_size); + return 0; + } + + return 1; +} + +int fs_on_ssd(const char *p) { + struct stat st; + struct udev *udev = NULL; + struct udev_device *udev_device = NULL, *look_at = NULL; + bool b = false; + const char *devtype, *rotational, *model, *id; + + assert(p); + + if (stat(p, &st) < 0) + return -errno; + + if (major(st.st_dev) == 0) + return false; + + if (!(udev = udev_new())) + return -ENOMEM; + + if (!(udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev))) + goto finish; + + if ((devtype = udev_device_get_property_value(udev_device, "DEVTYPE")) && + streq(devtype, "partition")) + look_at = udev_device_get_parent(udev_device); + else + look_at = udev_device; + + if (!look_at) + goto finish; + + /* First, try high-level property */ + if ((id = udev_device_get_property_value(look_at, "ID_SSD"))) { + b = streq(id, "1"); + goto finish; + } + + /* Second, try kernel attribute */ + if ((rotational = udev_device_get_sysattr_value(look_at, "queue/rotational"))) + if ((b = streq(rotational, "0"))) + goto finish; + + /* Finally, fallback to heuristics */ + if (!(look_at = udev_device_get_parent(look_at))) + goto finish; + + if ((model = udev_device_get_sysattr_value(look_at, "model"))) + b = !!strstr(model, "SSD"); + +finish: + if (udev_device) + udev_device_unref(udev_device); + + if (udev) + udev_unref(udev); + + return b; +} + +int fs_on_read_only(const char *p) { + struct stat st; + struct udev *udev = NULL; + struct udev_device *udev_device = NULL; + bool b = false; + const char *read_only; + + assert(p); + + if (stat(p, &st) < 0) + return -errno; + + if (major(st.st_dev) == 0) + return false; + + if (!(udev = udev_new())) + return -ENOMEM; + + if (!(udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev))) + goto finish; + + if ((read_only = udev_device_get_sysattr_value(udev_device, "ro"))) + if ((b = streq(read_only, "1"))) + goto finish; + +finish: + if (udev_device) + udev_device_unref(udev_device); + + if (udev) + udev_unref(udev); + + return b; +} + +bool enough_ram(void) { + struct sysinfo si; + + assert_se(sysinfo(&si) >= 0); + + /* Enable readahead only with at least 128MB memory */ + return si.totalram > 127 * 1024*1024 / si.mem_unit; +} + +int open_inotify(void) { + int fd; + + if ((fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) { + log_error("Failed to create inotify handle: %m"); + return -errno; + } + + mkdir("/run/systemd", 0755); + mkdir("/run/systemd/readahead", 0755); + + if (inotify_add_watch(fd, "/run/systemd/readahead", IN_CREATE) < 0) { + log_error("Failed to watch /run/systemd/readahead: %m"); + close_nointr_nofail(fd); + return -errno; + } + + return fd; +} + +ReadaheadShared *shared_get(void) { + int fd; + ReadaheadShared *m = NULL; + + mkdir("/run/systemd", 0755); + mkdir("/run/systemd/readahead", 0755); + + if ((fd = open("/run/systemd/readahead/shared", O_CREAT|O_RDWR|O_CLOEXEC, 0644)) < 0) { + log_error("Failed to create shared memory segment: %m"); + goto finish; + } + + if (ftruncate(fd, sizeof(ReadaheadShared)) < 0) { + log_error("Failed to truncate shared memory segment: %m"); + goto finish; + } + + if ((m = mmap(NULL, sizeof(ReadaheadShared), PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { + log_error("Failed to mmap shared memory segment: %m"); + m = NULL; + goto finish; + } + +finish: + if (fd >= 0) + close_nointr_nofail(fd); + + return m; +} + +#define BUMP_REQUEST_NR (16*1024) + +int bump_request_nr(const char *p) { + struct stat st; + uint64_t u; + char *ap = NULL, *line = NULL; + int r; + dev_t d; + + assert(p); + + if (stat(p, &st) < 0) + return -errno; + + if (major(st.st_dev) == 0) + return 0; + + d = st.st_dev; + block_get_whole_disk(d, &d); + + if (asprintf(&ap, "/sys/dev/block/%u:%u/queue/nr_requests", major(d), minor(d)) < 0) { + r= -ENOMEM; + goto finish; + } + + r = read_one_line_file(ap, &line); + if (r < 0) { + if (r == -ENOENT) + r = 0; + goto finish; + } + + r = safe_atou64(line, &u); + if (r >= 0 && u >= BUMP_REQUEST_NR) { + r = 0; + goto finish; + } + + free(line); + line = NULL; + + if (asprintf(&line, "%lu", (unsigned long) BUMP_REQUEST_NR) < 0) { + r = -ENOMEM; + goto finish; + } + + r = write_one_line_file(ap, line); + if (r < 0) + goto finish; + + log_info("Bumped block_nr parameter of %u:%u to %lu. This is a temporary hack and should be removed one day.", major(d), minor(d), (unsigned long) BUMP_REQUEST_NR); + r = 1; + +finish: + free(ap); + free(line); + + return r; +} diff --git a/src/readahead/readahead-common.h b/src/readahead/readahead-common.h new file mode 100644 index 000000000..9547ad201 --- /dev/null +++ b/src/readahead/readahead-common.h @@ -0,0 +1,50 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooreadaheadcommonhfoo +#define fooreadaheadcommonhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "macro.h" + +#define READAHEAD_FILE_SIZE_MAX (10*1024*1024) + +int file_verify(int fd, const char *fn, off_t file_size_max, struct stat *st); + +int fs_on_ssd(const char *p); +int fs_on_read_only(const char *p); + +bool enough_ram(void); + +int open_inotify(void); + +typedef struct ReadaheadShared { + pid_t collect; + pid_t replay; +} _packed_ ReadaheadShared; + +ReadaheadShared *shared_get(void); + +int bump_request_nr(const char *p); + +#endif diff --git a/src/readahead/readahead-replay.c b/src/readahead/readahead-replay.c new file mode 100644 index 000000000..0c739c82b --- /dev/null +++ b/src/readahead/readahead-replay.c @@ -0,0 +1,378 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "missing.h" +#include "util.h" +#include "set.h" +#include "ioprio.h" +#include "readahead-common.h" +#include "virt.h" + +static off_t arg_file_size_max = READAHEAD_FILE_SIZE_MAX; + +static ReadaheadShared *shared = NULL; + +static int unpack_file(FILE *pack) { + char fn[PATH_MAX]; + int r = 0, fd = -1; + bool any = false; + struct stat st; + + assert(pack); + + if (!fgets(fn, sizeof(fn), pack)) + return 0; + + char_array_0(fn); + truncate_nl(fn); + + if ((fd = open(fn, O_RDONLY|O_CLOEXEC|O_NOATIME|O_NOCTTY|O_NOFOLLOW)) < 0) { + + if (errno != ENOENT && errno != EPERM && errno != EACCES) + log_warning("open(%s) failed: %m", fn); + + } else if (file_verify(fd, fn, arg_file_size_max, &st) <= 0) { + close_nointr_nofail(fd); + fd = -1; + } + + for (;;) { + uint32_t b, c; + + if (fread(&b, sizeof(b), 1, pack) != 1 || + fread(&c, sizeof(c), 1, pack) != 1) { + log_error("Premature end of pack file."); + r = -EIO; + goto finish; + } + + if (b == 0 && c == 0) + break; + + if (c <= b) { + log_error("Invalid pack file."); + r = -EIO; + goto finish; + } + + log_debug("%s: page %u to %u", fn, b, c); + + any = true; + + if (fd >= 0) + if (posix_fadvise(fd, b * page_size(), (c - b) * page_size(), POSIX_FADV_WILLNEED) < 0) { + log_warning("posix_fadvise() failed: %m"); + goto finish; + } + } + + if (!any && fd >= 0) { + /* if no range is encoded in the pack file this is + * intended to mean that the whole file shall be + * read */ + + if (posix_fadvise(fd, 0, st.st_size, POSIX_FADV_WILLNEED) < 0) { + log_warning("posix_fadvise() failed: %m"); + goto finish; + } + } + +finish: + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +static int replay(const char *root) { + FILE *pack = NULL; + char line[LINE_MAX]; + int r = 0; + char *pack_fn = NULL; + int c; + bool on_ssd, ready = false; + int prio; + int inotify_fd = -1; + + assert(root); + + write_one_line_file("/proc/self/oom_score_adj", "1000"); + bump_request_nr(root); + + if (asprintf(&pack_fn, "%s/.readahead", root) < 0) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + if ((!(pack = fopen(pack_fn, "re")))) { + if (errno == ENOENT) + log_debug("No pack file found."); + else { + log_error("Failed to open pack file: %m"); + r = -errno; + } + + goto finish; + } + + posix_fadvise(fileno(pack), 0, 0, POSIX_FADV_WILLNEED); + + if ((inotify_fd = open_inotify()) < 0) { + r = inotify_fd; + goto finish; + } + + if (!(fgets(line, sizeof(line), pack))) { + log_error("Premature end of pack file."); + r = -EIO; + goto finish; + } + + char_array_0(line); + + if (!streq(line, CANONICAL_HOST "\n")) { + log_debug("Pack file host type mismatch."); + goto finish; + } + + if ((c = getc(pack)) == EOF) { + log_debug("Premature end of pack file."); + r = -EIO; + goto finish; + } + + /* We do not retest SSD here, so that we can start replaying + * before udev is up.*/ + on_ssd = c == 'S'; + log_debug("On SSD: %s", yes_no(on_ssd)); + + if (on_ssd) + prio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0); + else + /* We are not using RT here, since we'd starve IO that + we didn't record (which is for example blkid, since + its disk accesses go directly to the block device and + are thus not visible in fallocate) to death. However, + we do ask for an IO prio that is slightly higher than + the default (which is BE. 4) */ + prio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 2); + + if (ioprio_set(IOPRIO_WHO_PROCESS, getpid(), prio) < 0) + log_warning("Failed to set IDLE IO priority class: %m"); + + sd_notify(0, "STATUS=Replaying readahead data"); + + log_debug("Replaying..."); + + if (access("/run/systemd/readahead/noreplay", F_OK) >= 0) { + log_debug("Got termination request"); + goto done; + } + + while (!feof(pack) && !ferror(pack)) { + uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX]; + int k; + ssize_t n; + + if ((n = read(inotify_fd, &inotify_buffer, sizeof(inotify_buffer))) < 0) { + if (errno != EINTR && errno != EAGAIN) { + log_error("Failed to read inotify event: %m"); + r = -errno; + goto finish; + } + } else { + struct inotify_event *e = (struct inotify_event*) inotify_buffer; + + while (n > 0) { + size_t step; + + if ((e->mask & IN_CREATE) && streq(e->name, "noreplay")) { + log_debug("Got termination request"); + goto done; + } + + step = sizeof(struct inotify_event) + e->len; + assert(step <= (size_t) n); + + e = (struct inotify_event*) ((uint8_t*) e + step); + n -= step; + } + } + + if ((k = unpack_file(pack)) < 0) { + r = k; + goto finish; + } + + if (!ready) { + /* We delay the ready notification until we + * queued at least one read */ + sd_notify(0, "READY=1"); + ready = true; + } + } + +done: + if (!ready) + sd_notify(0, "READY=1"); + + if (ferror(pack)) { + log_error("Failed to read pack file."); + r = -EIO; + goto finish; + } + + log_debug("Done."); + +finish: + if (pack) + fclose(pack); + + if (inotify_fd >= 0) + close_nointr_nofail(inotify_fd); + + free(pack_fn); + + return r; +} + + +static int help(void) { + + printf("%s [OPTIONS...] [DIRECTORY]\n\n" + "Replay collected read-ahead data on early boot.\n\n" + " -h --help Show this help\n" + " --max-file-size=BYTES Maximum size of files to read ahead\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_FILE_SIZE_MAX + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "file-size-max", required_argument, NULL, ARG_FILE_SIZE_MAX }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_FILE_SIZE_MAX: { + unsigned long long ull; + + if (safe_atollu(optarg, &ull) < 0 || ull <= 0) { + log_error("Failed to parse maximum file size %s.", optarg); + return -EINVAL; + } + + arg_file_size_max = (off_t) ull; + break; + } + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (optind != argc && + optind != argc-1) { + help(); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char*argv[]) { + int r; + const char *root; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if ((r = parse_argv(argc, argv)) <= 0) + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + + root = optind < argc ? argv[optind] : "/"; + + if (!enough_ram()) { + log_info("Disabling readahead replay due to low memory."); + return 0; + } + + if (detect_virtualization(NULL) > 0) { + log_info("Disabling readahead replay due to execution in virtualized environment."); + return 0; + } + + if (!(shared = shared_get())) + return 1; + + shared->replay = getpid(); + __sync_synchronize(); + + if (replay(root) < 0) + return 1; + + return 0; +} diff --git a/src/readahead/sd-readahead.c b/src/readahead/sd-readahead.c new file mode 100644 index 000000000..a3340666d --- /dev/null +++ b/src/readahead/sd-readahead.c @@ -0,0 +1,88 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + Copyright 2010 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +#include "sd-readahead.h" + +#if (__GNUC__ >= 4) +#ifdef SD_EXPORT_SYMBOLS +/* Export symbols */ +#define _sd_export_ __attribute__ ((visibility("default"))) +#else +/* Don't export the symbols */ +#define _sd_export_ __attribute__ ((visibility("hidden"))) +#endif +#else +#define _sd_export_ +#endif + +static int touch(const char *path) { + +#if !defined(DISABLE_SYSTEMD) && defined(__linux__) + int fd; + + mkdir("/run/systemd", 0755); + mkdir("/run/systemd/readahead", 0755); + + if ((fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0666)) < 0) + return -errno; + + for (;;) { + if (close(fd) >= 0) + break; + + if (errno != -EINTR) + return -errno; + } + +#endif + return 0; +} + +_sd_export_ int sd_readahead(const char *action) { + + if (!action) + return -EINVAL; + + if (strcmp(action, "cancel") == 0) + return touch("/run/systemd/readahead/cancel"); + else if (strcmp(action, "done") == 0) + return touch("/run/systemd/readahead/done"); + else if (strcmp(action, "noreplay") == 0) + return touch("/run/systemd/readahead/noreplay"); + + return -EINVAL; +} diff --git a/src/remount-api-vfs.c b/src/remount-api-vfs.c new file mode 100644 index 000000000..3e146ebb5 --- /dev/null +++ b/src/remount-api-vfs.c @@ -0,0 +1,161 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "util.h" +#include "set.h" +#include "mount-setup.h" +#include "exit-status.h" + +/* Goes through /etc/fstab and remounts all API file systems, applying + * options that are in /etc/fstab that systemd might not have + * respected */ + +int main(int argc, char *argv[]) { + int ret = EXIT_FAILURE; + FILE *f = NULL; + struct mntent* me; + Hashmap *pids = NULL; + + if (argc > 1) { + log_error("This program takes no argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + f = setmntent("/etc/fstab", "r"); + if (!f) { + log_error("Failed to open /etc/fstab: %m"); + goto finish; + } + + pids = hashmap_new(trivial_hash_func, trivial_compare_func); + if (!pids) { + log_error("Failed to allocate set"); + goto finish; + } + + ret = EXIT_SUCCESS; + + while ((me = getmntent(f))) { + pid_t pid; + int k; + char *s; + + if (!mount_point_is_api(me->mnt_dir)) + continue; + + log_debug("Remounting %s", me->mnt_dir); + + pid = fork(); + if (pid < 0) { + log_error("Failed to fork: %m"); + ret = EXIT_FAILURE; + continue; + } + + if (pid == 0) { + const char *arguments[5]; + /* Child */ + + arguments[0] = "/bin/mount"; + arguments[1] = me->mnt_dir; + arguments[2] = "-o"; + arguments[3] = "remount"; + arguments[4] = NULL; + + execv("/bin/mount", (char **) arguments); + + log_error("Failed to execute /bin/mount: %m"); + _exit(EXIT_FAILURE); + } + + /* Parent */ + + s = strdup(me->mnt_dir); + if (!s) { + log_error("Out of memory."); + ret = EXIT_FAILURE; + continue; + } + + + k = hashmap_put(pids, UINT_TO_PTR(pid), s); + if (k < 0) { + log_error("Failed to add PID to set: %s", strerror(-k)); + ret = EXIT_FAILURE; + continue; + } + } + + while (!hashmap_isempty(pids)) { + siginfo_t si; + char *s; + + zero(si); + if (waitid(P_ALL, 0, &si, WEXITED) < 0) { + + if (errno == EINTR) + continue; + + log_error("waitid() failed: %m"); + ret = EXIT_FAILURE; + break; + } + + s = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)); + if (s) { + if (!is_clean_exit(si.si_code, si.si_status)) { + if (si.si_code == CLD_EXITED) + log_error("/bin/mount for %s exited with exit status %i.", s, si.si_status); + else + log_error("/bin/mount for %s terminated by signal %s.", s, signal_to_string(si.si_status)); + + ret = EXIT_FAILURE; + } + + free(s); + } + } + +finish: + + if (pids) + hashmap_free_free(pids); + + if (f) + endmntent(f); + + return ret; +} diff --git a/src/reply-password.c b/src/reply-password.c new file mode 100644 index 000000000..3a96049d7 --- /dev/null +++ b/src/reply-password.c @@ -0,0 +1,109 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "macro.h" +#include "util.h" + +static int send_on_socket(int fd, const char *socket_name, const void *packet, size_t size) { + union { + struct sockaddr sa; + struct sockaddr_un un; + } sa; + + assert(fd >= 0); + assert(socket_name); + assert(packet); + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path)); + + if (sendto(fd, packet, size, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) { + log_error("Failed to send: %m"); + return -1; + } + + return 0; +} + +int main(int argc, char *argv[]) { + int fd = -1, r = EXIT_FAILURE; + char packet[LINE_MAX]; + size_t length; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + if (argc != 3) { + log_error("Wrong number of arguments."); + goto finish; + } + + if (streq(argv[1], "1")) { + + packet[0] = '+'; + if (!fgets(packet+1, sizeof(packet)-1, stdin)) { + log_error("Failed to read password: %m"); + goto finish; + } + + truncate_nl(packet+1); + length = 1 + strlen(packet+1) + 1; + } else if (streq(argv[1], "0")) { + packet[0] = '-'; + length = 1; + } else { + log_error("Invalid first argument %s", argv[1]); + goto finish; + } + + if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) { + log_error("socket() failed: %m"); + goto finish; + } + + if (send_on_socket(fd, argv[2], packet, length) < 0) + goto finish; + + r = EXIT_SUCCESS; + +finish: + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} diff --git a/src/sd-id128.c b/src/sd-id128.c new file mode 100644 index 000000000..b4184e1c7 --- /dev/null +++ b/src/sd-id128.c @@ -0,0 +1,221 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "sd-id128.h" + +#include "util.h" +#include "macro.h" + +_public_ char *sd_id128_to_string(sd_id128_t id, char s[33]) { + unsigned n; + + if (!s) + return NULL; + + for (n = 0; n < 16; n++) { + s[n*2] = hexchar(id.bytes[n] >> 4); + s[n*2+1] = hexchar(id.bytes[n] & 0xF); + } + + s[32] = 0; + + return s; +} + +_public_ int sd_id128_from_string(const char s[33], sd_id128_t *ret) { + unsigned n; + sd_id128_t t; + + if (!s) + return -EINVAL; + if (!ret) + return -EINVAL; + + for (n = 0; n < 16; n++) { + int a, b; + + a = unhexchar(s[n*2]); + if (a < 0) + return -EINVAL; + + b = unhexchar(s[n*2+1]); + if (b < 0) + return -EINVAL; + + t.bytes[n] = (a << 4) | b; + } + + if (s[32] != 0) + return -EINVAL; + + *ret = t; + return 0; +} + +static sd_id128_t make_v4_uuid(sd_id128_t id) { + /* Stolen from generate_random_uuid() of drivers/char/random.c + * in the kernel sources */ + + /* Set UUID version to 4 --- truly random generation */ + id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40; + + /* Set the UUID variant to DCE */ + id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80; + + return id; +} + +_public_ int sd_id128_get_machine(sd_id128_t *ret) { + static __thread sd_id128_t saved_machine_id; + static __thread bool saved_machine_id_valid = false; + int fd; + char buf[32]; + ssize_t k; + unsigned j; + sd_id128_t t; + + if (!ret) + return -EINVAL; + + if (saved_machine_id_valid) { + *ret = saved_machine_id; + return 0; + } + + fd = open("/etc/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + k = loop_read(fd, buf, 32, false); + close_nointr_nofail(fd); + + if (k < 0) + return (int) k; + + if (k < 32) + return -EIO; + + for (j = 0; j < 16; j++) { + int a, b; + + a = unhexchar(buf[j*2]); + b = unhexchar(buf[j*2+1]); + + if (a < 0 || b < 0) + return -EIO; + + t.bytes[j] = a << 4 | b; + } + + saved_machine_id = t; + saved_machine_id_valid = true; + + *ret = t; + return 0; +} + +_public_ int sd_id128_get_boot(sd_id128_t *ret) { + static __thread sd_id128_t saved_boot_id; + static __thread bool saved_boot_id_valid = false; + int fd; + char buf[36]; + ssize_t k; + unsigned j; + sd_id128_t t; + char *p; + + if (!ret) + return -EINVAL; + + if (saved_boot_id_valid) { + *ret = saved_boot_id; + return 0; + } + + fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + k = loop_read(fd, buf, 36, false); + close_nointr_nofail(fd); + + if (k < 0) + return (int) k; + + if (k < 36) + return -EIO; + + for (j = 0, p = buf; j < 16; j++) { + int a, b; + + if (*p == '-') + p++; + + a = unhexchar(p[0]); + b = unhexchar(p[1]); + + if (a < 0 || b < 0) + return -EIO; + + t.bytes[j] = a << 4 | b; + + p += 2; + } + + saved_boot_id = t; + saved_boot_id_valid = true; + + *ret = t; + return 0; +} + +_public_ int sd_id128_randomize(sd_id128_t *ret) { + int fd; + ssize_t k; + sd_id128_t t; + + if (!ret) + return -EINVAL; + + fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + k = loop_read(fd, &t, 16, false); + close_nointr_nofail(fd); + + if (k < 0) + return (int) k; + + if (k < 16) + return -EIO; + + /* Turn this into a valid v4 UUID, to be nice. Note that we + * only guarantee this for newly generated UUIDs, not for + * pre-existing ones.*/ + + *ret = make_v4_uuid(t); + return 0; +} diff --git a/src/securebits.h b/src/securebits.h new file mode 100644 index 000000000..ba0bba535 --- /dev/null +++ b/src/securebits.h @@ -0,0 +1,45 @@ +#ifndef _LINUX_SECUREBITS_H +#define _LINUX_SECUREBITS_H 1 + +/* This is minimal version of Linux' linux/securebits.h header file, + * which is licensed GPL2 */ + +#define SECUREBITS_DEFAULT 0x00000000 + +/* When set UID 0 has no special privileges. When unset, we support + inheritance of root-permissions and suid-root executable under + compatibility mode. We raise the effective and inheritable bitmasks + *of the executable file* if the effective uid of the new process is + 0. If the real uid is 0, we raise the effective (legacy) bit of the + executable file. */ +#define SECURE_NOROOT 0 +#define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */ + +/* When set, setuid to/from uid 0 does not trigger capability-"fixup". + When unset, to provide compatibility with old programs relying on + set*uid to gain/lose privilege, transitions to/from uid 0 cause + capabilities to be gained/lost. */ +#define SECURE_NO_SETUID_FIXUP 2 +#define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */ + +/* When set, a process can retain its capabilities even after + transitioning to a non-root user (the set-uid fixup suppressed by + bit 2). Bit-4 is cleared when a process calls exec(); setting both + bit 4 and 5 will create a barrier through exec that no exec()'d + child can use this feature again. */ +#define SECURE_KEEP_CAPS 4 +#define SECURE_KEEP_CAPS_LOCKED 5 /* make bit-4 immutable */ + +/* Each securesetting is implemented using two bits. One bit specifies + whether the setting is on or off. The other bit specify whether the + setting is locked or not. A setting which is locked cannot be + changed from user-level. */ +#define issecure_mask(X) (1 << (X)) +#define issecure(X) (issecure_mask(X) & current_cred_xxx(securebits)) + +#define SECURE_ALL_BITS (issecure_mask(SECURE_NOROOT) | \ + issecure_mask(SECURE_NO_SETUID_FIXUP) | \ + issecure_mask(SECURE_KEEP_CAPS)) +#define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1) + +#endif /* !_LINUX_SECUREBITS_H */ diff --git a/src/selinux-setup.c b/src/selinux-setup.c new file mode 100644 index 000000000..a7e1fa400 --- /dev/null +++ b/src/selinux-setup.c @@ -0,0 +1,112 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#ifdef HAVE_SELINUX +#include +#endif + +#include "selinux-setup.h" +#include "mount-setup.h" +#include "macro.h" +#include "util.h" +#include "log.h" +#include "label.h" + +int selinux_setup(bool *loaded_policy) { + +#ifdef HAVE_SELINUX + int enforce = 0; + usec_t before_load, after_load; + security_context_t con; + int r; + + assert(loaded_policy); + + /* Make sure getcon() works, which needs /proc and /sys */ + mount_setup_early(); + + /* Already initialized by somebody else? */ + r = getcon_raw(&con); + if (r == 0) { + bool initialized; + + initialized = !streq(con, "kernel"); + freecon(con); + + if (initialized) + return 0; + } + + /* Make sure we have no fds open while loading the policy and + * transitioning */ + log_close(); + + /* Now load the policy */ + before_load = now(CLOCK_MONOTONIC); + r = selinux_init_load_policy(&enforce); + + if (r == 0) { + char timespan[FORMAT_TIMESPAN_MAX]; + char *label; + + label_retest_selinux(); + + /* Transition to the new context */ + r = label_get_create_label_from_exe(SYSTEMD_BINARY_PATH, &label); + if (r < 0 || label == NULL) { + log_open(); + log_error("Failed to compute init label, ignoring."); + } else { + r = setcon(label); + + log_open(); + if (r < 0) + log_error("Failed to transition into init label '%s', ignoring.", label); + + label_free(label); + } + + after_load = now(CLOCK_MONOTONIC); + + log_info("Successfully loaded SELinux policy in %s.", + format_timespan(timespan, sizeof(timespan), after_load - before_load)); + + *loaded_policy = true; + + } else { + log_open(); + + if (enforce > 0) { + log_error("Failed to load SELinux policy. Freezing."); + return -EIO; + } else + log_debug("Unable to load SELinux policy. Ignoring."); + } +#endif + + return 0; +} diff --git a/src/selinux-setup.h b/src/selinux-setup.h new file mode 100644 index 000000000..6b8fe00b7 --- /dev/null +++ b/src/selinux-setup.h @@ -0,0 +1,29 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooselinuxsetuphfoo +#define fooselinuxsetuphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +int selinux_setup(bool *loaded_policy); + +#endif diff --git a/src/service.c b/src/service.c new file mode 100644 index 000000000..bf2e0a9d9 --- /dev/null +++ b/src/service.c @@ -0,0 +1,3797 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "manager.h" +#include "unit.h" +#include "service.h" +#include "load-fragment.h" +#include "load-dropin.h" +#include "log.h" +#include "strv.h" +#include "unit-name.h" +#include "dbus-service.h" +#include "special.h" +#include "bus-errors.h" +#include "exit-status.h" +#include "def.h" +#include "util.h" +#include "utf8.h" + +#ifdef HAVE_SYSV_COMPAT + +#define DEFAULT_SYSV_TIMEOUT_USEC (5*USEC_PER_MINUTE) + +typedef enum RunlevelType { + RUNLEVEL_UP, + RUNLEVEL_DOWN, + RUNLEVEL_SYSINIT +} RunlevelType; + +static const struct { + const char *path; + const char *target; + const RunlevelType type; +} rcnd_table[] = { + /* Standard SysV runlevels for start-up */ + { "rc1.d", SPECIAL_RESCUE_TARGET, RUNLEVEL_UP }, + { "rc2.d", SPECIAL_RUNLEVEL2_TARGET, RUNLEVEL_UP }, + { "rc3.d", SPECIAL_RUNLEVEL3_TARGET, RUNLEVEL_UP }, + { "rc4.d", SPECIAL_RUNLEVEL4_TARGET, RUNLEVEL_UP }, + { "rc5.d", SPECIAL_RUNLEVEL5_TARGET, RUNLEVEL_UP }, + +#ifdef TARGET_SUSE + /* SUSE style boot.d */ + { "boot.d", SPECIAL_SYSINIT_TARGET, RUNLEVEL_SYSINIT }, +#endif + +#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) + /* Debian style rcS.d */ + { "rcS.d", SPECIAL_SYSINIT_TARGET, RUNLEVEL_SYSINIT }, +#endif + + /* Standard SysV runlevels for shutdown */ + { "rc0.d", SPECIAL_POWEROFF_TARGET, RUNLEVEL_DOWN }, + { "rc6.d", SPECIAL_REBOOT_TARGET, RUNLEVEL_DOWN } + + /* Note that the order here matters, as we read the + directories in this order, and we want to make sure that + sysv_start_priority is known when we first load the + unit. And that value we only know from S links. Hence + UP/SYSINIT must be read before DOWN */ +}; + +#define RUNLEVELS_UP "12345" +/* #define RUNLEVELS_DOWN "06" */ +#define RUNLEVELS_BOOT "bBsS" +#endif + +static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = UNIT_INACTIVE, + [SERVICE_START_PRE] = UNIT_ACTIVATING, + [SERVICE_START] = UNIT_ACTIVATING, + [SERVICE_START_POST] = UNIT_ACTIVATING, + [SERVICE_RUNNING] = UNIT_ACTIVE, + [SERVICE_EXITED] = UNIT_ACTIVE, + [SERVICE_RELOAD] = UNIT_RELOADING, + [SERVICE_STOP] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, + [SERVICE_STOP_POST] = UNIT_DEACTIVATING, + [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, + [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, + [SERVICE_FAILED] = UNIT_FAILED, + [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING +}; + +static void service_init(Unit *u) { + Service *s = SERVICE(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + s->timeout_usec = DEFAULT_TIMEOUT_USEC; + s->restart_usec = DEFAULT_RESTART_USEC; + + s->watchdog_watch.type = WATCH_INVALID; + + s->timer_watch.type = WATCH_INVALID; +#ifdef HAVE_SYSV_COMPAT + s->sysv_start_priority = -1; + s->sysv_start_priority_from_rcnd = -1; +#endif + s->socket_fd = -1; + s->guess_main_pid = true; + + exec_context_init(&s->exec_context); + + RATELIMIT_INIT(s->start_limit, 10*USEC_PER_SEC, 5); + + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; +} + +static void service_unwatch_control_pid(Service *s) { + assert(s); + + if (s->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->control_pid); + s->control_pid = 0; +} + +static void service_unwatch_main_pid(Service *s) { + assert(s); + + if (s->main_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->main_pid); + s->main_pid = 0; +} + +static void service_unwatch_pid_file(Service *s) { + if (!s->pid_file_pathspec) + return; + + log_debug("Stopping watch for %s's PID file %s", UNIT(s)->id, s->pid_file_pathspec->path); + path_spec_unwatch(s->pid_file_pathspec, UNIT(s)); + path_spec_done(s->pid_file_pathspec); + free(s->pid_file_pathspec); + s->pid_file_pathspec = NULL; +} + +static int service_set_main_pid(Service *s, pid_t pid) { + pid_t ppid; + + assert(s); + + if (pid <= 1) + return -EINVAL; + + if (pid == getpid()) + return -EINVAL; + + s->main_pid = pid; + s->main_pid_known = true; + + if (get_parent_of_pid(pid, &ppid) >= 0 && ppid != getpid()) { + log_warning("%s: Supervising process %lu which is not our child. We'll most likely not notice when it exits.", + UNIT(s)->id, (unsigned long) pid); + + s->main_pid_alien = true; + } else + s->main_pid_alien = false; + + exec_status_start(&s->main_exec_status, pid); + + return 0; +} + +static void service_close_socket_fd(Service *s) { + assert(s); + + if (s->socket_fd < 0) + return; + + close_nointr_nofail(s->socket_fd); + s->socket_fd = -1; +} + +static void service_connection_unref(Service *s) { + assert(s); + + if (!UNIT_DEREF(s->accept_socket)) + return; + + socket_connection_unref(SOCKET(UNIT_DEREF(s->accept_socket))); + unit_ref_unset(&s->accept_socket); +} + +static void service_stop_watchdog(Service *s) { + assert(s); + + unit_unwatch_timer(UNIT(s), &s->watchdog_watch); + s->watchdog_timestamp.realtime = 0; + s->watchdog_timestamp.monotonic = 0; +} + +static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart); + +static void service_handle_watchdog(Service *s) { + usec_t offset; + int r; + + assert(s); + + if (s->watchdog_usec == 0) + return; + + offset = now(CLOCK_MONOTONIC) - s->watchdog_timestamp.monotonic; + if (offset >= s->watchdog_usec) { + log_error("%s watchdog timeout!", UNIT(s)->id); + service_enter_dead(s, SERVICE_FAILURE_WATCHDOG, true); + return; + } + + r = unit_watch_timer(UNIT(s), s->watchdog_usec - offset, &s->watchdog_watch); + if (r < 0) + log_warning("%s failed to install watchdog timer: %s", UNIT(s)->id, strerror(-r)); +} + +static void service_reset_watchdog(Service *s) { + assert(s); + + dual_timestamp_get(&s->watchdog_timestamp); + service_handle_watchdog(s); +} + +static void service_done(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + free(s->pid_file); + s->pid_file = NULL; + +#ifdef HAVE_SYSV_COMPAT + free(s->sysv_path); + s->sysv_path = NULL; + + free(s->sysv_runlevels); + s->sysv_runlevels = NULL; +#endif + + free(s->status_text); + s->status_text = NULL; + + exec_context_done(&s->exec_context); + exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX); + s->control_command = NULL; + s->main_command = NULL; + + /* This will leak a process, but at least no memory or any of + * our resources */ + service_unwatch_main_pid(s); + service_unwatch_control_pid(s); + service_unwatch_pid_file(s); + + if (s->bus_name) { + unit_unwatch_bus_name(u, s->bus_name); + free(s->bus_name); + s->bus_name = NULL; + } + + service_close_socket_fd(s); + service_connection_unref(s); + + unit_ref_unset(&s->accept_socket); + + service_stop_watchdog(s); + + unit_unwatch_timer(u, &s->timer_watch); +} + +#ifdef HAVE_SYSV_COMPAT +static char *sysv_translate_name(const char *name) { + char *r; + + if (!(r = new(char, strlen(name) + sizeof(".service")))) + return NULL; + +#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) + if (endswith(name, ".sh")) + /* Drop Debian-style .sh suffix */ + strcpy(stpcpy(r, name) - 3, ".service"); +#endif +#ifdef TARGET_SUSE + if (startswith(name, "boot.")) + /* Drop SuSE-style boot. prefix */ + strcpy(stpcpy(r, name + 5), ".service"); +#endif +#ifdef TARGET_FRUGALWARE + if (startswith(name, "rc.")) + /* Drop Frugalware-style rc. prefix */ + strcpy(stpcpy(r, name + 3), ".service"); +#endif + else + /* Normal init scripts */ + strcpy(stpcpy(r, name), ".service"); + + return r; +} + +static int sysv_translate_facility(const char *name, const char *filename, char **_r) { + + /* We silently ignore the $ prefix here. According to the LSB + * spec it simply indicates whether something is a + * standardized name or a distribution-specific one. Since we + * just follow what already exists and do not introduce new + * uses or names we don't care who introduced a new name. */ + + static const char * const table[] = { + /* LSB defined facilities */ + "local_fs", SPECIAL_LOCAL_FS_TARGET, +#if defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA) +#else + /* Due to unfortunate name selection in Mandriva, + * $network is provided by network-up which is ordered + * after network which actually starts interfaces. + * To break the loop, just ignore it */ + "network", SPECIAL_NETWORK_TARGET, +#endif + "named", SPECIAL_NSS_LOOKUP_TARGET, + "portmap", SPECIAL_RPCBIND_TARGET, + "remote_fs", SPECIAL_REMOTE_FS_TARGET, + "syslog", SPECIAL_SYSLOG_TARGET, + "time", SPECIAL_TIME_SYNC_TARGET, + + /* common extensions */ + "mail-transfer-agent", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, + "x-display-manager", SPECIAL_DISPLAY_MANAGER_SERVICE, + "null", NULL, + +#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) + "mail-transport-agent", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, +#endif + +#ifdef TARGET_FEDORA + "MTA", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, + "smtpdaemon", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, + "httpd", SPECIAL_HTTP_DAEMON_TARGET, +#endif + +#ifdef TARGET_SUSE + "smtp", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, +#endif + }; + + unsigned i; + char *r; + const char *n; + + assert(name); + assert(_r); + + n = *name == '$' ? name + 1 : name; + + for (i = 0; i < ELEMENTSOF(table); i += 2) { + + if (!streq(table[i], n)) + continue; + + if (!table[i+1]) + return 0; + + if (!(r = strdup(table[i+1]))) + return -ENOMEM; + + goto finish; + } + + /* If we don't know this name, fallback heuristics to figure + * out whether something is a target or a service alias. */ + + if (*name == '$') { + if (!unit_prefix_is_valid(n)) + return -EINVAL; + + /* Facilities starting with $ are most likely targets */ + r = unit_name_build(n, NULL, ".target"); + } else if (filename && streq(name, filename)) + /* Names equaling the file name of the services are redundant */ + return 0; + else + /* Everything else we assume to be normal service names */ + r = sysv_translate_name(n); + + if (!r) + return -ENOMEM; + +finish: + *_r = r; + + return 1; +} + +static int sysv_fix_order(Service *s) { + Unit *other; + int r; + + assert(s); + + if (s->sysv_start_priority < 0) + return 0; + + /* For each pair of services where at least one lacks a LSB + * header, we use the start priority value to order things. */ + + LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_SERVICE]) { + Service *t; + UnitDependency d; + bool special_s, special_t; + + t = SERVICE(other); + + if (s == t) + continue; + + if (UNIT(t)->load_state != UNIT_LOADED) + continue; + + if (t->sysv_start_priority < 0) + continue; + + /* If both units have modern headers we don't care + * about the priorities */ + if ((UNIT(s)->fragment_path || s->sysv_has_lsb) && + (UNIT(t)->fragment_path || t->sysv_has_lsb)) + continue; + + special_s = s->sysv_runlevels && !chars_intersect(RUNLEVELS_UP, s->sysv_runlevels); + special_t = t->sysv_runlevels && !chars_intersect(RUNLEVELS_UP, t->sysv_runlevels); + + if (special_t && !special_s) + d = UNIT_AFTER; + else if (special_s && !special_t) + d = UNIT_BEFORE; + else if (t->sysv_start_priority < s->sysv_start_priority) + d = UNIT_AFTER; + else if (t->sysv_start_priority > s->sysv_start_priority) + d = UNIT_BEFORE; + else + continue; + + /* FIXME: Maybe we should compare the name here lexicographically? */ + + if ((r = unit_add_dependency(UNIT(s), d, UNIT(t), true)) < 0) + return r; + } + + return 0; +} + +static ExecCommand *exec_command_new(const char *path, const char *arg1) { + ExecCommand *c; + + if (!(c = new0(ExecCommand, 1))) + return NULL; + + if (!(c->path = strdup(path))) { + free(c); + return NULL; + } + + if (!(c->argv = strv_new(path, arg1, NULL))) { + free(c->path); + free(c); + return NULL; + } + + return c; +} + +static int sysv_exec_commands(Service *s) { + ExecCommand *c; + + assert(s); + assert(s->sysv_path); + + if (!(c = exec_command_new(s->sysv_path, "start"))) + return -ENOMEM; + exec_command_append_list(s->exec_command+SERVICE_EXEC_START, c); + + if (!(c = exec_command_new(s->sysv_path, "stop"))) + return -ENOMEM; + exec_command_append_list(s->exec_command+SERVICE_EXEC_STOP, c); + + if (!(c = exec_command_new(s->sysv_path, "reload"))) + return -ENOMEM; + exec_command_append_list(s->exec_command+SERVICE_EXEC_RELOAD, c); + + return 0; +} + +static int service_load_sysv_path(Service *s, const char *path) { + FILE *f; + Unit *u; + unsigned line = 0; + int r; + enum { + NORMAL, + DESCRIPTION, + LSB, + LSB_DESCRIPTION + } state = NORMAL; + char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL, *description; + struct stat st; + + assert(s); + assert(path); + + u = UNIT(s); + + if (!(f = fopen(path, "re"))) { + r = errno == ENOENT ? 0 : -errno; + goto finish; + } + + zero(st); + if (fstat(fileno(f), &st) < 0) { + r = -errno; + goto finish; + } + + free(s->sysv_path); + if (!(s->sysv_path = strdup(path))) { + r = -ENOMEM; + goto finish; + } + + s->sysv_mtime = timespec_load(&st.st_mtim); + + if (null_or_empty(&st)) { + u->load_state = UNIT_MASKED; + r = 0; + goto finish; + } + + while (!feof(f)) { + char l[LINE_MAX], *t; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + r = -errno; + log_error("Failed to read configuration file '%s': %s", path, strerror(-r)); + goto finish; + } + + line++; + + t = strstrip(l); + if (*t != '#') + continue; + + if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) { + state = LSB; + s->sysv_has_lsb = true; + continue; + } + + if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) { + state = NORMAL; + continue; + } + + t++; + t += strspn(t, WHITESPACE); + + if (state == NORMAL) { + + /* Try to parse Red Hat style chkconfig headers */ + + if (startswith_no_case(t, "chkconfig:")) { + int start_priority; + char runlevels[16], *k; + + state = NORMAL; + + if (sscanf(t+10, "%15s %i %*i", + runlevels, + &start_priority) != 2) { + + log_warning("[%s:%u] Failed to parse chkconfig line. Ignoring.", path, line); + continue; + } + + /* A start priority gathered from the + * symlink farms is preferred over the + * data from the LSB header. */ + if (start_priority < 0 || start_priority > 99) + log_warning("[%s:%u] Start priority out of range. Ignoring.", path, line); + else + s->sysv_start_priority = start_priority; + + char_array_0(runlevels); + k = delete_chars(runlevels, WHITESPACE "-"); + + if (k[0]) { + char *d; + + if (!(d = strdup(k))) { + r = -ENOMEM; + goto finish; + } + + free(s->sysv_runlevels); + s->sysv_runlevels = d; + } + + } else if (startswith_no_case(t, "description:")) { + + size_t k = strlen(t); + char *d; + const char *j; + + if (t[k-1] == '\\') { + state = DESCRIPTION; + t[k-1] = 0; + } + + if ((j = strstrip(t+12)) && *j) { + if (!(d = strdup(j))) { + r = -ENOMEM; + goto finish; + } + } else + d = NULL; + + free(chkconfig_description); + chkconfig_description = d; + + } else if (startswith_no_case(t, "pidfile:")) { + + char *fn; + + state = NORMAL; + + fn = strstrip(t+8); + if (!path_is_absolute(fn)) { + log_warning("[%s:%u] PID file not absolute. Ignoring.", path, line); + continue; + } + + if (!(fn = strdup(fn))) { + r = -ENOMEM; + goto finish; + } + + free(s->pid_file); + s->pid_file = fn; + } + + } else if (state == DESCRIPTION) { + + /* Try to parse Red Hat style description + * continuation */ + + size_t k = strlen(t); + char *j; + + if (t[k-1] == '\\') + t[k-1] = 0; + else + state = NORMAL; + + if ((j = strstrip(t)) && *j) { + char *d = NULL; + + if (chkconfig_description) + d = join(chkconfig_description, " ", j, NULL); + else + d = strdup(j); + + if (!d) { + r = -ENOMEM; + goto finish; + } + + free(chkconfig_description); + chkconfig_description = d; + } + + } else if (state == LSB || state == LSB_DESCRIPTION) { + + if (startswith_no_case(t, "Provides:")) { + char *i, *w; + size_t z; + + state = LSB; + + FOREACH_WORD_QUOTED(w, z, t+9, i) { + char *n, *m; + + if (!(n = strndup(w, z))) { + r = -ENOMEM; + goto finish; + } + + r = sysv_translate_facility(n, file_name_from_path(path), &m); + free(n); + + if (r < 0) + goto finish; + + if (r == 0) + continue; + + if (unit_name_to_type(m) == UNIT_SERVICE) + r = unit_add_name(u, m); + else + /* NB: SysV targets + * which are provided + * by a service are + * pulled in by the + * services, as an + * indication that the + * generic service is + * now available. This + * is strictly + * one-way. The + * targets do NOT pull + * in the SysV + * services! */ + r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, UNIT_WANTS, m, NULL, true); + + if (r < 0) + log_error("[%s:%u] Failed to add LSB Provides name %s, ignoring: %s", path, line, m, strerror(-r)); + + free(m); + } + + } else if (startswith_no_case(t, "Required-Start:") || + startswith_no_case(t, "Should-Start:") || + startswith_no_case(t, "X-Start-Before:") || + startswith_no_case(t, "X-Start-After:")) { + char *i, *w; + size_t z; + + state = LSB; + + FOREACH_WORD_QUOTED(w, z, strchr(t, ':')+1, i) { + char *n, *m; + + if (!(n = strndup(w, z))) { + r = -ENOMEM; + goto finish; + } + + r = sysv_translate_facility(n, file_name_from_path(path), &m); + + if (r < 0) { + log_error("[%s:%u] Failed to translate LSB dependency %s, ignoring: %s", path, line, n, strerror(-r)); + free(n); + continue; + } + + free(n); + + if (r == 0) + continue; + + r = unit_add_dependency_by_name(u, startswith_no_case(t, "X-Start-Before:") ? UNIT_BEFORE : UNIT_AFTER, m, NULL, true); + + if (r < 0) + log_error("[%s:%u] Failed to add dependency on %s, ignoring: %s", path, line, m, strerror(-r)); + + free(m); + } + } else if (startswith_no_case(t, "Default-Start:")) { + char *k, *d; + + state = LSB; + + k = delete_chars(t+14, WHITESPACE "-"); + + if (k[0] != 0) { + if (!(d = strdup(k))) { + r = -ENOMEM; + goto finish; + } + + free(s->sysv_runlevels); + s->sysv_runlevels = d; + } + + } else if (startswith_no_case(t, "Description:")) { + char *d, *j; + + state = LSB_DESCRIPTION; + + if ((j = strstrip(t+12)) && *j) { + if (!(d = strdup(j))) { + r = -ENOMEM; + goto finish; + } + } else + d = NULL; + + free(long_description); + long_description = d; + + } else if (startswith_no_case(t, "Short-Description:")) { + char *d, *j; + + state = LSB; + + if ((j = strstrip(t+18)) && *j) { + if (!(d = strdup(j))) { + r = -ENOMEM; + goto finish; + } + } else + d = NULL; + + free(short_description); + short_description = d; + + } else if (state == LSB_DESCRIPTION) { + + if (startswith(l, "#\t") || startswith(l, "# ")) { + char *j; + + if ((j = strstrip(t)) && *j) { + char *d = NULL; + + if (long_description) + d = join(long_description, " ", t, NULL); + else + d = strdup(j); + + if (!d) { + r = -ENOMEM; + goto finish; + } + + free(long_description); + long_description = d; + } + + } else + state = LSB; + } + } + } + + if ((r = sysv_exec_commands(s)) < 0) + goto finish; + if (s->sysv_runlevels && + chars_intersect(RUNLEVELS_BOOT, s->sysv_runlevels) && + chars_intersect(RUNLEVELS_UP, s->sysv_runlevels)) { + /* Service has both boot and "up" runlevels + configured. Kill the "up" ones. */ + delete_chars(s->sysv_runlevels, RUNLEVELS_UP); + } + + if (s->sysv_runlevels && !chars_intersect(RUNLEVELS_UP, s->sysv_runlevels)) { + /* If there a runlevels configured for this service + * but none of the standard ones, then we assume this + * is some special kind of service (which might be + * needed for early boot) and don't create any links + * to it. */ + + UNIT(s)->default_dependencies = false; + + /* Don't timeout special services during boot (like fsck) */ + s->timeout_usec = 0; + } else + s->timeout_usec = DEFAULT_SYSV_TIMEOUT_USEC; + + /* Special setting for all SysV services */ + s->type = SERVICE_FORKING; + s->remain_after_exit = !s->pid_file; + s->guess_main_pid = false; + s->restart = SERVICE_RESTART_NO; + s->exec_context.ignore_sigpipe = false; + + if (UNIT(s)->manager->sysv_console) + s->exec_context.std_output = EXEC_OUTPUT_JOURNAL_AND_CONSOLE; + + s->exec_context.kill_mode = KILL_PROCESS; + + /* We use the long description only if + * no short description is set. */ + + if (short_description) + description = short_description; + else if (chkconfig_description) + description = chkconfig_description; + else if (long_description) + description = long_description; + else + description = NULL; + + if (description) { + char *d; + + if (!(d = strappend(s->sysv_has_lsb ? "LSB: " : "SYSV: ", description))) { + r = -ENOMEM; + goto finish; + } + + u->description = d; + } + + /* The priority that has been set in /etc/rcN.d/ hierarchies + * takes precedence over what is stored as default in the LSB + * header */ + if (s->sysv_start_priority_from_rcnd >= 0) + s->sysv_start_priority = s->sysv_start_priority_from_rcnd; + + u->load_state = UNIT_LOADED; + r = 0; + +finish: + + if (f) + fclose(f); + + free(short_description); + free(long_description); + free(chkconfig_description); + + return r; +} + +static int service_load_sysv_name(Service *s, const char *name) { + char **p; + + assert(s); + assert(name); + + /* For SysV services we strip the boot.*, rc.* and *.sh + * prefixes/suffixes. */ +#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) + if (endswith(name, ".sh.service")) + return -ENOENT; +#endif + +#ifdef TARGET_SUSE + if (startswith(name, "boot.")) + return -ENOENT; +#endif + +#ifdef TARGET_FRUGALWARE + if (startswith(name, "rc.")) + return -ENOENT; +#endif + + STRV_FOREACH(p, UNIT(s)->manager->lookup_paths.sysvinit_path) { + char *path; + int r; + + path = join(*p, "/", name, NULL); + if (!path) + return -ENOMEM; + + assert(endswith(path, ".service")); + path[strlen(path)-8] = 0; + + r = service_load_sysv_path(s, path); + +#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) + if (r >= 0 && UNIT(s)->load_state == UNIT_STUB) { + /* Try Debian style *.sh source'able init scripts */ + strcat(path, ".sh"); + r = service_load_sysv_path(s, path); + } +#endif + free(path); + +#ifdef TARGET_SUSE + if (r >= 0 && UNIT(s)->load_state == UNIT_STUB) { + /* Try SUSE style boot.* init scripts */ + + path = join(*p, "/boot.", name, NULL); + if (!path) + return -ENOMEM; + + /* Drop .service suffix */ + path[strlen(path)-8] = 0; + r = service_load_sysv_path(s, path); + free(path); + } +#endif + +#ifdef TARGET_FRUGALWARE + if (r >= 0 && UNIT(s)->load_state == UNIT_STUB) { + /* Try Frugalware style rc.* init scripts */ + + path = join(*p, "/rc.", name, NULL); + if (!path) + return -ENOMEM; + + /* Drop .service suffix */ + path[strlen(path)-8] = 0; + r = service_load_sysv_path(s, path); + free(path); + } +#endif + + if (r < 0) + return r; + + if ((UNIT(s)->load_state != UNIT_STUB)) + break; + } + + return 0; +} + +static int service_load_sysv(Service *s) { + const char *t; + Iterator i; + int r; + + assert(s); + + /* Load service data from SysV init scripts, preferably with + * LSB headers ... */ + + if (strv_isempty(UNIT(s)->manager->lookup_paths.sysvinit_path)) + return 0; + + if ((t = UNIT(s)->id)) + if ((r = service_load_sysv_name(s, t)) < 0) + return r; + + if (UNIT(s)->load_state == UNIT_STUB) + SET_FOREACH(t, UNIT(s)->names, i) { + if (t == UNIT(s)->id) + continue; + + if ((r = service_load_sysv_name(s, t)) < 0) + return r; + + if (UNIT(s)->load_state != UNIT_STUB) + break; + } + + return 0; +} +#endif + +static int fsck_fix_order(Service *s) { + Unit *other; + int r; + + assert(s); + + if (s->fsck_passno <= 0) + return 0; + + /* For each pair of services where both have an fsck priority + * we order things based on it. */ + + LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_SERVICE]) { + Service *t; + UnitDependency d; + + t = SERVICE(other); + + if (s == t) + continue; + + if (UNIT(t)->load_state != UNIT_LOADED) + continue; + + if (t->fsck_passno <= 0) + continue; + + if (t->fsck_passno < s->fsck_passno) + d = UNIT_AFTER; + else if (t->fsck_passno > s->fsck_passno) + d = UNIT_BEFORE; + else + continue; + + if ((r = unit_add_dependency(UNIT(s), d, UNIT(t), true)) < 0) + return r; + } + + return 0; +} + +static int service_verify(Service *s) { + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (!s->exec_command[SERVICE_EXEC_START]) { + log_error("%s lacks ExecStart setting. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->type != SERVICE_ONESHOT && + s->exec_command[SERVICE_EXEC_START]->command_next) { + log_error("%s has more than one ExecStart setting, which is only allowed for Type=oneshot services. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->type == SERVICE_ONESHOT && + s->exec_command[SERVICE_EXEC_RELOAD]) { + log_error("%s has an ExecReload setting, which is not allowed for Type=oneshot services. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->type == SERVICE_DBUS && !s->bus_name) { + log_error("%s is of type D-Bus but no D-Bus service name has been specified. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->exec_context.pam_name && s->exec_context.kill_mode != KILL_CONTROL_GROUP) { + log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + return 0; +} + +static int service_add_default_dependencies(Service *s) { + int r; + + assert(s); + + /* Add a number of automatic dependencies useful for the + * majority of services. */ + + /* First, pull in base system */ + if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { + + if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL, true)) < 0) + return r; + + } else if (UNIT(s)->manager->running_as == MANAGER_USER) { + + if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SOCKETS_TARGET, NULL, true)) < 0) + return r; + } + + /* Second, activate normal shutdown */ + return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static void service_fix_output(Service *s) { + assert(s); + + /* If nothing has been explicitly configured, patch default + * output in. If input is socket/tty we avoid this however, + * since in that case we want output to default to the same + * place as we read input from. */ + + if (s->exec_context.std_error == EXEC_OUTPUT_INHERIT && + s->exec_context.std_output == EXEC_OUTPUT_INHERIT && + s->exec_context.std_input == EXEC_INPUT_NULL) + s->exec_context.std_error = UNIT(s)->manager->default_std_error; + + if (s->exec_context.std_output == EXEC_OUTPUT_INHERIT && + s->exec_context.std_input == EXEC_INPUT_NULL) + s->exec_context.std_output = UNIT(s)->manager->default_std_output; +} + +static int service_load(Unit *u) { + int r; + Service *s = SERVICE(u); + + assert(s); + + /* Load a .service file */ + if ((r = unit_load_fragment(u)) < 0) + return r; + +#ifdef HAVE_SYSV_COMPAT + /* Load a classic init script as a fallback, if we couldn't find anything */ + if (u->load_state == UNIT_STUB) + if ((r = service_load_sysv(s)) < 0) + return r; +#endif + + /* Still nothing found? Then let's give up */ + if (u->load_state == UNIT_STUB) + return -ENOENT; + + /* We were able to load something, then let's add in the + * dropin directories. */ + if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + service_fix_output(s); + + if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0) + return r; + + if ((r = unit_add_default_cgroups(u)) < 0) + return r; + +#ifdef HAVE_SYSV_COMPAT + if ((r = sysv_fix_order(s)) < 0) + return r; +#endif + + if ((r = fsck_fix_order(s)) < 0) + return r; + + if (s->bus_name) + if ((r = unit_watch_bus_name(u, s->bus_name)) < 0) + return r; + + if (s->type == SERVICE_NOTIFY && s->notify_access == NOTIFY_NONE) + s->notify_access = NOTIFY_MAIN; + + if (s->watchdog_usec > 0 && s->notify_access == NOTIFY_NONE) + s->notify_access = NOTIFY_MAIN; + + if (s->type == SERVICE_DBUS || s->bus_name) + if ((r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_DBUS_SOCKET, NULL, true)) < 0) + return r; + + if (UNIT(s)->default_dependencies) + if ((r = service_add_default_dependencies(s)) < 0) + return r; + } + + return service_verify(s); +} + +static void service_dump(Unit *u, FILE *f, const char *prefix) { + + ServiceExecCommand c; + Service *s = SERVICE(u); + const char *prefix2; + char *p2; + + assert(s); + + p2 = strappend(prefix, "\t"); + prefix2 = p2 ? p2 : prefix; + + fprintf(f, + "%sService State: %s\n" + "%sResult: %s\n" + "%sReload Result: %s\n" + "%sPermissionsStartOnly: %s\n" + "%sRootDirectoryStartOnly: %s\n" + "%sRemainAfterExit: %s\n" + "%sGuessMainPID: %s\n" + "%sType: %s\n" + "%sRestart: %s\n" + "%sNotifyAccess: %s\n", + prefix, service_state_to_string(s->state), + prefix, service_result_to_string(s->result), + prefix, service_result_to_string(s->reload_result), + prefix, yes_no(s->permissions_start_only), + prefix, yes_no(s->root_directory_start_only), + prefix, yes_no(s->remain_after_exit), + prefix, yes_no(s->guess_main_pid), + prefix, service_type_to_string(s->type), + prefix, service_restart_to_string(s->restart), + prefix, notify_access_to_string(s->notify_access)); + + if (s->control_pid > 0) + fprintf(f, + "%sControl PID: %lu\n", + prefix, (unsigned long) s->control_pid); + + if (s->main_pid > 0) + fprintf(f, + "%sMain PID: %lu\n" + "%sMain PID Known: %s\n" + "%sMain PID Alien: %s\n", + prefix, (unsigned long) s->main_pid, + prefix, yes_no(s->main_pid_known), + prefix, yes_no(s->main_pid_alien)); + + if (s->pid_file) + fprintf(f, + "%sPIDFile: %s\n", + prefix, s->pid_file); + + if (s->bus_name) + fprintf(f, + "%sBusName: %s\n" + "%sBus Name Good: %s\n", + prefix, s->bus_name, + prefix, yes_no(s->bus_name_good)); + + exec_context_dump(&s->exec_context, f, prefix); + + for (c = 0; c < _SERVICE_EXEC_COMMAND_MAX; c++) { + + if (!s->exec_command[c]) + continue; + + fprintf(f, "%s-> %s:\n", + prefix, service_exec_command_to_string(c)); + + exec_command_dump_list(s->exec_command[c], f, prefix2); + } + +#ifdef HAVE_SYSV_COMPAT + if (s->sysv_path) + fprintf(f, + "%sSysV Init Script Path: %s\n" + "%sSysV Init Script has LSB Header: %s\n" + "%sSysVEnabled: %s\n", + prefix, s->sysv_path, + prefix, yes_no(s->sysv_has_lsb), + prefix, yes_no(s->sysv_enabled)); + + if (s->sysv_start_priority >= 0) + fprintf(f, + "%sSysVStartPriority: %i\n", + prefix, s->sysv_start_priority); + + if (s->sysv_runlevels) + fprintf(f, "%sSysVRunLevels: %s\n", + prefix, s->sysv_runlevels); +#endif + + if (s->fsck_passno > 0) + fprintf(f, + "%sFsckPassNo: %i\n", + prefix, s->fsck_passno); + + if (s->status_text) + fprintf(f, "%sStatus Text: %s\n", + prefix, s->status_text); + + free(p2); +} + +static int service_load_pid_file(Service *s, bool may_warn) { + char *k; + int r; + pid_t pid; + + assert(s); + + if (!s->pid_file) + return -ENOENT; + + if ((r = read_one_line_file(s->pid_file, &k)) < 0) { + if (may_warn) + log_info("PID file %s not readable (yet?) after %s.", + s->pid_file, service_state_to_string(s->state)); + return r; + } + + r = parse_pid(k, &pid); + free(k); + + if (r < 0) + return r; + + if (kill(pid, 0) < 0 && errno != EPERM) { + if (may_warn) + log_info("PID %lu read from file %s does not exist.", + (unsigned long) pid, s->pid_file); + return -ESRCH; + } + + if (s->main_pid_known) { + if (pid == s->main_pid) + return 0; + + log_debug("Main PID changing: %lu -> %lu", + (unsigned long) s->main_pid, (unsigned long) pid); + service_unwatch_main_pid(s); + s->main_pid_known = false; + } else + log_debug("Main PID loaded: %lu", (unsigned long) pid); + + if ((r = service_set_main_pid(s, pid)) < 0) + return r; + + if ((r = unit_watch_pid(UNIT(s), pid)) < 0) + /* FIXME: we need to do something here */ + return r; + + return 0; +} + +static int service_search_main_pid(Service *s) { + pid_t pid; + int r; + + assert(s); + + /* If we know it anyway, don't ever fallback to unreliable + * heuristics */ + if (s->main_pid_known) + return 0; + + if (!s->guess_main_pid) + return 0; + + assert(s->main_pid <= 0); + + if ((pid = cgroup_bonding_search_main_pid_list(UNIT(s)->cgroup_bondings)) <= 0) + return -ENOENT; + + log_debug("Main PID guessed: %lu", (unsigned long) pid); + if ((r = service_set_main_pid(s, pid)) < 0) + return r; + + if ((r = unit_watch_pid(UNIT(s), pid)) < 0) + /* FIXME: we need to do something here */ + return r; + + return 0; +} + +static void service_notify_sockets_dead(Service *s, bool failed_permanent) { + Iterator i; + Unit *u; + + assert(s); + + /* Notifies all our sockets when we die */ + + if (s->socket_fd >= 0) + return; + + SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) + if (u->type == UNIT_SOCKET) + socket_notify_service_dead(SOCKET(u), failed_permanent); + + return; +} + +static void service_set_state(Service *s, ServiceState state) { + ServiceState old_state; + assert(s); + + old_state = s->state; + s->state = state; + + service_unwatch_pid_file(s); + + if (state != SERVICE_START_PRE && + state != SERVICE_START && + state != SERVICE_START_POST && + state != SERVICE_RELOAD && + state != SERVICE_STOP && + state != SERVICE_STOP_SIGTERM && + state != SERVICE_STOP_SIGKILL && + state != SERVICE_STOP_POST && + state != SERVICE_FINAL_SIGTERM && + state != SERVICE_FINAL_SIGKILL && + state != SERVICE_AUTO_RESTART) + unit_unwatch_timer(UNIT(s), &s->timer_watch); + + if (state != SERVICE_START && + state != SERVICE_START_POST && + state != SERVICE_RUNNING && + state != SERVICE_RELOAD && + state != SERVICE_STOP && + state != SERVICE_STOP_SIGTERM && + state != SERVICE_STOP_SIGKILL) { + service_unwatch_main_pid(s); + s->main_command = NULL; + } + + if (state != SERVICE_START_PRE && + state != SERVICE_START && + state != SERVICE_START_POST && + state != SERVICE_RELOAD && + state != SERVICE_STOP && + state != SERVICE_STOP_SIGTERM && + state != SERVICE_STOP_SIGKILL && + state != SERVICE_STOP_POST && + state != SERVICE_FINAL_SIGTERM && + state != SERVICE_FINAL_SIGKILL) { + service_unwatch_control_pid(s); + s->control_command = NULL; + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; + } + + if (state == SERVICE_DEAD || + state == SERVICE_STOP || + state == SERVICE_STOP_SIGTERM || + state == SERVICE_STOP_SIGKILL || + state == SERVICE_STOP_POST || + state == SERVICE_FINAL_SIGTERM || + state == SERVICE_FINAL_SIGKILL || + state == SERVICE_FAILED || + state == SERVICE_AUTO_RESTART) + service_notify_sockets_dead(s, false); + + if (state != SERVICE_START_PRE && + state != SERVICE_START && + state != SERVICE_START_POST && + state != SERVICE_RUNNING && + state != SERVICE_RELOAD && + state != SERVICE_STOP && + state != SERVICE_STOP_SIGTERM && + state != SERVICE_STOP_SIGKILL && + state != SERVICE_STOP_POST && + state != SERVICE_FINAL_SIGTERM && + state != SERVICE_FINAL_SIGKILL && + !(state == SERVICE_DEAD && UNIT(s)->job)) { + service_close_socket_fd(s); + service_connection_unref(s); + } + + if (state == SERVICE_STOP) + service_stop_watchdog(s); + + /* For the inactive states unit_notify() will trim the cgroup, + * but for exit we have to do that ourselves... */ + if (state == SERVICE_EXITED && UNIT(s)->manager->n_reloading <= 0) + cgroup_bonding_trim_list(UNIT(s)->cgroup_bondings, true); + + if (old_state != state) + log_debug("%s changed %s -> %s", UNIT(s)->id, service_state_to_string(old_state), service_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], s->reload_result == SERVICE_SUCCESS); + s->reload_result = SERVICE_SUCCESS; +} + +static int service_coldplug(Unit *u) { + Service *s = SERVICE(u); + int r; + + assert(s); + assert(s->state == SERVICE_DEAD); + + if (s->deserialized_state != s->state) { + + if (s->deserialized_state == SERVICE_START_PRE || + s->deserialized_state == SERVICE_START || + s->deserialized_state == SERVICE_START_POST || + s->deserialized_state == SERVICE_RELOAD || + s->deserialized_state == SERVICE_STOP || + s->deserialized_state == SERVICE_STOP_SIGTERM || + s->deserialized_state == SERVICE_STOP_SIGKILL || + s->deserialized_state == SERVICE_STOP_POST || + s->deserialized_state == SERVICE_FINAL_SIGTERM || + s->deserialized_state == SERVICE_FINAL_SIGKILL || + s->deserialized_state == SERVICE_AUTO_RESTART) { + + if (s->deserialized_state == SERVICE_AUTO_RESTART || s->timeout_usec > 0) { + usec_t k; + + k = s->deserialized_state == SERVICE_AUTO_RESTART ? s->restart_usec : s->timeout_usec; + + if ((r = unit_watch_timer(UNIT(s), k, &s->timer_watch)) < 0) + return r; + } + } + + if ((s->deserialized_state == SERVICE_START && + (s->type == SERVICE_FORKING || + s->type == SERVICE_DBUS || + s->type == SERVICE_ONESHOT || + s->type == SERVICE_NOTIFY)) || + s->deserialized_state == SERVICE_START_POST || + s->deserialized_state == SERVICE_RUNNING || + s->deserialized_state == SERVICE_RELOAD || + s->deserialized_state == SERVICE_STOP || + s->deserialized_state == SERVICE_STOP_SIGTERM || + s->deserialized_state == SERVICE_STOP_SIGKILL) + if (s->main_pid > 0) + if ((r = unit_watch_pid(UNIT(s), s->main_pid)) < 0) + return r; + + if (s->deserialized_state == SERVICE_START_PRE || + s->deserialized_state == SERVICE_START || + s->deserialized_state == SERVICE_START_POST || + s->deserialized_state == SERVICE_RELOAD || + s->deserialized_state == SERVICE_STOP || + s->deserialized_state == SERVICE_STOP_SIGTERM || + s->deserialized_state == SERVICE_STOP_SIGKILL || + s->deserialized_state == SERVICE_STOP_POST || + s->deserialized_state == SERVICE_FINAL_SIGTERM || + s->deserialized_state == SERVICE_FINAL_SIGKILL) + if (s->control_pid > 0) + if ((r = unit_watch_pid(UNIT(s), s->control_pid)) < 0) + return r; + + if (s->deserialized_state == SERVICE_START_POST || + s->deserialized_state == SERVICE_RUNNING) + service_handle_watchdog(s); + + service_set_state(s, s->deserialized_state); + } + return 0; +} + +static int service_collect_fds(Service *s, int **fds, unsigned *n_fds) { + Iterator i; + int r; + int *rfds = NULL; + unsigned rn_fds = 0; + Unit *u; + + assert(s); + assert(fds); + assert(n_fds); + + if (s->socket_fd >= 0) + return 0; + + SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) { + int *cfds; + unsigned cn_fds; + Socket *sock; + + if (u->type != UNIT_SOCKET) + continue; + + sock = SOCKET(u); + + if ((r = socket_collect_fds(sock, &cfds, &cn_fds)) < 0) + goto fail; + + if (!cfds) + continue; + + if (!rfds) { + rfds = cfds; + rn_fds = cn_fds; + } else { + int *t; + + if (!(t = new(int, rn_fds+cn_fds))) { + free(cfds); + r = -ENOMEM; + goto fail; + } + + memcpy(t, rfds, rn_fds * sizeof(int)); + memcpy(t+rn_fds, cfds, cn_fds * sizeof(int)); + free(rfds); + free(cfds); + + rfds = t; + rn_fds = rn_fds+cn_fds; + } + } + + *fds = rfds; + *n_fds = rn_fds; + + return 0; + +fail: + free(rfds); + + return r; +} + +static int service_spawn( + Service *s, + ExecCommand *c, + bool timeout, + bool pass_fds, + bool apply_permissions, + bool apply_chroot, + bool apply_tty_stdin, + bool set_notify_socket, + pid_t *_pid) { + + pid_t pid; + int r; + int *fds = NULL, *fdsbuf = NULL; + unsigned n_fds = 0, n_env = 0; + char **argv = NULL, **final_env = NULL, **our_env = NULL; + + assert(s); + assert(c); + assert(_pid); + + if (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) { + + if (s->socket_fd >= 0) { + fds = &s->socket_fd; + n_fds = 1; + } else { + if ((r = service_collect_fds(s, &fdsbuf, &n_fds)) < 0) + goto fail; + + fds = fdsbuf; + } + } + + if (timeout && s->timeout_usec) { + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; + } else + unit_unwatch_timer(UNIT(s), &s->timer_watch); + + if (!(argv = unit_full_printf_strv(UNIT(s), c->argv))) { + r = -ENOMEM; + goto fail; + } + + if (!(our_env = new0(char*, 4))) { + r = -ENOMEM; + goto fail; + } + + if (set_notify_socket) + if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) { + r = -ENOMEM; + goto fail; + } + + if (s->main_pid > 0) + if (asprintf(our_env + n_env++, "MAINPID=%lu", (unsigned long) s->main_pid) < 0) { + r = -ENOMEM; + goto fail; + } + + if (s->watchdog_usec > 0) + if (asprintf(our_env + n_env++, "WATCHDOG_USEC=%llu", (unsigned long long) s->watchdog_usec) < 0) { + r = -ENOMEM; + goto fail; + } + + if (!(final_env = strv_env_merge(2, + UNIT(s)->manager->environment, + our_env, + NULL))) { + r = -ENOMEM; + goto fail; + } + + r = exec_spawn(c, + argv, + &s->exec_context, + fds, n_fds, + final_env, + apply_permissions, + apply_chroot, + apply_tty_stdin, + UNIT(s)->manager->confirm_spawn, + UNIT(s)->cgroup_bondings, + UNIT(s)->cgroup_attributes, + &pid); + + if (r < 0) + goto fail; + + + if ((r = unit_watch_pid(UNIT(s), pid)) < 0) + /* FIXME: we need to do something here */ + goto fail; + + free(fdsbuf); + strv_free(argv); + strv_free(our_env); + strv_free(final_env); + + *_pid = pid; + + return 0; + +fail: + free(fdsbuf); + strv_free(argv); + strv_free(our_env); + strv_free(final_env); + + if (timeout) + unit_unwatch_timer(UNIT(s), &s->timer_watch); + + return r; +} + +static int main_pid_good(Service *s) { + assert(s); + + /* Returns 0 if the pid is dead, 1 if it is good, -1 if we + * don't know */ + + /* If we know the pid file, then lets just check if it is + * still valid */ + if (s->main_pid_known) { + + /* If it's an alien child let's check if it is still + * alive ... */ + if (s->main_pid_alien) + return kill(s->main_pid, 0) >= 0 || errno != ESRCH; + + /* .. otherwise assume we'll get a SIGCHLD for it, + * which we really should wait for to collect exit + * status and code */ + return s->main_pid > 0; + } + + /* We don't know the pid */ + return -EAGAIN; +} + +static int control_pid_good(Service *s) { + assert(s); + + return s->control_pid > 0; +} + +static int cgroup_good(Service *s) { + int r; + + assert(s); + + if ((r = cgroup_bonding_is_empty_list(UNIT(s)->cgroup_bondings)) < 0) + return r; + + return !r; +} + +static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { + int r; + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + if (allow_restart && + !s->forbid_restart && + (s->restart == SERVICE_RESTART_ALWAYS || + (s->restart == SERVICE_RESTART_ON_SUCCESS && s->result == SERVICE_SUCCESS) || + (s->restart == SERVICE_RESTART_ON_FAILURE && s->result != SERVICE_SUCCESS) || + (s->restart == SERVICE_RESTART_ON_ABORT && (s->result == SERVICE_FAILURE_SIGNAL || + s->result == SERVICE_FAILURE_CORE_DUMP)))) { + + r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_AUTO_RESTART); + } else + service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD); + + s->forbid_restart = false; + + return; + +fail: + log_warning("%s failed to run install restart timer: %s", UNIT(s)->id, strerror(-r)); + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); +} + +static void service_enter_signal(Service *s, ServiceState state, ServiceResult f); + +static void service_enter_stop_post(Service *s, ServiceResult f) { + int r; + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + service_unwatch_control_pid(s); + + if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST])) { + s->control_command_id = SERVICE_EXEC_STOP_POST; + + if ((r = service_spawn(s, + s->control_command, + true, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + true, + false, + &s->control_pid)) < 0) + goto fail; + + + service_set_state(s, SERVICE_STOP_POST); + } else + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_SUCCESS); + + return; + +fail: + log_warning("%s failed to run 'stop-post' task: %s", UNIT(s)->id, strerror(-r)); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +static void service_enter_signal(Service *s, ServiceState state, ServiceResult f) { + int r; + Set *pid_set = NULL; + bool wait_for_exit = false; + + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + if (s->exec_context.kill_mode != KILL_NONE) { + int sig = (state == SERVICE_STOP_SIGTERM || state == SERVICE_FINAL_SIGTERM) ? s->exec_context.kill_signal : SIGKILL; + + if (s->main_pid > 0) { + if (kill_and_sigcont(s->main_pid, sig) < 0 && errno != ESRCH) + log_warning("Failed to kill main process %li: %m", (long) s->main_pid); + else + wait_for_exit = !s->main_pid_alien; + } + + if (s->control_pid > 0) { + if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH) + log_warning("Failed to kill control process %li: %m", (long) s->control_pid); + else + wait_for_exit = true; + } + + if (s->exec_context.kill_mode == KILL_CONTROL_GROUP) { + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) { + r = -ENOMEM; + goto fail; + } + + /* Exclude the main/control pids from being killed via the cgroup */ + if (s->main_pid > 0) + if ((r = set_put(pid_set, LONG_TO_PTR(s->main_pid))) < 0) + goto fail; + + if (s->control_pid > 0) + if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) + goto fail; + + if ((r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, pid_set)) < 0) { + if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) + log_warning("Failed to kill control group: %s", strerror(-r)); + } else if (r > 0) + wait_for_exit = true; + + set_free(pid_set); + pid_set = NULL; + } + } + + if (wait_for_exit) { + if (s->timeout_usec > 0) + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; + + service_set_state(s, state); + } else if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL) + service_enter_stop_post(s, SERVICE_SUCCESS); + else + service_enter_dead(s, SERVICE_SUCCESS, true); + + return; + +fail: + log_warning("%s failed to kill processes: %s", UNIT(s)->id, strerror(-r)); + + if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL) + service_enter_stop_post(s, SERVICE_FAILURE_RESOURCES); + else + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); + + if (pid_set) + set_free(pid_set); +} + +static void service_enter_stop(Service *s, ServiceResult f) { + int r; + + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + service_unwatch_control_pid(s); + + if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP])) { + s->control_command_id = SERVICE_EXEC_STOP; + + if ((r = service_spawn(s, + s->control_command, + true, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + false, + false, + &s->control_pid)) < 0) + goto fail; + + service_set_state(s, SERVICE_STOP); + } else + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); + + return; + +fail: + log_warning("%s failed to run 'stop' task: %s", UNIT(s)->id, strerror(-r)); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +static void service_enter_running(Service *s, ServiceResult f) { + int main_pid_ok, cgroup_ok; + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + main_pid_ok = main_pid_good(s); + cgroup_ok = cgroup_good(s); + + if ((main_pid_ok > 0 || (main_pid_ok < 0 && cgroup_ok != 0)) && + (s->bus_name_good || s->type != SERVICE_DBUS)) + service_set_state(s, SERVICE_RUNNING); + else if (s->remain_after_exit) + service_set_state(s, SERVICE_EXITED); + else + service_enter_stop(s, SERVICE_SUCCESS); +} + +static void service_enter_start_post(Service *s) { + int r; + assert(s); + + service_unwatch_control_pid(s); + + if (s->watchdog_usec > 0) + service_reset_watchdog(s); + + if ((s->control_command = s->exec_command[SERVICE_EXEC_START_POST])) { + s->control_command_id = SERVICE_EXEC_START_POST; + + if ((r = service_spawn(s, + s->control_command, + true, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + false, + false, + &s->control_pid)) < 0) + goto fail; + + service_set_state(s, SERVICE_START_POST); + } else + service_enter_running(s, SERVICE_SUCCESS); + + return; + +fail: + log_warning("%s failed to run 'start-post' task: %s", UNIT(s)->id, strerror(-r)); + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); +} + +static void service_enter_start(Service *s) { + pid_t pid; + int r; + ExecCommand *c; + + assert(s); + + assert(s->exec_command[SERVICE_EXEC_START]); + assert(!s->exec_command[SERVICE_EXEC_START]->command_next || s->type == SERVICE_ONESHOT); + + if (s->type == SERVICE_FORKING) + service_unwatch_control_pid(s); + else + service_unwatch_main_pid(s); + + /* We want to ensure that nobody leaks processes from + * START_PRE here, so let's go on a killing spree, People + * should not spawn long running processes from START_PRE. */ + cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, NULL); + + if (s->type == SERVICE_FORKING) { + s->control_command_id = SERVICE_EXEC_START; + c = s->control_command = s->exec_command[SERVICE_EXEC_START]; + + s->main_command = NULL; + } else { + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; + s->control_command = NULL; + + c = s->main_command = s->exec_command[SERVICE_EXEC_START]; + } + + if ((r = service_spawn(s, + c, + s->type == SERVICE_FORKING || s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY, + true, + true, + true, + true, + s->notify_access != NOTIFY_NONE, + &pid)) < 0) + goto fail; + + if (s->type == SERVICE_SIMPLE) { + /* For simple services we immediately start + * the START_POST binaries. */ + + service_set_main_pid(s, pid); + service_enter_start_post(s); + + } else if (s->type == SERVICE_FORKING) { + + /* For forking services we wait until the start + * process exited. */ + + s->control_pid = pid; + service_set_state(s, SERVICE_START); + + } else if (s->type == SERVICE_ONESHOT || + s->type == SERVICE_DBUS || + s->type == SERVICE_NOTIFY) { + + /* For oneshot services we wait until the start + * process exited, too, but it is our main process. */ + + /* For D-Bus services we know the main pid right away, + * but wait for the bus name to appear on the + * bus. Notify services are similar. */ + + service_set_main_pid(s, pid); + service_set_state(s, SERVICE_START); + } else + assert_not_reached("Unknown service type"); + + return; + +fail: + log_warning("%s failed to run 'start' task: %s", UNIT(s)->id, strerror(-r)); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +static void service_enter_start_pre(Service *s) { + int r; + + assert(s); + + service_unwatch_control_pid(s); + + if ((s->control_command = s->exec_command[SERVICE_EXEC_START_PRE])) { + + /* Before we start anything, let's clear up what might + * be left from previous runs. */ + cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, NULL); + + s->control_command_id = SERVICE_EXEC_START_PRE; + + if ((r = service_spawn(s, + s->control_command, + true, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + true, + false, + &s->control_pid)) < 0) + goto fail; + + service_set_state(s, SERVICE_START_PRE); + } else + service_enter_start(s); + + return; + +fail: + log_warning("%s failed to run 'start-pre' task: %s", UNIT(s)->id, strerror(-r)); + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); +} + +static void service_enter_restart(Service *s) { + int r; + DBusError error; + + assert(s); + dbus_error_init(&error); + + if (UNIT(s)->job) { + log_info("Job pending for unit, delaying automatic restart."); + + if ((r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch)) < 0) + goto fail; + } + + /* Any units that are bound to this service must also be + * restarted. We use JOB_RESTART (instead of the more obvious + * JOB_START) here so that those dependency jobs will be added + * as well. */ + r = manager_add_job(UNIT(s)->manager, JOB_RESTART, UNIT(s), JOB_FAIL, false, &error, NULL); + if (r < 0) + goto fail; + + log_debug("%s scheduled restart job.", UNIT(s)->id); + return; + +fail: + log_warning("%s failed to schedule restart job: %s", UNIT(s)->id, bus_error(&error, -r)); + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); + + dbus_error_free(&error); +} + +static void service_enter_reload(Service *s) { + int r; + + assert(s); + + service_unwatch_control_pid(s); + + if ((s->control_command = s->exec_command[SERVICE_EXEC_RELOAD])) { + s->control_command_id = SERVICE_EXEC_RELOAD; + + if ((r = service_spawn(s, + s->control_command, + true, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + false, + false, + &s->control_pid)) < 0) + goto fail; + + service_set_state(s, SERVICE_RELOAD); + } else + service_enter_running(s, SERVICE_SUCCESS); + + return; + +fail: + log_warning("%s failed to run 'reload' task: %s", UNIT(s)->id, strerror(-r)); + s->reload_result = SERVICE_FAILURE_RESOURCES; + service_enter_running(s, SERVICE_SUCCESS); +} + +static void service_run_next_control(Service *s) { + int r; + + assert(s); + assert(s->control_command); + assert(s->control_command->command_next); + + assert(s->control_command_id != SERVICE_EXEC_START); + + s->control_command = s->control_command->command_next; + service_unwatch_control_pid(s); + + if ((r = service_spawn(s, + s->control_command, + true, + 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, + false, + &s->control_pid)) < 0) + goto fail; + + return; + +fail: + log_warning("%s failed to run next control task: %s", UNIT(s)->id, strerror(-r)); + + if (s->state == SERVICE_START_PRE) + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); + else if (s->state == SERVICE_STOP) + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); + else if (s->state == SERVICE_STOP_POST) + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); + else if (s->state == SERVICE_RELOAD) { + s->reload_result = SERVICE_FAILURE_RESOURCES; + service_enter_running(s, SERVICE_SUCCESS); + } else + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); +} + +static void service_run_next_main(Service *s) { + pid_t pid; + int r; + + assert(s); + assert(s->main_command); + assert(s->main_command->command_next); + assert(s->type == SERVICE_ONESHOT); + + s->main_command = s->main_command->command_next; + service_unwatch_main_pid(s); + + if ((r = service_spawn(s, + s->main_command, + false, + true, + true, + true, + true, + s->notify_access != NOTIFY_NONE, + &pid)) < 0) + goto fail; + + service_set_main_pid(s, pid); + + return; + +fail: + log_warning("%s failed to run next main task: %s", UNIT(s)->id, strerror(-r)); + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); +} + +static int service_start_limit_test(Service *s) { + assert(s); + + if (ratelimit_test(&s->start_limit)) + return 0; + + switch (s->start_limit_action) { + + case SERVICE_START_LIMIT_NONE: + log_warning("%s start request repeated too quickly, refusing to start.", UNIT(s)->id); + break; + + case SERVICE_START_LIMIT_REBOOT: { + DBusError error; + int r; + + dbus_error_init(&error); + + log_warning("%s start request repeated too quickly, rebooting.", UNIT(s)->id); + + r = manager_add_job_by_name(UNIT(s)->manager, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE, true, &error, NULL); + if (r < 0) { + log_error("Failed to reboot: %s.", bus_error(&error, r)); + dbus_error_free(&error); + } + + break; + } + + case SERVICE_START_LIMIT_REBOOT_FORCE: + log_warning("%s start request repeated too quickly, forcibly rebooting.", UNIT(s)->id); + UNIT(s)->manager->exit_code = MANAGER_REBOOT; + break; + + case SERVICE_START_LIMIT_REBOOT_IMMEDIATE: + log_warning("%s start request repeated too quickly, rebooting immediately.", UNIT(s)->id); + reboot(RB_AUTOBOOT); + break; + + default: + log_error("start limit action=%i", s->start_limit_action); + assert_not_reached("Unknown StartLimitAction."); + } + + return -ECANCELED; +} + +static int service_start(Unit *u) { + Service *s = SERVICE(u); + int r; + + assert(s); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (s->state == SERVICE_STOP || + s->state == SERVICE_STOP_SIGTERM || + s->state == SERVICE_STOP_SIGKILL || + s->state == SERVICE_STOP_POST || + s->state == SERVICE_FINAL_SIGTERM || + s->state == SERVICE_FINAL_SIGKILL) + return -EAGAIN; + + /* Already on it! */ + if (s->state == SERVICE_START_PRE || + s->state == SERVICE_START || + s->state == SERVICE_START_POST) + return 0; + + assert(s->state == SERVICE_DEAD || s->state == SERVICE_FAILED || s->state == SERVICE_AUTO_RESTART); + + /* Make sure we don't enter a busy loop of some kind. */ + r = service_start_limit_test(s); + if (r < 0) { + service_notify_sockets_dead(s, true); + return r; + } + + s->result = SERVICE_SUCCESS; + s->reload_result = SERVICE_SUCCESS; + s->main_pid_known = false; + s->main_pid_alien = false; + s->forbid_restart = false; + + service_enter_start_pre(s); + return 0; +} + +static int service_stop(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + /* This is a user request, so don't do restarts on this + * shutdown. */ + s->forbid_restart = true; + + /* Already on it */ + if (s->state == SERVICE_STOP || + s->state == SERVICE_STOP_SIGTERM || + s->state == SERVICE_STOP_SIGKILL || + s->state == SERVICE_STOP_POST || + s->state == SERVICE_FINAL_SIGTERM || + s->state == SERVICE_FINAL_SIGKILL) + return 0; + + /* Don't allow a restart */ + if (s->state == SERVICE_AUTO_RESTART) { + service_set_state(s, SERVICE_DEAD); + return 0; + } + + /* If there's already something running we go directly into + * kill mode. */ + if (s->state == SERVICE_START_PRE || + s->state == SERVICE_START || + s->state == SERVICE_START_POST || + s->state == SERVICE_RELOAD) { + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); + return 0; + } + + assert(s->state == SERVICE_RUNNING || + s->state == SERVICE_EXITED); + + service_enter_stop(s, SERVICE_SUCCESS); + return 0; +} + +static int service_reload(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + assert(s->state == SERVICE_RUNNING || s->state == SERVICE_EXITED); + + service_enter_reload(s); + return 0; +} + +static bool service_can_reload(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + return !!s->exec_command[SERVICE_EXEC_RELOAD]; +} + +static int service_serialize(Unit *u, FILE *f, FDSet *fds) { + Service *s = SERVICE(u); + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", service_state_to_string(s->state)); + unit_serialize_item(u, f, "result", service_result_to_string(s->result)); + unit_serialize_item(u, f, "reload-result", service_result_to_string(s->reload_result)); + + if (s->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", "%lu", (unsigned long) s->control_pid); + + if (s->main_pid_known && s->main_pid > 0) + unit_serialize_item_format(u, f, "main-pid", "%lu", (unsigned long) s->main_pid); + + unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known)); + + if (s->status_text) + unit_serialize_item(u, f, "status-text", s->status_text); + + /* FIXME: There's a minor uncleanliness here: if there are + * multiple commands attached here, we will start from the + * first one again */ + if (s->control_command_id >= 0) + unit_serialize_item(u, f, "control-command", service_exec_command_to_string(s->control_command_id)); + + if (s->socket_fd >= 0) { + int copy; + + if ((copy = fdset_put_dup(fds, s->socket_fd)) < 0) + return copy; + + unit_serialize_item_format(u, f, "socket-fd", "%i", copy); + } + + if (s->main_exec_status.pid > 0) { + unit_serialize_item_format(u, f, "main-exec-status-pid", "%lu", (unsigned long) s->main_exec_status.pid); + dual_timestamp_serialize(f, "main-exec-status-start", &s->main_exec_status.start_timestamp); + dual_timestamp_serialize(f, "main-exec-status-exit", &s->main_exec_status.exit_timestamp); + + if (dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) { + unit_serialize_item_format(u, f, "main-exec-status-code", "%i", s->main_exec_status.code); + unit_serialize_item_format(u, f, "main-exec-status-status", "%i", s->main_exec_status.status); + } + } + if (dual_timestamp_is_set(&s->watchdog_timestamp)) + dual_timestamp_serialize(f, "watchdog-timestamp", &s->watchdog_timestamp); + + return 0; +} + +static int service_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Service *s = SERVICE(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + ServiceState state; + + if ((state = service_state_from_string(value)) < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + } else if (streq(key, "result")) { + ServiceResult f; + + f = service_result_from_string(value); + if (f < 0) + log_debug("Failed to parse result value %s", value); + else if (f != SERVICE_SUCCESS) + s->result = f; + + } else if (streq(key, "reload-result")) { + ServiceResult f; + + f = service_result_from_string(value); + if (f < 0) + log_debug("Failed to parse reload result value %s", value); + else if (f != SERVICE_SUCCESS) + s->reload_result = f; + + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_debug("Failed to parse control-pid value %s", value); + else + s->control_pid = pid; + } else if (streq(key, "main-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_debug("Failed to parse main-pid value %s", value); + else + service_set_main_pid(s, (pid_t) pid); + } else if (streq(key, "main-pid-known")) { + int b; + + if ((b = parse_boolean(value)) < 0) + log_debug("Failed to parse main-pid-known value %s", value); + else + s->main_pid_known = b; + } else if (streq(key, "status-text")) { + char *t; + + if ((t = strdup(value))) { + free(s->status_text); + s->status_text = t; + } + + } else if (streq(key, "control-command")) { + ServiceExecCommand id; + + if ((id = service_exec_command_from_string(value)) < 0) + log_debug("Failed to parse exec-command value %s", value); + else { + s->control_command_id = id; + s->control_command = s->exec_command[id]; + } + } else if (streq(key, "socket-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse socket-fd value %s", value); + else { + + if (s->socket_fd >= 0) + close_nointr_nofail(s->socket_fd); + s->socket_fd = fdset_remove(fds, fd); + } + } else if (streq(key, "main-exec-status-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_debug("Failed to parse main-exec-status-pid value %s", value); + else + s->main_exec_status.pid = pid; + } else if (streq(key, "main-exec-status-code")) { + int i; + + if (safe_atoi(value, &i) < 0) + log_debug("Failed to parse main-exec-status-code value %s", value); + else + s->main_exec_status.code = i; + } else if (streq(key, "main-exec-status-status")) { + int i; + + if (safe_atoi(value, &i) < 0) + log_debug("Failed to parse main-exec-status-status value %s", value); + else + s->main_exec_status.status = i; + } else if (streq(key, "main-exec-status-start")) + dual_timestamp_deserialize(value, &s->main_exec_status.start_timestamp); + else if (streq(key, "main-exec-status-exit")) + dual_timestamp_deserialize(value, &s->main_exec_status.exit_timestamp); + else if (streq(key, "watchdog-timestamp")) + dual_timestamp_deserialize(value, &s->watchdog_timestamp); + else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState service_active_state(Unit *u) { + assert(u); + + return state_translation_table[SERVICE(u)->state]; +} + +static const char *service_sub_state_to_string(Unit *u) { + assert(u); + + return service_state_to_string(SERVICE(u)->state); +} + +static bool service_check_gc(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + /* Never clean up services that still have a process around, + * even if the service is formally dead. */ + if (cgroup_good(s) > 0 || + main_pid_good(s) > 0 || + control_pid_good(s) > 0) + return true; + +#ifdef HAVE_SYSV_COMPAT + if (s->sysv_path) + return true; +#endif + + return false; +} + +static bool service_check_snapshot(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + return !s->got_socket_fd; +} + +static int service_retry_pid_file(Service *s) { + int r; + + assert(s->pid_file); + assert(s->state == SERVICE_START || s->state == SERVICE_START_POST); + + r = service_load_pid_file(s, false); + if (r < 0) + return r; + + service_unwatch_pid_file(s); + + service_enter_running(s, SERVICE_SUCCESS); + return 0; +} + +static int service_watch_pid_file(Service *s) { + int r; + + log_debug("Setting watch for %s's PID file %s", UNIT(s)->id, s->pid_file_pathspec->path); + r = path_spec_watch(s->pid_file_pathspec, UNIT(s)); + if (r < 0) + goto fail; + + /* the pidfile might have appeared just before we set the watch */ + service_retry_pid_file(s); + + return 0; +fail: + log_error("Failed to set a watch for %s's PID file %s: %s", + UNIT(s)->id, s->pid_file_pathspec->path, strerror(-r)); + service_unwatch_pid_file(s); + return r; +} + +static int service_demand_pid_file(Service *s) { + PathSpec *ps; + + assert(s->pid_file); + assert(!s->pid_file_pathspec); + + ps = new0(PathSpec, 1); + if (!ps) + return -ENOMEM; + + ps->path = strdup(s->pid_file); + if (!ps->path) { + free(ps); + return -ENOMEM; + } + + path_kill_slashes(ps->path); + + /* PATH_CHANGED would not be enough. There are daemons (sendmail) that + * keep their PID file open all the time. */ + ps->type = PATH_MODIFIED; + ps->inotify_fd = -1; + + s->pid_file_pathspec = ps; + + return service_watch_pid_file(s); +} + +static void service_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { + Service *s = SERVICE(u); + + assert(s); + assert(fd >= 0); + assert(s->state == SERVICE_START || s->state == SERVICE_START_POST); + assert(s->pid_file_pathspec); + assert(path_spec_owns_inotify_fd(s->pid_file_pathspec, fd)); + + log_debug("inotify event for %s", u->id); + + if (path_spec_fd_event(s->pid_file_pathspec, events) < 0) + goto fail; + + if (service_retry_pid_file(s) == 0) + return; + + if (service_watch_pid_file(s) < 0) + goto fail; + + return; +fail: + service_unwatch_pid_file(s); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Service *s = SERVICE(u); + ServiceResult f; + + assert(s); + assert(pid >= 0); + + if (UNIT(s)->fragment_path ? is_clean_exit(code, status) : is_clean_exit_lsb(code, status)) + f = SERVICE_SUCCESS; + else if (code == CLD_EXITED) + f = SERVICE_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = SERVICE_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = SERVICE_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown code"); + + if (s->main_pid == pid) { + /* Forking services may occasionally move to a new PID. + * As long as they update the PID file before exiting the old + * PID, they're fine. */ + if (service_load_pid_file(s, false) == 0) + return; + + s->main_pid = 0; + exec_status_exit(&s->main_exec_status, &s->exec_context, pid, code, status); + + /* If this is not a forking service than the main + * process got started and hence we copy the exit + * status so that it is recorded both as main and as + * control process exit status */ + if (s->main_command) { + s->main_command->exec_status = s->main_exec_status; + + if (s->main_command->ignore) + f = SERVICE_SUCCESS; + } + + log_full(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + "%s: main process exited, code=%s, status=%i", u->id, sigchld_code_to_string(code), status); + + if (f != SERVICE_SUCCESS) + s->result = f; + + if (s->main_command && + s->main_command->command_next && + f == SERVICE_SUCCESS) { + + /* There is another command to * + * execute, so let's do that. */ + + log_debug("%s running next main command for state %s", u->id, service_state_to_string(s->state)); + service_run_next_main(s); + + } else { + + /* The service exited, so the service is officially + * gone. */ + s->main_command = NULL; + + switch (s->state) { + + case SERVICE_START_POST: + case SERVICE_RELOAD: + case SERVICE_STOP: + /* Need to wait until the operation is + * done */ + break; + + case SERVICE_START: + if (s->type == SERVICE_ONESHOT) { + /* This was our main goal, so let's go on */ + if (f == SERVICE_SUCCESS) + service_enter_start_post(s); + else + service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + break; + } else { + assert(s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY); + + /* Fall through */ + } + + case SERVICE_RUNNING: + service_enter_running(s, f); + break; + + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + + if (!control_pid_good(s)) + service_enter_stop_post(s, f); + + /* If there is still a control process, wait for that first */ + break; + + default: + assert_not_reached("Uh, main process died at wrong time."); + } + } + + } else if (s->control_pid == pid) { + + s->control_pid = 0; + + if (s->control_command) { + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); + + if (s->control_command->ignore) + f = SERVICE_SUCCESS; + } + + log_full(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + "%s: control process exited, code=%s status=%i", u->id, sigchld_code_to_string(code), status); + + if (f != SERVICE_SUCCESS) + s->result = f; + + if (s->control_command && + s->control_command->command_next && + f == SERVICE_SUCCESS) { + + /* There is another command to * + * execute, so let's do that. */ + + log_debug("%s running next control command for state %s", u->id, service_state_to_string(s->state)); + service_run_next_control(s); + + } else { + /* No further commands for this step, so let's + * figure out what to do next */ + + s->control_command = NULL; + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; + + log_debug("%s got final SIGCHLD for state %s", u->id, service_state_to_string(s->state)); + + switch (s->state) { + + case SERVICE_START_PRE: + if (f == SERVICE_SUCCESS) + service_enter_start(s); + else + service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + break; + + case SERVICE_START: + assert(s->type == SERVICE_FORKING); + + if (f != SERVICE_SUCCESS) { + service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + break; + } + + if (s->pid_file) { + bool has_start_post; + int r; + + /* Let's try to load the pid file here if we can. + * The PID file might actually be created by a START_POST + * script. In that case don't worry if the loading fails. */ + + has_start_post = !!s->exec_command[SERVICE_EXEC_START_POST]; + r = service_load_pid_file(s, !has_start_post); + if (!has_start_post && r < 0) { + r = service_demand_pid_file(s); + if (r < 0 || !cgroup_good(s)) + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); + break; + } + } else + service_search_main_pid(s); + + service_enter_start_post(s); + break; + + case SERVICE_START_POST: + if (f != SERVICE_SUCCESS) { + service_enter_stop(s, f); + break; + } + + if (s->pid_file) { + int r; + + r = service_load_pid_file(s, true); + if (r < 0) { + r = service_demand_pid_file(s); + if (r < 0 || !cgroup_good(s)) + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); + break; + } + } else + service_search_main_pid(s); + + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_RELOAD: + if (f == SERVICE_SUCCESS) { + service_load_pid_file(s, true); + service_search_main_pid(s); + } + + s->reload_result = f; + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_STOP: + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); + break; + + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + if (main_pid_good(s) <= 0) + service_enter_stop_post(s, f); + + /* If there is still a service + * process around, wait until + * that one quit, too */ + break; + + case SERVICE_STOP_POST: + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + service_enter_dead(s, f, true); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + } + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); +} + +static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) { + Service *s = SERVICE(u); + + assert(s); + assert(elapsed == 1); + + if (w == &s->watchdog_watch) { + service_handle_watchdog(s); + return; + } + + assert(w == &s->timer_watch); + + switch (s->state) { + + case SERVICE_START_PRE: + case SERVICE_START: + log_warning("%s operation timed out. Terminating.", u->id); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_START_POST: + log_warning("%s operation timed out. Stopping.", u->id); + service_enter_stop(s, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_RELOAD: + log_warning("%s operation timed out. Stopping.", u->id); + s->reload_result = SERVICE_FAILURE_TIMEOUT; + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_STOP: + log_warning("%s stopping timed out. Terminating.", u->id); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_STOP_SIGTERM: + if (s->exec_context.send_sigkill) { + log_warning("%s stopping timed out. Killing.", u->id); + service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_FAILURE_TIMEOUT); + } else { + log_warning("%s stopping timed out. Skipping SIGKILL.", u->id); + service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT); + } + + break; + + case SERVICE_STOP_SIGKILL: + /* Uh, we sent a SIGKILL and it is still not gone? + * Must be something we cannot kill, so let's just be + * weirded out and continue */ + + log_warning("%s still around after SIGKILL. Ignoring.", u->id); + service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_STOP_POST: + log_warning("%s stopping timed out (2). Terminating.", u->id); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_FINAL_SIGTERM: + if (s->exec_context.send_sigkill) { + log_warning("%s stopping timed out (2). Killing.", u->id); + service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_FAILURE_TIMEOUT); + } else { + log_warning("%s stopping timed out (2). Skipping SIGKILL. Entering failed mode.", u->id); + service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, false); + } + + break; + + case SERVICE_FINAL_SIGKILL: + log_warning("%s still around after SIGKILL (2). Entering failed mode.", u->id); + service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, true); + break; + + case SERVICE_AUTO_RESTART: + log_info("%s holdoff time over, scheduling restart.", u->id); + service_enter_restart(s); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } +} + +static void service_cgroup_notify_event(Unit *u) { + Service *s = SERVICE(u); + + assert(u); + + log_debug("%s: cgroup is empty", u->id); + + switch (s->state) { + + /* Waiting for SIGCHLD is usually more interesting, + * because it includes return codes/signals. Which is + * why we ignore the cgroup events for most cases, + * except when we don't know pid which to expect the + * SIGCHLD for. */ + + case SERVICE_START: + case SERVICE_START_POST: + /* If we were hoping for the daemon to write its PID file, + * we can give up now. */ + if (s->pid_file_pathspec) { + log_warning("%s never wrote its PID file. Failing.", UNIT(s)->id); + service_unwatch_pid_file(s); + if (s->state == SERVICE_START) + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); + else + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); + } + break; + + case SERVICE_RUNNING: + /* service_enter_running() will figure out what to do */ + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + + if (main_pid_good(s) <= 0 && !control_pid_good(s)) + service_enter_stop_post(s, SERVICE_SUCCESS); + + break; + + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + if (main_pid_good(s) <= 0 && !control_pid_good(s)) + service_enter_dead(s, SERVICE_SUCCESS, SERVICE_SUCCESS); + + break; + + default: + ; + } +} + +static void service_notify_message(Unit *u, pid_t pid, char **tags) { + Service *s = SERVICE(u); + const char *e; + + assert(u); + + if (s->notify_access == NOTIFY_NONE) { + log_warning("%s: Got notification message from PID %lu, but reception is disabled.", + u->id, (unsigned long) pid); + return; + } + + if (s->notify_access == NOTIFY_MAIN && pid != s->main_pid) { + log_warning("%s: Got notification message from PID %lu, but reception only permitted for PID %lu", + u->id, (unsigned long) pid, (unsigned long) s->main_pid); + return; + } + + log_debug("%s: Got message", u->id); + + /* Interpret MAINPID= */ + if ((e = strv_find_prefix(tags, "MAINPID=")) && + (s->state == SERVICE_START || + s->state == SERVICE_START_POST || + s->state == SERVICE_RUNNING || + s->state == SERVICE_RELOAD)) { + + if (parse_pid(e + 8, &pid) < 0) + log_warning("Failed to parse notification message %s", e); + else { + log_debug("%s: got %s", u->id, e); + service_set_main_pid(s, pid); + } + } + + /* Interpret READY= */ + if (s->type == SERVICE_NOTIFY && + s->state == SERVICE_START && + strv_find(tags, "READY=1")) { + log_debug("%s: got READY=1", u->id); + + service_enter_start_post(s); + } + + /* Interpret STATUS= */ + e = strv_find_prefix(tags, "STATUS="); + if (e) { + char *t; + + if (e[7]) { + + if (!utf8_is_valid(e+7)) { + log_warning("Status message in notification is not UTF-8 clean."); + return; + } + + t = strdup(e+7); + if (!t) { + log_error("Failed to allocate string."); + return; + } + + log_debug("%s: got %s", u->id, e); + + free(s->status_text); + s->status_text = t; + } else { + free(s->status_text); + s->status_text = NULL; + } + + } + if (strv_find(tags, "WATCHDOG=1")) { + log_debug("%s: got WATCHDOG=1", u->id); + service_reset_watchdog(s); + } + + /* Notify clients about changed status or main pid */ + unit_add_to_dbus_queue(u); +} + +#ifdef HAVE_SYSV_COMPAT + +#ifdef TARGET_SUSE +static void sysv_facility_in_insserv_conf(Manager *mgr) { + FILE *f=NULL; + int r; + + if (!(f = fopen("/etc/insserv.conf", "re"))) { + r = errno == ENOENT ? 0 : -errno; + goto finish; + } + + while (!feof(f)) { + char l[LINE_MAX], *t; + char **parsed = NULL; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + r = -errno; + log_error("Failed to read configuration file '/etc/insserv.conf': %s", strerror(-r)); + goto finish; + } + + t = strstrip(l); + if (*t != '$' && *t != '<') + continue; + + parsed = strv_split(t,WHITESPACE); + /* we ignore , not used, equivalent to X-Interactive */ + if (parsed && !startswith_no_case (parsed[0], "")) { + char *facility; + Unit *u; + if (sysv_translate_facility(parsed[0], NULL, &facility) < 0) + continue; + if ((u = manager_get_unit(mgr, facility)) && (u->type == UNIT_TARGET)) { + UnitDependency e; + char *dep = NULL, *name, **j; + + STRV_FOREACH (j, parsed+1) { + if (*j[0]=='+') { + e = UNIT_WANTS; + name = *j+1; + } + else { + e = UNIT_REQUIRES; + name = *j; + } + if (sysv_translate_facility(name, NULL, &dep) < 0) + continue; + + r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, e, dep, NULL, true); + free(dep); + } + } + free(facility); + } + strv_free(parsed); + } +finish: + if (f) + fclose(f); + +} +#endif + +static int service_enumerate(Manager *m) { + char **p; + unsigned i; + DIR *d = NULL; + char *path = NULL, *fpath = NULL, *name = NULL; + Set *runlevel_services[ELEMENTSOF(rcnd_table)], *shutdown_services = NULL; + Unit *service; + Iterator j; + int r; + + assert(m); + + if (m->running_as != MANAGER_SYSTEM) + return 0; + + zero(runlevel_services); + + STRV_FOREACH(p, m->lookup_paths.sysvrcnd_path) + for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) { + struct dirent *de; + + free(path); + path = join(*p, "/", rcnd_table[i].path, NULL); + if (!path) { + r = -ENOMEM; + goto finish; + } + + if (d) + closedir(d); + + if (!(d = opendir(path))) { + if (errno != ENOENT) + log_warning("opendir() failed on %s: %s", path, strerror(errno)); + + continue; + } + + while ((de = readdir(d))) { + int a, b; + + if (ignore_file(de->d_name)) + continue; + + if (de->d_name[0] != 'S' && de->d_name[0] != 'K') + continue; + + if (strlen(de->d_name) < 4) + continue; + + a = undecchar(de->d_name[1]); + b = undecchar(de->d_name[2]); + + if (a < 0 || b < 0) + continue; + + free(fpath); + fpath = join(path, "/", de->d_name, NULL); + if (!fpath) { + r = -ENOMEM; + goto finish; + } + + if (access(fpath, X_OK) < 0) { + + if (errno != ENOENT) + log_warning("access() failed on %s: %s", fpath, strerror(errno)); + + continue; + } + + free(name); + if (!(name = sysv_translate_name(de->d_name + 3))) { + r = -ENOMEM; + goto finish; + } + + if ((r = manager_load_unit_prepare(m, name, NULL, NULL, &service)) < 0) { + log_warning("Failed to prepare unit %s: %s", name, strerror(-r)); + continue; + } + + if (de->d_name[0] == 'S') { + + if (rcnd_table[i].type == RUNLEVEL_UP || rcnd_table[i].type == RUNLEVEL_SYSINIT) { + SERVICE(service)->sysv_start_priority_from_rcnd = + MAX(a*10 + b, SERVICE(service)->sysv_start_priority_from_rcnd); + + SERVICE(service)->sysv_enabled = true; + } + + if ((r = set_ensure_allocated(&runlevel_services[i], trivial_hash_func, trivial_compare_func)) < 0) + goto finish; + + if ((r = set_put(runlevel_services[i], service)) < 0) + goto finish; + + } else if (de->d_name[0] == 'K' && + (rcnd_table[i].type == RUNLEVEL_DOWN || + rcnd_table[i].type == RUNLEVEL_SYSINIT)) { + + if ((r = set_ensure_allocated(&shutdown_services, trivial_hash_func, trivial_compare_func)) < 0) + goto finish; + + if ((r = set_put(shutdown_services, service)) < 0) + goto finish; + } + } + } + + /* Now we loaded all stubs and are aware of the lowest + start-up priority for all services, not let's actually load + the services, this will also tell us which services are + actually native now */ + manager_dispatch_load_queue(m); + + /* If this is a native service, rely on native ways to pull in + * a service, don't pull it in via sysv rcN.d links. */ + for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) + SET_FOREACH(service, runlevel_services[i], j) { + service = unit_follow_merge(service); + + if (service->fragment_path) + continue; + + if ((r = unit_add_two_dependencies_by_name_inverse(service, UNIT_AFTER, UNIT_WANTS, rcnd_table[i].target, NULL, true)) < 0) + goto finish; + } + + /* We honour K links only for halt/reboot. For the normal + * runlevels we assume the stop jobs will be implicitly added + * by the core logic. Also, we don't really distinguish here + * between the runlevels 0 and 6 and just add them to the + * special shutdown target. On SUSE the boot.d/ runlevel is + * also used for shutdown, so we add links for that too to the + * shutdown target.*/ + SET_FOREACH(service, shutdown_services, j) { + service = unit_follow_merge(service); + + if (service->fragment_path) + continue; + + if ((r = unit_add_two_dependencies_by_name(service, UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true)) < 0) + goto finish; + } + + r = 0; + +#ifdef TARGET_SUSE + sysv_facility_in_insserv_conf (m); +#endif + +finish: + free(path); + free(fpath); + free(name); + + for (i = 0; i < ELEMENTSOF(rcnd_table); i++) + set_free(runlevel_services[i]); + set_free(shutdown_services); + + if (d) + closedir(d); + + return r; +} +#endif + +static void service_bus_name_owner_change( + Unit *u, + const char *name, + const char *old_owner, + const char *new_owner) { + + Service *s = SERVICE(u); + + assert(s); + assert(name); + + assert(streq(s->bus_name, name)); + assert(old_owner || new_owner); + + if (old_owner && new_owner) + log_debug("%s's D-Bus name %s changed owner from %s to %s", u->id, name, old_owner, new_owner); + else if (old_owner) + log_debug("%s's D-Bus name %s no longer registered by %s", u->id, name, old_owner); + else + log_debug("%s's D-Bus name %s now registered by %s", u->id, name, new_owner); + + s->bus_name_good = !!new_owner; + + if (s->type == SERVICE_DBUS) { + + /* service_enter_running() will figure out what to + * do */ + if (s->state == SERVICE_RUNNING) + service_enter_running(s, SERVICE_SUCCESS); + else if (s->state == SERVICE_START && new_owner) + service_enter_start_post(s); + + } else if (new_owner && + s->main_pid <= 0 && + (s->state == SERVICE_START || + s->state == SERVICE_START_POST || + s->state == SERVICE_RUNNING || + s->state == SERVICE_RELOAD)) { + + /* Try to acquire PID from bus service */ + log_debug("Trying to acquire PID from D-Bus name..."); + + bus_query_pid(u->manager, name); + } +} + +static void service_bus_query_pid_done( + Unit *u, + const char *name, + pid_t pid) { + + Service *s = SERVICE(u); + + assert(s); + assert(name); + + log_debug("%s's D-Bus name %s is now owned by process %u", u->id, name, (unsigned) pid); + + if (s->main_pid <= 0 && + (s->state == SERVICE_START || + s->state == SERVICE_START_POST || + s->state == SERVICE_RUNNING || + s->state == SERVICE_RELOAD)) + service_set_main_pid(s, pid); +} + +int service_set_socket_fd(Service *s, int fd, Socket *sock) { + + assert(s); + assert(fd >= 0); + + /* This is called by the socket code when instantiating a new + * service for a stream socket and the socket needs to be + * configured. */ + + if (UNIT(s)->load_state != UNIT_LOADED) + return -EINVAL; + + if (s->socket_fd >= 0) + return -EBUSY; + + if (s->state != SERVICE_DEAD) + return -EAGAIN; + + s->socket_fd = fd; + s->got_socket_fd = true; + + unit_ref_set(&s->accept_socket, UNIT(sock)); + + return unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false); +} + +static void service_reset_failed(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + if (s->state == SERVICE_FAILED) + service_set_state(s, SERVICE_DEAD); + + s->result = SERVICE_SUCCESS; + s->reload_result = SERVICE_SUCCESS; +} + +static bool service_need_daemon_reload(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + +#ifdef HAVE_SYSV_COMPAT + if (s->sysv_path) { + struct stat st; + + zero(st); + if (stat(s->sysv_path, &st) < 0) + /* What, cannot access this anymore? */ + return true; + + if (s->sysv_mtime > 0 && + timespec_load(&st.st_mtim) != s->sysv_mtime) + return true; + } +#endif + + return false; +} + +static int service_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) { + Service *s = SERVICE(u); + int r = 0; + Set *pid_set = NULL; + + assert(s); + + if (s->main_pid <= 0 && who == KILL_MAIN) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill"); + return -ESRCH; + } + + if (s->control_pid <= 0 && who == KILL_CONTROL) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); + return -ESRCH; + } + + if (who == KILL_CONTROL || who == KILL_ALL) + if (s->control_pid > 0) + if (kill(s->control_pid, signo) < 0) + r = -errno; + + if (who == KILL_MAIN || who == KILL_ALL) + if (s->main_pid > 0) + if (kill(s->main_pid, signo) < 0) + r = -errno; + + if (who == KILL_ALL && mode == KILL_CONTROL_GROUP) { + int q; + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + /* Exclude the control/main pid from being killed via the cgroup */ + if (s->control_pid > 0) + if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) { + r = q; + goto finish; + } + + if (s->main_pid > 0) + if ((q = set_put(pid_set, LONG_TO_PTR(s->main_pid))) < 0) { + r = q; + goto finish; + } + + if ((q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, pid_set)) < 0) + if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + } + +finish: + if (pid_set) + set_free(pid_set); + + return r; +} + +static const char* const service_state_table[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = "dead", + [SERVICE_START_PRE] = "start-pre", + [SERVICE_START] = "start", + [SERVICE_START_POST] = "start-post", + [SERVICE_RUNNING] = "running", + [SERVICE_EXITED] = "exited", + [SERVICE_RELOAD] = "reload", + [SERVICE_STOP] = "stop", + [SERVICE_STOP_SIGTERM] = "stop-sigterm", + [SERVICE_STOP_SIGKILL] = "stop-sigkill", + [SERVICE_STOP_POST] = "stop-post", + [SERVICE_FINAL_SIGTERM] = "final-sigterm", + [SERVICE_FINAL_SIGKILL] = "final-sigkill", + [SERVICE_FAILED] = "failed", + [SERVICE_AUTO_RESTART] = "auto-restart", +}; + +DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState); + +static const char* const service_restart_table[_SERVICE_RESTART_MAX] = { + [SERVICE_RESTART_NO] = "no", + [SERVICE_RESTART_ON_SUCCESS] = "on-success", + [SERVICE_RESTART_ON_FAILURE] = "on-failure", + [SERVICE_RESTART_ON_ABORT] = "on-abort", + [SERVICE_RESTART_ALWAYS] = "always" +}; + +DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart); + +static const char* const service_type_table[_SERVICE_TYPE_MAX] = { + [SERVICE_SIMPLE] = "simple", + [SERVICE_FORKING] = "forking", + [SERVICE_ONESHOT] = "oneshot", + [SERVICE_DBUS] = "dbus", + [SERVICE_NOTIFY] = "notify" +}; + +DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType); + +static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = { + [SERVICE_EXEC_START_PRE] = "ExecStartPre", + [SERVICE_EXEC_START] = "ExecStart", + [SERVICE_EXEC_START_POST] = "ExecStartPost", + [SERVICE_EXEC_RELOAD] = "ExecReload", + [SERVICE_EXEC_STOP] = "ExecStop", + [SERVICE_EXEC_STOP_POST] = "ExecStopPost", +}; + +DEFINE_STRING_TABLE_LOOKUP(service_exec_command, ServiceExecCommand); + +static const char* const notify_access_table[_NOTIFY_ACCESS_MAX] = { + [NOTIFY_NONE] = "none", + [NOTIFY_MAIN] = "main", + [NOTIFY_ALL] = "all" +}; + +DEFINE_STRING_TABLE_LOOKUP(notify_access, NotifyAccess); + +static const char* const service_result_table[_SERVICE_RESULT_MAX] = { + [SERVICE_SUCCESS] = "success", + [SERVICE_FAILURE_RESOURCES] = "resources", + [SERVICE_FAILURE_TIMEOUT] = "timeout", + [SERVICE_FAILURE_EXIT_CODE] = "exit-code", + [SERVICE_FAILURE_SIGNAL] = "signal", + [SERVICE_FAILURE_CORE_DUMP] = "core-dump", + [SERVICE_FAILURE_WATCHDOG] = "watchdog" +}; + +DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult); + +static const char* const start_limit_action_table[_SERVICE_START_LIMIT_MAX] = { + [SERVICE_START_LIMIT_NONE] = "none", + [SERVICE_START_LIMIT_REBOOT] = "reboot", + [SERVICE_START_LIMIT_REBOOT_FORCE] = "reboot-force", + [SERVICE_START_LIMIT_REBOOT_IMMEDIATE] = "reboot-immediate" +}; +DEFINE_STRING_TABLE_LOOKUP(start_limit_action, StartLimitAction); + +const UnitVTable service_vtable = { + .suffix = ".service", + .object_size = sizeof(Service), + .sections = + "Unit\0" + "Service\0" + "Install\0", + .show_status = true, + + .init = service_init, + .done = service_done, + .load = service_load, + + .coldplug = service_coldplug, + + .dump = service_dump, + + .start = service_start, + .stop = service_stop, + .reload = service_reload, + + .can_reload = service_can_reload, + + .kill = service_kill, + + .serialize = service_serialize, + .deserialize_item = service_deserialize_item, + + .active_state = service_active_state, + .sub_state_to_string = service_sub_state_to_string, + + .check_gc = service_check_gc, + .check_snapshot = service_check_snapshot, + + .sigchld_event = service_sigchld_event, + .timer_event = service_timer_event, + .fd_event = service_fd_event, + + .reset_failed = service_reset_failed, + + .need_daemon_reload = service_need_daemon_reload, + + .cgroup_notify_empty = service_cgroup_notify_event, + .notify_message = service_notify_message, + + .bus_name_owner_change = service_bus_name_owner_change, + .bus_query_pid_done = service_bus_query_pid_done, + + .bus_interface = "org.freedesktop.systemd1.Service", + .bus_message_handler = bus_service_message_handler, + .bus_invalidating_properties = bus_service_invalidating_properties, + +#ifdef HAVE_SYSV_COMPAT + .enumerate = service_enumerate +#endif +}; diff --git a/src/service.h b/src/service.h new file mode 100644 index 000000000..60b10516e --- /dev/null +++ b/src/service.h @@ -0,0 +1,220 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooservicehfoo +#define fooservicehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Service Service; + +#include "unit.h" +#include "path.h" +#include "ratelimit.h" +#include "service.h" + +typedef enum ServiceState { + SERVICE_DEAD, + SERVICE_START_PRE, + SERVICE_START, + SERVICE_START_POST, + SERVICE_RUNNING, + SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */ + SERVICE_RELOAD, + SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */ + SERVICE_STOP_SIGTERM, + SERVICE_STOP_SIGKILL, + SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */ + SERVICE_FINAL_SIGKILL, + SERVICE_FAILED, + SERVICE_AUTO_RESTART, + _SERVICE_STATE_MAX, + _SERVICE_STATE_INVALID = -1 +} ServiceState; + +typedef enum ServiceRestart { + SERVICE_RESTART_NO, + SERVICE_RESTART_ON_SUCCESS, + SERVICE_RESTART_ON_FAILURE, + SERVICE_RESTART_ON_ABORT, + SERVICE_RESTART_ALWAYS, + _SERVICE_RESTART_MAX, + _SERVICE_RESTART_INVALID = -1 +} ServiceRestart; + +typedef enum ServiceType { + SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons) */ + SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */ + SERVICE_ONESHOT, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */ + SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */ + SERVICE_NOTIFY, /* we fork and wait until a daemon sends us a ready message with sd_notify() */ + _SERVICE_TYPE_MAX, + _SERVICE_TYPE_INVALID = -1 +} ServiceType; + +typedef enum ServiceExecCommand { + SERVICE_EXEC_START_PRE, + SERVICE_EXEC_START, + SERVICE_EXEC_START_POST, + SERVICE_EXEC_RELOAD, + SERVICE_EXEC_STOP, + SERVICE_EXEC_STOP_POST, + _SERVICE_EXEC_COMMAND_MAX, + _SERVICE_EXEC_COMMAND_INVALID = -1 +} ServiceExecCommand; + +typedef enum NotifyAccess { + NOTIFY_NONE, + NOTIFY_ALL, + NOTIFY_MAIN, + _NOTIFY_ACCESS_MAX, + _NOTIFY_ACCESS_INVALID = -1 +} NotifyAccess; + +typedef enum ServiceResult { + SERVICE_SUCCESS, + SERVICE_FAILURE_RESOURCES, + SERVICE_FAILURE_TIMEOUT, + SERVICE_FAILURE_EXIT_CODE, + SERVICE_FAILURE_SIGNAL, + SERVICE_FAILURE_CORE_DUMP, + SERVICE_FAILURE_WATCHDOG, + _SERVICE_RESULT_MAX, + _SERVICE_RESULT_INVALID = -1 +} ServiceResult; + +typedef enum StartLimitAction { + SERVICE_START_LIMIT_NONE, + SERVICE_START_LIMIT_REBOOT, + SERVICE_START_LIMIT_REBOOT_FORCE, + SERVICE_START_LIMIT_REBOOT_IMMEDIATE, + _SERVICE_START_LIMIT_MAX, + _SERVICE_START_LIMIT_INVALID = -1 +} StartLimitAction; + +struct Service { + Unit meta; + + ServiceType type; + ServiceRestart restart; + + /* If set we'll read the main daemon PID from this file */ + char *pid_file; + + usec_t restart_usec; + usec_t timeout_usec; + + dual_timestamp watchdog_timestamp; + usec_t watchdog_usec; + Watch watchdog_watch; + + ExecCommand* exec_command[_SERVICE_EXEC_COMMAND_MAX]; + ExecContext exec_context; + + ServiceState state, deserialized_state; + + /* The exit status of the real main process */ + ExecStatus main_exec_status; + + /* The currently executed control process */ + ExecCommand *control_command; + + /* The currently executed main process, which may be NULL if + * the main process got started via forking mode and not by + * us */ + ExecCommand *main_command; + + /* The ID of the control command currently being executed */ + ServiceExecCommand control_command_id; + + pid_t main_pid, control_pid; + int socket_fd; + + int fsck_passno; + + bool permissions_start_only; + bool root_directory_start_only; + bool remain_after_exit; + bool guess_main_pid; + + /* If we shut down, remember why */ + ServiceResult result; + ServiceResult reload_result; + + bool main_pid_known:1; + bool main_pid_alien:1; + bool bus_name_good:1; + bool forbid_restart:1; + bool got_socket_fd:1; +#ifdef HAVE_SYSV_COMPAT + bool sysv_has_lsb:1; + bool sysv_enabled:1; + int sysv_start_priority_from_rcnd; + int sysv_start_priority; + + char *sysv_path; + char *sysv_runlevels; + usec_t sysv_mtime; +#endif + + char *bus_name; + + char *status_text; + + RateLimit start_limit; + StartLimitAction start_limit_action; + + + UnitRef accept_socket; + + Watch timer_watch; + PathSpec *pid_file_pathspec; + + NotifyAccess notify_access; +}; + +extern const UnitVTable service_vtable; + +struct Socket; + +int service_set_socket_fd(Service *s, int fd, struct Socket *socket); + +const char* service_state_to_string(ServiceState i); +ServiceState service_state_from_string(const char *s); + +const char* service_restart_to_string(ServiceRestart i); +ServiceRestart service_restart_from_string(const char *s); + +const char* service_type_to_string(ServiceType i); +ServiceType service_type_from_string(const char *s); + +const char* service_exec_command_to_string(ServiceExecCommand i); +ServiceExecCommand service_exec_command_from_string(const char *s); + +const char* notify_access_to_string(NotifyAccess i); +NotifyAccess notify_access_from_string(const char *s); + +const char* service_result_to_string(ServiceResult i); +ServiceResult service_result_from_string(const char *s); + +const char* start_limit_action_to_string(StartLimitAction i); +StartLimitAction start_limit_action_from_string(const char *s); + +#endif diff --git a/src/set.c b/src/set.c new file mode 100644 index 000000000..097b9d3aa --- /dev/null +++ b/src/set.c @@ -0,0 +1,118 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "set.h" +#include "hashmap.h" + +#define MAKE_SET(h) ((Set*) (h)) +#define MAKE_HASHMAP(s) ((Hashmap*) (s)) + +/* For now this is not much more than a wrapper around a hashmap */ + +Set *set_new(hash_func_t hash_func, compare_func_t compare_func) { + return MAKE_SET(hashmap_new(hash_func, compare_func)); +} + +void set_free(Set* s) { + hashmap_free(MAKE_HASHMAP(s)); +} + +void set_free_free(Set *s) { + hashmap_free_free(MAKE_HASHMAP(s)); +} + +int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func) { + return hashmap_ensure_allocated((Hashmap**) s, hash_func, compare_func); +} + +int set_put(Set *s, void *value) { + return hashmap_put(MAKE_HASHMAP(s), value, value); +} + +int set_replace(Set *s, void *value) { + return hashmap_replace(MAKE_HASHMAP(s), value, value); +} + +void *set_get(Set *s, void *value) { + return hashmap_get(MAKE_HASHMAP(s), value); +} + +void *set_remove(Set *s, void *value) { + return hashmap_remove(MAKE_HASHMAP(s), value); +} + +int set_remove_and_put(Set *s, void *old_value, void *new_value) { + return hashmap_remove_and_put(MAKE_HASHMAP(s), old_value, new_value, new_value); +} + +unsigned set_size(Set *s) { + return hashmap_size(MAKE_HASHMAP(s)); +} + +bool set_isempty(Set *s) { + return hashmap_isempty(MAKE_HASHMAP(s)); +} + +void *set_iterate(Set *s, Iterator *i) { + return hashmap_iterate(MAKE_HASHMAP(s), i, NULL); +} + +void *set_iterate_backwards(Set *s, Iterator *i) { + return hashmap_iterate_backwards(MAKE_HASHMAP(s), i, NULL); +} + +void *set_iterate_skip(Set *s, void *value, Iterator *i) { + return hashmap_iterate_skip(MAKE_HASHMAP(s), value, i); +} + +void *set_steal_first(Set *s) { + return hashmap_steal_first(MAKE_HASHMAP(s)); +} + +void* set_first(Set *s) { + return hashmap_first(MAKE_HASHMAP(s)); +} + +void* set_last(Set *s) { + return hashmap_last(MAKE_HASHMAP(s)); +} + +int set_merge(Set *s, Set *other) { + return hashmap_merge(MAKE_HASHMAP(s), MAKE_HASHMAP(other)); +} + +void set_move(Set *s, Set *other) { + return hashmap_move(MAKE_HASHMAP(s), MAKE_HASHMAP(other)); +} + +int set_move_one(Set *s, Set *other, void *value) { + return hashmap_move_one(MAKE_HASHMAP(s), MAKE_HASHMAP(other), value); +} + +Set* set_copy(Set *s) { + return MAKE_SET(hashmap_copy(MAKE_HASHMAP(s))); +} + +void set_clear(Set *s) { + hashmap_clear(MAKE_HASHMAP(s)); +} diff --git a/src/set.h b/src/set.h new file mode 100644 index 000000000..885780c4c --- /dev/null +++ b/src/set.h @@ -0,0 +1,69 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosethfoo +#define foosethfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +/* Pretty straightforward set implementation. Internally based on the + * hashmap. That means that as a minor optimization a NULL set + * object will be treated as empty set for all read + * operations. That way it is not necessary to instantiate an object + * for each set use. */ + +#include "hashmap.h" + +typedef struct Set Set; + +Set *set_new(hash_func_t hash_func, compare_func_t compare_func); +void set_free(Set* s); +void set_free_free(Set *s); +Set* set_copy(Set *s); +int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func); + +int set_put(Set *s, void *value); +int set_replace(Set *s, void *value); +void *set_get(Set *s, void *value); +void *set_remove(Set *s, void *value); +int set_remove_and_put(Set *s, void *old_value, void *new_value); + +int set_merge(Set *s, Set *other); +void set_move(Set *s, Set *other); +int set_move_one(Set *s, Set *other, void *value); + +unsigned set_size(Set *s); +bool set_isempty(Set *s); + +void *set_iterate(Set *s, Iterator *i); +void *set_iterate_backwards(Set *s, Iterator *i); +void *set_iterate_skip(Set *s, void *value, Iterator *i); + +void set_clear(Set *s); +void *set_steal_first(Set *s); +void* set_first(Set *s); +void* set_last(Set *s); + +#define SET_FOREACH(e, s, i) \ + for ((i) = ITERATOR_FIRST, (e) = set_iterate((s), &(i)); (e); (e) = set_iterate((s), &(i))) + +#define SET_FOREACH_BACKWARDS(e, s, i) \ + for ((i) = ITERATOR_LAST, (e) = set_iterate_backwards((s), &(i)); (e); (e) = set_iterate_backwards((s), &(i))) + +#endif diff --git a/src/shutdown.c b/src/shutdown.c new file mode 100644 index 000000000..d157e0fbf --- /dev/null +++ b/src/shutdown.c @@ -0,0 +1,485 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 ProFUSION embedded systems + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "missing.h" +#include "log.h" +#include "umount.h" +#include "util.h" +#include "virt.h" + +#define TIMEOUT_USEC (5 * USEC_PER_SEC) +#define FINALIZE_ATTEMPTS 50 + +static bool ignore_proc(pid_t pid) { + char buf[PATH_MAX]; + FILE *f; + char c; + size_t count; + uid_t uid; + int r; + + /* We are PID 1, let's not commit suicide */ + if (pid == 1) + return true; + + r = get_process_uid(pid, &uid); + if (r < 0) + return true; /* not really, but better safe than sorry */ + + /* Non-root processes otherwise are always subject to be killed */ + if (uid != 0) + return false; + + snprintf(buf, sizeof(buf), "/proc/%lu/cmdline", (unsigned long) pid); + char_array_0(buf); + + f = fopen(buf, "re"); + if (!f) + return true; /* not really, but has the desired effect */ + + count = fread(&c, 1, 1, f); + fclose(f); + + /* Kernel threads have an empty cmdline */ + if (count <= 0) + return true; + + /* Processes with argv[0][0] = '@' we ignore from the killing + * spree. + * + * http://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons */ + if (count == 1 && c == '@') + return true; + + return false; +} + +static int killall(int sign) { + DIR *dir; + struct dirent *d; + unsigned int n_processes = 0; + + dir = opendir("/proc"); + if (!dir) + return -errno; + + while ((d = readdir(dir))) { + pid_t pid; + + if (parse_pid(d->d_name, &pid) < 0) + continue; + + if (ignore_proc(pid)) + continue; + + if (kill(pid, sign) == 0) + n_processes++; + else + log_warning("Could not kill %d: %m", pid); + } + + closedir(dir); + + return n_processes; +} + +static void wait_for_children(int n_processes, sigset_t *mask) { + usec_t until; + + assert(mask); + + until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC; + for (;;) { + struct timespec ts; + int k; + usec_t n; + + for (;;) { + pid_t pid = waitpid(-1, NULL, WNOHANG); + + if (pid == 0) + break; + + if (pid < 0 && errno == ECHILD) + return; + + if (n_processes > 0) + if (--n_processes == 0) + return; + } + + n = now(CLOCK_MONOTONIC); + if (n >= until) + return; + + timespec_store(&ts, until - n); + + if ((k = sigtimedwait(mask, NULL, &ts)) != SIGCHLD) { + + if (k < 0 && errno != EAGAIN) { + log_error("sigtimedwait() failed: %m"); + return; + } + + if (k >= 0) + log_warning("sigtimedwait() returned unexpected signal."); + } + } +} + +static void send_signal(int sign) { + sigset_t mask, oldmask; + int n_processes; + + assert_se(sigemptyset(&mask) == 0); + assert_se(sigaddset(&mask, SIGCHLD) == 0); + assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0); + + if (kill(-1, SIGSTOP) < 0 && errno != ESRCH) + log_warning("kill(-1, SIGSTOP) failed: %m"); + + n_processes = killall(sign); + + if (kill(-1, SIGCONT) < 0 && errno != ESRCH) + log_warning("kill(-1, SIGCONT) failed: %m"); + + if (n_processes <= 0) + goto finish; + + wait_for_children(n_processes, &mask); + +finish: + sigprocmask(SIG_SETMASK, &oldmask, NULL); +} + +static void ultimate_send_signal(int sign) { + sigset_t mask, oldmask; + int r; + + assert_se(sigemptyset(&mask) == 0); + assert_se(sigaddset(&mask, SIGCHLD) == 0); + assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0); + + if (kill(-1, SIGSTOP) < 0 && errno != ESRCH) + log_warning("kill(-1, SIGSTOP) failed: %m"); + + r = kill(-1, sign); + if (r < 0 && errno != ESRCH) + log_warning("kill(-1, %s) failed: %m", signal_to_string(sign)); + + if (kill(-1, SIGCONT) < 0 && errno != ESRCH) + log_warning("kill(-1, SIGCONT) failed: %m"); + + if (r < 0) + goto finish; + + wait_for_children(0, &mask); + +finish: + sigprocmask(SIG_SETMASK, &oldmask, NULL); +} + +static int prepare_new_root(void) { + static const char dirs[] = + "/run/initramfs/oldroot\0" + "/run/initramfs/proc\0" + "/run/initramfs/sys\0" + "/run/initramfs/dev\0" + "/run/initramfs/run\0"; + + const char *dir; + + if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0) { + log_error("Failed to mount bind /run/initramfs on /run/initramfs: %m"); + return -errno; + } + + if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) { + log_error("Failed to make /run/initramfs private mount: %m"); + return -errno; + } + + NULSTR_FOREACH(dir, dirs) + if (mkdir_p(dir, 0755) < 0 && errno != EEXIST) { + log_error("Failed to mkdir %s: %m", dir); + return -errno; + } + + if (mount("/sys", "/run/initramfs/sys", NULL, MS_BIND, NULL) < 0) { + log_error("Failed to mount bind /sys on /run/initramfs/sys: %m"); + return -errno; + } + + if (mount("/proc", "/run/initramfs/proc", NULL, MS_BIND, NULL) < 0) { + log_error("Failed to mount bind /proc on /run/initramfs/proc: %m"); + return -errno; + } + + if (mount("/dev", "/run/initramfs/dev", NULL, MS_BIND, NULL) < 0) { + log_error("Failed to mount bind /dev on /run/initramfs/dev: %m"); + return -errno; + } + + if (mount("/run", "/run/initramfs/run", NULL, MS_BIND, NULL) < 0) { + log_error("Failed to mount bind /run on /run/initramfs/run: %m"); + return -errno; + } + + return 0; +} + +static int pivot_to_new_root(void) { + int fd; + + chdir("/run/initramfs"); + + /* + In case some evil process made "/" MS_SHARED + It works for pivot_root, but the ref count for the root device + is not decreasing :-/ + */ + if (mount(NULL, "/", NULL, MS_PRIVATE, NULL) < 0) { + log_error("Failed to make \"/\" private mount %m"); + return -errno; + } + + if (pivot_root(".", "oldroot") < 0) { + log_error("pivot failed: %m"); + /* only chroot if pivot root succeded */ + return -errno; + } + + chroot("."); + log_info("Successfully changed into root pivot."); + + fd = open("/dev/console", O_RDWR); + if (fd < 0) + log_error("Failed to open /dev/console: %m"); + else { + make_stdio(fd); + + /* Initialize the controlling terminal */ + setsid(); + ioctl(STDIN_FILENO, TIOCSCTTY, NULL); + } + + return 0; +} + +int main(int argc, char *argv[]) { + int cmd, r; + unsigned retries; + bool need_umount = true, need_swapoff = true, need_loop_detach = true, need_dm_detach = true; + bool killed_everbody = false, in_container; + + log_parse_environment(); + log_set_target(LOG_TARGET_CONSOLE); /* syslog will die if not gone yet */ + log_open(); + + umask(0022); + + if (getpid() != 1) { + log_error("Not executed by init (pid 1)."); + r = -EPERM; + goto error; + } + + if (argc != 2) { + log_error("Invalid number of arguments."); + r = -EINVAL; + goto error; + } + + in_container = detect_container(NULL) > 0; + + if (streq(argv[1], "reboot")) + cmd = RB_AUTOBOOT; + else if (streq(argv[1], "poweroff")) + cmd = RB_POWER_OFF; + else if (streq(argv[1], "halt")) + cmd = RB_HALT_SYSTEM; + else if (streq(argv[1], "kexec")) + cmd = LINUX_REBOOT_CMD_KEXEC; + else { + log_error("Unknown action '%s'.", argv[1]); + r = -EINVAL; + goto error; + } + + /* lock us into memory */ + if (mlockall(MCL_CURRENT|MCL_FUTURE) != 0) + log_warning("Cannot lock process memory: %m"); + + log_info("Sending SIGTERM to remaining processes..."); + send_signal(SIGTERM); + + log_info("Sending SIGKILL to remaining processes..."); + send_signal(SIGKILL); + + if (in_container) + need_swapoff = false; + + /* Unmount all mountpoints, swaps, and loopback devices */ + for (retries = 0; retries < FINALIZE_ATTEMPTS; retries++) { + bool changed = false; + + if (need_umount) { + log_info("Unmounting file systems."); + r = umount_all(&changed); + if (r == 0) + need_umount = false; + else if (r > 0) + log_info("Not all file systems unmounted, %d left.", r); + else + log_error("Failed to unmount file systems: %s", strerror(-r)); + } + + if (need_swapoff) { + log_info("Disabling swaps."); + r = swapoff_all(&changed); + if (r == 0) + need_swapoff = false; + else if (r > 0) + log_info("Not all swaps are turned off, %d left.", r); + else + log_error("Failed to turn off swaps: %s", strerror(-r)); + } + + if (need_loop_detach) { + log_info("Detaching loop devices."); + r = loopback_detach_all(&changed); + if (r == 0) + need_loop_detach = false; + else if (r > 0) + log_info("Not all loop devices detached, %d left.", r); + else + log_error("Failed to detach loop devices: %s", strerror(-r)); + } + + if (need_dm_detach) { + log_info("Detaching DM devices."); + r = dm_detach_all(&changed); + if (r == 0) + need_dm_detach = false; + else if (r > 0) + log_warning("Not all DM devices detached, %d left.", r); + else + log_error("Failed to detach DM devices: %s", strerror(-r)); + } + + if (!need_umount && !need_swapoff && !need_loop_detach && !need_dm_detach) { + if (retries > 0) + log_info("All filesystems, swaps, loop devices, DM devices detached."); + /* Yay, done */ + break; + } + + /* If in this iteration we didn't manage to + * unmount/deactivate anything, we either kill more + * processes, or simply give up */ + if (!changed) { + + if (killed_everbody) { + /* Hmm, we already killed everybody, + * let's just give up */ + log_error("Cannot finalize remaining file systems and devices, giving up."); + break; + } + + log_warning("Cannot finalize remaining file systems and devices, trying to kill remaining processes."); + ultimate_send_signal(SIGTERM); + ultimate_send_signal(SIGKILL); + killed_everbody = true; + } + + log_debug("Couldn't finalize remaining file systems and devices after %u retries, trying again.", retries+1); + } + + if (retries >= FINALIZE_ATTEMPTS) + log_error("Too many iterations, giving up."); + + execute_directory(SYSTEM_SHUTDOWN_PATH, NULL, NULL); + + /* If we are in a container, just exit, this will kill our + * container for good. */ + if (in_container) { + log_error("Exiting container."); + exit(0); + } + + if (access("/run/initramfs/shutdown", X_OK) == 0) { + + if (prepare_new_root() >= 0 && + pivot_to_new_root() >= 0) { + execv("/shutdown", argv); + log_error("Failed to execute shutdown binary: %m"); + } + } + + sync(); + + if (cmd == LINUX_REBOOT_CMD_KEXEC) { + /* We cheat and exec kexec to avoid doing all its work */ + pid_t pid = fork(); + + if (pid < 0) + log_error("Could not fork: %m. Falling back to normal reboot."); + else if (pid > 0) { + wait_for_terminate_and_warn("kexec", pid); + log_warning("kexec failed. Falling back to normal reboot."); + } else { + /* Child */ + const char *args[3] = { "/sbin/kexec", "-e", NULL }; + execv(args[0], (char * const *) args); + return EXIT_FAILURE; + } + + cmd = RB_AUTOBOOT; + } + + reboot(cmd); + log_error("Failed to invoke reboot(): %m"); + r = -errno; + + error: + log_error("Critical error while doing system shutdown: %s", strerror(-r)); + + freeze(); + return EXIT_FAILURE; +} diff --git a/src/shutdownd.c b/src/shutdownd.c new file mode 100644 index 000000000..b4052d493 --- /dev/null +++ b/src/shutdownd.c @@ -0,0 +1,366 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "shutdownd.h" +#include "log.h" +#include "macro.h" +#include "util.h" +#include "utmp-wtmp.h" + +static int read_packet(int fd, struct shutdownd_command *_c) { + struct msghdr msghdr; + struct iovec iovec; + struct ucred *ucred; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; + } control; + struct shutdownd_command c; + ssize_t n; + + assert(fd >= 0); + assert(_c); + + zero(iovec); + iovec.iov_base = &c; + iovec.iov_len = sizeof(c); + + zero(control); + zero(msghdr); + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + if ((n = recvmsg(fd, &msghdr, MSG_DONTWAIT)) <= 0) { + if (n >= 0) { + log_error("Short read"); + return -EIO; + } + + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_error("recvmsg(): %m"); + return -errno; + } + + if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || + control.cmsghdr.cmsg_level != SOL_SOCKET || + control.cmsghdr.cmsg_type != SCM_CREDENTIALS || + control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { + log_warning("Received message without credentials. Ignoring."); + return 0; + } + + ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); + if (ucred->uid != 0) { + log_warning("Got request from unprivileged user. Ignoring."); + return 0; + } + + if (n != sizeof(c)) { + log_warning("Message has invalid size. Ignoring"); + return 0; + } + + char_array_0(c.wall_message); + + *_c = c; + return 1; +} + +static void warn_wall(usec_t n, struct shutdownd_command *c) { + char date[FORMAT_TIMESTAMP_MAX]; + const char *prefix; + char *l = NULL; + + assert(c); + assert(c->warn_wall); + + if (n >= c->elapse) + return; + + if (c->mode == 'H') + prefix = "The system is going down for system halt at "; + else if (c->mode == 'P') + prefix = "The system is going down for power-off at "; + else if (c->mode == 'r') + prefix = "The system is going down for reboot at "; + else + assert_not_reached("Unknown mode!"); + + if (asprintf(&l, "%s%s%s%s!", c->wall_message, c->wall_message[0] ? "\n" : "", + prefix, format_timestamp(date, sizeof(date), c->elapse)) < 0) + log_error("Failed to allocate wall message"); + else { + utmp_wall(l, NULL); + free(l); + } +} + +static usec_t when_wall(usec_t n, usec_t elapse) { + + static const struct { + usec_t delay; + usec_t interval; + } table[] = { + { 10 * USEC_PER_MINUTE, USEC_PER_MINUTE }, + { USEC_PER_HOUR, 15 * USEC_PER_MINUTE }, + { 3 * USEC_PER_HOUR, 30 * USEC_PER_MINUTE } + }; + + usec_t left, sub; + unsigned i; + + /* If the time is already passed, then don't announce */ + if (n >= elapse) + return 0; + + left = elapse - n; + for (i = 0; i < ELEMENTSOF(table); i++) + if (n + table[i].delay >= elapse) { + sub = ((left / table[i].interval) * table[i].interval); + break; + } + + if (i >= ELEMENTSOF(table)) + sub = ((left / USEC_PER_HOUR) * USEC_PER_HOUR); + + return elapse > sub ? elapse - sub : 1; +} + +static usec_t when_nologin(usec_t elapse) { + return elapse > 5*USEC_PER_MINUTE ? elapse - 5*USEC_PER_MINUTE : 1; +} + +int main(int argc, char *argv[]) { + enum { + FD_SOCKET, + FD_WALL_TIMER, + FD_NOLOGIN_TIMER, + FD_SHUTDOWN_TIMER, + _FD_MAX + }; + + int r = EXIT_FAILURE, n_fds; + struct shutdownd_command c; + struct pollfd pollfd[_FD_MAX]; + bool exec_shutdown = false, unlink_nologin = false, failed = false; + unsigned i; + + if (getppid() != 1) { + log_error("This program should be invoked by init only."); + return EXIT_FAILURE; + } + + if (argc > 1) { + log_error("This program does not take arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if ((n_fds = sd_listen_fds(true)) < 0) { + log_error("Failed to read listening file descriptors from environment: %s", strerror(-r)); + return EXIT_FAILURE; + } + + if (n_fds != 1) { + log_error("Need exactly one file descriptor."); + return EXIT_FAILURE; + } + + zero(c); + zero(pollfd); + + pollfd[FD_SOCKET].fd = SD_LISTEN_FDS_START; + pollfd[FD_SOCKET].events = POLLIN; + + for (i = 0; i < _FD_MAX; i++) { + + if (i == FD_SOCKET) + continue; + + pollfd[i].events = POLLIN; + + if ((pollfd[i].fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) { + log_error("timerfd_create(): %m"); + failed = true; + } + } + + if (failed) + goto finish; + + log_debug("systemd-shutdownd running as pid %lu", (unsigned long) getpid()); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + do { + int k; + usec_t n; + + if (poll(pollfd, _FD_MAX, -1) < 0) { + + if (errno == EAGAIN || errno == EINTR) + continue; + + log_error("poll(): %m"); + goto finish; + } + + n = now(CLOCK_REALTIME); + + if (pollfd[FD_SOCKET].revents) { + + if ((k = read_packet(pollfd[FD_SOCKET].fd, &c)) < 0) + goto finish; + else if (k > 0 && c.elapse > 0) { + struct itimerspec its; + char date[FORMAT_TIMESTAMP_MAX]; + + if (c.warn_wall) { + /* Send wall messages every so often */ + zero(its); + timespec_store(&its.it_value, when_wall(n, c.elapse)); + if (timerfd_settime(pollfd[FD_WALL_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { + log_error("timerfd_settime(): %m"); + goto finish; + } + + /* Warn immediately if less than 15 minutes are left */ + if (n < c.elapse && + n + 15*USEC_PER_MINUTE >= c.elapse) + warn_wall(n, &c); + } + + /* Disallow logins 5 minutes prior to shutdown */ + zero(its); + timespec_store(&its.it_value, when_nologin(c.elapse)); + if (timerfd_settime(pollfd[FD_NOLOGIN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { + log_error("timerfd_settime(): %m"); + goto finish; + } + + /* Shutdown after the specified time is reached */ + zero(its); + timespec_store(&its.it_value, c.elapse); + if (timerfd_settime(pollfd[FD_SHUTDOWN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { + log_error("timerfd_settime(): %m"); + goto finish; + } + + sd_notifyf(false, + "STATUS=Shutting down at %s...", + format_timestamp(date, sizeof(date), c.elapse)); + } + } + + if (pollfd[FD_WALL_TIMER].revents) { + struct itimerspec its; + + warn_wall(n, &c); + flush_fd(pollfd[FD_WALL_TIMER].fd); + + /* Restart timer */ + zero(its); + timespec_store(&its.it_value, when_wall(n, c.elapse)); + if (timerfd_settime(pollfd[FD_WALL_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { + log_error("timerfd_settime(): %m"); + goto finish; + } + } + + if (pollfd[FD_NOLOGIN_TIMER].revents) { + int e; + + log_info("Creating /run/nologin, blocking further logins..."); + + if ((e = write_one_line_file_atomic("/run/nologin", "System is going down.")) < 0) + log_error("Failed to create /run/nologin: %s", strerror(-e)); + else + unlink_nologin = true; + + flush_fd(pollfd[FD_NOLOGIN_TIMER].fd); + } + + if (pollfd[FD_SHUTDOWN_TIMER].revents) { + exec_shutdown = true; + goto finish; + } + + } while (c.elapse > 0); + + r = EXIT_SUCCESS; + + log_debug("systemd-shutdownd stopped as pid %lu", (unsigned long) getpid()); + +finish: + + for (i = 0; i < _FD_MAX; i++) + if (pollfd[i].fd >= 0) + close_nointr_nofail(pollfd[i].fd); + + if (unlink_nologin) + unlink("/run/nologin"); + + if (exec_shutdown && !c.dry_run) { + char sw[3]; + + sw[0] = '-'; + sw[1] = c.mode; + sw[2] = 0; + + execl(SYSTEMCTL_BINARY_PATH, + "shutdown", + sw, + "now", + (c.warn_wall && c.wall_message[0]) ? c.wall_message : + (c.warn_wall ? NULL : "--no-wall"), + NULL); + + log_error("Failed to execute /sbin/shutdown: %m"); + } + + sd_notify(false, + "STATUS=Exiting..."); + + return r; +} diff --git a/src/shutdownd.h b/src/shutdownd.h new file mode 100644 index 000000000..458164973 --- /dev/null +++ b/src/shutdownd.h @@ -0,0 +1,46 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooshutdowndhfoo +#define fooshutdowndhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "util.h" +#include "macro.h" + +/* This is a private message, we don't care much about ABI + * stability. */ + +_packed_ struct shutdownd_command { + usec_t elapse; + char mode; /* H, P, r, i.e. the switches usually passed to + * shutdown to select whether to halt, power-off or + * reboot the machine */ + bool dry_run; + bool warn_wall; + + /* Yepp, sometimes we are lazy and use fixed-size strings like + * this one. Shame on us. But then again, we'd have to + * pre-allocate the receive buffer anyway, so there's nothing + * too bad here. */ + char wall_message[4096]; +}; + +#endif diff --git a/src/snapshot.c b/src/snapshot.c new file mode 100644 index 000000000..82ec5104d --- /dev/null +++ b/src/snapshot.c @@ -0,0 +1,309 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" +#include "snapshot.h" +#include "unit-name.h" +#include "dbus-snapshot.h" +#include "bus-errors.h" + +static const UnitActiveState state_translation_table[_SNAPSHOT_STATE_MAX] = { + [SNAPSHOT_DEAD] = UNIT_INACTIVE, + [SNAPSHOT_ACTIVE] = UNIT_ACTIVE +}; + +static void snapshot_init(Unit *u) { + Snapshot *s = SNAPSHOT(u); + + assert(s); + assert(UNIT(s)->load_state == UNIT_STUB); + + UNIT(s)->ignore_on_isolate = true; + UNIT(s)->ignore_on_snapshot = true; +} + +static void snapshot_set_state(Snapshot *s, SnapshotState state) { + SnapshotState old_state; + assert(s); + + old_state = s->state; + s->state = state; + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(s)->id, + snapshot_state_to_string(old_state), + snapshot_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); +} + +static int snapshot_load(Unit *u) { + Snapshot *s = SNAPSHOT(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + /* Make sure that only snapshots created via snapshot_create() + * can be loaded */ + if (!s->by_snapshot_create && UNIT(s)->manager->n_reloading <= 0) + return -ENOENT; + + u->load_state = UNIT_LOADED; + return 0; +} + +static int snapshot_coldplug(Unit *u) { + Snapshot *s = SNAPSHOT(u); + + assert(s); + assert(s->state == SNAPSHOT_DEAD); + + if (s->deserialized_state != s->state) + snapshot_set_state(s, s->deserialized_state); + + return 0; +} + +static void snapshot_dump(Unit *u, FILE *f, const char *prefix) { + Snapshot *s = SNAPSHOT(u); + + assert(s); + assert(f); + + fprintf(f, + "%sSnapshot State: %s\n" + "%sClean Up: %s\n", + prefix, snapshot_state_to_string(s->state), + prefix, yes_no(s->cleanup)); +} + +static int snapshot_start(Unit *u) { + Snapshot *s = SNAPSHOT(u); + + assert(s); + assert(s->state == SNAPSHOT_DEAD); + + snapshot_set_state(s, SNAPSHOT_ACTIVE); + + if (s->cleanup) + unit_add_to_cleanup_queue(u); + + return 0; +} + +static int snapshot_stop(Unit *u) { + Snapshot *s = SNAPSHOT(u); + + assert(s); + assert(s->state == SNAPSHOT_ACTIVE); + + snapshot_set_state(s, SNAPSHOT_DEAD); + return 0; +} + +static int snapshot_serialize(Unit *u, FILE *f, FDSet *fds) { + Snapshot *s = SNAPSHOT(u); + Unit *other; + Iterator i; + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", snapshot_state_to_string(s->state)); + unit_serialize_item(u, f, "cleanup", yes_no(s->cleanup)); + SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) + unit_serialize_item(u, f, "wants", other->id); + + return 0; +} + +static int snapshot_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Snapshot *s = SNAPSHOT(u); + int r; + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + SnapshotState state; + + if ((state = snapshot_state_from_string(value)) < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + + } else if (streq(key, "cleanup")) { + + if ((r = parse_boolean(value)) < 0) + log_debug("Failed to parse cleanup value %s", value); + else + s->cleanup = r; + + } else if (streq(key, "wants")) { + + if ((r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, value, NULL, true)) < 0) + return r; + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState snapshot_active_state(Unit *u) { + assert(u); + + return state_translation_table[SNAPSHOT(u)->state]; +} + +static const char *snapshot_sub_state_to_string(Unit *u) { + assert(u); + + return snapshot_state_to_string(SNAPSHOT(u)->state); +} + +int snapshot_create(Manager *m, const char *name, bool cleanup, DBusError *e, Snapshot **_s) { + Iterator i; + Unit *other, *u = NULL; + char *n = NULL; + int r; + const char *k; + + assert(m); + assert(_s); + + if (name) { + if (!unit_name_is_valid(name, false)) { + dbus_set_error(e, BUS_ERROR_INVALID_NAME, "Unit name %s is not valid.", name); + return -EINVAL; + } + + if (unit_name_to_type(name) != UNIT_SNAPSHOT) { + dbus_set_error(e, BUS_ERROR_UNIT_TYPE_MISMATCH, "Unit name %s lacks snapshot suffix.", name); + return -EINVAL; + } + + if (manager_get_unit(m, name)) { + dbus_set_error(e, BUS_ERROR_UNIT_EXISTS, "Snapshot %s exists already.", name); + return -EEXIST; + } + + } else { + + for (;;) { + if (asprintf(&n, "snapshot-%u.snapshot", ++ m->n_snapshots) < 0) + return -ENOMEM; + + if (!manager_get_unit(m, n)) + break; + + free(n); + } + + name = n; + } + + r = manager_load_unit_prepare(m, name, NULL, e, &u); + free(n); + + if (r < 0) + goto fail; + + SNAPSHOT(u)->by_snapshot_create = true; + manager_dispatch_load_queue(m); + assert(u->load_state == UNIT_LOADED); + + HASHMAP_FOREACH_KEY(other, k, m->units, i) { + + if (other->ignore_on_snapshot) + continue; + + if (k != other->id) + continue; + + if (UNIT_VTABLE(other)->check_snapshot) + if (!UNIT_VTABLE(other)->check_snapshot(other)) + continue; + + if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + continue; + + if ((r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_WANTS, other, true)) < 0) + goto fail; + } + + SNAPSHOT(u)->cleanup = cleanup; + *_s = SNAPSHOT(u); + + return 0; + +fail: + if (u) + unit_add_to_cleanup_queue(u); + + return r; +} + +void snapshot_remove(Snapshot *s) { + assert(s); + + unit_add_to_cleanup_queue(UNIT(s)); +} + +static const char* const snapshot_state_table[_SNAPSHOT_STATE_MAX] = { + [SNAPSHOT_DEAD] = "dead", + [SNAPSHOT_ACTIVE] = "active" +}; + +DEFINE_STRING_TABLE_LOOKUP(snapshot_state, SnapshotState); + +const UnitVTable snapshot_vtable = { + .suffix = ".snapshot", + .object_size = sizeof(Snapshot), + + .no_alias = true, + .no_instances = true, + .no_gc = true, + + .init = snapshot_init, + + .load = snapshot_load, + .coldplug = snapshot_coldplug, + + .dump = snapshot_dump, + + .start = snapshot_start, + .stop = snapshot_stop, + + .serialize = snapshot_serialize, + .deserialize_item = snapshot_deserialize_item, + + .active_state = snapshot_active_state, + .sub_state_to_string = snapshot_sub_state_to_string, + + .bus_interface = "org.freedesktop.systemd1.Snapshot", + .bus_message_handler = bus_snapshot_message_handler +}; diff --git a/src/snapshot.h b/src/snapshot.h new file mode 100644 index 000000000..bf92e99a8 --- /dev/null +++ b/src/snapshot.h @@ -0,0 +1,53 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosnapshothfoo +#define foosnapshothfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Snapshot Snapshot; + +#include "unit.h" + +typedef enum SnapshotState { + SNAPSHOT_DEAD, + SNAPSHOT_ACTIVE, + _SNAPSHOT_STATE_MAX, + _SNAPSHOT_STATE_INVALID = -1 +} SnapshotState; + +struct Snapshot { + Unit meta; + + SnapshotState state, deserialized_state; + + bool cleanup; + bool by_snapshot_create:1; +}; + +extern const UnitVTable snapshot_vtable; + +int snapshot_create(Manager *m, const char *name, bool cleanup, DBusError *e, Snapshot **s); +void snapshot_remove(Snapshot *s); + +const char* snapshot_state_to_string(SnapshotState i); +SnapshotState snapshot_state_from_string(const char *s); + +#endif diff --git a/src/socket-util.c b/src/socket-util.c new file mode 100644 index 000000000..acc4d3337 --- /dev/null +++ b/src/socket-util.c @@ -0,0 +1,652 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "macro.h" +#include "util.h" +#include "socket-util.h" +#include "missing.h" +#include "label.h" + +int socket_address_parse(SocketAddress *a, const char *s) { + int r; + char *e, *n; + unsigned u; + + assert(a); + assert(s); + + zero(*a); + a->type = SOCK_STREAM; + + if (*s == '[') { + /* IPv6 in [x:.....:z]:p notation */ + + if (!socket_ipv6_is_supported()) { + log_warning("Binding to IPv6 address not available since kernel does not support IPv6."); + return -EAFNOSUPPORT; + } + + if (!(e = strchr(s+1, ']'))) + return -EINVAL; + + if (!(n = strndup(s+1, e-s-1))) + return -ENOMEM; + + errno = 0; + if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0) { + free(n); + return errno != 0 ? -errno : -EINVAL; + } + + free(n); + + e++; + if (*e != ':') + return -EINVAL; + + e++; + if ((r = safe_atou(e, &u)) < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + a->sockaddr.in6.sin6_family = AF_INET6; + a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->size = sizeof(struct sockaddr_in6); + + } else if (*s == '/') { + /* AF_UNIX socket */ + + size_t l; + + l = strlen(s); + if (l >= sizeof(a->sockaddr.un.sun_path)) + return -EINVAL; + + a->sockaddr.un.sun_family = AF_UNIX; + memcpy(a->sockaddr.un.sun_path, s, l); + a->size = offsetof(struct sockaddr_un, sun_path) + l + 1; + + } else if (*s == '@') { + /* Abstract AF_UNIX socket */ + size_t l; + + l = strlen(s+1); + if (l >= sizeof(a->sockaddr.un.sun_path) - 1) + return -EINVAL; + + a->sockaddr.un.sun_family = AF_UNIX; + memcpy(a->sockaddr.un.sun_path+1, s+1, l); + a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l; + + } else { + + if ((e = strchr(s, ':'))) { + + if ((r = safe_atou(e+1, &u)) < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + if (!(n = strndup(s, e-s))) + return -ENOMEM; + + /* IPv4 in w.x.y.z:p notation? */ + if ((r = inet_pton(AF_INET, n, &a->sockaddr.in4.sin_addr)) < 0) { + free(n); + return -errno; + } + + if (r > 0) { + /* Gotcha, it's a traditional IPv4 address */ + free(n); + + a->sockaddr.in4.sin_family = AF_INET; + a->sockaddr.in4.sin_port = htons((uint16_t) u); + a->size = sizeof(struct sockaddr_in); + } else { + unsigned idx; + + if (strlen(n) > IF_NAMESIZE-1) { + free(n); + return -EINVAL; + } + + /* Uh, our last resort, an interface name */ + idx = if_nametoindex(n); + free(n); + + if (idx == 0) + return -EINVAL; + + if (!socket_ipv6_is_supported()) { + log_warning("Binding to interface is not available since kernel does not support IPv6."); + return -EAFNOSUPPORT; + } + + a->sockaddr.in6.sin6_family = AF_INET6; + a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->sockaddr.in6.sin6_scope_id = idx; + a->sockaddr.in6.sin6_addr = in6addr_any; + a->size = sizeof(struct sockaddr_in6); + } + } else { + + /* Just a port */ + if ((r = safe_atou(s, &u)) < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + if (socket_ipv6_is_supported()) { + a->sockaddr.in6.sin6_family = AF_INET6; + a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->sockaddr.in6.sin6_addr = in6addr_any; + a->size = sizeof(struct sockaddr_in6); + } else { + a->sockaddr.in4.sin_family = AF_INET; + a->sockaddr.in4.sin_port = htons((uint16_t) u); + a->sockaddr.in4.sin_addr.s_addr = INADDR_ANY; + a->size = sizeof(struct sockaddr_in); + } + } + } + + return 0; +} + +int socket_address_parse_netlink(SocketAddress *a, const char *s) { + int family; + unsigned group = 0; + char* sfamily = NULL; + assert(a); + assert(s); + + zero(*a); + a->type = SOCK_RAW; + + errno = 0; + if (sscanf(s, "%ms %u", &sfamily, &group) < 1) + return errno ? -errno : -EINVAL; + + if ((family = netlink_family_from_string(sfamily)) < 0) + if (safe_atoi(sfamily, &family) < 0) { + free(sfamily); + return -EINVAL; + } + + free(sfamily); + + a->sockaddr.nl.nl_family = AF_NETLINK; + a->sockaddr.nl.nl_groups = group; + + a->type = SOCK_RAW; + a->size = sizeof(struct sockaddr_nl); + a->protocol = family; + + return 0; +} + +int socket_address_verify(const SocketAddress *a) { + assert(a); + + switch (socket_address_family(a)) { + + case AF_INET: + if (a->size != sizeof(struct sockaddr_in)) + return -EINVAL; + + if (a->sockaddr.in4.sin_port == 0) + return -EINVAL; + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + + case AF_INET6: + if (a->size != sizeof(struct sockaddr_in6)) + return -EINVAL; + + if (a->sockaddr.in6.sin6_port == 0) + return -EINVAL; + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + + case AF_UNIX: + if (a->size < offsetof(struct sockaddr_un, sun_path)) + return -EINVAL; + + if (a->size > offsetof(struct sockaddr_un, sun_path)) { + + if (a->sockaddr.un.sun_path[0] != 0) { + char *e; + + /* path */ + if (!(e = memchr(a->sockaddr.un.sun_path, 0, sizeof(a->sockaddr.un.sun_path)))) + return -EINVAL; + + if (a->size != offsetof(struct sockaddr_un, sun_path) + (e - a->sockaddr.un.sun_path) + 1) + return -EINVAL; + } + } + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM && a->type != SOCK_SEQPACKET) + return -EINVAL; + + return 0; + + case AF_NETLINK: + + if (a->size != sizeof(struct sockaddr_nl)) + return -EINVAL; + + if (a->type != SOCK_RAW && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + + default: + return -EAFNOSUPPORT; + } +} + +int socket_address_print(const SocketAddress *a, char **p) { + int r; + assert(a); + assert(p); + + if ((r = socket_address_verify(a)) < 0) + return r; + + switch (socket_address_family(a)) { + + case AF_INET: { + char *ret; + + if (!(ret = new(char, INET_ADDRSTRLEN+1+5+1))) + return -ENOMEM; + + if (!inet_ntop(AF_INET, &a->sockaddr.in4.sin_addr, ret, INET_ADDRSTRLEN)) { + free(ret); + return -errno; + } + + sprintf(strchr(ret, 0), ":%u", ntohs(a->sockaddr.in4.sin_port)); + *p = ret; + return 0; + } + + case AF_INET6: { + char *ret; + + if (!(ret = new(char, 1+INET6_ADDRSTRLEN+2+5+1))) + return -ENOMEM; + + ret[0] = '['; + if (!inet_ntop(AF_INET6, &a->sockaddr.in6.sin6_addr, ret+1, INET6_ADDRSTRLEN)) { + free(ret); + return -errno; + } + + sprintf(strchr(ret, 0), "]:%u", ntohs(a->sockaddr.in6.sin6_port)); + *p = ret; + return 0; + } + + case AF_UNIX: { + char *ret; + + if (a->size <= offsetof(struct sockaddr_un, sun_path)) { + + if (!(ret = strdup(""))) + return -ENOMEM; + + } else if (a->sockaddr.un.sun_path[0] == 0) { + /* abstract */ + + /* FIXME: We assume we can print the + * socket path here and that it hasn't + * more than one NUL byte. That is + * actually an invalid assumption */ + + if (!(ret = new(char, sizeof(a->sockaddr.un.sun_path)+1))) + return -ENOMEM; + + ret[0] = '@'; + memcpy(ret+1, a->sockaddr.un.sun_path+1, sizeof(a->sockaddr.un.sun_path)-1); + ret[sizeof(a->sockaddr.un.sun_path)] = 0; + + } else { + + if (!(ret = strdup(a->sockaddr.un.sun_path))) + return -ENOMEM; + } + + *p = ret; + return 0; + } + + case AF_NETLINK: { + const char *sfamily; + + if ((sfamily = netlink_family_to_string(a->protocol))) + r = asprintf(p, "%s %u", sfamily, a->sockaddr.nl.nl_groups); + else + r = asprintf(p, "%i %u", a->protocol, a->sockaddr.nl.nl_groups); + + if (r < 0) + return -ENOMEM; + + return 0; + } + + default: + return -EINVAL; + } +} + +int socket_address_listen( + const SocketAddress *a, + int backlog, + SocketAddressBindIPv6Only only, + const char *bind_to_device, + bool free_bind, + bool transparent, + mode_t directory_mode, + mode_t socket_mode, + const char *label, + int *ret) { + + int r, fd, one; + assert(a); + assert(ret); + + if ((r = socket_address_verify(a)) < 0) + return r; + + if (socket_address_family(a) == AF_INET6 && !socket_ipv6_is_supported()) + return -EAFNOSUPPORT; + + r = label_socket_set(label); + if (r < 0) + return r; + + fd = socket(socket_address_family(a), a->type | SOCK_NONBLOCK | SOCK_CLOEXEC, a->protocol); + r = fd < 0 ? -errno : 0; + + label_socket_clear(); + + if (r < 0) + return r; + + if (socket_address_family(a) == AF_INET6 && only != SOCKET_ADDRESS_DEFAULT) { + int flag = only == SOCKET_ADDRESS_IPV6_ONLY; + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) < 0) + goto fail; + } + + if (socket_address_family(a) == AF_INET || socket_address_family(a) == AF_INET6) { + if (bind_to_device) + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, bind_to_device, strlen(bind_to_device)+1) < 0) + goto fail; + + if (free_bind) { + one = 1; + if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0) + log_warning("IP_FREEBIND failed: %m"); + } + + if (transparent) { + one = 1; + if (setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &one, sizeof(one)) < 0) + log_warning("IP_TRANSPARENT failed: %m"); + } + } + + one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) + goto fail; + + if (socket_address_family(a) == AF_UNIX && a->sockaddr.un.sun_path[0] != 0) { + mode_t old_mask; + + /* Create parents */ + mkdir_parents(a->sockaddr.un.sun_path, directory_mode); + + /* Enforce the right access mode for the socket*/ + old_mask = umask(~ socket_mode); + + /* Include the original umask in our mask */ + umask(~socket_mode | old_mask); + + r = label_bind(fd, &a->sockaddr.sa, a->size); + + if (r < 0 && errno == EADDRINUSE) { + /* Unlink and try again */ + unlink(a->sockaddr.un.sun_path); + r = bind(fd, &a->sockaddr.sa, a->size); + } + + umask(old_mask); + } else + r = bind(fd, &a->sockaddr.sa, a->size); + + if (r < 0) + goto fail; + + if (socket_address_can_accept(a)) + if (listen(fd, backlog) < 0) + goto fail; + + *ret = fd; + return 0; + +fail: + r = -errno; + close_nointr_nofail(fd); + return r; +} + +bool socket_address_can_accept(const SocketAddress *a) { + assert(a); + + return + a->type == SOCK_STREAM || + a->type == SOCK_SEQPACKET; +} + +bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) { + assert(a); + assert(b); + + /* Invalid addresses are unequal to all */ + if (socket_address_verify(a) < 0 || + socket_address_verify(b) < 0) + return false; + + if (a->type != b->type) + return false; + + if (a->size != b->size) + return false; + + if (socket_address_family(a) != socket_address_family(b)) + return false; + + switch (socket_address_family(a)) { + + case AF_INET: + if (a->sockaddr.in4.sin_addr.s_addr != b->sockaddr.in4.sin_addr.s_addr) + return false; + + if (a->sockaddr.in4.sin_port != b->sockaddr.in4.sin_port) + return false; + + break; + + case AF_INET6: + if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0) + return false; + + if (a->sockaddr.in6.sin6_port != b->sockaddr.in6.sin6_port) + return false; + + break; + + case AF_UNIX: + + if ((a->sockaddr.un.sun_path[0] == 0) != (b->sockaddr.un.sun_path[0] == 0)) + return false; + + if (a->sockaddr.un.sun_path[0]) { + if (strncmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, sizeof(a->sockaddr.un.sun_path)) != 0) + return false; + } else { + if (memcmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, a->size) != 0) + return false; + } + + break; + + case AF_NETLINK: + + if (a->protocol != b->protocol) + return false; + + if (a->sockaddr.nl.nl_groups != b->sockaddr.nl.nl_groups) + return false; + + break; + + default: + /* Cannot compare, so we assume the addresses are different */ + return false; + } + + return true; +} + +bool socket_address_is(const SocketAddress *a, const char *s, int type) { + struct SocketAddress b; + + assert(a); + assert(s); + + if (socket_address_parse(&b, s) < 0) + return false; + + b.type = type; + + return socket_address_equal(a, &b); +} + +bool socket_address_is_netlink(const SocketAddress *a, const char *s) { + struct SocketAddress b; + + assert(a); + assert(s); + + if (socket_address_parse_netlink(&b, s) < 0) + return false; + + return socket_address_equal(a, &b); +} + +bool socket_address_needs_mount(const SocketAddress *a, const char *prefix) { + assert(a); + + if (socket_address_family(a) != AF_UNIX) + return false; + + if (a->sockaddr.un.sun_path[0] == 0) + return false; + + return path_startswith(a->sockaddr.un.sun_path, prefix); +} + +bool socket_ipv6_is_supported(void) { + char *l = 0; + bool enabled; + + if (access("/sys/module/ipv6", F_OK) != 0) + return 0; + + /* If we can't check "disable" parameter, assume enabled */ + if (read_one_line_file("/sys/module/ipv6/parameters/disable", &l) < 0) + return 1; + + /* If module was loaded with disable=1 no IPv6 available */ + enabled = l[0] == '0'; + free(l); + + return enabled; +} + +static const char* const netlink_family_table[] = { + [NETLINK_ROUTE] = "route", + [NETLINK_FIREWALL] = "firewall", + [NETLINK_INET_DIAG] = "inet-diag", + [NETLINK_NFLOG] = "nflog", + [NETLINK_XFRM] = "xfrm", + [NETLINK_SELINUX] = "selinux", + [NETLINK_ISCSI] = "iscsi", + [NETLINK_AUDIT] = "audit", + [NETLINK_FIB_LOOKUP] = "fib-lookup", + [NETLINK_CONNECTOR] = "connector", + [NETLINK_NETFILTER] = "netfilter", + [NETLINK_IP6_FW] = "ip6-fw", + [NETLINK_DNRTMSG] = "dnrtmsg", + [NETLINK_KOBJECT_UEVENT] = "kobject-uevent", + [NETLINK_GENERIC] = "generic", + [NETLINK_SCSITRANSPORT] = "scsitransport", + [NETLINK_ECRYPTFS] = "ecryptfs" +}; + +DEFINE_STRING_TABLE_LOOKUP(netlink_family, int); + +static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = { + [SOCKET_ADDRESS_DEFAULT] = "default", + [SOCKET_ADDRESS_BOTH] = "both", + [SOCKET_ADDRESS_IPV6_ONLY] = "ipv6-only" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); diff --git a/src/socket-util.h b/src/socket-util.h new file mode 100644 index 000000000..8ccbd371c --- /dev/null +++ b/src/socket-util.h @@ -0,0 +1,102 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosocketutilhfoo +#define foosocketutilhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include + +#include "macro.h" +#include "util.h" + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + struct sockaddr_un un; + struct sockaddr_nl nl; + struct sockaddr_storage storage; +}; + +typedef struct SocketAddress { + union sockaddr_union sockaddr; + + /* We store the size here explicitly due to the weird + * sockaddr_un semantics for abstract sockets */ + socklen_t size; + + /* Socket type, i.e. SOCK_STREAM, SOCK_DGRAM, ... */ + int type; + + /* Socket protocol, IPPROTO_xxx, usually 0, except for netlink */ + int protocol; +} SocketAddress; + +typedef enum SocketAddressBindIPv6Only { + SOCKET_ADDRESS_DEFAULT, + SOCKET_ADDRESS_BOTH, + SOCKET_ADDRESS_IPV6_ONLY, + _SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX, + _SOCKET_ADDRESS_BIND_IPV6_ONLY_INVALID = -1 +} SocketAddressBindIPv6Only; + +#define socket_address_family(a) ((a)->sockaddr.sa.sa_family) + +int socket_address_parse(SocketAddress *a, const char *s); +int socket_address_parse_netlink(SocketAddress *a, const char *s); +int socket_address_print(const SocketAddress *a, char **p); +int socket_address_verify(const SocketAddress *a); + +bool socket_address_can_accept(const SocketAddress *a); + +int socket_address_listen( + const SocketAddress *a, + int backlog, + SocketAddressBindIPv6Only only, + const char *bind_to_device, + bool free_bind, + bool transparent, + mode_t directory_mode, + mode_t socket_mode, + const char *label, + int *ret); + +bool socket_address_is(const SocketAddress *a, const char *s, int type); +bool socket_address_is_netlink(const SocketAddress *a, const char *s); + +bool socket_address_equal(const SocketAddress *a, const SocketAddress *b); + +bool socket_address_needs_mount(const SocketAddress *a, const char *prefix); + +const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b); +SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s); + +const char* netlink_family_to_string(int b); +int netlink_family_from_string(const char *s); + +bool socket_ipv6_is_supported(void); + +#endif diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 000000000..bb75d960a --- /dev/null +++ b/src/socket.c @@ -0,0 +1,2217 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unit.h" +#include "socket.h" +#include "netinet/tcp.h" +#include "log.h" +#include "load-dropin.h" +#include "load-fragment.h" +#include "strv.h" +#include "unit-name.h" +#include "dbus-socket.h" +#include "missing.h" +#include "special.h" +#include "bus-errors.h" +#include "label.h" +#include "exit-status.h" +#include "def.h" + +static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = { + [SOCKET_DEAD] = UNIT_INACTIVE, + [SOCKET_START_PRE] = UNIT_ACTIVATING, + [SOCKET_START_POST] = UNIT_ACTIVATING, + [SOCKET_LISTENING] = UNIT_ACTIVE, + [SOCKET_RUNNING] = UNIT_ACTIVE, + [SOCKET_STOP_PRE] = UNIT_DEACTIVATING, + [SOCKET_STOP_PRE_SIGTERM] = UNIT_DEACTIVATING, + [SOCKET_STOP_PRE_SIGKILL] = UNIT_DEACTIVATING, + [SOCKET_STOP_POST] = UNIT_DEACTIVATING, + [SOCKET_FINAL_SIGTERM] = UNIT_DEACTIVATING, + [SOCKET_FINAL_SIGKILL] = UNIT_DEACTIVATING, + [SOCKET_FAILED] = UNIT_FAILED +}; + +static void socket_init(Unit *u) { + Socket *s = SOCKET(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + s->backlog = SOMAXCONN; + s->timeout_usec = DEFAULT_TIMEOUT_USEC; + s->directory_mode = 0755; + s->socket_mode = 0666; + + s->max_connections = 64; + + s->priority = -1; + s->ip_tos = -1; + s->ip_ttl = -1; + s->mark = -1; + + exec_context_init(&s->exec_context); + s->exec_context.std_output = u->manager->default_std_output; + s->exec_context.std_error = u->manager->default_std_error; + + s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; +} + +static void socket_unwatch_control_pid(Socket *s) { + assert(s); + + if (s->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->control_pid); + s->control_pid = 0; +} + +static void socket_done(Unit *u) { + Socket *s = SOCKET(u); + SocketPort *p; + + assert(s); + + while ((p = s->ports)) { + LIST_REMOVE(SocketPort, port, s->ports, p); + + if (p->fd >= 0) { + unit_unwatch_fd(UNIT(s), &p->fd_watch); + close_nointr_nofail(p->fd); + } + + free(p->path); + free(p); + } + + exec_context_done(&s->exec_context); + exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX); + s->control_command = NULL; + + socket_unwatch_control_pid(s); + + unit_ref_unset(&s->service); + + free(s->tcp_congestion); + s->tcp_congestion = NULL; + + free(s->bind_to_device); + s->bind_to_device = NULL; + + unit_unwatch_timer(u, &s->timer_watch); +} + +static int socket_instantiate_service(Socket *s) { + char *prefix, *name; + int r; + Unit *u; + + assert(s); + + /* This fills in s->service if it isn't filled in yet. For + * Accept=yes sockets we create the next connection service + * here. For Accept=no this is mostly a NOP since the service + * is figured out at load time anyway. */ + + if (UNIT_DEREF(s->service)) + return 0; + + assert(s->accept); + + if (!(prefix = unit_name_to_prefix(UNIT(s)->id))) + return -ENOMEM; + + r = asprintf(&name, "%s@%u.service", prefix, s->n_accepted); + free(prefix); + + if (r < 0) + return -ENOMEM; + + r = manager_load_unit(UNIT(s)->manager, name, NULL, NULL, &u); + free(name); + + if (r < 0) + return r; + +#ifdef HAVE_SYSV_COMPAT + if (SERVICE(u)->sysv_path) { + log_error("Using SysV services for socket activation is not supported. Refusing."); + return -ENOENT; + } +#endif + + u->no_gc = true; + unit_ref_set(&s->service, u); + + return unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, u, false); +} + +static bool have_non_accept_socket(Socket *s) { + SocketPort *p; + + assert(s); + + if (!s->accept) + return true; + + LIST_FOREACH(port, p, s->ports) { + + if (p->type != SOCKET_SOCKET) + return true; + + if (!socket_address_can_accept(&p->address)) + return true; + } + + return false; +} + +static int socket_verify(Socket *s) { + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (!s->ports) { + log_error("%s lacks Listen setting. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->accept && have_non_accept_socket(s)) { + log_error("%s configured for accepting sockets, but sockets are non-accepting. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->accept && s->max_connections <= 0) { + log_error("%s's MaxConnection setting too small. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->accept && UNIT_DEREF(s->service)) { + log_error("Explicit service configuration for accepting sockets not supported on %s. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->exec_context.pam_name && s->exec_context.kill_mode != KILL_CONTROL_GROUP) { + log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + return 0; +} + +static bool socket_needs_mount(Socket *s, const char *prefix) { + SocketPort *p; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + + if (p->type == SOCKET_SOCKET) { + if (socket_address_needs_mount(&p->address, prefix)) + return true; + } else if (p->type == SOCKET_FIFO || p->type == SOCKET_SPECIAL) { + if (path_startswith(p->path, prefix)) + return true; + } + } + + return false; +} + +int socket_add_one_mount_link(Socket *s, Mount *m) { + int r; + + assert(s); + assert(m); + + if (UNIT(s)->load_state != UNIT_LOADED || + UNIT(m)->load_state != UNIT_LOADED) + return 0; + + if (!socket_needs_mount(s, m->where)) + return 0; + + if ((r = unit_add_two_dependencies(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0) + return r; + + return 0; +} + +static int socket_add_mount_links(Socket *s) { + Unit *other; + int r; + + assert(s); + + LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_MOUNT]) + if ((r = socket_add_one_mount_link(s, MOUNT(other))) < 0) + return r; + + return 0; +} + +static int socket_add_device_link(Socket *s) { + char *t; + int r; + + assert(s); + + if (!s->bind_to_device) + return 0; + + if (asprintf(&t, "/sys/subsystem/net/devices/%s", s->bind_to_device) < 0) + return -ENOMEM; + + r = unit_add_node_link(UNIT(s), t, false); + free(t); + + return r; +} + +static int socket_add_default_dependencies(Socket *s) { + int r; + assert(s); + + if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { + if ((r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SOCKETS_TARGET, NULL, true)) < 0) + return r; + + if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0) + return r; + } + + return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static bool socket_has_exec(Socket *s) { + unsigned i; + assert(s); + + for (i = 0; i < _SOCKET_EXEC_COMMAND_MAX; i++) + if (s->exec_command[i]) + return true; + + return false; +} + +static int socket_load(Unit *u) { + Socket *s = SOCKET(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + if ((r = unit_load_fragment_and_dropin(u)) < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + + if (have_non_accept_socket(s)) { + + if (!UNIT_DEREF(s->service)) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + unit_ref_set(&s->service, x); + } + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(s->service), true); + if (r < 0) + return r; + } + + if ((r = socket_add_mount_links(s)) < 0) + return r; + + if ((r = socket_add_device_link(s)) < 0) + return r; + + if (socket_has_exec(s)) + if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0) + return r; + + if ((r = unit_add_default_cgroups(u)) < 0) + return r; + + if (UNIT(s)->default_dependencies) + if ((r = socket_add_default_dependencies(s)) < 0) + return r; + } + + return socket_verify(s); +} + +static const char* listen_lookup(int family, int type) { + + if (family == AF_NETLINK) + return "ListenNetlink"; + + if (type == SOCK_STREAM) + return "ListenStream"; + else if (type == SOCK_DGRAM) + return "ListenDatagram"; + else if (type == SOCK_SEQPACKET) + return "ListenSequentialPacket"; + + assert_not_reached("Unknown socket type"); + return NULL; +} + +static void socket_dump(Unit *u, FILE *f, const char *prefix) { + + SocketExecCommand c; + Socket *s = SOCKET(u); + SocketPort *p; + const char *prefix2; + char *p2; + + assert(s); + assert(f); + + p2 = strappend(prefix, "\t"); + prefix2 = p2 ? p2 : prefix; + + fprintf(f, + "%sSocket State: %s\n" + "%sResult: %s\n" + "%sBindIPv6Only: %s\n" + "%sBacklog: %u\n" + "%sSocketMode: %04o\n" + "%sDirectoryMode: %04o\n" + "%sKeepAlive: %s\n" + "%sFreeBind: %s\n" + "%sTransparent: %s\n" + "%sBroadcast: %s\n" + "%sPassCredentials: %s\n" + "%sPassSecurity: %s\n" + "%sTCPCongestion: %s\n", + prefix, socket_state_to_string(s->state), + prefix, socket_result_to_string(s->result), + prefix, socket_address_bind_ipv6_only_to_string(s->bind_ipv6_only), + prefix, s->backlog, + prefix, s->socket_mode, + prefix, s->directory_mode, + prefix, yes_no(s->keep_alive), + prefix, yes_no(s->free_bind), + prefix, yes_no(s->transparent), + prefix, yes_no(s->broadcast), + prefix, yes_no(s->pass_cred), + prefix, yes_no(s->pass_sec), + prefix, strna(s->tcp_congestion)); + + if (s->control_pid > 0) + fprintf(f, + "%sControl PID: %lu\n", + prefix, (unsigned long) s->control_pid); + + if (s->bind_to_device) + fprintf(f, + "%sBindToDevice: %s\n", + prefix, s->bind_to_device); + + if (s->accept) + fprintf(f, + "%sAccepted: %u\n" + "%sNConnections: %u\n" + "%sMaxConnections: %u\n", + prefix, s->n_accepted, + prefix, s->n_connections, + prefix, s->max_connections); + + if (s->priority >= 0) + fprintf(f, + "%sPriority: %i\n", + prefix, s->priority); + + if (s->receive_buffer > 0) + fprintf(f, + "%sReceiveBuffer: %zu\n", + prefix, s->receive_buffer); + + if (s->send_buffer > 0) + fprintf(f, + "%sSendBuffer: %zu\n", + prefix, s->send_buffer); + + if (s->ip_tos >= 0) + fprintf(f, + "%sIPTOS: %i\n", + prefix, s->ip_tos); + + if (s->ip_ttl >= 0) + fprintf(f, + "%sIPTTL: %i\n", + prefix, s->ip_ttl); + + if (s->pipe_size > 0) + fprintf(f, + "%sPipeSize: %zu\n", + prefix, s->pipe_size); + + if (s->mark >= 0) + fprintf(f, + "%sMark: %i\n", + prefix, s->mark); + + if (s->mq_maxmsg > 0) + fprintf(f, + "%sMessageQueueMaxMessages: %li\n", + prefix, s->mq_maxmsg); + + if (s->mq_msgsize > 0) + fprintf(f, + "%sMessageQueueMessageSize: %li\n", + prefix, s->mq_msgsize); + + LIST_FOREACH(port, p, s->ports) { + + if (p->type == SOCKET_SOCKET) { + const char *t; + int r; + char *k = NULL; + + if ((r = socket_address_print(&p->address, &k)) < 0) + t = strerror(-r); + else + t = k; + + fprintf(f, "%s%s: %s\n", prefix, listen_lookup(socket_address_family(&p->address), p->address.type), t); + free(k); + } else if (p->type == SOCKET_SPECIAL) + fprintf(f, "%sListenSpecial: %s\n", prefix, p->path); + else if (p->type == SOCKET_MQUEUE) + fprintf(f, "%sListenMessageQueue: %s\n", prefix, p->path); + else + fprintf(f, "%sListenFIFO: %s\n", prefix, p->path); + } + + exec_context_dump(&s->exec_context, f, prefix); + + for (c = 0; c < _SOCKET_EXEC_COMMAND_MAX; c++) { + if (!s->exec_command[c]) + continue; + + fprintf(f, "%s-> %s:\n", + prefix, socket_exec_command_to_string(c)); + + exec_command_dump_list(s->exec_command[c], f, prefix2); + } + + free(p2); +} + +static int instance_from_socket(int fd, unsigned nr, char **instance) { + socklen_t l; + char *r; + union { + struct sockaddr sa; + struct sockaddr_un un; + struct sockaddr_in in; + struct sockaddr_in6 in6; + struct sockaddr_storage storage; + } local, remote; + + assert(fd >= 0); + assert(instance); + + l = sizeof(local); + if (getsockname(fd, &local.sa, &l) < 0) + return -errno; + + l = sizeof(remote); + if (getpeername(fd, &remote.sa, &l) < 0) + return -errno; + + switch (local.sa.sa_family) { + + case AF_INET: { + uint32_t + a = ntohl(local.in.sin_addr.s_addr), + b = ntohl(remote.in.sin_addr.s_addr); + + if (asprintf(&r, + "%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", + nr, + a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF, + ntohs(local.in.sin_port), + b >> 24, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF, + ntohs(remote.in.sin_port)) < 0) + return -ENOMEM; + + break; + } + + case AF_INET6: { + static const char ipv4_prefix[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF + }; + + if (memcmp(&local.in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0 && + memcmp(&remote.in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0) { + const uint8_t + *a = local.in6.sin6_addr.s6_addr+12, + *b = remote.in6.sin6_addr.s6_addr+12; + + if (asprintf(&r, + "%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", + nr, + a[0], a[1], a[2], a[3], + ntohs(local.in6.sin6_port), + b[0], b[1], b[2], b[3], + ntohs(remote.in6.sin6_port)) < 0) + return -ENOMEM; + } else { + char a[INET6_ADDRSTRLEN], b[INET6_ADDRSTRLEN]; + + if (asprintf(&r, + "%u-%s:%u-%s:%u", + nr, + inet_ntop(AF_INET6, &local.in6.sin6_addr, a, sizeof(a)), + ntohs(local.in6.sin6_port), + inet_ntop(AF_INET6, &remote.in6.sin6_addr, b, sizeof(b)), + ntohs(remote.in6.sin6_port)) < 0) + return -ENOMEM; + } + + break; + } + + case AF_UNIX: { + struct ucred ucred; + + l = sizeof(ucred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0) + return -errno; + + if (asprintf(&r, + "%u-%lu-%lu", + nr, + (unsigned long) ucred.pid, + (unsigned long) ucred.uid) < 0) + return -ENOMEM; + + break; + } + + default: + assert_not_reached("Unhandled socket type."); + } + + *instance = r; + return 0; +} + +static void socket_close_fds(Socket *s) { + SocketPort *p; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + continue; + + unit_unwatch_fd(UNIT(s), &p->fd_watch); + close_nointr_nofail(p->fd); + + /* One little note: we should never delete any sockets + * in the file system here! After all some other + * process we spawned might still have a reference of + * this fd and wants to continue to use it. Therefore + * we delete sockets in the file system before we + * create a new one, not after we stopped using + * one! */ + + p->fd = -1; + } +} + +static void socket_apply_socket_options(Socket *s, int fd) { + assert(s); + assert(fd >= 0); + + if (s->keep_alive) { + int b = s->keep_alive; + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &b, sizeof(b)) < 0) + log_warning("SO_KEEPALIVE failed: %m"); + } + + if (s->broadcast) { + int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one)) < 0) + log_warning("SO_BROADCAST failed: %m"); + } + + if (s->pass_cred) { + int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) + log_warning("SO_PASSCRED failed: %m"); + } + + if (s->pass_sec) { + int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)) < 0) + log_warning("SO_PASSSEC failed: %m"); + } + + if (s->priority >= 0) + if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &s->priority, sizeof(s->priority)) < 0) + log_warning("SO_PRIORITY failed: %m"); + + if (s->receive_buffer > 0) { + int value = (int) s->receive_buffer; + + /* We first try with SO_RCVBUFFORCE, in case we have the perms for that */ + + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0) + log_warning("SO_RCVBUF failed: %m"); + } + + if (s->send_buffer > 0) { + int value = (int) s->send_buffer; + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0) + log_warning("SO_SNDBUF failed: %m"); + } + + if (s->mark >= 0) + if (setsockopt(fd, SOL_SOCKET, SO_MARK, &s->mark, sizeof(s->mark)) < 0) + log_warning("SO_MARK failed: %m"); + + if (s->ip_tos >= 0) + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &s->ip_tos, sizeof(s->ip_tos)) < 0) + log_warning("IP_TOS failed: %m"); + + if (s->ip_ttl >= 0) { + int r, x; + + r = setsockopt(fd, IPPROTO_IP, IP_TTL, &s->ip_ttl, sizeof(s->ip_ttl)); + + if (socket_ipv6_is_supported()) + x = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &s->ip_ttl, sizeof(s->ip_ttl)); + else { + x = -1; + errno = EAFNOSUPPORT; + } + + if (r < 0 && x < 0) + log_warning("IP_TTL/IPV6_UNICAST_HOPS failed: %m"); + } + + if (s->tcp_congestion) + if (setsockopt(fd, SOL_TCP, TCP_CONGESTION, s->tcp_congestion, strlen(s->tcp_congestion)+1) < 0) + log_warning("TCP_CONGESTION failed: %m"); +} + +static void socket_apply_fifo_options(Socket *s, int fd) { + assert(s); + assert(fd >= 0); + + if (s->pipe_size > 0) + if (fcntl(fd, F_SETPIPE_SZ, s->pipe_size) < 0) + log_warning("F_SETPIPE_SZ: %m"); +} + +static int fifo_address_create( + const char *path, + mode_t directory_mode, + mode_t socket_mode, + int *_fd) { + + int fd = -1, r = 0; + struct stat st; + mode_t old_mask; + + assert(path); + assert(_fd); + + mkdir_parents(path, directory_mode); + + if ((r = label_fifofile_set(path)) < 0) + goto fail; + + /* Enforce the right access mode for the fifo */ + old_mask = umask(~ socket_mode); + + /* Include the original umask in our mask */ + umask(~socket_mode | old_mask); + + r = mkfifo(path, socket_mode); + umask(old_mask); + + if (r < 0 && errno != EEXIST) { + r = -errno; + goto fail; + } + + if ((fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW)) < 0) { + r = -errno; + goto fail; + } + + label_file_clear(); + + if (fstat(fd, &st) < 0) { + r = -errno; + goto fail; + } + + if (!S_ISFIFO(st.st_mode) || + (st.st_mode & 0777) != (socket_mode & ~old_mask) || + st.st_uid != getuid() || + st.st_gid != getgid()) { + + r = -EEXIST; + goto fail; + } + + *_fd = fd; + return 0; + +fail: + label_file_clear(); + + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +static int special_address_create( + const char *path, + int *_fd) { + + int fd = -1, r = 0; + struct stat st; + + assert(path); + assert(_fd); + + if ((fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW)) < 0) { + r = -errno; + goto fail; + } + + if (fstat(fd, &st) < 0) { + r = -errno; + goto fail; + } + + /* Check whether this is a /proc, /sys or /dev file or char device */ + if (!S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode)) { + r = -EEXIST; + goto fail; + } + + *_fd = fd; + return 0; + +fail: + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +static int mq_address_create( + const char *path, + mode_t mq_mode, + long maxmsg, + long msgsize, + int *_fd) { + + int fd = -1, r = 0; + struct stat st; + mode_t old_mask; + struct mq_attr _attr, *attr = NULL; + + assert(path); + assert(_fd); + + if (maxmsg > 0 && msgsize > 0) { + zero(_attr); + _attr.mq_flags = O_NONBLOCK; + _attr.mq_maxmsg = maxmsg; + _attr.mq_msgsize = msgsize; + attr = &_attr; + } + + /* Enforce the right access mode for the mq */ + old_mask = umask(~ mq_mode); + + /* Include the original umask in our mask */ + umask(~mq_mode | old_mask); + + fd = mq_open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_CREAT, mq_mode, attr); + umask(old_mask); + + if (fd < 0) { + r = -errno; + goto fail; + } + + if (fstat(fd, &st) < 0) { + r = -errno; + goto fail; + } + + if ((st.st_mode & 0777) != (mq_mode & ~old_mask) || + st.st_uid != getuid() || + st.st_gid != getgid()) { + + r = -EEXIST; + goto fail; + } + + *_fd = fd; + return 0; + +fail: + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +static int socket_open_fds(Socket *s) { + SocketPort *p; + int r; + char *label = NULL; + bool know_label = false; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + + if (p->fd >= 0) + continue; + + if (p->type == SOCKET_SOCKET) { + + if (!know_label) { + + if ((r = socket_instantiate_service(s)) < 0) + return r; + + if (UNIT_DEREF(s->service) && + SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]) { + r = label_get_create_label_from_exe(SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]->path, &label); + + if (r < 0) { + if (r != -EPERM) + return r; + } + } + + know_label = true; + } + + if ((r = socket_address_listen( + &p->address, + s->backlog, + s->bind_ipv6_only, + s->bind_to_device, + s->free_bind, + s->transparent, + s->directory_mode, + s->socket_mode, + label, + &p->fd)) < 0) + goto rollback; + + socket_apply_socket_options(s, p->fd); + + } else if (p->type == SOCKET_SPECIAL) { + + if ((r = special_address_create( + p->path, + &p->fd)) < 0) + goto rollback; + + } else if (p->type == SOCKET_FIFO) { + + if ((r = fifo_address_create( + p->path, + s->directory_mode, + s->socket_mode, + &p->fd)) < 0) + goto rollback; + + socket_apply_fifo_options(s, p->fd); + } else if (p->type == SOCKET_MQUEUE) { + + if ((r = mq_address_create( + p->path, + s->socket_mode, + s->mq_maxmsg, + s->mq_msgsize, + &p->fd)) < 0) + goto rollback; + } else + assert_not_reached("Unknown port type"); + } + + label_free(label); + return 0; + +rollback: + socket_close_fds(s); + label_free(label); + return r; +} + +static void socket_unwatch_fds(Socket *s) { + SocketPort *p; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + continue; + + unit_unwatch_fd(UNIT(s), &p->fd_watch); + } +} + +static int socket_watch_fds(Socket *s) { + SocketPort *p; + int r; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + continue; + + p->fd_watch.socket_accept = + s->accept && + p->type == SOCKET_SOCKET && + socket_address_can_accept(&p->address); + + if ((r = unit_watch_fd(UNIT(s), p->fd, EPOLLIN, &p->fd_watch)) < 0) + goto fail; + } + + return 0; + +fail: + socket_unwatch_fds(s); + return r; +} + +static void socket_set_state(Socket *s, SocketState state) { + SocketState old_state; + assert(s); + + old_state = s->state; + s->state = state; + + if (state != SOCKET_START_PRE && + state != SOCKET_START_POST && + state != SOCKET_STOP_PRE && + state != SOCKET_STOP_PRE_SIGTERM && + state != SOCKET_STOP_PRE_SIGKILL && + state != SOCKET_STOP_POST && + state != SOCKET_FINAL_SIGTERM && + state != SOCKET_FINAL_SIGKILL) { + unit_unwatch_timer(UNIT(s), &s->timer_watch); + socket_unwatch_control_pid(s); + s->control_command = NULL; + s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; + } + + if (state != SOCKET_LISTENING) + socket_unwatch_fds(s); + + if (state != SOCKET_START_POST && + state != SOCKET_LISTENING && + state != SOCKET_RUNNING && + state != SOCKET_STOP_PRE && + state != SOCKET_STOP_PRE_SIGTERM && + state != SOCKET_STOP_PRE_SIGKILL) + socket_close_fds(s); + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(s)->id, + socket_state_to_string(old_state), + socket_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); +} + +static int socket_coldplug(Unit *u) { + Socket *s = SOCKET(u); + int r; + + assert(s); + assert(s->state == SOCKET_DEAD); + + if (s->deserialized_state != s->state) { + + if (s->deserialized_state == SOCKET_START_PRE || + s->deserialized_state == SOCKET_START_POST || + s->deserialized_state == SOCKET_STOP_PRE || + s->deserialized_state == SOCKET_STOP_PRE_SIGTERM || + s->deserialized_state == SOCKET_STOP_PRE_SIGKILL || + s->deserialized_state == SOCKET_STOP_POST || + s->deserialized_state == SOCKET_FINAL_SIGTERM || + s->deserialized_state == SOCKET_FINAL_SIGKILL) { + + if (s->control_pid <= 0) + return -EBADMSG; + + if ((r = unit_watch_pid(UNIT(s), s->control_pid)) < 0) + return r; + + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + return r; + } + + if (s->deserialized_state == SOCKET_START_POST || + s->deserialized_state == SOCKET_LISTENING || + s->deserialized_state == SOCKET_RUNNING || + s->deserialized_state == SOCKET_STOP_PRE || + s->deserialized_state == SOCKET_STOP_PRE_SIGTERM || + s->deserialized_state == SOCKET_STOP_PRE_SIGKILL) + if ((r = socket_open_fds(s)) < 0) + return r; + + if (s->deserialized_state == SOCKET_LISTENING) + if ((r = socket_watch_fds(s)) < 0) + return r; + + socket_set_state(s, s->deserialized_state); + } + + return 0; +} + +static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { + pid_t pid; + int r; + char **argv; + + assert(s); + assert(c); + assert(_pid); + + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; + + if (!(argv = unit_full_printf_strv(UNIT(s), c->argv))) { + r = -ENOMEM; + goto fail; + } + + r = exec_spawn(c, + argv, + &s->exec_context, + NULL, 0, + UNIT(s)->manager->environment, + true, + true, + true, + UNIT(s)->manager->confirm_spawn, + UNIT(s)->cgroup_bondings, + UNIT(s)->cgroup_attributes, + &pid); + + strv_free(argv); + if (r < 0) + goto fail; + + if ((r = unit_watch_pid(UNIT(s), pid)) < 0) + /* FIXME: we need to do something here */ + goto fail; + + *_pid = pid; + + return 0; + +fail: + unit_unwatch_timer(UNIT(s), &s->timer_watch); + + return r; +} + +static void socket_enter_dead(Socket *s, SocketResult f) { + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD); +} + +static void socket_enter_signal(Socket *s, SocketState state, SocketResult f); + +static void socket_enter_stop_post(Socket *s, SocketResult f) { + int r; + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + socket_unwatch_control_pid(s); + + s->control_command_id = SOCKET_EXEC_STOP_POST; + + if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST])) { + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) + goto fail; + + socket_set_state(s, SOCKET_STOP_POST); + } else + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_SUCCESS); + + return; + +fail: + log_warning("%s failed to run 'stop-post' task: %s", UNIT(s)->id, strerror(-r)); + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_signal(Socket *s, SocketState state, SocketResult f) { + int r; + Set *pid_set = NULL; + bool wait_for_exit = false; + + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + if (s->exec_context.kill_mode != KILL_NONE) { + int sig = (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_FINAL_SIGTERM) ? s->exec_context.kill_signal : SIGKILL; + + if (s->control_pid > 0) { + if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH) + + log_warning("Failed to kill control process %li: %m", (long) s->control_pid); + else + wait_for_exit = true; + } + + if (s->exec_context.kill_mode == KILL_CONTROL_GROUP) { + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) { + r = -ENOMEM; + goto fail; + } + + /* Exclude the control pid from being killed via the cgroup */ + if (s->control_pid > 0) + if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) + goto fail; + + if ((r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, pid_set)) < 0) { + if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) + log_warning("Failed to kill control group: %s", strerror(-r)); + } else if (r > 0) + wait_for_exit = true; + + set_free(pid_set); + pid_set = NULL; + } + } + + if (wait_for_exit) { + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; + + socket_set_state(s, state); + } else if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL) + socket_enter_stop_post(s, SOCKET_SUCCESS); + else + socket_enter_dead(s, SOCKET_SUCCESS); + + return; + +fail: + log_warning("%s failed to kill processes: %s", UNIT(s)->id, strerror(-r)); + + if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL) + socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES); + else + socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); + + if (pid_set) + set_free(pid_set); +} + +static void socket_enter_stop_pre(Socket *s, SocketResult f) { + int r; + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + socket_unwatch_control_pid(s); + + s->control_command_id = SOCKET_EXEC_STOP_PRE; + + if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE])) { + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) + goto fail; + + socket_set_state(s, SOCKET_STOP_PRE); + } else + socket_enter_stop_post(s, SOCKET_SUCCESS); + + return; + +fail: + log_warning("%s failed to run 'stop-pre' task: %s", UNIT(s)->id, strerror(-r)); + socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_listening(Socket *s) { + int r; + assert(s); + + r = socket_watch_fds(s); + if (r < 0) { + log_warning("%s failed to watch sockets: %s", UNIT(s)->id, strerror(-r)); + goto fail; + } + + socket_set_state(s, SOCKET_LISTENING); + return; + +fail: + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_start_post(Socket *s) { + int r; + assert(s); + + r = socket_open_fds(s); + if (r < 0) { + log_warning("%s failed to listen on sockets: %s", UNIT(s)->id, strerror(-r)); + goto fail; + } + + socket_unwatch_control_pid(s); + + s->control_command_id = SOCKET_EXEC_START_POST; + + if ((s->control_command = s->exec_command[SOCKET_EXEC_START_POST])) { + r = socket_spawn(s, s->control_command, &s->control_pid); + if (r < 0) { + log_warning("%s failed to run 'start-post' task: %s", UNIT(s)->id, strerror(-r)); + goto fail; + } + + socket_set_state(s, SOCKET_START_POST); + } else + socket_enter_listening(s); + + return; + +fail: + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_start_pre(Socket *s) { + int r; + assert(s); + + socket_unwatch_control_pid(s); + + s->control_command_id = SOCKET_EXEC_START_PRE; + + if ((s->control_command = s->exec_command[SOCKET_EXEC_START_PRE])) { + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) + goto fail; + + socket_set_state(s, SOCKET_START_PRE); + } else + socket_enter_start_post(s); + + return; + +fail: + log_warning("%s failed to run 'start-pre' task: %s", UNIT(s)->id, strerror(-r)); + socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_running(Socket *s, int cfd) { + int r; + DBusError error; + + assert(s); + dbus_error_init(&error); + + /* We don't take connections anymore if we are supposed to + * shut down anyway */ + if (unit_pending_inactive(UNIT(s))) { + log_debug("Suppressing connection request on %s since unit stop is scheduled.", UNIT(s)->id); + + if (cfd >= 0) + close_nointr_nofail(cfd); + else { + /* Flush all sockets by closing and reopening them */ + socket_close_fds(s); + + r = socket_watch_fds(s); + if (r < 0) { + log_warning("%s failed to watch sockets: %s", UNIT(s)->id, strerror(-r)); + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + } + } + + return; + } + + if (cfd < 0) { + Iterator i; + Unit *u; + bool pending = false; + + /* If there's already a start pending don't bother to + * do anything */ + SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERS], i) + if (unit_pending_active(u)) { + pending = true; + break; + } + + if (!pending) { + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_REPLACE, true, &error, NULL); + if (r < 0) + goto fail; + } + + socket_set_state(s, SOCKET_RUNNING); + } else { + char *prefix, *instance = NULL, *name; + Service *service; + + if (s->n_connections >= s->max_connections) { + log_warning("Too many incoming connections (%u)", s->n_connections); + close_nointr_nofail(cfd); + return; + } + + r = socket_instantiate_service(s); + if (r < 0) + goto fail; + + r = instance_from_socket(cfd, s->n_accepted, &instance); + if (r < 0) { + if (r != -ENOTCONN) + goto fail; + + /* ENOTCONN is legitimate if TCP RST was received. + * This connection is over, but the socket unit lives on. */ + close_nointr_nofail(cfd); + return; + } + + prefix = unit_name_to_prefix(UNIT(s)->id); + if (!prefix) { + free(instance); + r = -ENOMEM; + goto fail; + } + + name = unit_name_build(prefix, instance, ".service"); + free(prefix); + free(instance); + + if (!name) { + r = -ENOMEM; + goto fail; + } + + r = unit_add_name(UNIT_DEREF(s->service), name); + if (r < 0) { + free(name); + goto fail; + } + + service = SERVICE(UNIT_DEREF(s->service)); + unit_ref_unset(&s->service); + s->n_accepted ++; + + UNIT(service)->no_gc = false; + + unit_choose_id(UNIT(service), name); + free(name); + + r = service_set_socket_fd(service, cfd, s); + if (r < 0) + goto fail; + + cfd = -1; + s->n_connections ++; + + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, true, &error, NULL); + if (r < 0) + goto fail; + + /* Notify clients about changed counters */ + unit_add_to_dbus_queue(UNIT(s)); + } + + return; + +fail: + log_warning("%s failed to queue socket startup job: %s", UNIT(s)->id, bus_error(&error, r)); + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + + if (cfd >= 0) + close_nointr_nofail(cfd); + + dbus_error_free(&error); +} + +static void socket_run_next(Socket *s) { + int r; + + assert(s); + assert(s->control_command); + assert(s->control_command->command_next); + + socket_unwatch_control_pid(s); + + s->control_command = s->control_command->command_next; + + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) + goto fail; + + return; + +fail: + log_warning("%s failed to run next task: %s", UNIT(s)->id, strerror(-r)); + + if (s->state == SOCKET_START_POST) + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + else if (s->state == SOCKET_STOP_POST) + socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); + else + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); +} + +static int socket_start(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (s->state == SOCKET_STOP_PRE || + s->state == SOCKET_STOP_PRE_SIGKILL || + s->state == SOCKET_STOP_PRE_SIGTERM || + s->state == SOCKET_STOP_POST || + s->state == SOCKET_FINAL_SIGTERM || + s->state == SOCKET_FINAL_SIGKILL) + return -EAGAIN; + + if (s->state == SOCKET_START_PRE || + s->state == SOCKET_START_POST) + return 0; + + /* Cannot run this without the service being around */ + if (UNIT_DEREF(s->service)) { + Service *service; + + service = SERVICE(UNIT_DEREF(s->service)); + + if (UNIT(service)->load_state != UNIT_LOADED) { + log_error("Socket service %s not loaded, refusing.", UNIT(service)->id); + return -ENOENT; + } + + /* If the service is already active we cannot start the + * socket */ + if (service->state != SERVICE_DEAD && + service->state != SERVICE_FAILED && + service->state != SERVICE_AUTO_RESTART) { + log_error("Socket service %s already active, refusing.", UNIT(service)->id); + return -EBUSY; + } + +#ifdef HAVE_SYSV_COMPAT + if (service->sysv_path) { + log_error("Using SysV services for socket activation is not supported. Refusing."); + return -ENOENT; + } +#endif + } + + assert(s->state == SOCKET_DEAD || s->state == SOCKET_FAILED); + + s->result = SOCKET_SUCCESS; + socket_enter_start_pre(s); + return 0; +} + +static int socket_stop(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + /* Already on it */ + if (s->state == SOCKET_STOP_PRE || + s->state == SOCKET_STOP_PRE_SIGTERM || + s->state == SOCKET_STOP_PRE_SIGKILL || + s->state == SOCKET_STOP_POST || + s->state == SOCKET_FINAL_SIGTERM || + s->state == SOCKET_FINAL_SIGKILL) + return 0; + + /* If there's already something running we go directly into + * kill mode. */ + if (s->state == SOCKET_START_PRE || + s->state == SOCKET_START_POST) { + socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_SUCCESS); + return -EAGAIN; + } + + assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING); + + socket_enter_stop_pre(s, SOCKET_SUCCESS); + return 0; +} + +static int socket_serialize(Unit *u, FILE *f, FDSet *fds) { + Socket *s = SOCKET(u); + SocketPort *p; + int r; + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", socket_state_to_string(s->state)); + unit_serialize_item(u, f, "result", socket_result_to_string(s->result)); + unit_serialize_item_format(u, f, "n-accepted", "%u", s->n_accepted); + + if (s->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", "%lu", (unsigned long) s->control_pid); + + if (s->control_command_id >= 0) + unit_serialize_item(u, f, "control-command", socket_exec_command_to_string(s->control_command_id)); + + LIST_FOREACH(port, p, s->ports) { + int copy; + + if (p->fd < 0) + continue; + + if ((copy = fdset_put_dup(fds, p->fd)) < 0) + return copy; + + if (p->type == SOCKET_SOCKET) { + char *t; + + if ((r = socket_address_print(&p->address, &t)) < 0) + return r; + + if (socket_address_family(&p->address) == AF_NETLINK) + unit_serialize_item_format(u, f, "netlink", "%i %s", copy, t); + else + unit_serialize_item_format(u, f, "socket", "%i %i %s", copy, p->address.type, t); + free(t); + } else if (p->type == SOCKET_SPECIAL) + unit_serialize_item_format(u, f, "special", "%i %s", copy, p->path); + else { + assert(p->type == SOCKET_FIFO); + unit_serialize_item_format(u, f, "fifo", "%i %s", copy, p->path); + } + } + + return 0; +} + +static int socket_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Socket *s = SOCKET(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + SocketState state; + + if ((state = socket_state_from_string(value)) < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + } else if (streq(key, "result")) { + SocketResult f; + + f = socket_result_from_string(value); + if (f < 0) + log_debug("Failed to parse result value %s", value); + else if (f != SOCKET_SUCCESS) + s->result = f; + + } else if (streq(key, "n-accepted")) { + unsigned k; + + if (safe_atou(value, &k) < 0) + log_debug("Failed to parse n-accepted value %s", value); + else + s->n_accepted += k; + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_debug("Failed to parse control-pid value %s", value); + else + s->control_pid = pid; + } else if (streq(key, "control-command")) { + SocketExecCommand id; + + if ((id = socket_exec_command_from_string(value)) < 0) + log_debug("Failed to parse exec-command value %s", value); + else { + s->control_command_id = id; + s->control_command = s->exec_command[id]; + } + } else if (streq(key, "fifo")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse fifo value %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (p->type == SOCKET_FIFO && + streq_ptr(p->path, value+skip)) + break; + + if (p) { + if (p->fd >= 0) + close_nointr_nofail(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "special")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse special value %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (p->type == SOCKET_SPECIAL && + streq_ptr(p->path, value+skip)) + break; + + if (p) { + if (p->fd >= 0) + close_nointr_nofail(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "socket")) { + int fd, type, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %i %n", &fd, &type, &skip) < 2 || fd < 0 || type < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse socket value %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (socket_address_is(&p->address, value+skip, type)) + break; + + if (p) { + if (p->fd >= 0) + close_nointr_nofail(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "netlink")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse socket value %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (socket_address_is_netlink(&p->address, value+skip)) + break; + + if (p) { + if (p->fd >= 0) + close_nointr_nofail(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState socket_active_state(Unit *u) { + assert(u); + + return state_translation_table[SOCKET(u)->state]; +} + +static const char *socket_sub_state_to_string(Unit *u) { + assert(u); + + return socket_state_to_string(SOCKET(u)->state); +} + +static bool socket_check_gc(Unit *u) { + Socket *s = SOCKET(u); + + assert(u); + + return s->n_connections > 0; +} + +static void socket_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { + Socket *s = SOCKET(u); + int cfd = -1; + + assert(s); + assert(fd >= 0); + + if (s->state != SOCKET_LISTENING) + return; + + log_debug("Incoming traffic on %s", u->id); + + if (events != EPOLLIN) { + + if (events & EPOLLHUP) + log_error("%s: Got POLLHUP on a listening socket. The service probably invoked shutdown() on it, and should better not do that.", u->id); + else + log_error("%s: Got unexpected poll event (0x%x) on socket.", u->id, events); + + goto fail; + } + + if (w->socket_accept) { + for (;;) { + + if ((cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK)) < 0) { + + if (errno == EINTR) + continue; + + log_error("Failed to accept socket: %m"); + goto fail; + } + + break; + } + + socket_apply_socket_options(s, cfd); + } + + socket_enter_running(s, cfd); + return; + +fail: + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Socket *s = SOCKET(u); + SocketResult f; + + assert(s); + assert(pid >= 0); + + if (pid != s->control_pid) + return; + + s->control_pid = 0; + + if (is_clean_exit(code, status)) + f = SOCKET_SUCCESS; + else if (code == CLD_EXITED) + f = SOCKET_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = SOCKET_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = SOCKET_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown code"); + + if (s->control_command) { + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); + + if (s->control_command->ignore) + f = SOCKET_SUCCESS; + } + + log_full(f == SOCKET_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + "%s control process exited, code=%s status=%i", u->id, sigchld_code_to_string(code), status); + + if (f != SOCKET_SUCCESS) + s->result = f; + + if (s->control_command && + s->control_command->command_next && + f == SOCKET_SUCCESS) { + + log_debug("%s running next command for state %s", u->id, socket_state_to_string(s->state)); + socket_run_next(s); + } else { + s->control_command = NULL; + s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; + + /* No further commands for this step, so let's figure + * out what to do next */ + + log_debug("%s got final SIGCHLD for state %s", u->id, socket_state_to_string(s->state)); + + switch (s->state) { + + case SOCKET_START_PRE: + if (f == SOCKET_SUCCESS) + socket_enter_start_post(s); + else + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, f); + break; + + case SOCKET_START_POST: + if (f == SOCKET_SUCCESS) + socket_enter_listening(s); + else + socket_enter_stop_pre(s, f); + break; + + case SOCKET_STOP_PRE: + case SOCKET_STOP_PRE_SIGTERM: + case SOCKET_STOP_PRE_SIGKILL: + socket_enter_stop_post(s, f); + break; + + case SOCKET_STOP_POST: + case SOCKET_FINAL_SIGTERM: + case SOCKET_FINAL_SIGKILL: + socket_enter_dead(s, f); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); +} + +static void socket_timer_event(Unit *u, uint64_t elapsed, Watch *w) { + Socket *s = SOCKET(u); + + assert(s); + assert(elapsed == 1); + assert(w == &s->timer_watch); + + switch (s->state) { + + case SOCKET_START_PRE: + log_warning("%s starting timed out. Terminating.", u->id); + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_START_POST: + log_warning("%s starting timed out. Stopping.", u->id); + socket_enter_stop_pre(s, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_STOP_PRE: + log_warning("%s stopping timed out. Terminating.", u->id); + socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_STOP_PRE_SIGTERM: + if (s->exec_context.send_sigkill) { + log_warning("%s stopping timed out. Killing.", u->id); + socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, SOCKET_FAILURE_TIMEOUT); + } else { + log_warning("%s stopping timed out. Skipping SIGKILL. Ignoring.", u->id); + socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); + } + break; + + case SOCKET_STOP_PRE_SIGKILL: + log_warning("%s still around after SIGKILL. Ignoring.", u->id); + socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_STOP_POST: + log_warning("%s stopping timed out (2). Terminating.", u->id); + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_FINAL_SIGTERM: + if (s->exec_context.send_sigkill) { + log_warning("%s stopping timed out (2). Killing.", u->id); + socket_enter_signal(s, SOCKET_FINAL_SIGKILL, SOCKET_FAILURE_TIMEOUT); + } else { + log_warning("%s stopping timed out (2). Skipping SIGKILL. Ignoring.", u->id); + socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); + } + break; + + case SOCKET_FINAL_SIGKILL: + log_warning("%s still around after SIGKILL (2). Entering failed mode.", u->id); + socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } +} + +int socket_collect_fds(Socket *s, int **fds, unsigned *n_fds) { + int *rfds; + unsigned rn_fds, k; + SocketPort *p; + + assert(s); + assert(fds); + assert(n_fds); + + /* Called from the service code for requesting our fds */ + + rn_fds = 0; + LIST_FOREACH(port, p, s->ports) + if (p->fd >= 0) + rn_fds++; + + if (rn_fds <= 0) { + *fds = NULL; + *n_fds = 0; + return 0; + } + + if (!(rfds = new(int, rn_fds))) + return -ENOMEM; + + k = 0; + LIST_FOREACH(port, p, s->ports) + if (p->fd >= 0) + rfds[k++] = p->fd; + + assert(k == rn_fds); + + *fds = rfds; + *n_fds = rn_fds; + + return 0; +} + +void socket_notify_service_dead(Socket *s, bool failed_permanent) { + assert(s); + + /* The service is dead. Dang! + * + * This is strictly for one-instance-for-all-connections + * services. */ + + if (s->state == SOCKET_RUNNING) { + log_debug("%s got notified about service death (failed permanently: %s)", UNIT(s)->id, yes_no(failed_permanent)); + if (failed_permanent) + socket_enter_stop_pre(s, SOCKET_FAILURE_SERVICE_FAILED_PERMANENT); + else + socket_enter_listening(s); + } +} + +void socket_connection_unref(Socket *s) { + assert(s); + + /* The service is dead. Yay! + * + * This is strictly for one-instance-per-connection + * services. */ + + assert(s->n_connections > 0); + s->n_connections--; + + log_debug("%s: One connection closed, %u left.", UNIT(s)->id, s->n_connections); +} + +static void socket_reset_failed(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + if (s->state == SOCKET_FAILED) + socket_set_state(s, SOCKET_DEAD); + + s->result = SOCKET_SUCCESS; +} + +static int socket_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) { + Socket *s = SOCKET(u); + int r = 0; + Set *pid_set = NULL; + + assert(s); + + if (who == KILL_MAIN) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Socket units have no main processes"); + return -ESRCH; + } + + if (s->control_pid <= 0 && who == KILL_CONTROL) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); + return -ESRCH; + } + + if (who == KILL_CONTROL || who == KILL_ALL) + if (s->control_pid > 0) + if (kill(s->control_pid, signo) < 0) + r = -errno; + + if (who == KILL_ALL && mode == KILL_CONTROL_GROUP) { + int q; + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + /* Exclude the control pid from being killed via the cgroup */ + if (s->control_pid > 0) + if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) { + r = q; + goto finish; + } + + if ((q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, pid_set)) < 0) + if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + } + +finish: + if (pid_set) + set_free(pid_set); + + return r; +} + +static const char* const socket_state_table[_SOCKET_STATE_MAX] = { + [SOCKET_DEAD] = "dead", + [SOCKET_START_PRE] = "start-pre", + [SOCKET_START_POST] = "start-post", + [SOCKET_LISTENING] = "listening", + [SOCKET_RUNNING] = "running", + [SOCKET_STOP_PRE] = "stop-pre", + [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm", + [SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill", + [SOCKET_STOP_POST] = "stop-post", + [SOCKET_FINAL_SIGTERM] = "final-sigterm", + [SOCKET_FINAL_SIGKILL] = "final-sigkill", + [SOCKET_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_state, SocketState); + +static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = { + [SOCKET_EXEC_START_PRE] = "StartPre", + [SOCKET_EXEC_START_POST] = "StartPost", + [SOCKET_EXEC_STOP_PRE] = "StopPre", + [SOCKET_EXEC_STOP_POST] = "StopPost" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_exec_command, SocketExecCommand); + +static const char* const socket_result_table[_SOCKET_RESULT_MAX] = { + [SOCKET_SUCCESS] = "success", + [SOCKET_FAILURE_RESOURCES] = "resources", + [SOCKET_FAILURE_TIMEOUT] = "timeout", + [SOCKET_FAILURE_EXIT_CODE] = "exit-code", + [SOCKET_FAILURE_SIGNAL] = "signal", + [SOCKET_FAILURE_CORE_DUMP] = "core-dump", + [SOCKET_FAILURE_SERVICE_FAILED_PERMANENT] = "service-failed-permanent" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_result, SocketResult); + +const UnitVTable socket_vtable = { + .suffix = ".socket", + .object_size = sizeof(Socket), + .sections = + "Unit\0" + "Socket\0" + "Install\0", + + .init = socket_init, + .done = socket_done, + .load = socket_load, + + .kill = socket_kill, + + .coldplug = socket_coldplug, + + .dump = socket_dump, + + .start = socket_start, + .stop = socket_stop, + + .serialize = socket_serialize, + .deserialize_item = socket_deserialize_item, + + .active_state = socket_active_state, + .sub_state_to_string = socket_sub_state_to_string, + + .check_gc = socket_check_gc, + + .fd_event = socket_fd_event, + .sigchld_event = socket_sigchld_event, + .timer_event = socket_timer_event, + + .reset_failed = socket_reset_failed, + + .bus_interface = "org.freedesktop.systemd1.Socket", + .bus_message_handler = bus_socket_message_handler, + .bus_invalidating_properties = bus_socket_invalidating_properties +}; diff --git a/src/socket.h b/src/socket.h new file mode 100644 index 000000000..6470d8b63 --- /dev/null +++ b/src/socket.h @@ -0,0 +1,173 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosockethfoo +#define foosockethfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Socket Socket; + +#include "manager.h" +#include "unit.h" +#include "socket-util.h" +#include "mount.h" +#include "service.h" + +typedef enum SocketState { + SOCKET_DEAD, + SOCKET_START_PRE, + SOCKET_START_POST, + SOCKET_LISTENING, + SOCKET_RUNNING, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL, + SOCKET_FAILED, + _SOCKET_STATE_MAX, + _SOCKET_STATE_INVALID = -1 +} SocketState; + +typedef enum SocketExecCommand { + SOCKET_EXEC_START_PRE, + SOCKET_EXEC_START_POST, + SOCKET_EXEC_STOP_PRE, + SOCKET_EXEC_STOP_POST, + _SOCKET_EXEC_COMMAND_MAX, + _SOCKET_EXEC_COMMAND_INVALID = -1 +} SocketExecCommand; + +typedef enum SocketType { + SOCKET_SOCKET, + SOCKET_FIFO, + SOCKET_SPECIAL, + SOCKET_MQUEUE, + _SOCKET_FIFO_MAX, + _SOCKET_FIFO_INVALID = -1 +} SocketType; + +typedef enum SocketResult { + SOCKET_SUCCESS, + SOCKET_FAILURE_RESOURCES, + SOCKET_FAILURE_TIMEOUT, + SOCKET_FAILURE_EXIT_CODE, + SOCKET_FAILURE_SIGNAL, + SOCKET_FAILURE_CORE_DUMP, + SOCKET_FAILURE_SERVICE_FAILED_PERMANENT, + _SOCKET_RESULT_MAX, + _SOCKET_RESULT_INVALID = -1 +} SocketResult; + +typedef struct SocketPort { + SocketType type; + int fd; + + SocketAddress address; + char *path; + Watch fd_watch; + + LIST_FIELDS(struct SocketPort, port); +} SocketPort; + +struct Socket { + Unit meta; + + LIST_HEAD(SocketPort, ports); + + unsigned n_accepted; + unsigned n_connections; + unsigned max_connections; + + unsigned backlog; + usec_t timeout_usec; + + ExecCommand* exec_command[_SOCKET_EXEC_COMMAND_MAX]; + ExecContext exec_context; + + /* For Accept=no sockets refers to the one service we'll + activate. For Accept=yes sockets is either NULL, or filled + when the next service we spawn. */ + UnitRef service; + + SocketState state, deserialized_state; + + Watch timer_watch; + + ExecCommand* control_command; + SocketExecCommand control_command_id; + pid_t control_pid; + + mode_t directory_mode; + mode_t socket_mode; + + SocketResult result; + + bool accept; + + /* Socket options */ + bool keep_alive; + bool free_bind; + bool transparent; + bool broadcast; + bool pass_cred; + bool pass_sec; + int priority; + int mark; + size_t receive_buffer; + size_t send_buffer; + int ip_tos; + int ip_ttl; + size_t pipe_size; + char *bind_to_device; + char *tcp_congestion; + long mq_maxmsg; + long mq_msgsize; + + /* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */ + SocketAddressBindIPv6Only bind_ipv6_only; +}; + +/* Called from the service code when collecting fds */ +int socket_collect_fds(Socket *s, int **fds, unsigned *n_fds); + +/* Called from the service when it shut down */ +void socket_notify_service_dead(Socket *s, bool failed_permanent); + +/* Called from the mount code figure out if a mount is a dependency of + * any of the sockets of this socket */ +int socket_add_one_mount_link(Socket *s, Mount *m); + +/* Called from the service code when a per-connection service ended */ +void socket_connection_unref(Socket *s); + +extern const UnitVTable socket_vtable; + +const char* socket_state_to_string(SocketState i); +SocketState socket_state_from_string(const char *s); + +const char* socket_exec_command_to_string(SocketExecCommand i); +SocketExecCommand socket_exec_command_from_string(const char *s); + +const char* socket_result_to_string(SocketResult i); +SocketResult socket_result_from_string(const char *s); + +#endif diff --git a/src/spawn-agent.c b/src/spawn-agent.c new file mode 100644 index 000000000..2de253088 --- /dev/null +++ b/src/spawn-agent.c @@ -0,0 +1,120 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "util.h" +#include "spawn-agent.h" + +static pid_t agent_pid = 0; + +void agent_open(void) { + pid_t parent_pid; + + if (agent_pid > 0) + return; + + /* We check STDIN here, not STDOUT, since this is about input, + * not output */ + if (!isatty(STDIN_FILENO)) + return; + + parent_pid = getpid(); + + /* Spawns a temporary TTY agent, making sure it goes away when + * we go away */ + + agent_pid = fork(); + if (agent_pid < 0) { + log_error("Failed to fork agent: %m"); + return; + } + + if (agent_pid == 0) { + /* In the child */ + + int fd; + bool stdout_is_tty, stderr_is_tty; + + /* Make sure the agent goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + /* Check whether our parent died before we were able + * to set the death signal */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + /* Don't leak fds to the agent */ + close_all_fds(NULL, 0); + + stdout_is_tty = isatty(STDOUT_FILENO); + stderr_is_tty = isatty(STDERR_FILENO); + + if (!stdout_is_tty || !stderr_is_tty) { + /* Detach from stdout/stderr. and reopen + * /dev/tty for them. This is important to + * ensure that when systemctl is started via + * popen() or a similar call that expects to + * read EOF we actually do generate EOF and + * not delay this indefinitely by because we + * keep an unused copy of stdin around. */ + fd = open("/dev/tty", O_WRONLY); + if (fd < 0) { + log_error("Failed to open /dev/tty: %m"); + _exit(EXIT_FAILURE); + } + + if (!stdout_is_tty) + dup2(fd, STDOUT_FILENO); + + if (!stderr_is_tty) + dup2(fd, STDERR_FILENO); + + if (fd > 2) + close(fd); + } + + execl(SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, "--watch", NULL); + + log_error("Unable to execute agent: %m"); + _exit(EXIT_FAILURE); + } +} + +void agent_close(void) { + + if (agent_pid <= 0) + return; + + /* Inform agent that we are done */ + kill(agent_pid, SIGTERM); + kill(agent_pid, SIGCONT); + wait_for_terminate(agent_pid, NULL); + agent_pid = 0; +} diff --git a/src/spawn-agent.h b/src/spawn-agent.h new file mode 100644 index 000000000..fd0a9109b --- /dev/null +++ b/src/spawn-agent.h @@ -0,0 +1,28 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foospawnagenthfoo +#define foospawnagenthfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +void agent_open(void); +void agent_close(void); + +#endif diff --git a/src/special.h b/src/special.h new file mode 100644 index 000000000..8185eaf60 --- /dev/null +++ b/src/special.h @@ -0,0 +1,88 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foospecialhfoo +#define foospecialhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#define SPECIAL_DEFAULT_TARGET "default.target" + +/* Shutdown targets */ +#define SPECIAL_UMOUNT_TARGET "umount.target" +/* This is not really intended to be started by directly. This is + * mostly so that other targets (reboot/halt/poweroff) can depend on + * it to bring all services down that want to be brought down on + * system shutdown. */ +#define SPECIAL_SHUTDOWN_TARGET "shutdown.target" +#define SPECIAL_HALT_TARGET "halt.target" +#define SPECIAL_POWEROFF_TARGET "poweroff.target" +#define SPECIAL_REBOOT_TARGET "reboot.target" +#define SPECIAL_KEXEC_TARGET "kexec.target" +#define SPECIAL_EXIT_TARGET "exit.target" + +/* Special boot targets */ +#define SPECIAL_RESCUE_TARGET "rescue.target" +#define SPECIAL_EMERGENCY_TARGET "emergency.target" + +/* Early boot targets */ +#define SPECIAL_SYSINIT_TARGET "sysinit.target" +#define SPECIAL_SOCKETS_TARGET "sockets.target" +#define SPECIAL_LOCAL_FS_TARGET "local-fs.target" /* LSB's $local_fs */ +#define SPECIAL_LOCAL_FS_PRE_TARGET "local-fs-pre.target" +#define SPECIAL_REMOTE_FS_TARGET "remote-fs.target" /* LSB's $remote_fs */ +#define SPECIAL_REMOTE_FS_PRE_TARGET "remote-fs-pre.target" +#define SPECIAL_SWAP_TARGET "swap.target" +#define SPECIAL_BASIC_TARGET "basic.target" + +/* LSB compatibility */ +#define SPECIAL_NETWORK_TARGET "network.target" /* LSB's $network */ +#define SPECIAL_NSS_LOOKUP_TARGET "nss-lookup.target" /* LSB's $named */ +#define SPECIAL_RPCBIND_TARGET "rpcbind.target" /* LSB's $portmap */ +#define SPECIAL_SYSLOG_TARGET "syslog.target" /* LSB's $syslog; Should pull in syslog.socket or syslog.service */ +#define SPECIAL_TIME_SYNC_TARGET "time-sync.target" /* LSB's $time */ +#define SPECIAL_DISPLAY_MANAGER_SERVICE "display-manager.service" /* Debian's $x-display-manager */ +#define SPECIAL_MAIL_TRANSFER_AGENT_TARGET "mail-transfer-agent.target" /* Debian's $mail-{transport|transfer-agent */ +#define SPECIAL_HTTP_DAEMON_TARGET "http-daemon.target" + +/* Magic early boot services */ +#define SPECIAL_FSCK_SERVICE "fsck@.service" +#define SPECIAL_QUOTACHECK_SERVICE "quotacheck.service" +#define SPECIAL_QUOTAON_SERVICE "quotaon.service" +#define SPECIAL_REMOUNT_ROOTFS_SERVICE "remount-rootfs.service" + +/* Services systemd relies on */ +#define SPECIAL_DBUS_SERVICE "dbus.service" +#define SPECIAL_DBUS_SOCKET "dbus.socket" +#define SPECIAL_JOURNALD_SOCKET "systemd-journald.socket" +#define SPECIAL_JOURNALD_SERVICE "systemd-journald.service" + +/* Magic init signals */ +#define SPECIAL_KBREQUEST_TARGET "kbrequest.target" +#define SPECIAL_SIGPWR_TARGET "sigpwr.target" +#define SPECIAL_CTRL_ALT_DEL_TARGET "ctrl-alt-del.target" + +/* For SysV compatibility. Usually an alias for a saner target. On + * SysV-free systems this doesn't exist. */ +#define SPECIAL_RUNLEVEL2_TARGET "runlevel2.target" +#define SPECIAL_RUNLEVEL3_TARGET "runlevel3.target" +#define SPECIAL_RUNLEVEL4_TARGET "runlevel4.target" +#define SPECIAL_RUNLEVEL5_TARGET "runlevel5.target" + +#endif diff --git a/src/specifier.c b/src/specifier.c new file mode 100644 index 000000000..a9fff88d3 --- /dev/null +++ b/src/specifier.c @@ -0,0 +1,108 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "macro.h" +#include "util.h" +#include "specifier.h" + +/* + * Generic infrastructure for replacing %x style specifiers in + * strings. Will call a callback for each replacement. + * + */ + +char *specifier_printf(const char *text, const Specifier table[], void *userdata) { + char *r, *t; + const char *f; + bool percent = false; + size_t l; + + assert(text); + assert(table); + + l = strlen(text); + if (!(r = new(char, l+1))) + return NULL; + + t = r; + + for (f = text; *f; f++, l--) { + + if (percent) { + if (*f == '%') + *(t++) = '%'; + else { + const Specifier *i; + + for (i = table; i->specifier; i++) + if (i->specifier == *f) + break; + + if (i->lookup) { + char *n, *w; + size_t k, j; + + if (!(w = i->lookup(i->specifier, i->data, userdata))) { + free(r); + return NULL; + } + + j = t - r; + k = strlen(w); + + if (!(n = new(char, j + k + l + 1))) { + free(r); + free(w); + return NULL; + } + + memcpy(n, r, j); + memcpy(n + j, w, k); + + free(r); + free(w); + + r = n; + t = n + j + k; + } else { + *(t++) = '%'; + *(t++) = *f; + } + } + + percent = false; + } else if (*f == '%') + percent = true; + else + *(t++) = *f; + } + + *t = 0; + return r; +} + +/* Generic handler for simple string replacements */ + +char* specifier_string(char specifier, void *data, void *userdata) { + return strdup(strempty(data)); +} diff --git a/src/specifier.h b/src/specifier.h new file mode 100644 index 000000000..041166c90 --- /dev/null +++ b/src/specifier.h @@ -0,0 +1,37 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foospecifierhfoo +#define foospecifierhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef char* (*SpecifierCallback)(char specifier, void *data, void *userdata); + +typedef struct Specifier { + const char specifier; + const SpecifierCallback lookup; + void *data; +} Specifier; + +char *specifier_printf(const char *text, const Specifier table[], void *userdata); + +char* specifier_string(char specifier, void *data, void *userdata); + +#endif diff --git a/src/strv.c b/src/strv.c new file mode 100644 index 000000000..bb309d9f9 --- /dev/null +++ b/src/strv.c @@ -0,0 +1,690 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "util.h" +#include "strv.h" + +char *strv_find(char **l, const char *name) { + char **i; + + assert(name); + + STRV_FOREACH(i, l) + if (streq(*i, name)) + return *i; + + return NULL; +} + +char *strv_find_prefix(char **l, const char *name) { + char **i; + + assert(name); + + STRV_FOREACH(i, l) + if (startswith(*i, name)) + return *i; + + return NULL; +} + +void strv_free(char **l) { + char **k; + + if (!l) + return; + + for (k = l; *k; k++) + free(*k); + + free(l); +} + +char **strv_copy(char **l) { + char **r, **k; + + k = r = new(char*, strv_length(l)+1); + if (!k) + return NULL; + + if (l) + for (; *l; k++, l++) + if (!(*k = strdup(*l))) + goto fail; + + *k = NULL; + return r; + +fail: + for (k--; k >= r; k--) + free(*k); + + free(r); + + return NULL; +} + +unsigned strv_length(char **l) { + unsigned n = 0; + + if (!l) + return 0; + + for (; *l; l++) + n++; + + return n; +} + +char **strv_new_ap(const char *x, va_list ap) { + const char *s; + char **a; + unsigned n = 0, i = 0; + va_list aq; + + if (x) { + n = 1; + + va_copy(aq, ap); + while (va_arg(aq, const char*)) + n++; + va_end(aq); + } + + if (!(a = new(char*, n+1))) + return NULL; + + if (x) { + if (!(a[i] = strdup(x))) { + free(a); + return NULL; + } + + i++; + + while ((s = va_arg(ap, const char*))) { + if (!(a[i] = strdup(s))) + goto fail; + + i++; + } + } + + a[i] = NULL; + + return a; + +fail: + + for (; i > 0; i--) + if (a[i-1]) + free(a[i-1]); + + free(a); + + return NULL; +} + +char **strv_new(const char *x, ...) { + char **r; + va_list ap; + + va_start(ap, x); + r = strv_new_ap(x, ap); + va_end(ap); + + return r; +} + +char **strv_merge(char **a, char **b) { + char **r, **k; + + if (!a) + return strv_copy(b); + + if (!b) + return strv_copy(a); + + if (!(r = new(char*, strv_length(a)+strv_length(b)+1))) + return NULL; + + for (k = r; *a; k++, a++) + if (!(*k = strdup(*a))) + goto fail; + for (; *b; k++, b++) + if (!(*k = strdup(*b))) + goto fail; + + *k = NULL; + return r; + +fail: + for (k--; k >= r; k--) + free(*k); + + free(r); + + return NULL; +} + +char **strv_merge_concat(char **a, char **b, const char *suffix) { + char **r, **k; + + /* Like strv_merge(), but appends suffix to all strings in b, before adding */ + + if (!b) + return strv_copy(a); + + r = new(char*, strv_length(a) + strv_length(b) + 1); + if (!r) + return NULL; + + k = r; + if (a) + for (; *a; k++, a++) { + *k = strdup(*a); + if (!*k) + goto fail; + } + + for (; *b; k++, b++) { + *k = strappend(*b, suffix); + if (!*k) + goto fail; + } + + *k = NULL; + return r; + +fail: + for (k--; k >= r; k--) + free(*k); + + free(r); + + return NULL; + +} + +char **strv_split(const char *s, const char *separator) { + char *state; + char *w; + size_t l; + unsigned n, i; + char **r; + + assert(s); + + n = 0; + FOREACH_WORD_SEPARATOR(w, l, s, separator, state) + n++; + + if (!(r = new(char*, n+1))) + return NULL; + + i = 0; + FOREACH_WORD_SEPARATOR(w, l, s, separator, state) + if (!(r[i++] = strndup(w, l))) { + strv_free(r); + return NULL; + } + + r[i] = NULL; + return r; +} + +char **strv_split_quoted(const char *s) { + char *state; + char *w; + size_t l; + unsigned n, i; + char **r; + + assert(s); + + n = 0; + FOREACH_WORD_QUOTED(w, l, s, state) + n++; + + if (!(r = new(char*, n+1))) + return NULL; + + i = 0; + FOREACH_WORD_QUOTED(w, l, s, state) + if (!(r[i++] = cunescape_length(w, l))) { + strv_free(r); + return NULL; + } + + r[i] = NULL; + return r; +} + +char *strv_join(char **l, const char *separator) { + char *r, *e; + char **s; + size_t n, k; + + if (!separator) + separator = " "; + + k = strlen(separator); + + n = 0; + STRV_FOREACH(s, l) { + if (n != 0) + n += k; + n += strlen(*s); + } + + if (!(r = new(char, n+1))) + return NULL; + + e = r; + STRV_FOREACH(s, l) { + if (e != r) + e = stpcpy(e, separator); + + e = stpcpy(e, *s); + } + + *e = 0; + + return r; +} + +char **strv_append(char **l, const char *s) { + char **r, **k; + + if (!l) + return strv_new(s, NULL); + + if (!s) + return strv_copy(l); + + r = new(char*, strv_length(l)+2); + if (!r) + return NULL; + + for (k = r; *l; k++, l++) + if (!(*k = strdup(*l))) + goto fail; + + if (!(*(k++) = strdup(s))) + goto fail; + + *k = NULL; + return r; + +fail: + for (k--; k >= r; k--) + free(*k); + + free(r); + + return NULL; +} + +char **strv_uniq(char **l) { + char **i; + + /* Drops duplicate entries. The first identical string will be + * kept, the others dropped */ + + STRV_FOREACH(i, l) + strv_remove(i+1, *i); + + return l; +} + +char **strv_remove(char **l, const char *s) { + char **f, **t; + + if (!l) + return NULL; + + assert(s); + + /* Drops every occurrence of s in the string list, edits + * in-place. */ + + for (f = t = l; *f; f++) { + + if (streq(*f, s)) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + +static int env_append(char **r, char ***k, char **a) { + assert(r); + assert(k); + + if (!a) + return 0; + + /* Add the entries of a to *k unless they already exist in *r + * in which case they are overridden instead. This assumes + * there is enough space in the r array. */ + + for (; *a; a++) { + char **j; + size_t n; + + n = strcspn(*a, "="); + + if ((*a)[n] == '=') + n++; + + for (j = r; j < *k; j++) + if (strncmp(*j, *a, n) == 0) + break; + + if (j >= *k) + (*k)++; + else + free(*j); + + if (!(*j = strdup(*a))) + return -ENOMEM; + } + + return 0; +} + +char **strv_env_merge(unsigned n_lists, ...) { + size_t n = 0; + char **l, **k, **r; + va_list ap; + unsigned i; + + /* Merges an arbitrary number of environment sets */ + + va_start(ap, n_lists); + for (i = 0; i < n_lists; i++) { + l = va_arg(ap, char**); + n += strv_length(l); + } + va_end(ap); + + if (!(r = new(char*, n+1))) + return NULL; + + k = r; + + va_start(ap, n_lists); + for (i = 0; i < n_lists; i++) { + l = va_arg(ap, char**); + if (env_append(r, &k, l) < 0) + goto fail; + } + va_end(ap); + + *k = NULL; + + return r; + +fail: + va_end(ap); + + for (k--; k >= r; k--) + free(*k); + + free(r); + + return NULL; +} + +static bool env_match(const char *t, const char *pattern) { + assert(t); + assert(pattern); + + /* pattern a matches string a + * a matches a= + * a matches a=b + * a= matches a= + * a=b matches a=b + * a= does not match a + * a=b does not match a= + * a=b does not match a + * a=b does not match a=c */ + + if (streq(t, pattern)) + return true; + + if (!strchr(pattern, '=')) { + size_t l = strlen(pattern); + + return strncmp(t, pattern, l) == 0 && t[l] == '='; + } + + return false; +} + +char **strv_env_delete(char **x, unsigned n_lists, ...) { + size_t n, i = 0; + char **k, **r; + va_list ap; + + /* Deletes every entry from x that is mentioned in the other + * string lists */ + + n = strv_length(x); + + r = new(char*, n+1); + if (!r) + return NULL; + + STRV_FOREACH(k, x) { + unsigned v; + + va_start(ap, n_lists); + for (v = 0; v < n_lists; v++) { + char **l, **j; + + l = va_arg(ap, char**); + STRV_FOREACH(j, l) + if (env_match(*k, *j)) + goto skip; + } + va_end(ap); + + r[i] = strdup(*k); + if (!r[i]) { + strv_free(r); + return NULL; + } + + i++; + continue; + + skip: + va_end(ap); + } + + r[i] = NULL; + + assert(i <= n); + + return r; +} + +char **strv_env_unset(char **l, const char *p) { + + char **f, **t; + + if (!l) + return NULL; + + assert(p); + + /* Drops every occurrence of the env var setting p in the + * string list. edits in-place. */ + + for (f = t = l; *f; f++) { + + if (env_match(*f, p)) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + +char **strv_env_set(char **x, const char *p) { + + char **k, **r; + char* m[2] = { (char*) p, NULL }; + + /* Overrides the env var setting of p, returns a new copy */ + + if (!(r = new(char*, strv_length(x)+2))) + return NULL; + + k = r; + if (env_append(r, &k, x) < 0) + goto fail; + + if (env_append(r, &k, m) < 0) + goto fail; + + *k = NULL; + + return r; + +fail: + for (k--; k >= r; k--) + free(*k); + + free(r); + + return NULL; + +} + +char *strv_env_get_with_length(char **l, const char *name, size_t k) { + char **i; + + assert(name); + + STRV_FOREACH(i, l) + if (strncmp(*i, name, k) == 0 && + (*i)[k] == '=') + return *i + k + 1; + + return NULL; +} + +char *strv_env_get(char **l, const char *name) { + return strv_env_get_with_length(l, name, strlen(name)); +} + +char **strv_env_clean(char **l) { + char **r, **ret; + + for (r = ret = l; *l; l++) { + const char *equal; + + equal = strchr(*l, '='); + + if (equal && equal[1] == 0) { + free(*l); + continue; + } + + *(r++) = *l; + } + + *r = NULL; + + return ret; +} + +char **strv_parse_nulstr(const char *s, size_t l) { + const char *p; + unsigned c = 0, i = 0; + char **v; + + assert(s || l <= 0); + + if (l <= 0) + return strv_new(NULL, NULL); + + for (p = s; p < s + l; p++) + if (*p == 0) + c++; + + if (s[l-1] != 0) + c++; + + if (!(v = new0(char*, c+1))) + return NULL; + + p = s; + while (p < s + l) { + const char *e; + + e = memchr(p, 0, s + l - p); + + if (!(v[i++] = strndup(p, e ? e - p : s + l - p))) { + strv_free(v); + return NULL; + } + + if (!e) + break; + + p = e + 1; + } + + assert(i == c); + + return v; +} + +bool strv_overlap(char **a, char **b) { + char **i, **j; + + STRV_FOREACH(i, a) { + STRV_FOREACH(j, b) { + if (streq(*i, *j)) + return true; + } + } + + return false; +} diff --git a/src/strv.h b/src/strv.h new file mode 100644 index 000000000..d038c9f3b --- /dev/null +++ b/src/strv.h @@ -0,0 +1,79 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foostrvhfoo +#define foostrvhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "macro.h" + +char *strv_find(char **l, const char *name); +char *strv_find_prefix(char **l, const char *name); + +void strv_free(char **l); +char **strv_copy(char **l) _malloc_; +unsigned strv_length(char **l); + +char **strv_merge(char **a, char **b); +char **strv_merge_concat(char **a, char **b, const char *suffix); +char **strv_append(char **l, const char *s); + +char **strv_remove(char **l, const char *s); +char **strv_uniq(char **l); + +#define strv_contains(l, s) (!!strv_find((l), (s))) + +char **strv_new(const char *x, ...) _sentinel_ _malloc_; +char **strv_new_ap(const char *x, va_list ap) _malloc_; + +static inline bool strv_isempty(char **l) { + return !l || !*l; +} + +char **strv_split(const char *s, const char *separator) _malloc_; +char **strv_split_quoted(const char *s) _malloc_; + +char *strv_join(char **l, const char *separator) _malloc_; + +char **strv_env_merge(unsigned n_lists, ...); +char **strv_env_delete(char **x, unsigned n_lists, ...); + +char **strv_env_set(char **x, const char *p); +char **strv_env_unset(char **l, const char *p); + +char *strv_env_get_with_length(char **l, const char *name, size_t k); +char *strv_env_get(char **x, const char *n); + +char **strv_env_clean(char **l); + +char **strv_parse_nulstr(const char *s, size_t l); + +bool strv_overlap(char **a, char **b); + +#define STRV_FOREACH(s, l) \ + for ((s) = (l); (s) && *(s); (s)++) + +#define STRV_FOREACH_BACKWARDS(s, l) \ + for (; (l) && ((s) >= (l)); (s)--) + +#endif diff --git a/src/swap.c b/src/swap.c new file mode 100644 index 000000000..9c72732b9 --- /dev/null +++ b/src/swap.c @@ -0,0 +1,1415 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unit.h" +#include "swap.h" +#include "load-fragment.h" +#include "load-dropin.h" +#include "unit-name.h" +#include "dbus-swap.h" +#include "special.h" +#include "bus-errors.h" +#include "exit-status.h" +#include "def.h" + +static const UnitActiveState state_translation_table[_SWAP_STATE_MAX] = { + [SWAP_DEAD] = UNIT_INACTIVE, + [SWAP_ACTIVATING] = UNIT_ACTIVATING, + [SWAP_ACTIVE] = UNIT_ACTIVE, + [SWAP_DEACTIVATING] = UNIT_DEACTIVATING, + [SWAP_ACTIVATING_SIGTERM] = UNIT_DEACTIVATING, + [SWAP_ACTIVATING_SIGKILL] = UNIT_DEACTIVATING, + [SWAP_DEACTIVATING_SIGTERM] = UNIT_DEACTIVATING, + [SWAP_DEACTIVATING_SIGKILL] = UNIT_DEACTIVATING, + [SWAP_FAILED] = UNIT_FAILED +}; + +static void swap_unset_proc_swaps(Swap *s) { + Swap *first; + + assert(s); + + if (!s->parameters_proc_swaps.what) + return; + + /* Remove this unit from the chain of swaps which share the + * same kernel swap device. */ + + first = hashmap_get(UNIT(s)->manager->swaps_by_proc_swaps, s->parameters_proc_swaps.what); + LIST_REMOVE(Swap, same_proc_swaps, first, s); + + if (first) + hashmap_remove_and_replace(UNIT(s)->manager->swaps_by_proc_swaps, s->parameters_proc_swaps.what, first->parameters_proc_swaps.what, first); + else + hashmap_remove(UNIT(s)->manager->swaps_by_proc_swaps, s->parameters_proc_swaps.what); + + free(s->parameters_proc_swaps.what); + s->parameters_proc_swaps.what = NULL; +} + +static void swap_init(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + assert(UNIT(s)->load_state == UNIT_STUB); + + s->timeout_usec = DEFAULT_TIMEOUT_USEC; + + exec_context_init(&s->exec_context); + s->exec_context.std_output = u->manager->default_std_output; + s->exec_context.std_error = u->manager->default_std_error; + + s->parameters_etc_fstab.priority = s->parameters_proc_swaps.priority = s->parameters_fragment.priority = -1; + + s->timer_watch.type = WATCH_INVALID; + + s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; + + UNIT(s)->ignore_on_isolate = true; +} + +static void swap_unwatch_control_pid(Swap *s) { + assert(s); + + if (s->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->control_pid); + s->control_pid = 0; +} + +static void swap_done(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + swap_unset_proc_swaps(s); + + free(s->what); + s->what = NULL; + + free(s->parameters_etc_fstab.what); + free(s->parameters_fragment.what); + s->parameters_etc_fstab.what = s->parameters_fragment.what = NULL; + + exec_context_done(&s->exec_context); + exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX); + s->control_command = NULL; + + swap_unwatch_control_pid(s); + + unit_unwatch_timer(u, &s->timer_watch); +} + +int swap_add_one_mount_link(Swap *s, Mount *m) { + int r; + + assert(s); + assert(m); + + if (UNIT(s)->load_state != UNIT_LOADED || + UNIT(m)->load_state != UNIT_LOADED) + return 0; + + if (is_device_path(s->what)) + return 0; + + if (!path_startswith(s->what, m->where)) + return 0; + + if ((r = unit_add_two_dependencies(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0) + return r; + + return 0; +} + +static int swap_add_mount_links(Swap *s) { + Unit *other; + int r; + + assert(s); + + LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_MOUNT]) + if ((r = swap_add_one_mount_link(s, MOUNT(other))) < 0) + return r; + + return 0; +} + +static int swap_add_target_links(Swap *s) { + Unit *tu; + SwapParameters *p; + int r; + + assert(s); + + if (s->from_fragment) + p = &s->parameters_fragment; + else if (s->from_etc_fstab) + p = &s->parameters_etc_fstab; + else + return 0; + + if ((r = manager_load_unit(UNIT(s)->manager, SPECIAL_SWAP_TARGET, NULL, NULL, &tu)) < 0) + return r; + + if (!p->noauto && + !p->nofail && + (p->handle || UNIT(s)->manager->swap_auto) && + s->from_etc_fstab && + UNIT(s)->manager->running_as == MANAGER_SYSTEM) + if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(s), true)) < 0) + return r; + + return unit_add_dependency(UNIT(s), UNIT_BEFORE, tu, true); +} + +static int swap_add_device_links(Swap *s) { + SwapParameters *p; + + assert(s); + + if (!s->what) + return 0; + + if (s->from_fragment) + p = &s->parameters_fragment; + else if (s->from_etc_fstab) + p = &s->parameters_etc_fstab; + else + return 0; + + if (is_device_path(s->what)) + return unit_add_node_link(UNIT(s), s->what, + !p->noauto && p->nofail && + UNIT(s)->manager->running_as == MANAGER_SYSTEM); + else + /* File based swap devices need to be ordered after + * remount-rootfs.service, since they might need a + * writable file system. */ + return unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_REMOUNT_ROOTFS_SERVICE, NULL, true); +} + +static int swap_add_default_dependencies(Swap *s) { + int r; + + assert(s); + + if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { + + if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0) + return r; + } + + return 0; +} + +static int swap_verify(Swap *s) { + bool b; + char *e; + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (!(e = unit_name_from_path(s->what, ".swap"))) + return -ENOMEM; + + b = unit_has_name(UNIT(s), e); + free(e); + + if (!b) { + log_error("%s: Value of \"What\" and unit name do not match, not loading.\n", UNIT(s)->id); + return -EINVAL; + } + + if (s->exec_context.pam_name && s->exec_context.kill_mode != KILL_CONTROL_GROUP) { + log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + return 0; +} + +static int swap_load(Unit *u) { + int r; + Swap *s = SWAP(u); + + assert(s); + assert(u->load_state == UNIT_STUB); + + /* Load a .swap file */ + if ((r = unit_load_fragment_and_dropin_optional(u)) < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0) + return r; + + if (UNIT(s)->fragment_path) + s->from_fragment = true; + + if (!s->what) { + if (s->parameters_fragment.what) + s->what = strdup(s->parameters_fragment.what); + else if (s->parameters_etc_fstab.what) + s->what = strdup(s->parameters_etc_fstab.what); + else if (s->parameters_proc_swaps.what) + s->what = strdup(s->parameters_proc_swaps.what); + else + s->what = unit_name_to_path(u->id); + + if (!s->what) + return -ENOMEM; + } + + path_kill_slashes(s->what); + + if (!UNIT(s)->description) + if ((r = unit_set_description(u, s->what)) < 0) + return r; + + if ((r = swap_add_device_links(s)) < 0) + return r; + + if ((r = swap_add_mount_links(s)) < 0) + return r; + + if ((r = swap_add_target_links(s)) < 0) + return r; + + if ((r = unit_add_default_cgroups(u)) < 0) + return r; + + if (UNIT(s)->default_dependencies) + if ((r = swap_add_default_dependencies(s)) < 0) + return r; + } + + return swap_verify(s); +} + +int swap_add_one( + Manager *m, + const char *what, + const char *what_proc_swaps, + int priority, + bool noauto, + bool nofail, + bool handle, + bool set_flags) { + + Unit *u = NULL; + char *e = NULL, *wp = NULL; + bool delete = false; + int r; + SwapParameters *p; + + assert(m); + assert(what); + + e = unit_name_from_path(what, ".swap"); + if (!e) + return -ENOMEM; + + u = manager_get_unit(m, e); + + if (what_proc_swaps && + u && + SWAP(u)->from_proc_swaps && + !path_equal(SWAP(u)->parameters_proc_swaps.what, what_proc_swaps)) + return -EEXIST; + + if (!u) { + delete = true; + + u = unit_new(m, sizeof(Swap)); + if (!u) { + free(e); + return -ENOMEM; + } + + r = unit_add_name(u, e); + if (r < 0) + goto fail; + + SWAP(u)->what = strdup(what); + if (!SWAP(u)->what) { + r = -ENOMEM; + goto fail; + } + + unit_add_to_load_queue(u); + } else + delete = false; + + if (what_proc_swaps) { + Swap *first; + + p = &SWAP(u)->parameters_proc_swaps; + + if (!p->what) { + if (!(wp = strdup(what_proc_swaps))) { + r = -ENOMEM; + goto fail; + } + + if (!m->swaps_by_proc_swaps) + if (!(m->swaps_by_proc_swaps = hashmap_new(string_hash_func, string_compare_func))) { + r = -ENOMEM; + goto fail; + } + + free(p->what); + p->what = wp; + + first = hashmap_get(m->swaps_by_proc_swaps, wp); + LIST_PREPEND(Swap, same_proc_swaps, first, SWAP(u)); + + if ((r = hashmap_replace(m->swaps_by_proc_swaps, wp, first)) < 0) + goto fail; + } + + if (set_flags) { + SWAP(u)->is_active = true; + SWAP(u)->just_activated = !SWAP(u)->from_proc_swaps; + } + + SWAP(u)->from_proc_swaps = true; + + } else { + p = &SWAP(u)->parameters_etc_fstab; + + if (!(wp = strdup(what))) { + r = -ENOMEM; + goto fail; + } + + free(p->what); + p->what = wp; + + SWAP(u)->from_etc_fstab = true; + } + + p->priority = priority; + p->noauto = noauto; + p->nofail = nofail; + p->handle = handle; + + unit_add_to_dbus_queue(u); + + free(e); + + return 0; + +fail: + log_warning("Failed to load swap unit: %s", strerror(-r)); + + free(wp); + free(e); + + if (delete && u) + unit_free(u); + + return r; +} + +static int swap_process_new_swap(Manager *m, const char *device, int prio, bool set_flags) { + struct stat st; + int r = 0, k; + + assert(m); + + if (stat(device, &st) >= 0 && S_ISBLK(st.st_mode)) { + struct udev_device *d; + const char *dn; + struct udev_list_entry *item = NULL, *first = NULL; + + /* So this is a proper swap device. Create swap units + * for all names this swap device is known under */ + + if (!(d = udev_device_new_from_devnum(m->udev, 'b', st.st_rdev))) + return -ENOMEM; + + if ((dn = udev_device_get_devnode(d))) + r = swap_add_one(m, dn, device, prio, false, false, false, set_flags); + + /* Add additional units for all symlinks */ + first = udev_device_get_devlinks_list_entry(d); + udev_list_entry_foreach(item, first) { + const char *p; + + /* Don't bother with the /dev/block links */ + p = udev_list_entry_get_name(item); + + if (path_startswith(p, "/dev/block/")) + continue; + + if (stat(p, &st) >= 0) + if ((!S_ISBLK(st.st_mode)) || st.st_rdev != udev_device_get_devnum(d)) + continue; + + if ((k = swap_add_one(m, p, device, prio, false, false, false, set_flags)) < 0) + r = k; + } + + udev_device_unref(d); + } + + if ((k = swap_add_one(m, device, device, prio, false, false, false, set_flags)) < 0) + r = k; + + return r; +} + +static void swap_set_state(Swap *s, SwapState state) { + SwapState old_state; + + assert(s); + + old_state = s->state; + s->state = state; + + if (state != SWAP_ACTIVATING && + state != SWAP_ACTIVATING_SIGTERM && + state != SWAP_ACTIVATING_SIGKILL && + state != SWAP_DEACTIVATING && + state != SWAP_DEACTIVATING_SIGTERM && + state != SWAP_DEACTIVATING_SIGKILL) { + unit_unwatch_timer(UNIT(s), &s->timer_watch); + swap_unwatch_control_pid(s); + s->control_command = NULL; + s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; + } + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(s)->id, + swap_state_to_string(old_state), + swap_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); +} + +static int swap_coldplug(Unit *u) { + Swap *s = SWAP(u); + SwapState new_state = SWAP_DEAD; + int r; + + assert(s); + assert(s->state == SWAP_DEAD); + + if (s->deserialized_state != s->state) + new_state = s->deserialized_state; + else if (s->from_proc_swaps) + new_state = SWAP_ACTIVE; + + if (new_state != s->state) { + + if (new_state == SWAP_ACTIVATING || + new_state == SWAP_ACTIVATING_SIGTERM || + new_state == SWAP_ACTIVATING_SIGKILL || + new_state == SWAP_DEACTIVATING || + new_state == SWAP_DEACTIVATING_SIGTERM || + new_state == SWAP_DEACTIVATING_SIGKILL) { + + if (s->control_pid <= 0) + return -EBADMSG; + + if ((r = unit_watch_pid(UNIT(s), s->control_pid)) < 0) + return r; + + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + return r; + } + + swap_set_state(s, new_state); + } + + return 0; +} + +static void swap_dump(Unit *u, FILE *f, const char *prefix) { + Swap *s = SWAP(u); + SwapParameters *p; + + assert(s); + assert(f); + + if (s->from_proc_swaps) + p = &s->parameters_proc_swaps; + else if (s->from_fragment) + p = &s->parameters_fragment; + else + p = &s->parameters_etc_fstab; + + fprintf(f, + "%sSwap State: %s\n" + "%sResult: %s\n" + "%sWhat: %s\n" + "%sPriority: %i\n" + "%sNoAuto: %s\n" + "%sNoFail: %s\n" + "%sHandle: %s\n" + "%sFrom /etc/fstab: %s\n" + "%sFrom /proc/swaps: %s\n" + "%sFrom fragment: %s\n", + prefix, swap_state_to_string(s->state), + prefix, swap_result_to_string(s->result), + prefix, s->what, + prefix, p->priority, + prefix, yes_no(p->noauto), + prefix, yes_no(p->nofail), + prefix, yes_no(p->handle), + prefix, yes_no(s->from_etc_fstab), + prefix, yes_no(s->from_proc_swaps), + prefix, yes_no(s->from_fragment)); + + if (s->control_pid > 0) + fprintf(f, + "%sControl PID: %lu\n", + prefix, (unsigned long) s->control_pid); + + exec_context_dump(&s->exec_context, f, prefix); +} + +static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { + pid_t pid; + int r; + + assert(s); + assert(c); + assert(_pid); + + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; + + if ((r = exec_spawn(c, + NULL, + &s->exec_context, + NULL, 0, + UNIT(s)->manager->environment, + true, + true, + true, + UNIT(s)->manager->confirm_spawn, + UNIT(s)->cgroup_bondings, + UNIT(s)->cgroup_attributes, + &pid)) < 0) + goto fail; + + if ((r = unit_watch_pid(UNIT(s), pid)) < 0) + /* FIXME: we need to do something here */ + goto fail; + + *_pid = pid; + + return 0; + +fail: + unit_unwatch_timer(UNIT(s), &s->timer_watch); + + return r; +} + +static void swap_enter_dead(Swap *s, SwapResult f) { + assert(s); + + if (f != SWAP_SUCCESS) + s->result = f; + + swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD); +} + +static void swap_enter_active(Swap *s, SwapResult f) { + assert(s); + + if (f != SWAP_SUCCESS) + s->result = f; + + swap_set_state(s, SWAP_ACTIVE); +} + +static void swap_enter_signal(Swap *s, SwapState state, SwapResult f) { + int r; + Set *pid_set = NULL; + bool wait_for_exit = false; + + assert(s); + + if (f != SWAP_SUCCESS) + s->result = f; + + if (s->exec_context.kill_mode != KILL_NONE) { + int sig = (state == SWAP_ACTIVATING_SIGTERM || + state == SWAP_DEACTIVATING_SIGTERM) ? s->exec_context.kill_signal : SIGKILL; + + if (s->control_pid > 0) { + if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH) + + log_warning("Failed to kill control process %li: %m", (long) s->control_pid); + else + wait_for_exit = true; + } + + if (s->exec_context.kill_mode == KILL_CONTROL_GROUP) { + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) { + r = -ENOMEM; + goto fail; + } + + /* Exclude the control pid from being killed via the cgroup */ + if (s->control_pid > 0) + if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) + goto fail; + + if ((r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, pid_set)) < 0) { + if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) + log_warning("Failed to kill control group: %s", strerror(-r)); + } else if (r > 0) + wait_for_exit = true; + + set_free(pid_set); + pid_set = NULL; + } + } + + if (wait_for_exit) { + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; + + swap_set_state(s, state); + } else + swap_enter_dead(s, SWAP_SUCCESS); + + return; + +fail: + log_warning("%s failed to kill processes: %s", UNIT(s)->id, strerror(-r)); + + swap_enter_dead(s, SWAP_FAILURE_RESOURCES); + + if (pid_set) + set_free(pid_set); +} + +static void swap_enter_activating(Swap *s) { + int r, priority; + + assert(s); + + s->control_command_id = SWAP_EXEC_ACTIVATE; + s->control_command = s->exec_command + SWAP_EXEC_ACTIVATE; + + if (s->from_fragment) + priority = s->parameters_fragment.priority; + else if (s->from_etc_fstab) + priority = s->parameters_etc_fstab.priority; + else + priority = -1; + + if (priority >= 0) { + char p[LINE_MAX]; + + snprintf(p, sizeof(p), "%i", priority); + char_array_0(p); + + r = exec_command_set( + s->control_command, + "/sbin/swapon", + "-p", + p, + s->what, + NULL); + } else + r = exec_command_set( + s->control_command, + "/sbin/swapon", + s->what, + NULL); + + if (r < 0) + goto fail; + + swap_unwatch_control_pid(s); + + if ((r = swap_spawn(s, s->control_command, &s->control_pid)) < 0) + goto fail; + + swap_set_state(s, SWAP_ACTIVATING); + + return; + +fail: + log_warning("%s failed to run 'swapon' task: %s", UNIT(s)->id, strerror(-r)); + swap_enter_dead(s, SWAP_FAILURE_RESOURCES); +} + +static void swap_enter_deactivating(Swap *s) { + int r; + + assert(s); + + s->control_command_id = SWAP_EXEC_DEACTIVATE; + s->control_command = s->exec_command + SWAP_EXEC_DEACTIVATE; + + if ((r = exec_command_set( + s->control_command, + "/sbin/swapoff", + s->what, + NULL)) < 0) + goto fail; + + swap_unwatch_control_pid(s); + + if ((r = swap_spawn(s, s->control_command, &s->control_pid)) < 0) + goto fail; + + swap_set_state(s, SWAP_DEACTIVATING); + + return; + +fail: + log_warning("%s failed to run 'swapoff' task: %s", UNIT(s)->id, strerror(-r)); + swap_enter_active(s, SWAP_FAILURE_RESOURCES); +} + +static int swap_start(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + /* We cannot fulfill this request right now, try again later + * please! */ + + if (s->state == SWAP_DEACTIVATING || + s->state == SWAP_DEACTIVATING_SIGTERM || + s->state == SWAP_DEACTIVATING_SIGKILL || + s->state == SWAP_ACTIVATING_SIGTERM || + s->state == SWAP_ACTIVATING_SIGKILL) + return -EAGAIN; + + if (s->state == SWAP_ACTIVATING) + return 0; + + assert(s->state == SWAP_DEAD || s->state == SWAP_FAILED); + + s->result = SWAP_SUCCESS; + swap_enter_activating(s); + return 0; +} + +static int swap_stop(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + if (s->state == SWAP_DEACTIVATING || + s->state == SWAP_DEACTIVATING_SIGTERM || + s->state == SWAP_DEACTIVATING_SIGKILL || + s->state == SWAP_ACTIVATING_SIGTERM || + s->state == SWAP_ACTIVATING_SIGKILL) + return 0; + + assert(s->state == SWAP_ACTIVATING || + s->state == SWAP_ACTIVE); + + swap_enter_deactivating(s); + return 0; +} + +static int swap_serialize(Unit *u, FILE *f, FDSet *fds) { + Swap *s = SWAP(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", swap_state_to_string(s->state)); + unit_serialize_item(u, f, "result", swap_result_to_string(s->result)); + + if (s->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", "%lu", (unsigned long) s->control_pid); + + if (s->control_command_id >= 0) + unit_serialize_item(u, f, "control-command", swap_exec_command_to_string(s->control_command_id)); + + return 0; +} + +static int swap_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Swap *s = SWAP(u); + + assert(s); + assert(fds); + + if (streq(key, "state")) { + SwapState state; + + if ((state = swap_state_from_string(value)) < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + } else if (streq(key, "result")) { + SwapResult f; + + f = swap_result_from_string(value); + if (f < 0) + log_debug("Failed to parse result value %s", value); + else if (f != SWAP_SUCCESS) + s->result = f; + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_debug("Failed to parse control-pid value %s", value); + else + s->control_pid = pid; + + } else if (streq(key, "control-command")) { + SwapExecCommand id; + + if ((id = swap_exec_command_from_string(value)) < 0) + log_debug("Failed to parse exec-command value %s", value); + else { + s->control_command_id = id; + s->control_command = s->exec_command + id; + } + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState swap_active_state(Unit *u) { + assert(u); + + return state_translation_table[SWAP(u)->state]; +} + +static const char *swap_sub_state_to_string(Unit *u) { + assert(u); + + return swap_state_to_string(SWAP(u)->state); +} + +static bool swap_check_gc(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + return s->from_etc_fstab || s->from_proc_swaps; +} + +static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Swap *s = SWAP(u); + SwapResult f; + + assert(s); + assert(pid >= 0); + + if (pid != s->control_pid) + return; + + s->control_pid = 0; + + if (is_clean_exit(code, status)) + f = SWAP_SUCCESS; + else if (code == CLD_EXITED) + f = SWAP_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = SWAP_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = SWAP_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown code"); + + if (f != SWAP_SUCCESS) + s->result = f; + + if (s->control_command) { + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); + + s->control_command = NULL; + s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; + } + + log_full(f == SWAP_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + "%s swap process exited, code=%s status=%i", u->id, sigchld_code_to_string(code), status); + + switch (s->state) { + + case SWAP_ACTIVATING: + case SWAP_ACTIVATING_SIGTERM: + case SWAP_ACTIVATING_SIGKILL: + + if (f == SWAP_SUCCESS) + swap_enter_active(s, f); + else + swap_enter_dead(s, f); + break; + + case SWAP_DEACTIVATING: + case SWAP_DEACTIVATING_SIGKILL: + case SWAP_DEACTIVATING_SIGTERM: + + if (f == SWAP_SUCCESS) + swap_enter_dead(s, f); + else + swap_enter_dead(s, f); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); + + /* Request a reload of /proc/swaps, so that following units + * can follow our state change */ + u->manager->request_reload = true; +} + +static void swap_timer_event(Unit *u, uint64_t elapsed, Watch *w) { + Swap *s = SWAP(u); + + assert(s); + assert(elapsed == 1); + assert(w == &s->timer_watch); + + switch (s->state) { + + case SWAP_ACTIVATING: + log_warning("%s activation timed out. Stopping.", u->id); + swap_enter_signal(s, SWAP_ACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); + break; + + case SWAP_DEACTIVATING: + log_warning("%s deactivation timed out. Stopping.", u->id); + swap_enter_signal(s, SWAP_DEACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); + break; + + case SWAP_ACTIVATING_SIGTERM: + if (s->exec_context.send_sigkill) { + log_warning("%s activation timed out. Killing.", u->id); + swap_enter_signal(s, SWAP_ACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); + } else { + log_warning("%s activation timed out. Skipping SIGKILL. Ignoring.", u->id); + swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); + } + break; + + case SWAP_DEACTIVATING_SIGTERM: + if (s->exec_context.send_sigkill) { + log_warning("%s deactivation timed out. Killing.", u->id); + swap_enter_signal(s, SWAP_DEACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); + } else { + log_warning("%s deactivation timed out. Skipping SIGKILL. Ignoring.", u->id); + swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); + } + break; + + case SWAP_ACTIVATING_SIGKILL: + case SWAP_DEACTIVATING_SIGKILL: + log_warning("%s swap process still around after SIGKILL. Ignoring.", u->id); + swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } +} + +static int swap_load_proc_swaps(Manager *m, bool set_flags) { + unsigned i; + int r = 0; + + assert(m); + + rewind(m->proc_swaps); + + (void) fscanf(m->proc_swaps, "%*s %*s %*s %*s %*s\n"); + + for (i = 1;; i++) { + char *dev = NULL, *d; + int prio = 0, k; + + if ((k = fscanf(m->proc_swaps, + "%ms " /* device/file */ + "%*s " /* type of swap */ + "%*s " /* swap size */ + "%*s " /* used */ + "%i\n", /* priority */ + &dev, &prio)) != 2) { + + if (k == EOF) + break; + + log_warning("Failed to parse /proc/swaps:%u.", i); + free(dev); + continue; + } + + d = cunescape(dev); + free(dev); + + if (!d) + return -ENOMEM; + + k = swap_process_new_swap(m, d, prio, set_flags); + free(d); + + if (k < 0) + r = k; + } + + return r; +} + +int swap_dispatch_reload(Manager *m) { + /* This function should go as soon as the kernel properly notifies us */ + + if (_likely_(!m->request_reload)) + return 0; + + m->request_reload = false; + + return swap_fd_event(m, EPOLLPRI); +} + +int swap_fd_event(Manager *m, int events) { + Unit *u; + int r; + + assert(m); + assert(events & EPOLLPRI); + + if ((r = swap_load_proc_swaps(m, true)) < 0) { + log_error("Failed to reread /proc/swaps: %s", strerror(-r)); + + /* Reset flags, just in case, for late calls */ + LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_SWAP]) { + Swap *swap = SWAP(u); + + swap->is_active = swap->just_activated = false; + } + + return 0; + } + + manager_dispatch_load_queue(m); + + LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_SWAP]) { + Swap *swap = SWAP(u); + + if (!swap->is_active) { + /* This has just been deactivated */ + + swap->from_proc_swaps = false; + swap_unset_proc_swaps(swap); + + switch (swap->state) { + + case SWAP_ACTIVE: + swap_enter_dead(swap, SWAP_SUCCESS); + break; + + default: + swap_set_state(swap, swap->state); + break; + } + + } else if (swap->just_activated) { + + /* New swap entry */ + + switch (swap->state) { + + case SWAP_DEAD: + case SWAP_FAILED: + swap_enter_active(swap, SWAP_SUCCESS); + break; + + default: + /* Nothing really changed, but let's + * issue an notification call + * nonetheless, in case somebody is + * waiting for this. */ + swap_set_state(swap, swap->state); + break; + } + } + + /* Reset the flags for later calls */ + swap->is_active = swap->just_activated = false; + } + + return 1; +} + +static Unit *swap_following(Unit *u) { + Swap *s = SWAP(u); + Swap *other, *first = NULL; + + assert(s); + + if (streq_ptr(s->what, s->parameters_proc_swaps.what)) + return NULL; + + /* Make everybody follow the unit that's named after the swap + * device in the kernel */ + + LIST_FOREACH_AFTER(same_proc_swaps, other, s) + if (streq_ptr(other->what, other->parameters_proc_swaps.what)) + return UNIT(other); + + LIST_FOREACH_BEFORE(same_proc_swaps, other, s) { + if (streq_ptr(other->what, other->parameters_proc_swaps.what)) + return UNIT(other); + + first = other; + } + + return UNIT(first); +} + +static int swap_following_set(Unit *u, Set **_set) { + Swap *s = SWAP(u); + Swap *other; + Set *set; + int r; + + assert(s); + assert(_set); + + if (LIST_JUST_US(same_proc_swaps, s)) { + *_set = NULL; + return 0; + } + + if (!(set = set_new(NULL, NULL))) + return -ENOMEM; + + LIST_FOREACH_AFTER(same_proc_swaps, other, s) + if ((r = set_put(set, other)) < 0) + goto fail; + + LIST_FOREACH_BEFORE(same_proc_swaps, other, s) + if ((r = set_put(set, other)) < 0) + goto fail; + + *_set = set; + return 1; + +fail: + set_free(set); + return r; +} + +static void swap_shutdown(Manager *m) { + assert(m); + + if (m->proc_swaps) { + fclose(m->proc_swaps); + m->proc_swaps = NULL; + } + + hashmap_free(m->swaps_by_proc_swaps); + m->swaps_by_proc_swaps = NULL; +} + +static int swap_enumerate(Manager *m) { + int r; + struct epoll_event ev; + assert(m); + + if (!m->proc_swaps) { + if (!(m->proc_swaps = fopen("/proc/swaps", "re"))) + return (errno == ENOENT) ? 0 : -errno; + + m->swap_watch.type = WATCH_SWAP; + m->swap_watch.fd = fileno(m->proc_swaps); + + zero(ev); + ev.events = EPOLLPRI; + ev.data.ptr = &m->swap_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->swap_watch.fd, &ev) < 0) + return -errno; + } + + /* We rely on mount.c to load /etc/fstab for us */ + + if ((r = swap_load_proc_swaps(m, false)) < 0) + swap_shutdown(m); + + return r; +} + +static void swap_reset_failed(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + if (s->state == SWAP_FAILED) + swap_set_state(s, SWAP_DEAD); + + s->result = SWAP_SUCCESS; +} + +static int swap_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) { + Swap *s = SWAP(u); + int r = 0; + Set *pid_set = NULL; + + assert(s); + + if (who == KILL_MAIN) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Swap units have no main processes"); + return -ESRCH; + } + + if (s->control_pid <= 0 && who == KILL_CONTROL) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); + return -ESRCH; + } + + if (who == KILL_CONTROL || who == KILL_ALL) + if (s->control_pid > 0) + if (kill(s->control_pid, signo) < 0) + r = -errno; + + if (who == KILL_ALL && mode == KILL_CONTROL_GROUP) { + int q; + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + /* Exclude the control pid from being killed via the cgroup */ + if (s->control_pid > 0) + if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) { + r = q; + goto finish; + } + + if ((q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, pid_set)) < 0) + if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + } + +finish: + if (pid_set) + set_free(pid_set); + + return r; +} + +static const char* const swap_state_table[_SWAP_STATE_MAX] = { + [SWAP_DEAD] = "dead", + [SWAP_ACTIVATING] = "activating", + [SWAP_ACTIVE] = "active", + [SWAP_DEACTIVATING] = "deactivating", + [SWAP_ACTIVATING_SIGTERM] = "activating-sigterm", + [SWAP_ACTIVATING_SIGKILL] = "activating-sigkill", + [SWAP_DEACTIVATING_SIGTERM] = "deactivating-sigterm", + [SWAP_DEACTIVATING_SIGKILL] = "deactivating-sigkill", + [SWAP_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState); + +static const char* const swap_exec_command_table[_SWAP_EXEC_COMMAND_MAX] = { + [SWAP_EXEC_ACTIVATE] = "ExecActivate", + [SWAP_EXEC_DEACTIVATE] = "ExecDeactivate", +}; + +DEFINE_STRING_TABLE_LOOKUP(swap_exec_command, SwapExecCommand); + +static const char* const swap_result_table[_SWAP_RESULT_MAX] = { + [SWAP_SUCCESS] = "success", + [SWAP_FAILURE_RESOURCES] = "resources", + [SWAP_FAILURE_TIMEOUT] = "timeout", + [SWAP_FAILURE_EXIT_CODE] = "exit-code", + [SWAP_FAILURE_SIGNAL] = "signal", + [SWAP_FAILURE_CORE_DUMP] = "core-dump" +}; + +DEFINE_STRING_TABLE_LOOKUP(swap_result, SwapResult); + +const UnitVTable swap_vtable = { + .suffix = ".swap", + .object_size = sizeof(Swap), + .sections = + "Unit\0" + "Swap\0" + "Install\0", + + .no_alias = true, + .no_instances = true, + .show_status = true, + + .init = swap_init, + .load = swap_load, + .done = swap_done, + + .coldplug = swap_coldplug, + + .dump = swap_dump, + + .start = swap_start, + .stop = swap_stop, + + .kill = swap_kill, + + .serialize = swap_serialize, + .deserialize_item = swap_deserialize_item, + + .active_state = swap_active_state, + .sub_state_to_string = swap_sub_state_to_string, + + .check_gc = swap_check_gc, + + .sigchld_event = swap_sigchld_event, + .timer_event = swap_timer_event, + + .reset_failed = swap_reset_failed, + + .bus_interface = "org.freedesktop.systemd1.Swap", + .bus_message_handler = bus_swap_message_handler, + .bus_invalidating_properties = bus_swap_invalidating_properties, + + .following = swap_following, + .following_set = swap_following_set, + + .enumerate = swap_enumerate, + .shutdown = swap_shutdown +}; diff --git a/src/swap.h b/src/swap.h new file mode 100644 index 000000000..62d08da30 --- /dev/null +++ b/src/swap.h @@ -0,0 +1,128 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooswaphfoo +#define fooswaphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Maarten Lankhorst + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Swap Swap; + +#include "unit.h" + +typedef enum SwapState { + SWAP_DEAD, + SWAP_ACTIVATING, + SWAP_ACTIVE, + SWAP_DEACTIVATING, + SWAP_ACTIVATING_SIGTERM, + SWAP_ACTIVATING_SIGKILL, + SWAP_DEACTIVATING_SIGTERM, + SWAP_DEACTIVATING_SIGKILL, + SWAP_FAILED, + _SWAP_STATE_MAX, + _SWAP_STATE_INVALID = -1 +} SwapState; + +typedef enum SwapExecCommand { + SWAP_EXEC_ACTIVATE, + SWAP_EXEC_DEACTIVATE, + _SWAP_EXEC_COMMAND_MAX, + _SWAP_EXEC_COMMAND_INVALID = -1 +} SwapExecCommand; + +typedef struct SwapParameters { + char *what; + int priority; + bool noauto:1; + bool nofail:1; + bool handle:1; +} SwapParameters; + +typedef enum SwapResult { + SWAP_SUCCESS, + SWAP_FAILURE_RESOURCES, + SWAP_FAILURE_TIMEOUT, + SWAP_FAILURE_EXIT_CODE, + SWAP_FAILURE_SIGNAL, + SWAP_FAILURE_CORE_DUMP, + _SWAP_RESULT_MAX, + _SWAP_RESULT_INVALID = -1 +} SwapResult; + +struct Swap { + Unit meta; + + char *what; + + SwapParameters parameters_etc_fstab; + SwapParameters parameters_proc_swaps; + SwapParameters parameters_fragment; + + bool from_etc_fstab:1; + bool from_proc_swaps:1; + bool from_fragment:1; + + /* Used while looking for swaps that vanished or got added + * from/to /proc/swaps */ + bool is_active:1; + bool just_activated:1; + + SwapResult result; + + usec_t timeout_usec; + + ExecCommand exec_command[_SWAP_EXEC_COMMAND_MAX]; + ExecContext exec_context; + + SwapState state, deserialized_state; + + ExecCommand* control_command; + SwapExecCommand control_command_id; + pid_t control_pid; + + Watch timer_watch; + + /* In order to be able to distinguish dependencies on + different device nodes we might end up creating multiple + devices for the same swap. We chain them up here. */ + + LIST_FIELDS(struct Swap, same_proc_swaps); +}; + +extern const UnitVTable swap_vtable; + +int swap_add_one(Manager *m, const char *what, const char *what_proc_swaps, int prio, bool no_auto, bool no_fail, bool handle, bool set_flags); + +int swap_add_one_mount_link(Swap *s, Mount *m); + +int swap_dispatch_reload(Manager *m); +int swap_fd_event(Manager *m, int events); + +const char* swap_state_to_string(SwapState i); +SwapState swap_state_from_string(const char *s); + +const char* swap_exec_command_to_string(SwapExecCommand i); +SwapExecCommand swap_exec_command_from_string(const char *s); + +const char* swap_result_to_string(SwapResult i); +SwapResult swap_result_from_string(const char *s); + +#endif diff --git a/src/sysctl.c b/src/sysctl.c new file mode 100644 index 000000000..17c671984 --- /dev/null +++ b/src/sysctl.c @@ -0,0 +1,272 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "strv.h" +#include "util.h" +#include "strv.h" + +#define PROC_SYS_PREFIX "/proc/sys/" + +static char **arg_prefixes = NULL; + +static int apply_sysctl(const char *property, const char *value) { + char *p, *n; + int r = 0, k; + + log_debug("Setting '%s' to '%s'", property, value); + + p = new(char, sizeof(PROC_SYS_PREFIX) + strlen(property)); + if (!p) { + log_error("Out of memory"); + return -ENOMEM; + } + + n = stpcpy(p, PROC_SYS_PREFIX); + strcpy(n, property); + + for (; *n; n++) + if (*n == '.') + *n = '/'; + + if (!strv_isempty(arg_prefixes)) { + char **i; + bool good = false; + + STRV_FOREACH(i, arg_prefixes) + if (path_startswith(p, *i)) { + good = true; + break; + } + + if (!good) { + log_debug("Skipping %s", p); + free(p); + return 0; + } + } + + k = write_one_line_file(p, value); + if (k < 0) { + + log_full(k == -ENOENT ? LOG_DEBUG : LOG_WARNING, + "Failed to write '%s' to '%s': %s", value, p, strerror(-k)); + + if (k != -ENOENT && r == 0) + r = k; + } + + free(p); + + return r; +} + +static int apply_file(const char *path, bool ignore_enoent) { + FILE *f; + int r = 0; + + assert(path); + + if (!(f = fopen(path, "re"))) { + if (ignore_enoent && errno == ENOENT) + return 0; + + log_error("Failed to open file '%s', ignoring: %m", path); + return -errno; + } + + log_debug("apply: %s\n", path); + while (!feof(f)) { + char l[LINE_MAX], *p, *value; + int k; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + log_error("Failed to read file '%s', ignoring: %m", path); + r = -errno; + goto finish; + } + + p = strstrip(l); + + if (!*p) + continue; + + if (strchr(COMMENTS, *p)) + continue; + + if (!(value = strchr(p, '='))) { + log_error("Line is not an assignment in file '%s': %s", path, value); + + if (r == 0) + r = -EINVAL; + continue; + } + + *value = 0; + value++; + + if ((k = apply_sysctl(strstrip(p), strstrip(value))) < 0 && r == 0) + r = k; + } + +finish: + fclose(f); + + return r; +} + +static int help(void) { + + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" + "Applies kernel sysctl settings.\n\n" + " -h --help Show this help\n" + " --prefix=PATH Only apply rules that apply to paths with the specified prefix\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_PREFIX + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "prefix", required_argument, NULL, ARG_PREFIX }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_PREFIX: { + char *p; + char **l; + + for (p = optarg; *p; p++) + if (*p == '.') + *p = '/'; + + l = strv_append(arg_prefixes, optarg); + if (!l) { + log_error("Out of memory"); + return -ENOMEM; + } + + strv_free(arg_prefixes); + arg_prefixes = l; + + break; + } + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r = 0; + + r = parse_argv(argc, argv); + if (r <= 0) + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc > optind) { + int i; + + for (i = optind; i < argc; i++) { + int k; + + k = apply_file(argv[i], false); + if (k < 0 && r == 0) + r = k; + } + } else { + char **files, **f; + int k; + + r = conf_files_list(&files, ".conf", + "/etc/sysctl.d", + "/run/sysctl.d", + "/usr/local/lib/sysctl.d", + "/usr/lib/sysctl.d", +#ifdef HAVE_SPLIT_USR + "/lib/sysctl.d", +#endif + NULL); + if (r < 0) { + log_error("Failed to enumerate sysctl.d files: %s", strerror(-r)); + goto finish; + } + + STRV_FOREACH(f, files) { + k = apply_file(*f, true); + if (k < 0 && r == 0) + r = k; + } + + k = apply_file("/etc/sysctl.conf", true); + if (k < 0 && r == 0) + r = k; + + strv_free(files); + } +finish: + strv_free(arg_prefixes); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/sysfs-show.h b/src/sysfs-show.h new file mode 100644 index 000000000..9939e8b06 --- /dev/null +++ b/src/sysfs-show.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosysfsshowhfoo +#define foosysfsshowhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +int show_sysfs(const char *seat, const char *prefix, unsigned columns); + +#endif diff --git a/src/system.conf b/src/system.conf new file mode 100644 index 000000000..33d09bcce --- /dev/null +++ b/src/system.conf @@ -0,0 +1,26 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# See systemd.conf(5) for details + +[Manager] +#LogLevel=info +#LogTarget=journal-or-kmsg +#LogColor=yes +#LogLocation=no +#DumpCore=yes +#CrashShell=no +#ShowStatus=yes +#SysVConsole=yes +#CrashChVT=1 +#CPUAffinity=1 2 +#MountAuto=yes +#SwapAuto=yes +#DefaultControllers=cpu +#DefaultStandardOutput=journal +#DefaultStandardError=inherit +#JoinControllers=cpu,cpuacct diff --git a/src/systemctl.c b/src/systemctl.c new file mode 100644 index 000000000..4b27b69c5 --- /dev/null +++ b/src/systemctl.c @@ -0,0 +1,5489 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "log.h" +#include "util.h" +#include "macro.h" +#include "set.h" +#include "utmp-wtmp.h" +#include "special.h" +#include "initreq.h" +#include "strv.h" +#include "dbus-common.h" +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "list.h" +#include "path-lookup.h" +#include "conf-parser.h" +#include "shutdownd.h" +#include "exit-status.h" +#include "bus-errors.h" +#include "build.h" +#include "unit-name.h" +#include "pager.h" +#include "spawn-agent.h" +#include "install.h" +#include "logs-show.h" + +static const char *arg_type = NULL; +static char **arg_property = NULL; +static bool arg_all = false; +static const char *arg_job_mode = "replace"; +static UnitFileScope arg_scope = UNIT_FILE_SYSTEM; +static bool arg_immediate = false; +static bool arg_no_block = false; +static bool arg_no_legend = false; +static bool arg_no_pager = false; +static bool arg_no_wtmp = false; +static bool arg_no_sync = false; +static bool arg_no_wall = false; +static bool arg_no_reload = false; +static bool arg_dry = false; +static bool arg_quiet = false; +static bool arg_full = false; +static int arg_force = 0; +static bool arg_ask_password = false; +static bool arg_failed = false; +static bool arg_runtime = false; +static char **arg_wall = NULL; +static const char *arg_kill_who = NULL; +static const char *arg_kill_mode = NULL; +static int arg_signal = SIGTERM; +static const char *arg_root = NULL; +static usec_t arg_when = 0; +static enum action { + ACTION_INVALID, + ACTION_SYSTEMCTL, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_KEXEC, + ACTION_EXIT, + ACTION_RUNLEVEL2, + ACTION_RUNLEVEL3, + ACTION_RUNLEVEL4, + ACTION_RUNLEVEL5, + ACTION_RESCUE, + ACTION_EMERGENCY, + ACTION_DEFAULT, + ACTION_RELOAD, + ACTION_REEXEC, + ACTION_RUNLEVEL, + ACTION_CANCEL_SHUTDOWN, + _ACTION_MAX +} arg_action = ACTION_SYSTEMCTL; +static enum dot { + DOT_ALL, + DOT_ORDER, + DOT_REQUIRE +} arg_dot = DOT_ALL; +static enum transport { + TRANSPORT_NORMAL, + TRANSPORT_SSH, + TRANSPORT_POLKIT +} arg_transport = TRANSPORT_NORMAL; +static const char *arg_host = NULL; +static bool arg_follow = false; +static unsigned arg_lines = 10; +static OutputMode arg_output = OUTPUT_SHORT; + +static bool private_bus = false; + +static int daemon_reload(DBusConnection *bus, char **args); +static void halt_now(enum action a); + +static bool on_tty(void) { + static int t = -1; + + /* Note that this is invoked relatively early, before we start + * the pager. That means the value we return reflects whether + * we originally were started on a tty, not if we currently + * are. But this is intended, since we want colour and so on + * when run in our own pager. */ + + if (_unlikely_(t < 0)) + t = isatty(STDOUT_FILENO) > 0; + + return t; +} + +static void pager_open_if_enabled(void) { + + /* Cache result before we open the pager */ + on_tty(); + + if (arg_no_pager) + return; + + pager_open(); +} + +static void agent_open_if_enabled(void) { + + /* Open the password agent as a child process if necessary */ + + if (!arg_ask_password) + return; + + if (arg_scope != UNIT_FILE_SYSTEM) + return; + + agent_open(); +} + +static const char *ansi_highlight_red(bool b) { + + if (!on_tty()) + return ""; + + return b ? ANSI_HIGHLIGHT_RED_ON : ANSI_HIGHLIGHT_OFF; +} + +static const char *ansi_highlight_green(bool b) { + + if (!on_tty()) + return ""; + + return b ? ANSI_HIGHLIGHT_GREEN_ON : ANSI_HIGHLIGHT_OFF; +} + +static bool error_is_no_service(const DBusError *error) { + assert(error); + + if (!dbus_error_is_set(error)) + return false; + + if (dbus_error_has_name(error, DBUS_ERROR_NAME_HAS_NO_OWNER)) + return true; + + if (dbus_error_has_name(error, DBUS_ERROR_SERVICE_UNKNOWN)) + return true; + + return startswith(error->name, "org.freedesktop.DBus.Error.Spawn."); +} + +static int translate_bus_error_to_exit_status(int r, const DBusError *error) { + assert(error); + + if (!dbus_error_is_set(error)) + return r; + + if (dbus_error_has_name(error, DBUS_ERROR_ACCESS_DENIED) || + dbus_error_has_name(error, BUS_ERROR_ONLY_BY_DEPENDENCY) || + dbus_error_has_name(error, BUS_ERROR_NO_ISOLATION) || + dbus_error_has_name(error, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE)) + return EXIT_NOPERMISSION; + + if (dbus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT)) + return EXIT_NOTINSTALLED; + + if (dbus_error_has_name(error, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE) || + dbus_error_has_name(error, BUS_ERROR_NOT_SUPPORTED)) + return EXIT_NOTIMPLEMENTED; + + if (dbus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) + return EXIT_NOTCONFIGURED; + + if (r != 0) + return r; + + return EXIT_FAILURE; +} + +static void warn_wall(enum action a) { + static const char *table[_ACTION_MAX] = { + [ACTION_HALT] = "The system is going down for system halt NOW!", + [ACTION_REBOOT] = "The system is going down for reboot NOW!", + [ACTION_POWEROFF] = "The system is going down for power-off NOW!", + [ACTION_KEXEC] = "The system is going down for kexec reboot NOW!", + [ACTION_RESCUE] = "The system is going down to rescue mode NOW!", + [ACTION_EMERGENCY] = "The system is going down to emergency mode NOW!" + }; + + if (arg_no_wall) + return; + + if (arg_wall) { + char *p; + + if (!(p = strv_join(arg_wall, " "))) { + log_error("Failed to join strings."); + return; + } + + if (*p) { + utmp_wall(p, NULL); + free(p); + return; + } + + free(p); + } + + if (!table[a]) + return; + + utmp_wall(table[a], NULL); +} + +static bool avoid_bus(void) { + + if (running_in_chroot() > 0) + return true; + + if (sd_booted() <= 0) + return true; + + if (!isempty(arg_root)) + return true; + + if (arg_scope == UNIT_FILE_GLOBAL) + return true; + + return false; +} + +struct unit_info { + const char *id; + const char *description; + const char *load_state; + const char *active_state; + const char *sub_state; + const char *following; + const char *unit_path; + uint32_t job_id; + const char *job_type; + const char *job_path; +}; + +static int compare_unit_info(const void *a, const void *b) { + const char *d1, *d2; + const struct unit_info *u = a, *v = b; + + d1 = strrchr(u->id, '.'); + d2 = strrchr(v->id, '.'); + + if (d1 && d2) { + int r; + + if ((r = strcasecmp(d1, d2)) != 0) + return r; + } + + return strcasecmp(u->id, v->id); +} + +static bool output_show_unit(const struct unit_info *u) { + const char *dot; + + if (arg_failed) + return streq(u->active_state, "failed"); + + return (!arg_type || ((dot = strrchr(u->id, '.')) && + streq(dot+1, arg_type))) && + (arg_all || !(streq(u->active_state, "inactive") || u->following[0]) || u->job_id > 0); +} + +static void output_units_list(const struct unit_info *unit_infos, unsigned c) { + unsigned id_len, max_id_len, active_len, sub_len, job_len, desc_len, n_shown = 0; + const struct unit_info *u; + + max_id_len = sizeof("UNIT")-1; + active_len = sizeof("ACTIVE")-1; + sub_len = sizeof("SUB")-1; + job_len = sizeof("JOB")-1; + desc_len = 0; + + for (u = unit_infos; u < unit_infos + c; u++) { + if (!output_show_unit(u)) + continue; + + max_id_len = MAX(max_id_len, strlen(u->id)); + active_len = MAX(active_len, strlen(u->active_state)); + sub_len = MAX(sub_len, strlen(u->sub_state)); + if (u->job_id != 0) + job_len = MAX(job_len, strlen(u->job_type)); + } + + if (!arg_full) { + unsigned basic_len; + id_len = MIN(max_id_len, 25); + basic_len = 5 + id_len + 6 + active_len + sub_len + job_len; + if (basic_len < (unsigned) columns()) { + unsigned extra_len, incr; + extra_len = columns() - basic_len; + /* Either UNIT already got 25, or is fully satisfied. + * Grant up to 25 to DESC now. */ + incr = MIN(extra_len, 25); + desc_len += incr; + extra_len -= incr; + /* split the remaining space between UNIT and DESC, + * but do not give UNIT more than it needs. */ + if (extra_len > 0) { + incr = MIN(extra_len / 2, max_id_len - id_len); + id_len += incr; + desc_len += extra_len - incr; + } + } + } else + id_len = max_id_len; + + if (!arg_no_legend) { + printf("%-*s %-6s %-*s %-*s %-*s ", id_len, "UNIT", "LOAD", + active_len, "ACTIVE", sub_len, "SUB", job_len, "JOB"); + if (!arg_full && arg_no_pager) + printf("%.*s\n", desc_len, "DESCRIPTION"); + else + printf("%s\n", "DESCRIPTION"); + } + + for (u = unit_infos; u < unit_infos + c; u++) { + char *e; + const char *on_loaded, *off_loaded; + const char *on_active, *off_active; + + if (!output_show_unit(u)) + continue; + + n_shown++; + + if (streq(u->load_state, "error")) { + on_loaded = ansi_highlight_red(true); + off_loaded = ansi_highlight_red(false); + } else + on_loaded = off_loaded = ""; + + if (streq(u->active_state, "failed")) { + on_active = ansi_highlight_red(true); + off_active = ansi_highlight_red(false); + } else + on_active = off_active = ""; + + e = arg_full ? NULL : ellipsize(u->id, id_len, 33); + + printf("%-*s %s%-6s%s %s%-*s %-*s%s %-*s ", + id_len, e ? e : u->id, + on_loaded, u->load_state, off_loaded, + on_active, active_len, u->active_state, + sub_len, u->sub_state, off_active, + job_len, u->job_id ? u->job_type : ""); + if (!arg_full && arg_no_pager) + printf("%.*s\n", desc_len, u->description); + else + printf("%s\n", u->description); + + free(e); + } + + if (!arg_no_legend) { + printf("\nLOAD = Reflects whether the unit definition was properly loaded.\n" + "ACTIVE = The high-level unit activation state, i.e. generalization of SUB.\n" + "SUB = The low-level unit activation state, values depend on unit type.\n" + "JOB = Pending job for the unit.\n"); + + if (arg_all) + printf("\n%u units listed.\n", n_shown); + else + printf("\n%u units listed. Pass --all to see inactive units, too.\n", n_shown); + } +} + +static int list_units(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + DBusMessageIter iter, sub, sub2; + unsigned c = 0, n_units = 0; + struct unit_info *unit_infos = NULL; + + dbus_error_init(&error); + + assert(bus); + + pager_open_if_enabled(); + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnits"))) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + struct unit_info *u; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + if (c >= n_units) { + struct unit_info *w; + + n_units = MAX(2*c, 16); + w = realloc(unit_infos, sizeof(struct unit_info) * n_units); + + if (!w) { + log_error("Failed to allocate unit array."); + r = -ENOMEM; + goto finish; + } + + unit_infos = w; + } + + u = unit_infos+c; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->id, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->description, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->load_state, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->active_state, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->sub_state, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->following, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &u->unit_path, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &u->job_id, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->job_type, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &u->job_path, false) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_next(&sub); + c++; + } + + if (c > 0) { + qsort(unit_infos, c, sizeof(struct unit_info), compare_unit_info); + output_units_list(unit_infos, c); + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + free(unit_infos); + + dbus_error_free(&error); + + return r; +} + +static int compare_unit_file_list(const void *a, const void *b) { + const char *d1, *d2; + const UnitFileList *u = a, *v = b; + + d1 = strrchr(u->path, '.'); + d2 = strrchr(v->path, '.'); + + if (d1 && d2) { + int r; + + r = strcasecmp(d1, d2); + if (r != 0) + return r; + } + + return strcasecmp(file_name_from_path(u->path), file_name_from_path(v->path)); +} + +static bool output_show_unit_file(const UnitFileList *u) { + const char *dot; + + return !arg_type || ((dot = strrchr(u->path, '.')) && streq(dot+1, arg_type)); +} + +static void output_unit_file_list(const UnitFileList *units, unsigned c) { + unsigned max_id_len, id_cols, state_cols, n_shown = 0; + const UnitFileList *u; + + max_id_len = sizeof("UNIT FILE")-1; + state_cols = sizeof("STATE")-1; + for (u = units; u < units + c; u++) { + if (!output_show_unit_file(u)) + continue; + + max_id_len = MAX(max_id_len, strlen(file_name_from_path(u->path))); + state_cols = MAX(state_cols, strlen(unit_file_state_to_string(u->state))); + } + + if (!arg_full) { + unsigned basic_cols; + id_cols = MIN(max_id_len, 25); + basic_cols = 1 + id_cols + state_cols; + if (basic_cols < (unsigned) columns()) + id_cols += MIN(columns() - basic_cols, max_id_len - id_cols); + } else + id_cols = max_id_len; + + if (!arg_no_legend) + printf("%-*s %-*s\n", id_cols, "UNIT FILE", state_cols, "STATE"); + + for (u = units; u < units + c; u++) { + char *e; + const char *on, *off; + const char *id; + + if (!output_show_unit_file(u)) + continue; + + n_shown++; + + if (u->state == UNIT_FILE_MASKED || + u->state == UNIT_FILE_MASKED_RUNTIME || + u->state == UNIT_FILE_DISABLED) { + on = ansi_highlight_red(true); + off = ansi_highlight_red(false); + } else if (u->state == UNIT_FILE_ENABLED) { + on = ansi_highlight_green(true); + off = ansi_highlight_green(false); + } else + on = off = ""; + + id = file_name_from_path(u->path); + + e = arg_full ? NULL : ellipsize(id, id_cols, 33); + + printf("%-*s %s%-*s%s\n", + id_cols, e ? e : id, + on, state_cols, unit_file_state_to_string(u->state), off); + + free(e); + } + + if (!arg_no_legend) + printf("\n%u unit files listed.\n", n_shown); +} + +static int list_unit_files(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + DBusMessageIter iter, sub, sub2; + unsigned c = 0, n_units = 0; + UnitFileList *units = NULL; + + dbus_error_init(&error); + + pager_open_if_enabled(); + + if (avoid_bus()) { + Hashmap *h; + UnitFileList *u; + Iterator i; + + h = hashmap_new(string_hash_func, string_compare_func); + if (!h) { + log_error("Out of memory"); + return -ENOMEM; + } + + r = unit_file_get_list(arg_scope, arg_root, h); + if (r < 0) { + unit_file_list_free(h); + log_error("Failed to get unit file list: %s", strerror(-r)); + return r; + } + + n_units = hashmap_size(h); + units = new(UnitFileList, n_units); + if (!units) { + unit_file_list_free(h); + log_error("Out of memory"); + return -ENOMEM; + } + + HASHMAP_FOREACH(u, h, i) { + memcpy(units + c++, u, sizeof(UnitFileList)); + free(u); + } + + hashmap_free(h); + } else { + assert(bus); + + m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnitFiles"); + if (!m) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + UnitFileList *u; + const char *state; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + if (c >= n_units) { + UnitFileList *w; + + n_units = MAX(2*c, 16); + w = realloc(units, sizeof(struct UnitFileList) * n_units); + + if (!w) { + log_error("Failed to allocate unit array."); + r = -ENOMEM; + goto finish; + } + + units = w; + } + + u = units+c; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->path, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &state, false) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + u->state = unit_file_state_from_string(state); + + dbus_message_iter_next(&sub); + c++; + } + } + + if (c > 0) { + qsort(units, c, sizeof(UnitFileList), compare_unit_file_list); + output_unit_file_list(units, c); + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + free(units); + + dbus_error_free(&error); + + return r; +} + +static int dot_one_property(const char *name, const char *prop, DBusMessageIter *iter) { + static const char * const colors[] = { + "Requires", "[color=\"black\"]", + "RequiresOverridable", "[color=\"black\"]", + "Requisite", "[color=\"darkblue\"]", + "RequisiteOverridable", "[color=\"darkblue\"]", + "Wants", "[color=\"darkgrey\"]", + "Conflicts", "[color=\"red\"]", + "ConflictedBy", "[color=\"red\"]", + "After", "[color=\"green\"]" + }; + + const char *c = NULL; + unsigned i; + + assert(name); + assert(prop); + assert(iter); + + for (i = 0; i < ELEMENTSOF(colors); i += 2) + if (streq(colors[i], prop)) { + c = colors[i+1]; + break; + } + + if (!c) + return 0; + + if (arg_dot != DOT_ALL) + if ((arg_dot == DOT_ORDER) != streq(prop, "After")) + return 0; + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_ARRAY: + + if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *s; + + assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING); + dbus_message_iter_get_basic(&sub, &s); + printf("\t\"%s\"->\"%s\" %s;\n", name, s, c); + + dbus_message_iter_next(&sub); + } + + return 0; + } + } + + return 0; +} + +static int dot_one(DBusConnection *bus, const char *name, const char *path) { + DBusMessage *m = NULL, *reply = NULL; + const char *interface = "org.freedesktop.systemd1.Unit"; + int r; + DBusError error; + DBusMessageIter iter, sub, sub2, sub3; + + assert(bus); + assert(path); + + dbus_error_init(&error); + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "GetAll"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *prop; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub2, &sub3); + + if (dot_one_property(name, prop, &sub3)) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_next(&sub); + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int dot(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + DBusMessageIter iter, sub, sub2; + + dbus_error_init(&error); + + assert(bus); + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnits"))) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + printf("digraph systemd {\n"); + + dbus_message_iter_recurse(&iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *id, *description, *load_state, *active_state, *sub_state, *following, *unit_path; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &description, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &load_state, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &active_state, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &sub_state, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &following, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &unit_path, true) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + if ((r = dot_one(bus, id, unit_path)) < 0) + goto finish; + + /* printf("\t\"%s\";\n", id); */ + dbus_message_iter_next(&sub); + } + + printf("}\n"); + + log_info(" Color legend: black = Requires\n" + " dark blue = Requisite\n" + " dark grey = Wants\n" + " red = Conflicts\n" + " green = After\n"); + + if (on_tty()) + log_notice("-- You probably want to process this output with graphviz' dot tool.\n" + "-- Try a shell pipeline like 'systemctl dot | dot -Tsvg > systemd.svg'!\n"); + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int list_jobs(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + DBusMessageIter iter, sub, sub2; + unsigned k = 0; + + dbus_error_init(&error); + + assert(bus); + + pager_open_if_enabled(); + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListJobs"))) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (on_tty()) + printf("%4s %-25s %-15s %-7s\n", "JOB", "UNIT", "TYPE", "STATE"); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *name, *type, *state, *job_path, *unit_path; + uint32_t id; + char *e; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &id, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &type, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &state, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &job_path, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &unit_path, false) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + e = arg_full ? NULL : ellipsize(name, 25, 33); + printf("%4u %-25s %-15s %-7s\n", id, e ? e : name, type, state); + free(e); + + k++; + + dbus_message_iter_next(&sub); + } + + if (on_tty()) + printf("\n%u jobs listed.\n", k); + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int load_unit(DBusConnection *bus, char **args) { + DBusMessage *m = NULL; + DBusError error; + int r; + char **name; + + dbus_error_init(&error); + + assert(bus); + assert(args); + + STRV_FOREACH(name, args+1) { + DBusMessage *reply; + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LoadUnit"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, name, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + + m = reply = NULL; + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return r; +} + +static int cancel_job(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + char **name; + + dbus_error_init(&error); + + assert(bus); + assert(args); + + if (strv_length(args) <= 1) + return daemon_reload(bus, args); + + STRV_FOREACH(name, args+1) { + unsigned id; + const char *path; + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetJob"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if ((r = safe_atou(*name, &id)) < 0) { + log_error("Failed to parse job id: %s", strerror(-r)); + goto finish; + } + + assert_cc(sizeof(uint32_t) == sizeof(id)); + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + dbus_message_unref(m); + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Job", + "Cancel"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + dbus_message_unref(reply); + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static bool need_daemon_reload(DBusConnection *bus, const char *unit) { + DBusMessage *m = NULL, *reply = NULL; + dbus_bool_t b = FALSE; + DBusMessageIter iter, sub; + const char + *interface = "org.freedesktop.systemd1.Unit", + *property = "NeedDaemonReload", + *path; + + /* We ignore all errors here, since this is used to show a warning only */ + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit"))) + goto finish; + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &unit, + DBUS_TYPE_INVALID)) + goto finish; + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, NULL))) + goto finish; + + if (!dbus_message_get_args(reply, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto finish; + + dbus_message_unref(m); + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "Get"))) + goto finish; + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + goto finish; + } + + dbus_message_unref(reply); + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, NULL))) + goto finish; + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + goto finish; + + dbus_message_iter_recurse(&iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) + goto finish; + + dbus_message_iter_get_basic(&sub, &b); + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return b; +} + +typedef struct WaitData { + Set *set; + char *result; +} WaitData; + +static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *message, void *data) { + DBusError error; + WaitData *d = data; + + assert(connection); + assert(message); + assert(d); + + dbus_error_init(&error); + + log_debug("Got D-Bus request: %s.%s() on %s", + dbus_message_get_interface(message), + dbus_message_get_member(message), + dbus_message_get_path(message)); + + if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) { + log_error("Warning! D-Bus connection terminated."); + dbus_connection_close(connection); + + } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) { + uint32_t id; + const char *path, *result; + dbus_bool_t success = true; + + if (dbus_message_get_args(message, &error, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_STRING, &result, + DBUS_TYPE_INVALID)) { + char *p; + + if ((p = set_remove(d->set, (char*) path))) + free(p); + + if (*result) + d->result = strdup(result); + + goto finish; + } +#ifndef LEGACY + dbus_error_free(&error); + + if (dbus_message_get_args(message, &error, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_BOOLEAN, &success, + DBUS_TYPE_INVALID)) { + char *p; + + /* Compatibility with older systemd versions < + * 19 during upgrades. This should be dropped + * one day */ + + if ((p = set_remove(d->set, (char*) path))) + free(p); + + if (!success) + d->result = strdup("failed"); + + goto finish; + } +#endif + + log_error("Failed to parse message: %s", bus_error_message(&error)); + } + +finish: + dbus_error_free(&error); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int enable_wait_for_jobs(DBusConnection *bus) { + DBusError error; + + assert(bus); + + if (private_bus) + return 0; + + dbus_error_init(&error); + dbus_bus_add_match(bus, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='JobRemoved'," + "path='/org/freedesktop/systemd1'", + &error); + + if (dbus_error_is_set(&error)) { + log_error("Failed to add match: %s", bus_error_message(&error)); + dbus_error_free(&error); + return -EIO; + } + + /* This is slightly dirty, since we don't undo the match registrations. */ + return 0; +} + +static int wait_for_jobs(DBusConnection *bus, Set *s) { + int r; + WaitData d; + + assert(bus); + assert(s); + + zero(d); + d.set = s; + + if (!dbus_connection_add_filter(bus, wait_filter, &d, NULL)) { + log_error("Failed to add filter."); + r = -ENOMEM; + goto finish; + } + + while (!set_isempty(s) && + dbus_connection_read_write_dispatch(bus, -1)) + ; + + if (!arg_quiet && d.result) { + if (streq(d.result, "timeout")) + log_error("Job timed out."); + else if (streq(d.result, "canceled")) + log_error("Job canceled."); + else if (streq(d.result, "dependency")) + log_error("A dependency job failed. See system journal for details."); + else if (!streq(d.result, "done") && !streq(d.result, "skipped")) + log_error("Job failed. See system journal and 'systemctl status' for details."); + } + + if (streq_ptr(d.result, "timeout")) + r = -ETIME; + else if (streq_ptr(d.result, "canceled")) + r = -ECANCELED; + else if (!streq_ptr(d.result, "done") && !streq_ptr(d.result, "skipped")) + r = -EIO; + else + r = 0; + + free(d.result); + +finish: + /* This is slightly dirty, since we don't undo the filter registration. */ + + return r; +} + +static int start_unit_one( + DBusConnection *bus, + const char *method, + const char *name, + const char *mode, + DBusError *error, + Set *s) { + + DBusMessage *m = NULL, *reply = NULL; + const char *path; + int r; + + assert(bus); + assert(method); + assert(name); + assert(mode); + assert(error); + assert(arg_no_block || s); + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error))) { + + if (arg_action != ACTION_SYSTEMCTL && error_is_no_service(error)) { + /* There's always a fallback possible for + * legacy actions. */ + r = -EADDRNOTAVAIL; + goto finish; + } + + log_error("Failed to issue method call: %s", bus_error_message(error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_get_args(reply, error, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(error)); + r = -EIO; + goto finish; + } + + if (need_daemon_reload(bus, name)) + log_warning("Warning: Unit file of created job changed on disk, 'systemctl %s daemon-reload' recommended.", + arg_scope == UNIT_FILE_SYSTEM ? "--system" : "--user"); + + if (!arg_no_block) { + char *p; + + if (!(p = strdup(path))) { + log_error("Failed to duplicate path."); + r = -ENOMEM; + goto finish; + } + + if ((r = set_put(s, p)) < 0) { + free(p); + log_error("Failed to add path to set."); + goto finish; + } + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return r; +} + +static enum action verb_to_action(const char *verb) { + if (streq(verb, "halt")) + return ACTION_HALT; + else if (streq(verb, "poweroff")) + return ACTION_POWEROFF; + else if (streq(verb, "reboot")) + return ACTION_REBOOT; + else if (streq(verb, "kexec")) + return ACTION_KEXEC; + else if (streq(verb, "rescue")) + return ACTION_RESCUE; + else if (streq(verb, "emergency")) + return ACTION_EMERGENCY; + else if (streq(verb, "default")) + return ACTION_DEFAULT; + else if (streq(verb, "exit")) + return ACTION_EXIT; + else + return ACTION_INVALID; +} + +static int start_unit(DBusConnection *bus, char **args) { + + static const char * const table[_ACTION_MAX] = { + [ACTION_HALT] = SPECIAL_HALT_TARGET, + [ACTION_POWEROFF] = SPECIAL_POWEROFF_TARGET, + [ACTION_REBOOT] = SPECIAL_REBOOT_TARGET, + [ACTION_KEXEC] = SPECIAL_KEXEC_TARGET, + [ACTION_RUNLEVEL2] = SPECIAL_RUNLEVEL2_TARGET, + [ACTION_RUNLEVEL3] = SPECIAL_RUNLEVEL3_TARGET, + [ACTION_RUNLEVEL4] = SPECIAL_RUNLEVEL4_TARGET, + [ACTION_RUNLEVEL5] = SPECIAL_RUNLEVEL5_TARGET, + [ACTION_RESCUE] = SPECIAL_RESCUE_TARGET, + [ACTION_EMERGENCY] = SPECIAL_EMERGENCY_TARGET, + [ACTION_DEFAULT] = SPECIAL_DEFAULT_TARGET, + [ACTION_EXIT] = SPECIAL_EXIT_TARGET + }; + + int r, ret = 0; + const char *method, *mode, *one_name; + Set *s = NULL; + DBusError error; + char **name; + + dbus_error_init(&error); + + assert(bus); + + agent_open_if_enabled(); + + if (arg_action == ACTION_SYSTEMCTL) { + method = + streq(args[0], "stop") || + streq(args[0], "condstop") ? "StopUnit" : + streq(args[0], "reload") ? "ReloadUnit" : + streq(args[0], "restart") ? "RestartUnit" : + + streq(args[0], "try-restart") || + streq(args[0], "condrestart") ? "TryRestartUnit" : + + streq(args[0], "reload-or-restart") ? "ReloadOrRestartUnit" : + + streq(args[0], "reload-or-try-restart") || + streq(args[0], "condreload") || + + streq(args[0], "force-reload") ? "ReloadOrTryRestartUnit" : + "StartUnit"; + + mode = + (streq(args[0], "isolate") || + streq(args[0], "rescue") || + streq(args[0], "emergency")) ? "isolate" : arg_job_mode; + + one_name = table[verb_to_action(args[0])]; + + } else { + assert(arg_action < ELEMENTSOF(table)); + assert(table[arg_action]); + + method = "StartUnit"; + + mode = (arg_action == ACTION_EMERGENCY || + arg_action == ACTION_RESCUE || + arg_action == ACTION_RUNLEVEL2 || + arg_action == ACTION_RUNLEVEL3 || + arg_action == ACTION_RUNLEVEL4 || + arg_action == ACTION_RUNLEVEL5) ? "isolate" : "replace"; + + one_name = table[arg_action]; + } + + if (!arg_no_block) { + if ((ret = enable_wait_for_jobs(bus)) < 0) { + log_error("Could not watch jobs: %s", strerror(-ret)); + goto finish; + } + + if (!(s = set_new(string_hash_func, string_compare_func))) { + log_error("Failed to allocate set."); + ret = -ENOMEM; + goto finish; + } + } + + if (one_name) { + if ((ret = start_unit_one(bus, method, one_name, mode, &error, s)) <= 0) + goto finish; + } else { + STRV_FOREACH(name, args+1) + if ((r = start_unit_one(bus, method, *name, mode, &error, s)) != 0) { + ret = translate_bus_error_to_exit_status(r, &error); + dbus_error_free(&error); + } + } + + if (!arg_no_block) + if ((r = wait_for_jobs(bus, s)) < 0) { + ret = r; + goto finish; + } + +finish: + if (s) + set_free_free(s); + + dbus_error_free(&error); + + return ret; +} + +/* ask systemd-logind, which might grant access to unprivileged users through polkit */ +static int reboot_with_logind(DBusConnection *bus, enum action a) { +#ifdef HAVE_LOGIND + const char *method; + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + dbus_bool_t interactive = true; + int r; + + dbus_error_init(&error); + + switch (a) { + + case ACTION_REBOOT: + method = "Reboot"; + break; + + case ACTION_POWEROFF: + method = "PowerOff"; + break; + + default: + return -EINVAL; + } + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + method); + if (!m) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + if (error_is_no_service(&error)) { + log_debug("Failed to issue method call: %s", bus_error_message(&error)); + r = -ENOENT; + goto finish; + } + + if (dbus_error_has_name(&error, DBUS_ERROR_ACCESS_DENIED)) { + log_debug("Failed to issue method call: %s", bus_error_message(&error)); + r = -EACCES; + goto finish; + } + + log_info("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +#else + return -ENOSYS; +#endif +} + +static int start_special(DBusConnection *bus, char **args) { + enum action a; + int r; + + assert(bus); + assert(args); + + a = verb_to_action(args[0]); + + if (arg_force >= 2 && a == ACTION_HALT) + halt_now(ACTION_HALT); + + if (arg_force >= 2 && a == ACTION_POWEROFF) + halt_now(ACTION_POWEROFF); + + if (arg_force >= 2 && a == ACTION_REBOOT) + halt_now(ACTION_REBOOT); + + if (arg_force && + (a == ACTION_HALT || + a == ACTION_POWEROFF || + a == ACTION_REBOOT || + a == ACTION_KEXEC || + a == ACTION_EXIT)) + return daemon_reload(bus, args); + + if (geteuid() != 0) { + /* first try logind, to allow authentication with polkit */ + if (a == ACTION_POWEROFF || + a == ACTION_REBOOT) { + r = reboot_with_logind(bus, a); + if (r >= 0) + return r; + } + } + + r = start_unit(bus, args); + if (r >= 0) + warn_wall(a); + + return r; +} + +static int check_unit(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + const char + *interface = "org.freedesktop.systemd1.Unit", + *property = "ActiveState"; + int r = 3; /* According to LSB: "program is not running" */ + DBusError error; + char **name; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + STRV_FOREACH(name, args+1) { + const char *path = NULL; + const char *state; + DBusMessageIter iter, sub; + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, name, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + + /* Hmm, cannot figure out anything about this unit... */ + if (!arg_quiet) + puts("unknown"); + + dbus_error_free(&error); + dbus_message_unref(m); + m = NULL; + continue; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + dbus_message_unref(m); + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "Get"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + dbus_message_unref(reply); + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_get_basic(&sub, &state); + + if (!arg_quiet) + puts(state); + + if (streq(state, "active") || streq(state, "reloading")) + r = 0; + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int kill_unit(DBusConnection *bus, char **args) { + DBusMessage *m = NULL; + int r = 0; + DBusError error; + char **name; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + if (!arg_kill_who) + arg_kill_who = "all"; + + if (!arg_kill_mode) + arg_kill_mode = streq(arg_kill_who, "all") ? "control-group" : "process"; + + STRV_FOREACH(name, args+1) { + DBusMessage *reply; + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KillUnit"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, name, + DBUS_TYPE_STRING, &arg_kill_who, + DBUS_TYPE_STRING, &arg_kill_mode, + DBUS_TYPE_INT32, &arg_signal, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + dbus_error_free(&error); + r = -EIO; + } + + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return r; +} + +typedef struct ExecStatusInfo { + char *name; + + char *path; + char **argv; + + bool ignore; + + usec_t start_timestamp; + usec_t exit_timestamp; + pid_t pid; + int code; + int status; + + LIST_FIELDS(struct ExecStatusInfo, exec); +} ExecStatusInfo; + +static void exec_status_info_free(ExecStatusInfo *i) { + assert(i); + + free(i->name); + free(i->path); + strv_free(i->argv); + free(i); +} + +static int exec_status_info_deserialize(DBusMessageIter *sub, ExecStatusInfo *i) { + uint64_t start_timestamp, exit_timestamp, start_timestamp_monotonic, exit_timestamp_monotonic; + DBusMessageIter sub2, sub3; + const char*path; + unsigned n; + uint32_t pid; + int32_t code, status; + dbus_bool_t ignore; + + assert(i); + assert(i); + + if (dbus_message_iter_get_arg_type(sub) != DBUS_TYPE_STRUCT) + return -EIO; + + dbus_message_iter_recurse(sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) < 0) + return -EIO; + + if (!(i->path = strdup(path))) + return -ENOMEM; + + if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&sub2) != DBUS_TYPE_STRING) + return -EIO; + + n = 0; + dbus_message_iter_recurse(&sub2, &sub3); + while (dbus_message_iter_get_arg_type(&sub3) != DBUS_TYPE_INVALID) { + assert(dbus_message_iter_get_arg_type(&sub3) == DBUS_TYPE_STRING); + dbus_message_iter_next(&sub3); + n++; + } + + + if (!(i->argv = new0(char*, n+1))) + return -ENOMEM; + + n = 0; + dbus_message_iter_recurse(&sub2, &sub3); + while (dbus_message_iter_get_arg_type(&sub3) != DBUS_TYPE_INVALID) { + const char *s; + + assert(dbus_message_iter_get_arg_type(&sub3) == DBUS_TYPE_STRING); + dbus_message_iter_get_basic(&sub3, &s); + dbus_message_iter_next(&sub3); + + if (!(i->argv[n++] = strdup(s))) + return -ENOMEM; + } + + if (!dbus_message_iter_next(&sub2) || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_BOOLEAN, &ignore, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &start_timestamp, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &start_timestamp_monotonic, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &exit_timestamp, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &exit_timestamp_monotonic, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &pid, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_INT32, &code, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_INT32, &status, false) < 0) + return -EIO; + + i->ignore = ignore; + i->start_timestamp = (usec_t) start_timestamp; + i->exit_timestamp = (usec_t) exit_timestamp; + i->pid = (pid_t) pid; + i->code = code; + i->status = status; + + return 0; +} + +typedef struct UnitStatusInfo { + const char *id; + const char *load_state; + const char *active_state; + const char *sub_state; + const char *unit_file_state; + + const char *description; + const char *following; + + const char *path; + const char *default_control_group; + + const char *load_error; + const char *result; + + usec_t inactive_exit_timestamp; + usec_t inactive_exit_timestamp_monotonic; + usec_t active_enter_timestamp; + usec_t active_exit_timestamp; + usec_t inactive_enter_timestamp; + + bool need_daemon_reload; + + /* Service */ + pid_t main_pid; + pid_t control_pid; + const char *status_text; + bool running:1; +#ifdef HAVE_SYSV_COMPAT + bool is_sysv:1; +#endif + + usec_t start_timestamp; + usec_t exit_timestamp; + + int exit_code, exit_status; + + usec_t condition_timestamp; + bool condition_result; + + /* Socket */ + unsigned n_accepted; + unsigned n_connections; + bool accept; + + /* Device */ + const char *sysfs_path; + + /* Mount, Automount */ + const char *where; + + /* Swap */ + const char *what; + + LIST_HEAD(ExecStatusInfo, exec); +} UnitStatusInfo; + +static void print_status_info(UnitStatusInfo *i) { + ExecStatusInfo *p; + const char *on, *off, *ss; + usec_t timestamp; + char since1[FORMAT_TIMESTAMP_PRETTY_MAX], *s1; + char since2[FORMAT_TIMESTAMP_MAX], *s2; + + assert(i); + + /* This shows pretty information about a unit. See + * print_property() for a low-level property printer */ + + printf("%s", strna(i->id)); + + if (i->description && !streq_ptr(i->id, i->description)) + printf(" - %s", i->description); + + printf("\n"); + + if (i->following) + printf("\t Follow: unit currently follows state of %s\n", i->following); + + if (streq_ptr(i->load_state, "error")) { + on = ansi_highlight_red(true); + off = ansi_highlight_red(false); + } else + on = off = ""; + + if (i->load_error) + printf("\t Loaded: %s%s%s (Reason: %s)\n", on, strna(i->load_state), off, i->load_error); + else if (i->path && i->unit_file_state) + printf("\t Loaded: %s%s%s (%s; %s)\n", on, strna(i->load_state), off, i->path, i->unit_file_state); + else if (i->path) + printf("\t Loaded: %s%s%s (%s)\n", on, strna(i->load_state), off, i->path); + else + printf("\t Loaded: %s%s%s\n", on, strna(i->load_state), off); + + ss = streq_ptr(i->active_state, i->sub_state) ? NULL : i->sub_state; + + if (streq_ptr(i->active_state, "failed")) { + on = ansi_highlight_red(true); + off = ansi_highlight_red(false); + } else if (streq_ptr(i->active_state, "active") || streq_ptr(i->active_state, "reloading")) { + on = ansi_highlight_green(true); + off = ansi_highlight_green(false); + } else + on = off = ""; + + if (ss) + printf("\t Active: %s%s (%s)%s", + on, + strna(i->active_state), + ss, + off); + else + printf("\t Active: %s%s%s", + on, + strna(i->active_state), + off); + + if (!isempty(i->result) && !streq(i->result, "success")) + printf(" (Result: %s)", i->result); + + timestamp = (streq_ptr(i->active_state, "active") || + streq_ptr(i->active_state, "reloading")) ? i->active_enter_timestamp : + (streq_ptr(i->active_state, "inactive") || + streq_ptr(i->active_state, "failed")) ? i->inactive_enter_timestamp : + streq_ptr(i->active_state, "activating") ? i->inactive_exit_timestamp : + i->active_exit_timestamp; + + s1 = format_timestamp_pretty(since1, sizeof(since1), timestamp); + s2 = format_timestamp(since2, sizeof(since2), timestamp); + + if (s1) + printf(" since %s; %s\n", s2, s1); + else if (s2) + printf(" since %s\n", s2); + else + printf("\n"); + + if (!i->condition_result && i->condition_timestamp > 0) { + s1 = format_timestamp_pretty(since1, sizeof(since1), i->condition_timestamp); + s2 = format_timestamp(since2, sizeof(since2), i->condition_timestamp); + + if (s1) + printf("\t start condition failed at %s; %s\n", s2, s1); + else if (s2) + printf("\t start condition failed at %s\n", s2); + } + + if (i->sysfs_path) + printf("\t Device: %s\n", i->sysfs_path); + if (i->where) + printf("\t Where: %s\n", i->where); + if (i->what) + printf("\t What: %s\n", i->what); + + if (i->accept) + printf("\tAccepted: %u; Connected: %u\n", i->n_accepted, i->n_connections); + + LIST_FOREACH(exec, p, i->exec) { + char *t; + bool good; + + /* Only show exited processes here */ + if (p->code == 0) + continue; + + t = strv_join(p->argv, " "); + printf("\t Process: %u %s=%s ", p->pid, p->name, strna(t)); + free(t); + +#ifdef HAVE_SYSV_COMPAT + if (i->is_sysv) + good = is_clean_exit_lsb(p->code, p->status); + else +#endif + good = is_clean_exit(p->code, p->status); + + if (!good) { + on = ansi_highlight_red(true); + off = ansi_highlight_red(false); + } else + on = off = ""; + + printf("%s(code=%s, ", on, sigchld_code_to_string(p->code)); + + if (p->code == CLD_EXITED) { + const char *c; + + printf("status=%i", p->status); + +#ifdef HAVE_SYSV_COMPAT + if ((c = exit_status_to_string(p->status, i->is_sysv ? EXIT_STATUS_LSB : EXIT_STATUS_SYSTEMD))) +#else + if ((c = exit_status_to_string(p->status, EXIT_STATUS_SYSTEMD))) +#endif + printf("/%s", c); + + } else + printf("signal=%s", signal_to_string(p->status)); + + printf(")%s\n", off); + + if (i->main_pid == p->pid && + i->start_timestamp == p->start_timestamp && + i->exit_timestamp == p->start_timestamp) + /* Let's not show this twice */ + i->main_pid = 0; + + if (p->pid == i->control_pid) + i->control_pid = 0; + } + + if (i->main_pid > 0 || i->control_pid > 0) { + printf("\t"); + + if (i->main_pid > 0) { + printf("Main PID: %u", (unsigned) i->main_pid); + + if (i->running) { + char *t = NULL; + get_process_comm(i->main_pid, &t); + if (t) { + printf(" (%s)", t); + free(t); + } + } else if (i->exit_code > 0) { + printf(" (code=%s, ", sigchld_code_to_string(i->exit_code)); + + if (i->exit_code == CLD_EXITED) { + const char *c; + + printf("status=%i", i->exit_status); + +#ifdef HAVE_SYSV_COMPAT + if ((c = exit_status_to_string(i->exit_status, i->is_sysv ? EXIT_STATUS_LSB : EXIT_STATUS_SYSTEMD))) +#else + if ((c = exit_status_to_string(i->exit_status, EXIT_STATUS_SYSTEMD))) +#endif + printf("/%s", c); + + } else + printf("signal=%s", signal_to_string(i->exit_status)); + printf(")"); + } + } + + if (i->main_pid > 0 && i->control_pid > 0) + printf(";"); + + if (i->control_pid > 0) { + char *t = NULL; + + printf(" Control: %u", (unsigned) i->control_pid); + + get_process_comm(i->control_pid, &t); + if (t) { + printf(" (%s)", t); + free(t); + } + } + + printf("\n"); + } + + if (i->status_text) + printf("\t Status: \"%s\"\n", i->status_text); + + if (i->default_control_group) { + unsigned c; + + printf("\t CGroup: %s\n", i->default_control_group); + + if (arg_transport != TRANSPORT_SSH) { + if ((c = columns()) > 18) + c -= 18; + else + c = 0; + + show_cgroup_by_path(i->default_control_group, "\t\t ", c, false); + } + } + + if (i->id && arg_transport != TRANSPORT_SSH) { + printf("\n"); + show_journal_by_unit(i->id, arg_output, 0, i->inactive_exit_timestamp_monotonic, arg_lines, arg_all, arg_follow); + } + + if (i->need_daemon_reload) + printf("\n%sWarning:%s Unit file changed on disk, 'systemctl %s daemon-reload' recommended.\n", + ansi_highlight_red(true), + ansi_highlight_red(false), + arg_scope == UNIT_FILE_SYSTEM ? "--system" : "--user"); +} + +static int status_property(const char *name, DBusMessageIter *iter, UnitStatusInfo *i) { + + assert(name); + assert(iter); + assert(i); + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_STRING: { + const char *s; + + dbus_message_iter_get_basic(iter, &s); + + if (!isempty(s)) { + if (streq(name, "Id")) + i->id = s; + else if (streq(name, "LoadState")) + i->load_state = s; + else if (streq(name, "ActiveState")) + i->active_state = s; + else if (streq(name, "SubState")) + i->sub_state = s; + else if (streq(name, "Description")) + i->description = s; + else if (streq(name, "FragmentPath")) + i->path = s; +#ifdef HAVE_SYSV_COMPAT + else if (streq(name, "SysVPath")) { + i->is_sysv = true; + i->path = s; + } +#endif + else if (streq(name, "DefaultControlGroup")) + i->default_control_group = s; + else if (streq(name, "StatusText")) + i->status_text = s; + else if (streq(name, "SysFSPath")) + i->sysfs_path = s; + else if (streq(name, "Where")) + i->where = s; + else if (streq(name, "What")) + i->what = s; + else if (streq(name, "Following")) + i->following = s; + else if (streq(name, "UnitFileState")) + i->unit_file_state = s; + else if (streq(name, "Result")) + i->result = s; + } + + break; + } + + case DBUS_TYPE_BOOLEAN: { + dbus_bool_t b; + + dbus_message_iter_get_basic(iter, &b); + + if (streq(name, "Accept")) + i->accept = b; + else if (streq(name, "NeedDaemonReload")) + i->need_daemon_reload = b; + else if (streq(name, "ConditionResult")) + i->condition_result = b; + + break; + } + + case DBUS_TYPE_UINT32: { + uint32_t u; + + dbus_message_iter_get_basic(iter, &u); + + if (streq(name, "MainPID")) { + if (u > 0) { + i->main_pid = (pid_t) u; + i->running = true; + } + } else if (streq(name, "ControlPID")) + i->control_pid = (pid_t) u; + else if (streq(name, "ExecMainPID")) { + if (u > 0) + i->main_pid = (pid_t) u; + } else if (streq(name, "NAccepted")) + i->n_accepted = u; + else if (streq(name, "NConnections")) + i->n_connections = u; + + break; + } + + case DBUS_TYPE_INT32: { + int32_t j; + + dbus_message_iter_get_basic(iter, &j); + + if (streq(name, "ExecMainCode")) + i->exit_code = (int) j; + else if (streq(name, "ExecMainStatus")) + i->exit_status = (int) j; + + break; + } + + case DBUS_TYPE_UINT64: { + uint64_t u; + + dbus_message_iter_get_basic(iter, &u); + + if (streq(name, "ExecMainStartTimestamp")) + i->start_timestamp = (usec_t) u; + else if (streq(name, "ExecMainExitTimestamp")) + i->exit_timestamp = (usec_t) u; + else if (streq(name, "ActiveEnterTimestamp")) + i->active_enter_timestamp = (usec_t) u; + else if (streq(name, "InactiveEnterTimestamp")) + i->inactive_enter_timestamp = (usec_t) u; + else if (streq(name, "InactiveExitTimestamp")) + i->inactive_exit_timestamp = (usec_t) u; + else if (streq(name, "InactiveExitTimestampMonotonic")) + i->inactive_exit_timestamp_monotonic = (usec_t) u; + else if (streq(name, "ActiveExitTimestamp")) + i->active_exit_timestamp = (usec_t) u; + else if (streq(name, "ConditionTimestamp")) + i->condition_timestamp = (usec_t) u; + + break; + } + + case DBUS_TYPE_ARRAY: { + + if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && + startswith(name, "Exec")) { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + ExecStatusInfo *info; + int r; + + if (!(info = new0(ExecStatusInfo, 1))) + return -ENOMEM; + + if (!(info->name = strdup(name))) { + free(info); + return -ENOMEM; + } + + if ((r = exec_status_info_deserialize(&sub, info)) < 0) { + free(info); + return r; + } + + LIST_PREPEND(ExecStatusInfo, exec, i->exec, info); + + dbus_message_iter_next(&sub); + } + } + + break; + } + + case DBUS_TYPE_STRUCT: { + + if (streq(name, "LoadError")) { + DBusMessageIter sub; + const char *n, *message; + int r; + + dbus_message_iter_recurse(iter, &sub); + + r = bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &n, true); + if (r < 0) + return r; + + r = bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &message, false); + if (r < 0) + return r; + + if (!isempty(message)) + i->load_error = message; + } + + break; + } + } + + return 0; +} + +static int print_property(const char *name, DBusMessageIter *iter) { + assert(name); + assert(iter); + + /* This is a low-level property printer, see + * print_status_info() for the nicer output */ + + if (arg_property && !strv_find(arg_property, name)) + return 0; + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_STRUCT: { + DBusMessageIter sub; + dbus_message_iter_recurse(iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32 && streq(name, "Job")) { + uint32_t u; + + dbus_message_iter_get_basic(&sub, &u); + + if (u) + printf("%s=%u\n", name, (unsigned) u); + else if (arg_all) + printf("%s=\n", name); + + return 0; + } else if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "Unit")) { + const char *s; + + dbus_message_iter_get_basic(&sub, &s); + + if (arg_all || s[0]) + printf("%s=%s\n", name, s); + + return 0; + } else if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "LoadError")) { + const char *a = NULL, *b = NULL; + + if (bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &a, true) >= 0) + bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &b, false); + + if (arg_all || !isempty(a) || !isempty(b)) + printf("%s=%s \"%s\"\n", name, strempty(a), strempty(b)); + + return 0; + } + + break; + } + + case DBUS_TYPE_ARRAY: + + if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "EnvironmentFiles")) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *path; + dbus_bool_t ignore; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_BOOLEAN, &ignore, false) >= 0) + printf("EnvironmentFile=%s (ignore_errors=%s)\n", path, yes_no(ignore)); + + dbus_message_iter_next(&sub); + } + + return 0; + + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Paths")) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *type, *path; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &type, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, false) >= 0) + printf("%s=%s\n", type, path); + + dbus_message_iter_next(&sub); + } + + return 0; + + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Timers")) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *base; + uint64_t value, next_elapse; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &base, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &value, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &next_elapse, false) >= 0) { + char timespan1[FORMAT_TIMESPAN_MAX], timespan2[FORMAT_TIMESPAN_MAX]; + + printf("%s={ value=%s ; next_elapse=%s }\n", + base, + format_timespan(timespan1, sizeof(timespan1), value), + format_timespan(timespan2, sizeof(timespan2), next_elapse)); + } + + dbus_message_iter_next(&sub); + } + + return 0; + + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "ControlGroupAttributes")) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *controller, *attr, *value; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &controller, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &attr, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &value, false) >= 0) { + + printf("ControlGroupAttribute={ controller=%s ; attribute=%s ; value=\"%s\" }\n", + controller, + attr, + value); + } + + dbus_message_iter_next(&sub); + } + + return 0; + + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && startswith(name, "Exec")) { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + ExecStatusInfo info; + + zero(info); + if (exec_status_info_deserialize(&sub, &info) >= 0) { + char timestamp1[FORMAT_TIMESTAMP_MAX], timestamp2[FORMAT_TIMESTAMP_MAX]; + char *t; + + t = strv_join(info.argv, " "); + + printf("%s={ path=%s ; argv[]=%s ; ignore_errors=%s ; start_time=[%s] ; stop_time=[%s] ; pid=%u ; code=%s ; status=%i%s%s }\n", + name, + strna(info.path), + strna(t), + yes_no(info.ignore), + strna(format_timestamp(timestamp1, sizeof(timestamp1), info.start_timestamp)), + strna(format_timestamp(timestamp2, sizeof(timestamp2), info.exit_timestamp)), + (unsigned) info. pid, + sigchld_code_to_string(info.code), + info.status, + info.code == CLD_EXITED ? "" : "/", + strempty(info.code == CLD_EXITED ? NULL : signal_to_string(info.status))); + + free(t); + } + + free(info.path); + strv_free(info.argv); + + dbus_message_iter_next(&sub); + } + + return 0; + } + + break; + } + + if (generic_print_property(name, iter, arg_all) > 0) + return 0; + + if (arg_all) + printf("%s=[unprintable]\n", name); + + return 0; +} + +static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) { + DBusMessage *m = NULL, *reply = NULL; + const char *interface = ""; + int r; + DBusError error; + DBusMessageIter iter, sub, sub2, sub3; + UnitStatusInfo info; + ExecStatusInfo *p; + + assert(bus); + assert(path); + assert(new_line); + + zero(info); + dbus_error_init(&error); + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "GetAll"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (*new_line) + printf("\n"); + + *new_line = true; + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *name; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub2, &sub3); + + if (show_properties) + r = print_property(name, &sub3); + else + r = status_property(name, &sub3, &info); + + if (r < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_next(&sub); + } + + r = 0; + + if (!show_properties) + print_status_info(&info); + + if (!streq_ptr(info.active_state, "active") && + !streq_ptr(info.active_state, "reloading") && + streq(verb, "status")) + /* According to LSB: "program not running" */ + r = 3; + + while ((p = info.exec)) { + LIST_REMOVE(ExecStatusInfo, exec, info.exec, p); + exec_status_info_free(p); + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int show(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + int r, ret = 0; + DBusError error; + bool show_properties, new_line = false; + char **name; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + show_properties = !streq(args[0], "status"); + + if (show_properties) + pager_open_if_enabled(); + + if (show_properties && strv_length(args) <= 1) { + /* If not argument is specified inspect the manager + * itself */ + + ret = show_one(args[0], bus, "/org/freedesktop/systemd1", show_properties, &new_line); + goto finish; + } + + STRV_FOREACH(name, args+1) { + const char *path = NULL; + uint32_t id; + + if (safe_atou32(*name, &id) < 0) { + + /* Interpret as unit name */ + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LoadUnit"))) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, name, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + + if (!dbus_error_has_name(&error, DBUS_ERROR_ACCESS_DENIED)) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_error_free(&error); + + dbus_message_unref(m); + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit"))) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, name, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + + if (dbus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT)) + ret = 4; /* According to LSB: "program or service status is unknown" */ + else + ret = -EIO; + goto finish; + } + } + + } else if (show_properties) { + + /* Interpret as job id */ + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetJob"))) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + } else { + + /* Interpret as PID */ + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitByPID"))) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + if ((r = show_one(args[0], bus, path, show_properties, &new_line)) != 0) + ret = r; + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return ret; +} + +static int dump(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + const char *text; + + dbus_error_init(&error); + + pager_open_if_enabled(); + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Dump"))) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + fputs(text, stdout); + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int snapshot(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + const char *name = "", *path, *id; + dbus_bool_t cleanup = FALSE; + DBusMessageIter iter, sub; + const char + *interface = "org.freedesktop.systemd1.Unit", + *property = "Id"; + + dbus_error_init(&error); + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "CreateSnapshot"))) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + if (strv_length(args) > 1) + name = args[1]; + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_BOOLEAN, &cleanup, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + dbus_message_unref(m); + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "Get"))) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + dbus_message_unref(reply); + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_get_basic(&sub, &id); + + if (!arg_quiet) + puts(id); + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int delete_snapshot(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + int r; + DBusError error; + char **name; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + STRV_FOREACH(name, args+1) { + const char *path = NULL; + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, name, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + dbus_message_unref(m); + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Snapshot", + "Remove"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + dbus_message_unref(reply); + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int daemon_reload(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + const char *method; + + dbus_error_init(&error); + + if (arg_action == ACTION_RELOAD) + method = "Reload"; + else if (arg_action == ACTION_REEXEC) + method = "Reexecute"; + else { + assert(arg_action == ACTION_SYSTEMCTL); + + method = + streq(args[0], "clear-jobs") || + streq(args[0], "cancel") ? "ClearJobs" : + streq(args[0], "daemon-reexec") ? "Reexecute" : + streq(args[0], "reset-failed") ? "ResetFailed" : + streq(args[0], "halt") ? "Halt" : + streq(args[0], "poweroff") ? "PowerOff" : + streq(args[0], "reboot") ? "Reboot" : + streq(args[0], "kexec") ? "KExec" : + streq(args[0], "exit") ? "Exit" : + /* "daemon-reload" */ "Reload"; + } + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method))) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + + if (arg_action != ACTION_SYSTEMCTL && error_is_no_service(&error)) { + /* There's always a fallback possible for + * legacy actions. */ + r = -EADDRNOTAVAIL; + goto finish; + } + + if (streq(method, "Reexecute") && dbus_error_has_name(&error, DBUS_ERROR_NO_REPLY)) { + /* On reexecution, we expect a disconnect, not + * a reply */ + r = 0; + goto finish; + } + + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int reset_failed(DBusConnection *bus, char **args) { + DBusMessage *m = NULL; + int r; + DBusError error; + char **name; + + assert(bus); + dbus_error_init(&error); + + if (strv_length(args) <= 1) + return daemon_reload(bus, args); + + STRV_FOREACH(name, args+1) { + DBusMessage *reply; + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ResetFailedUnit"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, name, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return r; +} + +static int show_enviroment(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + DBusMessageIter iter, sub, sub2; + int r; + const char + *interface = "org.freedesktop.systemd1.Manager", + *property = "Environment"; + + dbus_error_init(&error); + + pager_open_if_enabled(); + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.DBus.Properties", + "Get"))) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&sub) != DBUS_TYPE_STRING) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + while (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_INVALID) { + const char *text; + + if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_get_basic(&sub2, &text); + printf("%s\n", text); + + dbus_message_iter_next(&sub2); + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int set_environment(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int r; + const char *method; + DBusMessageIter iter, sub; + char **name; + + dbus_error_init(&error); + + method = streq(args[0], "set-environment") + ? "SetEnvironment" + : "UnsetEnvironment"; + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method))) { + + log_error("Could not allocate message."); + return -ENOMEM; + } + + dbus_message_iter_init_append(m, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + STRV_FOREACH(name, args+1) + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, name)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int enable_sysv_units(char **args) { + int r = 0; + +#if defined (HAVE_SYSV_COMPAT) && (defined(TARGET_FEDORA) || defined(TARGET_MANDRIVA) || defined(TARGET_SUSE) || defined(TARGET_MEEGO) || defined(TARGET_ALTLINUX) || defined(TARGET_MAGEIA)) + const char *verb = args[0]; + unsigned f = 1, t = 1; + LookupPaths paths; + + if (arg_scope != UNIT_FILE_SYSTEM) + return 0; + + if (!streq(verb, "enable") && + !streq(verb, "disable") && + !streq(verb, "is-enabled")) + return 0; + + /* Processes all SysV units, and reshuffles the array so that + * afterwards only the native units remain */ + + zero(paths); + r = lookup_paths_init(&paths, MANAGER_SYSTEM, false); + if (r < 0) + return r; + + r = 0; + + for (f = 1; args[f]; f++) { + const char *name; + char *p; + bool found_native = false, found_sysv; + unsigned c = 1; + const char *argv[6] = { "/sbin/chkconfig", NULL, NULL, NULL, NULL }; + char **k, *l, *q = NULL; + int j; + pid_t pid; + siginfo_t status; + + name = args[f]; + + if (!endswith(name, ".service")) + continue; + + if (path_is_absolute(name)) + continue; + + STRV_FOREACH(k, paths.unit_path) { + p = NULL; + + if (!isempty(arg_root)) + asprintf(&p, "%s/%s/%s", arg_root, *k, name); + else + asprintf(&p, "%s/%s", *k, name); + + if (!p) { + log_error("No memory"); + r = -ENOMEM; + goto finish; + } + + found_native = access(p, F_OK) >= 0; + free(p); + + if (found_native) + break; + } + + if (found_native) + continue; + + p = NULL; + if (!isempty(arg_root)) + asprintf(&p, "%s/" SYSTEM_SYSVINIT_PATH "/%s", arg_root, name); + else + asprintf(&p, SYSTEM_SYSVINIT_PATH "/%s", name); + if (!p) { + log_error("No memory"); + r = -ENOMEM; + goto finish; + } + + p[strlen(p) - sizeof(".service") + 1] = 0; + found_sysv = access(p, F_OK) >= 0; + + if (!found_sysv) { + free(p); + continue; + } + + /* Mark this entry, so that we don't try enabling it as native unit */ + args[f] = (char*) ""; + + log_info("%s is not a native service, redirecting to /sbin/chkconfig.", name); + + if (!isempty(arg_root)) + argv[c++] = q = strappend("--root=", arg_root); + + argv[c++] = file_name_from_path(p); + argv[c++] = + streq(verb, "enable") ? "on" : + streq(verb, "disable") ? "off" : "--level=5"; + argv[c] = NULL; + + l = strv_join((char**)argv, " "); + if (!l) { + log_error("No memory."); + free(q); + free(p); + r = -ENOMEM; + goto finish; + } + + log_info("Executing %s", l); + free(l); + + pid = fork(); + if (pid < 0) { + log_error("Failed to fork: %m"); + free(p); + free(q); + r = -errno; + goto finish; + } else if (pid == 0) { + /* Child */ + + execv(argv[0], (char**) argv); + _exit(EXIT_FAILURE); + } + + free(p); + free(q); + + j = wait_for_terminate(pid, &status); + if (j < 0) { + log_error("Failed to wait for child: %s", strerror(-r)); + r = j; + goto finish; + } + + if (status.si_code == CLD_EXITED) { + if (streq(verb, "is-enabled")) { + if (status.si_status == 0) { + if (!arg_quiet) + puts("enabled"); + r = 1; + } else { + if (!arg_quiet) + puts("disabled"); + } + + } else if (status.si_status != 0) { + r = -EINVAL; + goto finish; + } + } else { + r = -EPROTO; + goto finish; + } + } + +finish: + lookup_paths_free(&paths); + + /* Drop all SysV units */ + for (f = 1, t = 1; args[f]; f++) { + + if (isempty(args[f])) + continue; + + args[t++] = args[f]; + } + + args[t] = NULL; + +#endif + return r; +} + +static int enable_unit(DBusConnection *bus, char **args) { + const char *verb = args[0]; + UnitFileChange *changes = NULL; + unsigned n_changes = 0, i; + int carries_install_info = -1; + DBusMessage *m = NULL, *reply = NULL; + int r; + DBusError error; + + r = enable_sysv_units(args); + if (r < 0) + return r; + + if (!args[1]) + return 0; + + dbus_error_init(&error); + + if (!bus || avoid_bus()) { + if (streq(verb, "enable")) { + r = unit_file_enable(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(verb, "disable")) + r = unit_file_disable(arg_scope, arg_runtime, arg_root, args+1, &changes, &n_changes); + else if (streq(verb, "reenable")) { + r = unit_file_reenable(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(verb, "link")) + r = unit_file_link(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes); + else if (streq(verb, "preset")) { + r = unit_file_preset(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(verb, "mask")) + r = unit_file_mask(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes); + else if (streq(verb, "unmask")) + r = unit_file_unmask(arg_scope, arg_runtime, arg_root, args+1, &changes, &n_changes); + else + assert_not_reached("Unknown verb"); + + if (r < 0) { + log_error("Operation failed: %s", strerror(-r)); + goto finish; + } + + if (!arg_quiet) { + for (i = 0; i < n_changes; i++) { + if (changes[i].type == UNIT_FILE_SYMLINK) + log_info("ln -s '%s' '%s'", changes[i].source, changes[i].path); + else + log_info("rm '%s'", changes[i].path); + } + } + + } else { + const char *method; + bool send_force = true, expect_carries_install_info = false; + dbus_bool_t a, b; + DBusMessageIter iter, sub, sub2; + + if (streq(verb, "enable")) { + method = "EnableUnitFiles"; + expect_carries_install_info = true; + } else if (streq(verb, "disable")) { + method = "DisableUnitFiles"; + send_force = false; + } else if (streq(verb, "reenable")) { + method = "ReenableUnitFiles"; + expect_carries_install_info = true; + } else if (streq(verb, "link")) + method = "LinkUnitFiles"; + else if (streq(verb, "preset")) { + method = "PresetUnitFiles"; + expect_carries_install_info = true; + } else if (streq(verb, "mask")) + method = "MaskUnitFiles"; + else if (streq(verb, "unmask")) { + method = "UnmaskUnitFiles"; + send_force = false; + } else + assert_not_reached("Unknown verb"); + + m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method); + if (!m) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + dbus_message_iter_init_append(m, &iter); + + r = bus_append_strv_iter(&iter, args+1); + if (r < 0) { + log_error("Failed to append unit files."); + goto finish; + } + + a = arg_runtime; + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &a)) { + log_error("Failed to append runtime boolean."); + r = -ENOMEM; + goto finish; + } + + if (send_force) { + b = arg_force; + + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &b)) { + log_error("Failed to append force boolean."); + r = -ENOMEM; + goto finish; + } + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter)) { + log_error("Failed to initialize iterator."); + goto finish; + } + + if (expect_carries_install_info) { + r = bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &b, true); + if (r < 0) { + log_error("Failed to parse reply."); + goto finish; + } + + carries_install_info = b; + } + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *type, *path, *source; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &type, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &source, false) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + if (!arg_quiet) { + if (streq(type, "symlink")) + log_info("ln -s '%s' '%s'", source, path); + else + log_info("rm '%s'", path); + } + + dbus_message_iter_next(&sub); + } + + /* Try to reload if enabeld */ + if (!arg_no_reload) + r = daemon_reload(bus, args); + } + + if (carries_install_info == 0) + log_warning("Warning: unit files do not carry install information. No operation executed."); + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + unit_file_changes_free(changes, n_changes); + + dbus_error_free(&error); + return r; +} + +static int unit_is_enabled(DBusConnection *bus, char **args) { + DBusError error; + int r; + DBusMessage *m = NULL, *reply = NULL; + bool enabled; + char **name; + + dbus_error_init(&error); + + r = enable_sysv_units(args); + if (r < 0) + return r; + + enabled = r > 0; + + if (!bus || avoid_bus()) { + + STRV_FOREACH(name, args+1) { + UnitFileState state; + + state = unit_file_get_state(arg_scope, arg_root, *name); + if (state < 0) { + r = state; + goto finish; + } + + if (state == UNIT_FILE_ENABLED || + state == UNIT_FILE_ENABLED_RUNTIME || + state == UNIT_FILE_STATIC) + enabled = true; + + if (!arg_quiet) + puts(unit_file_state_to_string(state)); + } + + } else { + STRV_FOREACH(name, args+1) { + const char *s; + + m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitFileState"); + if (!m) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, name, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_STRING, &s, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + + if (streq(s, "enabled") || + streq(s, "enabled-runtime") || + streq(s, "static")) + enabled = true; + + if (!arg_quiet) + puts(s); + } + } + + r = enabled ? 0 : 1; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + return r; +} + +static int systemctl_help(void) { + + pager_open_if_enabled(); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Query or send control commands to the systemd manager.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -t --type=TYPE List only units of a particular type\n" + " -p --property=NAME Show only properties by this name\n" + " -a --all Show all units/properties, including dead/empty ones\n" + " --failed Show only failed units\n" + " --full Don't ellipsize unit names on output\n" + " --fail When queueing a new job, fail if conflicting jobs are\n" + " pending\n" + " --ignore-dependencies\n" + " When queueing a new job, ignore all its dependencies\n" + " --kill-who=WHO Who to send signal to\n" + " -s --signal=SIGNAL Which signal to send\n" + " -H --host=[USER@]HOST\n" + " Show information for remote host\n" + " -P --privileged Acquire privileges before execution\n" + " -q --quiet Suppress output\n" + " --no-block Do not wait until operation finished\n" + " --no-wall Don't send wall message before halt/power-off/reboot\n" + " --no-reload When enabling/disabling unit files, don't reload daemon\n" + " configuration\n" + " --no-legend Do not print a legend (column headers and hints)\n" + " --no-pager Do not pipe output into a pager\n" + " --no-ask-password\n" + " Do not ask for system passwords\n" + " --order When generating graph for dot, show only order\n" + " --require When generating graph for dot, show only requirement\n" + " --system Connect to system manager\n" + " --user Connect to user service manager\n" + " --global Enable/disable unit files globally\n" + " -f --force When enabling unit files, override existing symlinks\n" + " When shutting down, execute action immediately\n" + " --root=PATH Enable unit files in the specified root directory\n" + " --runtime Enable unit files only temporarily until next reboot\n" + " -n --lines=INTEGER Journal entries to show\n" + " --follow Follow journal\n" + " -o --output=STRING Change journal output mode (short, short-monotonic,\n" + " verbose, export, json, cat)\n\n" + "Unit Commands:\n" + " list-units List loaded units\n" + " start [NAME...] Start (activate) one or more units\n" + " stop [NAME...] Stop (deactivate) one or more units\n" + " reload [NAME...] Reload one or more units\n" + " restart [NAME...] Start or restart one or more units\n" + " try-restart [NAME...] Restart one or more units if active\n" + " reload-or-restart [NAME...] Reload one or more units is possible,\n" + " otherwise start or restart\n" + " reload-or-try-restart [NAME...] Reload one or more units is possible,\n" + " otherwise restart if active\n" + " isolate [NAME] Start one unit and stop all others\n" + " kill [NAME...] Send signal to processes of a unit\n" + " is-active [NAME...] Check whether units are active\n" + " status [NAME...|PID...] Show runtime status of one or more units\n" + " show [NAME...|JOB...] Show properties of one or more\n" + " units/jobs or the manager\n" + " reset-failed [NAME...] Reset failed state for all, one, or more\n" + " units\n" + " load [NAME...] Load one or more units\n\n" + "Unit File Commands:\n" + " list-unit-files List installed unit files\n" + " enable [NAME...] Enable one or more unit files\n" + " disable [NAME...] Disable one or more unit files\n" + " reenable [NAME...] Reenable one or more unit files\n" + " preset [NAME...] Enable/disable one or more unit files\n" + " based on preset configuration\n" + " mask [NAME...] Mask one or more units\n" + " unmask [NAME...] Unmask one or more units\n" + " link [PATH...] Link one or more units files into\n" + " the search path\n" + " is-enabled [NAME...] Check whether unit files are enabled\n\n" + "Job Commands:\n" + " list-jobs List jobs\n" + " cancel [JOB...] Cancel all, one, or more jobs\n\n" + "Status Commands:\n" + " dump Dump server status\n" + " dot Dump dependency graph for dot(1)\n\n" + "Snapshot Commands:\n" + " snapshot [NAME] Create a snapshot\n" + " delete [NAME...] Remove one or more snapshots\n\n" + "Environment Commands:\n" + " show-environment Dump environment\n" + " set-environment [NAME=VALUE...] Set one or more environment variables\n" + " unset-environment [NAME...] Unset one or more environment variables\n\n" + "Manager Lifecycle Commands:\n" + " daemon-reload Reload systemd manager configuration\n" + " daemon-reexec Reexecute systemd manager\n\n" + "System Commands:\n" + " default Enter system default mode\n" + " rescue Enter system rescue mode\n" + " emergency Enter system emergency mode\n" + " halt Shut down and halt the system\n" + " poweroff Shut down and power-off the system\n" + " reboot Shut down and reboot the system\n" + " kexec Shut down and reboot the system with kexec\n" + " exit Ask for user instance termination\n", + program_invocation_short_name); + + return 0; +} + +static int halt_help(void) { + + printf("%s [OPTIONS...]\n\n" + "%s the system.\n\n" + " --help Show this help\n" + " --halt Halt the machine\n" + " -p --poweroff Switch off the machine\n" + " --reboot Reboot the machine\n" + " -f --force Force immediate halt/power-off/reboot\n" + " -w --wtmp-only Don't halt/power-off/reboot, just write wtmp record\n" + " -d --no-wtmp Don't write wtmp record\n" + " -n --no-sync Don't sync before halt/power-off/reboot\n" + " --no-wall Don't send wall message before halt/power-off/reboot\n", + program_invocation_short_name, + arg_action == ACTION_REBOOT ? "Reboot" : + arg_action == ACTION_POWEROFF ? "Power off" : + "Halt"); + + return 0; +} + +static int shutdown_help(void) { + + printf("%s [OPTIONS...] [TIME] [WALL...]\n\n" + "Shut down the system.\n\n" + " --help Show this help\n" + " -H --halt Halt the machine\n" + " -P --poweroff Power-off the machine\n" + " -r --reboot Reboot the machine\n" + " -h Equivalent to --poweroff, overriden by --halt\n" + " -k Don't halt/power-off/reboot, just send warnings\n" + " --no-wall Don't send wall message before halt/power-off/reboot\n" + " -c Cancel a pending shutdown\n", + program_invocation_short_name); + + return 0; +} + +static int telinit_help(void) { + + printf("%s [OPTIONS...] {COMMAND}\n\n" + "Send control commands to the init daemon.\n\n" + " --help Show this help\n" + " --no-wall Don't send wall message before halt/power-off/reboot\n\n" + "Commands:\n" + " 0 Power-off the machine\n" + " 6 Reboot the machine\n" + " 2, 3, 4, 5 Start runlevelX.target unit\n" + " 1, s, S Enter rescue mode\n" + " q, Q Reload init daemon configuration\n" + " u, U Reexecute init daemon\n", + program_invocation_short_name); + + return 0; +} + +static int runlevel_help(void) { + + printf("%s [OPTIONS...]\n\n" + "Prints the previous and current runlevel of the init system.\n\n" + " --help Show this help\n", + program_invocation_short_name); + + return 0; +} + +static int systemctl_parse_argv(int argc, char *argv[]) { + + enum { + ARG_FAIL = 0x100, + ARG_IGNORE_DEPENDENCIES, + ARG_VERSION, + ARG_USER, + ARG_SYSTEM, + ARG_GLOBAL, + ARG_NO_BLOCK, + ARG_NO_LEGEND, + ARG_NO_PAGER, + ARG_NO_WALL, + ARG_ORDER, + ARG_REQUIRE, + ARG_ROOT, + ARG_FULL, + ARG_NO_RELOAD, + ARG_KILL_MODE, + ARG_KILL_WHO, + ARG_NO_ASK_PASSWORD, + ARG_FAILED, + ARG_RUNTIME, + ARG_FOLLOW, + ARG_FORCE + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "type", required_argument, NULL, 't' }, + { "property", required_argument, NULL, 'p' }, + { "all", no_argument, NULL, 'a' }, + { "failed", no_argument, NULL, ARG_FAILED }, + { "full", no_argument, NULL, ARG_FULL }, + { "fail", no_argument, NULL, ARG_FAIL }, + { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "global", no_argument, NULL, ARG_GLOBAL }, + { "no-block", no_argument, NULL, ARG_NO_BLOCK }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + { "quiet", no_argument, NULL, 'q' }, + { "order", no_argument, NULL, ARG_ORDER }, + { "require", no_argument, NULL, ARG_REQUIRE }, + { "root", required_argument, NULL, ARG_ROOT }, + { "force", no_argument, NULL, ARG_FORCE }, + { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, + { "kill-mode", required_argument, NULL, ARG_KILL_MODE }, /* undocumented on purpose */ + { "kill-who", required_argument, NULL, ARG_KILL_WHO }, + { "signal", required_argument, NULL, 's' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "host", required_argument, NULL, 'H' }, + { "privileged",no_argument, NULL, 'P' }, + { "runtime", no_argument, NULL, ARG_RUNTIME }, + { "lines", required_argument, NULL, 'n' }, + { "follow", no_argument, NULL, ARG_FOLLOW }, + { "output", required_argument, NULL, 'o' }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + /* Only when running as systemctl we ask for passwords */ + arg_ask_password = true; + + while ((c = getopt_long(argc, argv, "ht:p:aqfs:H:Pn:o:", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + systemctl_help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(DISTRIBUTION); + puts(SYSTEMD_FEATURES); + return 0; + + case 't': + arg_type = optarg; + break; + + case 'p': { + char **l; + + if (!(l = strv_append(arg_property, optarg))) + return -ENOMEM; + + strv_free(arg_property); + arg_property = l; + + /* If the user asked for a particular + * property, show it to him, even if it is + * empty. */ + arg_all = true; + break; + } + + case 'a': + arg_all = true; + break; + + case ARG_FAIL: + arg_job_mode = "fail"; + break; + + case ARG_IGNORE_DEPENDENCIES: + arg_job_mode = "ignore-dependencies"; + break; + + case ARG_USER: + arg_scope = UNIT_FILE_USER; + break; + + case ARG_SYSTEM: + arg_scope = UNIT_FILE_SYSTEM; + break; + + case ARG_GLOBAL: + arg_scope = UNIT_FILE_GLOBAL; + break; + + case ARG_NO_BLOCK: + arg_no_block = true; + break; + + case ARG_NO_LEGEND: + arg_no_legend = true; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_WALL: + arg_no_wall = true; + break; + + case ARG_ORDER: + arg_dot = DOT_ORDER; + break; + + case ARG_REQUIRE: + arg_dot = DOT_REQUIRE; + break; + + case ARG_ROOT: + arg_root = optarg; + break; + + case ARG_FULL: + arg_full = true; + break; + + case ARG_FAILED: + arg_failed = true; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_FORCE: + arg_force ++; + break; + + case ARG_FOLLOW: + arg_follow = true; + break; + + case 'f': + /* -f is short for both --follow and --force! */ + arg_force ++; + arg_follow = true; + break; + + case ARG_NO_RELOAD: + arg_no_reload = true; + break; + + case ARG_KILL_WHO: + arg_kill_who = optarg; + break; + + case ARG_KILL_MODE: + arg_kill_mode = optarg; + break; + + case 's': + if ((arg_signal = signal_from_string_try_harder(optarg)) < 0) { + log_error("Failed to parse signal string %s.", optarg); + return -EINVAL; + } + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case 'P': + arg_transport = TRANSPORT_POLKIT; + break; + + case 'H': + arg_transport = TRANSPORT_SSH; + arg_host = optarg; + break; + + case ARG_RUNTIME: + arg_runtime = true; + break; + + case 'n': + if (safe_atou(optarg, &arg_lines) < 0) { + log_error("Failed to parse lines '%s'", optarg); + return -EINVAL; + } + break; + + case 'o': + arg_output = output_mode_from_string(optarg); + if (arg_output < 0) { + log_error("Unknown output '%s'.", optarg); + return -EINVAL; + } + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (arg_transport != TRANSPORT_NORMAL && arg_scope != UNIT_FILE_SYSTEM) { + log_error("Cannot access user instance remotely."); + return -EINVAL; + } + + return 1; +} + +static int halt_parse_argv(int argc, char *argv[]) { + + enum { + ARG_HELP = 0x100, + ARG_HALT, + ARG_REBOOT, + ARG_NO_WALL + }; + + static const struct option options[] = { + { "help", no_argument, NULL, ARG_HELP }, + { "halt", no_argument, NULL, ARG_HALT }, + { "poweroff", no_argument, NULL, 'p' }, + { "reboot", no_argument, NULL, ARG_REBOOT }, + { "force", no_argument, NULL, 'f' }, + { "wtmp-only", no_argument, NULL, 'w' }, + { "no-wtmp", no_argument, NULL, 'd' }, + { "no-sync", no_argument, NULL, 'n' }, + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + { NULL, 0, NULL, 0 } + }; + + int c, runlevel; + + assert(argc >= 0); + assert(argv); + + if (utmp_get_runlevel(&runlevel, NULL) >= 0) + if (runlevel == '0' || runlevel == '6') + arg_immediate = true; + + while ((c = getopt_long(argc, argv, "pfwdnih", options, NULL)) >= 0) { + switch (c) { + + case ARG_HELP: + halt_help(); + return 0; + + case ARG_HALT: + arg_action = ACTION_HALT; + break; + + case 'p': + if (arg_action != ACTION_REBOOT) + arg_action = ACTION_POWEROFF; + break; + + case ARG_REBOOT: + arg_action = ACTION_REBOOT; + break; + + case 'f': + arg_immediate = true; + break; + + case 'w': + arg_dry = true; + break; + + case 'd': + arg_no_wtmp = true; + break; + + case 'n': + arg_no_sync = true; + break; + + case ARG_NO_WALL: + arg_no_wall = true; + break; + + case 'i': + case 'h': + /* Compatibility nops */ + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (optind < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + return 1; +} + +static int parse_time_spec(const char *t, usec_t *_u) { + assert(t); + assert(_u); + + if (streq(t, "now")) + *_u = 0; + else if (!strchr(t, ':')) { + uint64_t u; + + if (safe_atou64(t, &u) < 0) + return -EINVAL; + + *_u = now(CLOCK_REALTIME) + USEC_PER_MINUTE * u; + } else { + char *e = NULL; + long hour, minute; + struct tm tm; + time_t s; + usec_t n; + + errno = 0; + hour = strtol(t, &e, 10); + if (errno != 0 || *e != ':' || hour < 0 || hour > 23) + return -EINVAL; + + minute = strtol(e+1, &e, 10); + if (errno != 0 || *e != 0 || minute < 0 || minute > 59) + return -EINVAL; + + n = now(CLOCK_REALTIME); + s = (time_t) (n / USEC_PER_SEC); + + zero(tm); + assert_se(localtime_r(&s, &tm)); + + tm.tm_hour = (int) hour; + tm.tm_min = (int) minute; + tm.tm_sec = 0; + + assert_se(s = mktime(&tm)); + + *_u = (usec_t) s * USEC_PER_SEC; + + while (*_u <= n) + *_u += USEC_PER_DAY; + } + + return 0; +} + +static int shutdown_parse_argv(int argc, char *argv[]) { + + enum { + ARG_HELP = 0x100, + ARG_NO_WALL + }; + + static const struct option options[] = { + { "help", no_argument, NULL, ARG_HELP }, + { "halt", no_argument, NULL, 'H' }, + { "poweroff", no_argument, NULL, 'P' }, + { "reboot", no_argument, NULL, 'r' }, + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + { NULL, 0, NULL, 0 } + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "HPrhkt:afFc", options, NULL)) >= 0) { + switch (c) { + + case ARG_HELP: + shutdown_help(); + return 0; + + case 'H': + arg_action = ACTION_HALT; + break; + + case 'P': + arg_action = ACTION_POWEROFF; + break; + + case 'r': + if (kexec_loaded()) + arg_action = ACTION_KEXEC; + else + arg_action = ACTION_REBOOT; + break; + + case 'h': + if (arg_action != ACTION_HALT) + arg_action = ACTION_POWEROFF; + break; + + case 'k': + arg_dry = true; + break; + + case ARG_NO_WALL: + arg_no_wall = true; + break; + + case 't': + case 'a': + /* Compatibility nops */ + break; + + case 'c': + arg_action = ACTION_CANCEL_SHUTDOWN; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (argc > optind) { + if ((r = parse_time_spec(argv[optind], &arg_when)) < 0) { + log_error("Failed to parse time specification: %s", argv[optind]); + return r; + } + } else + arg_when = now(CLOCK_REALTIME) + USEC_PER_MINUTE; + + /* We skip the time argument */ + if (argc > optind + 1) + arg_wall = argv + optind + 1; + + optind = argc; + + return 1; +} + +static int telinit_parse_argv(int argc, char *argv[]) { + + enum { + ARG_HELP = 0x100, + ARG_NO_WALL + }; + + static const struct option options[] = { + { "help", no_argument, NULL, ARG_HELP }, + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + { NULL, 0, NULL, 0 } + }; + + static const struct { + char from; + enum action to; + } table[] = { + { '0', ACTION_POWEROFF }, + { '6', ACTION_REBOOT }, + { '1', ACTION_RESCUE }, + { '2', ACTION_RUNLEVEL2 }, + { '3', ACTION_RUNLEVEL3 }, + { '4', ACTION_RUNLEVEL4 }, + { '5', ACTION_RUNLEVEL5 }, + { 's', ACTION_RESCUE }, + { 'S', ACTION_RESCUE }, + { 'q', ACTION_RELOAD }, + { 'Q', ACTION_RELOAD }, + { 'u', ACTION_REEXEC }, + { 'U', ACTION_REEXEC } + }; + + unsigned i; + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) { + switch (c) { + + case ARG_HELP: + telinit_help(); + return 0; + + case ARG_NO_WALL: + arg_no_wall = true; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (optind >= argc) { + telinit_help(); + return -EINVAL; + } + + if (optind + 1 < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + if (strlen(argv[optind]) != 1) { + log_error("Expected single character argument."); + return -EINVAL; + } + + for (i = 0; i < ELEMENTSOF(table); i++) + if (table[i].from == argv[optind][0]) + break; + + if (i >= ELEMENTSOF(table)) { + log_error("Unknown command %s.", argv[optind]); + return -EINVAL; + } + + arg_action = table[i].to; + + optind ++; + + return 1; +} + +static int runlevel_parse_argv(int argc, char *argv[]) { + + enum { + ARG_HELP = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, ARG_HELP }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) { + switch (c) { + + case ARG_HELP: + runlevel_help(); + return 0; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (optind < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + return 1; +} + +static int parse_argv(int argc, char *argv[]) { + assert(argc >= 0); + assert(argv); + + if (program_invocation_short_name) { + + if (strstr(program_invocation_short_name, "halt")) { + arg_action = ACTION_HALT; + return halt_parse_argv(argc, argv); + } else if (strstr(program_invocation_short_name, "poweroff")) { + arg_action = ACTION_POWEROFF; + return halt_parse_argv(argc, argv); + } else if (strstr(program_invocation_short_name, "reboot")) { + if (kexec_loaded()) + arg_action = ACTION_KEXEC; + else + arg_action = ACTION_REBOOT; + return halt_parse_argv(argc, argv); + } else if (strstr(program_invocation_short_name, "shutdown")) { + arg_action = ACTION_POWEROFF; + return shutdown_parse_argv(argc, argv); + } else if (strstr(program_invocation_short_name, "init")) { + + if (sd_booted() > 0) { + arg_action = ACTION_INVALID; + return telinit_parse_argv(argc, argv); + } else { + /* Hmm, so some other init system is + * running, we need to forward this + * request to it. For now we simply + * guess that it is Upstart. */ + + execv("/lib/upstart/telinit", argv); + + log_error("Couldn't find an alternative telinit implementation to spawn."); + return -EIO; + } + + } else if (strstr(program_invocation_short_name, "runlevel")) { + arg_action = ACTION_RUNLEVEL; + return runlevel_parse_argv(argc, argv); + } + } + + arg_action = ACTION_SYSTEMCTL; + return systemctl_parse_argv(argc, argv); +} + +static int action_to_runlevel(void) { + + static const char table[_ACTION_MAX] = { + [ACTION_HALT] = '0', + [ACTION_POWEROFF] = '0', + [ACTION_REBOOT] = '6', + [ACTION_RUNLEVEL2] = '2', + [ACTION_RUNLEVEL3] = '3', + [ACTION_RUNLEVEL4] = '4', + [ACTION_RUNLEVEL5] = '5', + [ACTION_RESCUE] = '1' + }; + + assert(arg_action < _ACTION_MAX); + + return table[arg_action]; +} + +static int talk_upstart(void) { + DBusMessage *m = NULL, *reply = NULL; + DBusError error; + int previous, rl, r; + char + env1_buf[] = "RUNLEVEL=X", + env2_buf[] = "PREVLEVEL=X"; + char *env1 = env1_buf, *env2 = env2_buf; + const char *emit = "runlevel"; + dbus_bool_t b_false = FALSE; + DBusMessageIter iter, sub; + DBusConnection *bus; + + dbus_error_init(&error); + + if (!(rl = action_to_runlevel())) + return 0; + + if (utmp_get_runlevel(&previous, NULL) < 0) + previous = 'N'; + + if (!(bus = dbus_connection_open_private("unix:abstract=/com/ubuntu/upstart", &error))) { + if (dbus_error_has_name(&error, DBUS_ERROR_NO_SERVER)) { + r = 0; + goto finish; + } + + log_error("Failed to connect to Upstart bus: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if ((r = bus_check_peercred(bus)) < 0) { + log_error("Failed to verify owner of bus."); + goto finish; + } + + if (!(m = dbus_message_new_method_call( + "com.ubuntu.Upstart", + "/com/ubuntu/Upstart", + "com.ubuntu.Upstart0_6", + "EmitEvent"))) { + + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + dbus_message_iter_init_append(m, &iter); + + env1_buf[sizeof(env1_buf)-2] = rl; + env2_buf[sizeof(env2_buf)-2] = previous; + + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &emit) || + !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &env1) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &env2) || + !dbus_message_iter_close_container(&iter, &sub) || + !dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &b_false)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { + + if (error_is_no_service(&error)) { + r = -EADDRNOTAVAIL; + goto finish; + } + + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + r = 1; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + if (bus) { + dbus_connection_flush(bus); + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + dbus_error_free(&error); + + return r; +} + +static int talk_initctl(void) { + struct init_request request; + int r, fd; + char rl; + + if (!(rl = action_to_runlevel())) + return 0; + + zero(request); + request.magic = INIT_MAGIC; + request.sleeptime = 0; + request.cmd = INIT_CMD_RUNLVL; + request.runlevel = rl; + + if ((fd = open(INIT_FIFO, O_WRONLY|O_NDELAY|O_CLOEXEC|O_NOCTTY)) < 0) { + + if (errno == ENOENT) + return 0; + + log_error("Failed to open "INIT_FIFO": %m"); + return -errno; + } + + errno = 0; + r = loop_write(fd, &request, sizeof(request), false) != sizeof(request); + close_nointr_nofail(fd); + + if (r < 0) { + log_error("Failed to write to "INIT_FIFO": %m"); + return errno ? -errno : -EIO; + } + + return 1; +} + +static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) { + + static const struct { + const char* verb; + const enum { + MORE, + LESS, + EQUAL + } argc_cmp; + const int argc; + int (* const dispatch)(DBusConnection *bus, char **args); + } verbs[] = { + { "list-units", LESS, 1, list_units }, + { "list-unit-files", EQUAL, 1, list_unit_files }, + { "list-jobs", EQUAL, 1, list_jobs }, + { "clear-jobs", EQUAL, 1, daemon_reload }, + { "load", MORE, 2, load_unit }, + { "cancel", MORE, 2, cancel_job }, + { "start", MORE, 2, start_unit }, + { "stop", MORE, 2, start_unit }, + { "condstop", MORE, 2, start_unit }, /* For compatibility with ALTLinux */ + { "reload", MORE, 2, start_unit }, + { "restart", MORE, 2, start_unit }, + { "try-restart", MORE, 2, start_unit }, + { "reload-or-restart", MORE, 2, start_unit }, + { "reload-or-try-restart", MORE, 2, start_unit }, + { "force-reload", MORE, 2, start_unit }, /* For compatibility with SysV */ + { "condreload", MORE, 2, start_unit }, /* For compatibility with ALTLinux */ + { "condrestart", MORE, 2, start_unit }, /* For compatibility with RH */ + { "isolate", EQUAL, 2, start_unit }, + { "kill", MORE, 2, kill_unit }, + { "is-active", MORE, 2, check_unit }, + { "check", MORE, 2, check_unit }, + { "show", MORE, 1, show }, + { "status", MORE, 2, show }, + { "dump", EQUAL, 1, dump }, + { "dot", EQUAL, 1, dot }, + { "snapshot", LESS, 2, snapshot }, + { "delete", MORE, 2, delete_snapshot }, + { "daemon-reload", EQUAL, 1, daemon_reload }, + { "daemon-reexec", EQUAL, 1, daemon_reload }, + { "show-environment", EQUAL, 1, show_enviroment }, + { "set-environment", MORE, 2, set_environment }, + { "unset-environment", MORE, 2, set_environment }, + { "halt", EQUAL, 1, start_special }, + { "poweroff", EQUAL, 1, start_special }, + { "reboot", EQUAL, 1, start_special }, + { "kexec", EQUAL, 1, start_special }, + { "default", EQUAL, 1, start_special }, + { "rescue", EQUAL, 1, start_special }, + { "emergency", EQUAL, 1, start_special }, + { "exit", EQUAL, 1, start_special }, + { "reset-failed", MORE, 1, reset_failed }, + { "enable", MORE, 2, enable_unit }, + { "disable", MORE, 2, enable_unit }, + { "is-enabled", MORE, 2, unit_is_enabled }, + { "reenable", MORE, 2, enable_unit }, + { "preset", MORE, 2, enable_unit }, + { "mask", MORE, 2, enable_unit }, + { "unmask", MORE, 2, enable_unit }, + { "link", MORE, 2, enable_unit } + }; + + int left; + unsigned i; + + assert(argc >= 0); + assert(argv); + assert(error); + + left = argc - optind; + + if (left <= 0) + /* Special rule: no arguments means "list-units" */ + i = 0; + else { + if (streq(argv[optind], "help")) { + systemctl_help(); + return 0; + } + + for (i = 0; i < ELEMENTSOF(verbs); i++) + if (streq(argv[optind], verbs[i].verb)) + break; + + if (i >= ELEMENTSOF(verbs)) { + log_error("Unknown operation %s", argv[optind]); + return -EINVAL; + } + } + + switch (verbs[i].argc_cmp) { + + case EQUAL: + if (left != verbs[i].argc) { + log_error("Invalid number of arguments."); + return -EINVAL; + } + + break; + + case MORE: + if (left < verbs[i].argc) { + log_error("Too few arguments."); + return -EINVAL; + } + + break; + + case LESS: + if (left > verbs[i].argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + break; + + default: + assert_not_reached("Unknown comparison operator."); + } + + /* Require a bus connection for all operations but + * enable/disable */ + if (!streq(verbs[i].verb, "enable") && + !streq(verbs[i].verb, "disable") && + !streq(verbs[i].verb, "is-enabled") && + !streq(verbs[i].verb, "list-unit-files") && + !streq(verbs[i].verb, "reenable") && + !streq(verbs[i].verb, "preset") && + !streq(verbs[i].verb, "mask") && + !streq(verbs[i].verb, "unmask") && + !streq(verbs[i].verb, "link")) { + + if (running_in_chroot() > 0) { + log_info("Running in chroot, ignoring request."); + return 0; + } + + if (!bus) { + log_error("Failed to get D-Bus connection: %s", + dbus_error_is_set(error) ? error->message : "No connection to service manager."); + return -EIO; + } + + } else { + + if (!bus && !avoid_bus()) { + log_error("Failed to get D-Bus connection: %s", + dbus_error_is_set(error) ? error->message : "No connection to service manager."); + return -EIO; + } + } + + return verbs[i].dispatch(bus, argv + optind); +} + +static int send_shutdownd(usec_t t, char mode, bool dry_run, bool warn, const char *message) { + int fd = -1; + struct msghdr msghdr; + struct iovec iovec; + union sockaddr_union sockaddr; + struct shutdownd_command c; + + zero(c); + c.elapse = t; + c.mode = mode; + c.dry_run = dry_run; + c.warn_wall = warn; + + if (message) + strncpy(c.wall_message, message, sizeof(c.wall_message)); + + if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) + return -errno; + + zero(sockaddr); + sockaddr.sa.sa_family = AF_UNIX; + sockaddr.un.sun_path[0] = 0; + strncpy(sockaddr.un.sun_path, "/run/systemd/shutdownd", sizeof(sockaddr.un.sun_path)); + + zero(iovec); + iovec.iov_base = (char*) &c; + iovec.iov_len = sizeof(c); + + zero(msghdr); + msghdr.msg_name = &sockaddr; + msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + sizeof("/run/systemd/shutdownd") - 1; + + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + + if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + close_nointr_nofail(fd); + return 0; +} + +static int reload_with_fallback(DBusConnection *bus) { + + if (bus) { + /* First, try systemd via D-Bus. */ + if (daemon_reload(bus, NULL) >= 0) + return 0; + } + + /* Nothing else worked, so let's try signals */ + assert(arg_action == ACTION_RELOAD || arg_action == ACTION_REEXEC); + + if (kill(1, arg_action == ACTION_RELOAD ? SIGHUP : SIGTERM) < 0) { + log_error("kill() failed: %m"); + return -errno; + } + + return 0; +} + +static int start_with_fallback(DBusConnection *bus) { + + if (bus) { + /* First, try systemd via D-Bus. */ + if (start_unit(bus, NULL) >= 0) + goto done; + } + + /* Hmm, talking to systemd via D-Bus didn't work. Then + * let's try to talk to Upstart via D-Bus. */ + if (talk_upstart() > 0) + goto done; + + /* Nothing else worked, so let's try + * /dev/initctl */ + if (talk_initctl() > 0) + goto done; + + log_error("Failed to talk to init daemon."); + return -EIO; + +done: + warn_wall(arg_action); + return 0; +} + +static void halt_now(enum action a) { + + /* Make sure C-A-D is handled by the kernel from this + * point on... */ + reboot(RB_ENABLE_CAD); + + switch (a) { + + case ACTION_HALT: + log_info("Halting."); + reboot(RB_HALT_SYSTEM); + break; + + case ACTION_POWEROFF: + log_info("Powering off."); + reboot(RB_POWER_OFF); + break; + + case ACTION_REBOOT: + log_info("Rebooting."); + reboot(RB_AUTOBOOT); + break; + + default: + assert_not_reached("Unknown halt action."); + } + + assert_not_reached("Uh? This shouldn't happen."); +} + +static int halt_main(DBusConnection *bus) { + int r; + + if (geteuid() != 0) { + if (arg_action == ACTION_POWEROFF || + arg_action == ACTION_REBOOT) { + r = reboot_with_logind(bus, arg_action); + if (r >= 0) + return r; + } + + log_error("Must be root."); + return -EPERM; + } + + if (arg_when > 0) { + char *m; + char date[FORMAT_TIMESTAMP_MAX]; + + m = strv_join(arg_wall, " "); + r = send_shutdownd(arg_when, + arg_action == ACTION_HALT ? 'H' : + arg_action == ACTION_POWEROFF ? 'P' : + 'r', + arg_dry, + !arg_no_wall, + m); + free(m); + + if (r < 0) + log_warning("Failed to talk to shutdownd, proceeding with immediate shutdown: %s", strerror(-r)); + else { + log_info("Shutdown scheduled for %s, use 'shutdown -c' to cancel.", + format_timestamp(date, sizeof(date), arg_when)); + return 0; + } + } + + if (!arg_dry && !arg_immediate) + return start_with_fallback(bus); + + if (!arg_no_wtmp) { + if (sd_booted() > 0) + log_debug("Not writing utmp record, assuming that systemd-update-utmp is used."); + else if ((r = utmp_put_shutdown()) < 0) + log_warning("Failed to write utmp record: %s", strerror(-r)); + } + + if (!arg_no_sync) + sync(); + + if (arg_dry) + return 0; + + halt_now(arg_action); + /* We should never reach this. */ + return -ENOSYS; +} + +static int runlevel_main(void) { + int r, runlevel, previous; + + r = utmp_get_runlevel(&runlevel, &previous); + if (r < 0) { + puts("unknown"); + return r; + } + + printf("%c %c\n", + previous <= 0 ? 'N' : previous, + runlevel <= 0 ? 'N' : runlevel); + + return 0; +} + +int main(int argc, char*argv[]) { + int r, retval = EXIT_FAILURE; + DBusConnection *bus = NULL; + DBusError error; + + dbus_error_init(&error); + + log_parse_environment(); + log_open(); + + if ((r = parse_argv(argc, argv)) < 0) + goto finish; + else if (r == 0) { + retval = EXIT_SUCCESS; + goto finish; + } + + /* /sbin/runlevel doesn't need to communicate via D-Bus, so + * let's shortcut this */ + if (arg_action == ACTION_RUNLEVEL) { + r = runlevel_main(); + retval = r < 0 ? EXIT_FAILURE : r; + goto finish; + } + + if (running_in_chroot() > 0 && arg_action != ACTION_SYSTEMCTL) { + log_info("Running in chroot, ignoring request."); + retval = 0; + goto finish; + } + + if (!avoid_bus()) { + if (arg_transport == TRANSPORT_NORMAL) + bus_connect(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, &bus, &private_bus, &error); + else if (arg_transport == TRANSPORT_POLKIT) { + bus_connect_system_polkit(&bus, &error); + private_bus = false; + } else if (arg_transport == TRANSPORT_SSH) { + bus_connect_system_ssh(NULL, arg_host, &bus, &error); + private_bus = false; + } else + assert_not_reached("Uh, invalid transport..."); + } + + switch (arg_action) { + + case ACTION_SYSTEMCTL: + r = systemctl_main(bus, argc, argv, &error); + break; + + case ACTION_HALT: + case ACTION_POWEROFF: + case ACTION_REBOOT: + case ACTION_KEXEC: + r = halt_main(bus); + break; + + case ACTION_RUNLEVEL2: + case ACTION_RUNLEVEL3: + case ACTION_RUNLEVEL4: + case ACTION_RUNLEVEL5: + case ACTION_RESCUE: + case ACTION_EMERGENCY: + case ACTION_DEFAULT: + r = start_with_fallback(bus); + break; + + case ACTION_RELOAD: + case ACTION_REEXEC: + r = reload_with_fallback(bus); + break; + + case ACTION_CANCEL_SHUTDOWN: + r = send_shutdownd(0, 0, false, false, NULL); + break; + + case ACTION_INVALID: + case ACTION_RUNLEVEL: + default: + assert_not_reached("Unknown action"); + } + + retval = r < 0 ? EXIT_FAILURE : r; + +finish: + if (bus) { + dbus_connection_flush(bus); + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + dbus_error_free(&error); + + dbus_shutdown(); + + strv_free(arg_property); + + pager_close(); + agent_close(); + + return retval; +} diff --git a/src/systemd-analyze b/src/systemd-analyze new file mode 100755 index 000000000..a49fbb7eb --- /dev/null +++ b/src/systemd-analyze @@ -0,0 +1,276 @@ +#!/usr/bin/python + +import dbus, sys + +def acquire_time_data(): + + manager = dbus.Interface(bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1'), 'org.freedesktop.systemd1.Manager') + units = manager.ListUnits() + + l = [] + + for i in units: + if i[5] != "": + continue + + properties = dbus.Interface(bus.get_object('org.freedesktop.systemd1', i[6]), 'org.freedesktop.DBus.Properties') + + ixt = int(properties.Get('org.freedesktop.systemd1.Unit', 'InactiveExitTimestampMonotonic')) + aet = int(properties.Get('org.freedesktop.systemd1.Unit', 'ActiveEnterTimestampMonotonic')) + axt = int(properties.Get('org.freedesktop.systemd1.Unit', 'ActiveExitTimestampMonotonic')) + iet = int(properties.Get('org.freedesktop.systemd1.Unit', 'InactiveEnterTimestampMonotonic')) + + l.append((str(i[0]), ixt, aet, axt, iet)) + + return l + +def acquire_start_time(): + properties = dbus.Interface(bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1'), 'org.freedesktop.DBus.Properties') + + initrd_time = int(properties.Get('org.freedesktop.systemd1.Manager', 'InitRDTimestampMonotonic')) + startup_time = int(properties.Get('org.freedesktop.systemd1.Manager', 'StartupTimestampMonotonic')) + finish_time = int(properties.Get('org.freedesktop.systemd1.Manager', 'FinishTimestampMonotonic')) + + if finish_time == 0: + sys.stderr.write("Bootup is not yet finished. Please try again later.\n") + sys.exit(1) + + assert initrd_time <= startup_time + assert startup_time <= finish_time + + return initrd_time, startup_time, finish_time + +def draw_box(context, j, k, l, m, r = 0, g = 0, b = 0): + context.save() + context.set_source_rgb(r, g, b) + context.rectangle(j, k, l, m) + context.fill() + context.restore() + +def draw_text(context, x, y, text, size = 12, r = 0, g = 0, b = 0, vcenter = 0.5, hcenter = 0.5): + context.save() + + context.set_source_rgb(r, g, b) + context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) + context.set_font_size(size) + + if vcenter or hcenter: + x_bearing, y_bearing, width, height = context.text_extents(text)[:4] + + if hcenter: + x = x - width*hcenter - x_bearing + + if vcenter: + y = y - height*vcenter - y_bearing + + context.move_to(x, y) + context.show_text(text) + + context.restore() + +def help(): + sys.stdout.write("""systemd-analyze time +systemd-analyze blame +systemd-analyze plot + +Process systemd profiling information + + -h --help Show this help +""") + + +bus = dbus.SystemBus() + +if len(sys.argv) <= 1 or sys.argv[1] == 'time': + + initrd_time, start_time, finish_time = acquire_start_time() + + if initrd_time > 0: + print "Startup finished in %lums (kernel) + %lums (initramfs) + %lums (userspace) = %lums" % ( \ + initrd_time/1000, \ + (start_time - initrd_time)/1000, \ + (finish_time - start_time)/1000, \ + finish_time/1000) + else: + print "Startup finished in %lums (kernel) + %lums (userspace) = %lums" % ( \ + start_time/1000, \ + (finish_time - start_time)/1000, \ + finish_time/1000) + + +elif sys.argv[1] == 'blame': + + data = acquire_time_data() + s = sorted(data, key = lambda i: i[2] - i[1], reverse = True) + + for name, ixt, aet, axt, iet in s: + + if ixt <= 0 or aet <= 0: + continue + + if aet <= ixt: + continue + + sys.stdout.write("%6lums %s\n" % ((aet - ixt) / 1000, name)) + +elif sys.argv[1] == 'plot': + import cairo, os + + initrd_time, start_time, finish_time = acquire_start_time() + data = acquire_time_data() + s = sorted(data, key = lambda i: i[1]) + + # Account for kernel and initramfs bars if they exist + if initrd_time > 0: + count = 3 + else: + count = 2 + + for name, ixt, aet, axt, iet in s: + + if (ixt >= start_time and ixt <= finish_time) or \ + (aet >= start_time and aet <= finish_time) or \ + (axt >= start_time and axt <= finish_time): + count += 1 + + border = 100 + bar_height = 20 + bar_space = bar_height * 0.1 + + # 1000px = 10s, 1px = 10ms + width = finish_time/10000 + border*2 + height = count * (bar_height + bar_space) + border * 2 + + if width < 1000: + width = 1000 + + surface = cairo.SVGSurface(sys.stdout, width, height) + context = cairo.Context(surface) + + draw_box(context, 0, 0, width, height, 1, 1, 1) + + context.translate(border + 0.5, border + 0.5) + + context.save() + context.set_line_width(1) + context.set_source_rgb(0.7, 0.7, 0.7) + + for x in range(0, finish_time/10000 + 100, 100): + context.move_to(x, 0) + context.line_to(x, height-border*2) + + context.move_to(0, 0) + context.line_to(width-border*2, 0) + + context.move_to(0, height-border*2) + context.line_to(width-border*2, height-border*2) + + context.stroke() + context.restore() + + osrel = "Linux" + if os.path.exists("/etc/os-release"): + for line in open("/etc/os-release"): + if line.startswith('PRETTY_NAME='): + osrel = line[12:] + osrel = osrel.strip('\"\n') + break + + banner = "{} {} ({} {}) {}".format(osrel, *(os.uname()[1:5])) + draw_text(context, 0, -15, banner, hcenter = 0, vcenter = 1) + + for x in range(0, finish_time/10000 + 100, 100): + draw_text(context, x, -5, "%lus" % (x/100), vcenter = 0, hcenter = 0) + + y = 0 + + # draw boxes for kernel and initramfs boot time + if initrd_time > 0: + draw_box(context, 0, y, initrd_time/10000, bar_height, 0.7, 0.7, 0.7) + draw_text(context, 10, y + bar_height/2, "kernel", hcenter = 0) + y += bar_height + bar_space + + draw_box(context, initrd_time/10000, y, start_time/10000-initrd_time/10000, bar_height, 0.7, 0.7, 0.7) + draw_text(context, initrd_time/10000 + 10, y + bar_height/2, "initramfs", hcenter = 0) + y += bar_height + bar_space + + else: + draw_box(context, 0, y, start_time/10000, bar_height, 0.6, 0.6, 0.6) + draw_text(context, 10, y + bar_height/2, "kernel", hcenter = 0) + y += bar_height + bar_space + + draw_box(context, start_time/10000, y, finish_time/10000-start_time/10000, bar_height, 0.7, 0.7, 0.7) + draw_text(context, start_time/10000 + 10, y + bar_height/2, "userspace", hcenter = 0) + y += bar_height + bar_space + + for name, ixt, aet, axt, iet in s: + + drawn = False + left = -1 + + if ixt >= start_time and ixt <= finish_time: + + # Activating + a = ixt + b = min(filter(lambda x: x >= ixt, (aet, axt, iet, finish_time))) - ixt + + draw_box(context, a/10000, y, b/10000, bar_height, 1, 0, 0) + drawn = True + + if left < 0: + left = a + + if aet >= start_time and aet <= finish_time: + + # Active + a = aet + b = min(filter(lambda x: x >= aet, (axt, iet, finish_time))) - aet + + draw_box(context, a/10000, y, b/10000, bar_height, .8, .6, .6) + drawn = True + + if left < 0: + left = a + + if axt >= start_time and axt <= finish_time: + + # Deactivating + a = axt + b = min(filter(lambda x: x >= axt, (iet, finish_time))) - axt + + draw_box(context, a/10000, y, b/10000, bar_height, .6, .4, .4) + drawn = True + + if left < 0: + left = a + + if drawn: + x = left/10000 + + if x < width/2-border: + draw_text(context, x + 10, y + bar_height/2, name, hcenter = 0) + else: + draw_text(context, x - 10, y + bar_height/2, name, hcenter = 1) + + y += bar_height + bar_space + + draw_text(context, 0, height-border*2, "Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating", hcenter = 0, vcenter = -1) + + if initrd_time > 0: + draw_text(context, 0, height-border*2 + bar_height, "Startup finished in %lums (kernel) + %lums (initramfs) + %lums (userspace) = %lums" % ( \ + initrd_time/1000, \ + (start_time - initrd_time)/1000, \ + (finish_time - start_time)/1000, \ + finish_time/1000), hcenter = 0, vcenter = -1) + else: + draw_text(context, 0, height-border*2 + bar_height, "Startup finished in %lums (kernel) + %lums (userspace) = %lums" % ( \ + start_time/1000, \ + (finish_time - start_time)/1000, \ + finish_time/1000), hcenter = 0, vcenter = -1) + + surface.finish() +elif sys.argv[1] in ("help", "--help", "-h"): + help() +else: + sys.stderr.write("Unknown verb '%s'.\n" % sys.argv[1]) + sys.exit(1) diff --git a/src/systemd-bash-completion.sh b/src/systemd-bash-completion.sh new file mode 100644 index 000000000..62601de90 --- /dev/null +++ b/src/systemd-bash-completion.sh @@ -0,0 +1,281 @@ +# This file is part of systemd. +# +# Copyright 2010 Ran Benita +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with systemd; If not, see . + +__systemctl() { + systemctl --full --no-legend "$@" +} + +__contains_word () { + local word=$1; shift + for w in $*; do [[ $w = $word ]] && return 0; done + return 1 +} + +__filter_units_by_property () { + local property=$1 value=$2 ; shift 2 + local units=("$@") + local props + IFS=$'\n' read -rd '' -a props < \ + <(__systemctl show --property "$property" -- "${units[@]}") + for ((i=0; $i < ${#units[*]}; i++)); do + if [[ "${props[i]}" = "$property=$value" ]]; then + echo "${units[i]}" + fi + done +} + +__get_all_units () { __systemctl list-units --all \ + | { while read a b; do echo "$a"; done; }; } +__get_active_units () { __systemctl list-units \ + | { while read a b; do echo "$a"; done; }; } +__get_inactive_units () { __systemctl list-units --all \ + | { while read a b c d; do [[ $c == "inactive" ]] && echo "$a"; done; }; } +__get_failed_units () { __systemctl list-units \ + | { while read a b c d; do [[ $c == "failed" ]] && echo "$a"; done; }; } +__get_enabled_units () { __systemctl list-unit-files \ + | { while read a b c ; do [[ $b == "enabled" ]] && echo "$a"; done; }; } +__get_disabled_units () { __systemctl list-unit-files \ + | { while read a b c ; do [[ $b == "disabled" ]] && echo "$a"; done; }; } +__get_masked_units () { __systemctl list-unit-files \ + | { while read a b c ; do [[ $b == "masked" ]] && echo "$a"; done; }; } + +_systemctl () { + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + local verb comps + + local -A OPTS=( + [STANDALONE]='--all -a --defaults --fail --ignore-dependencies --failed --force -f --full --global + --help -h --no-ask-password --no-block --no-legend --no-pager --no-reload --no-wall + --order --require --quiet -q --privileged -P --system --user --version --runtime' + [ARG]='--host -H --kill-mode --kill-who --property -p --signal -s --type -t --root' + ) + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --signal|-s) + comps=$(compgen -A signal) + ;; + --type|-t) + comps='automount device mount path service snapshot socket swap target timer' + ;; + --kill-who) + comps='all control main' + ;; + --kill-mode) + comps='control-group process' + ;; + --root) + comps=$(compgen -A directory -- "$cur" ) + compopt -o filenames + ;; + --host|-H) + comps=$(compgen -A hostname) + ;; + --property|-p) + comps='' + ;; + esac + COMPREPLY=( $(compgen -W "$comps" -- "$cur") ) + return 0 + fi + + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W "${OPTS[*]}" -- "$cur") ) + return 0 + fi + + local -A VERBS=( + [ALL_UNITS]='is-active is-enabled status show mask preset' + [ENABLED_UNITS]='disable reenable' + [DISABLED_UNITS]='enable' + [FAILED_UNITS]='reset-failed' + [STARTABLE_UNITS]='start' + [STOPPABLE_UNITS]='stop condstop kill try-restart condrestart' + [ISOLATABLE_UNITS]='isolate' + [RELOADABLE_UNITS]='reload condreload reload-or-try-restart force-reload' + [RESTARTABLE_UNITS]='restart reload-or-restart' + [MASKED_UNITS]='unmask' + [JOBS]='cancel' + [SNAPSHOTS]='delete' + [ENVS]='set-environment unset-environment' + [STANDALONE]='daemon-reexec daemon-reload default dot dump + emergency exit halt kexec list-jobs list-units + list-unit-files poweroff reboot rescue show-environment' + [NAME]='snapshot load' + [FILE]='link' + ) + + for ((i=0; $i <= $COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} && + ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG}]}; then + verb=${COMP_WORDS[i]} + break + fi + done + + if [[ -z $verb ]]; then + comps="${VERBS[*]}" + + elif __contains_word "$verb" ${VERBS[ALL_UNITS]}; then + comps=$( __get_all_units ) + + elif __contains_word "$verb" ${VERBS[ENABLED_UNITS]}; then + comps=$( __get_enabled_units ) + + elif __contains_word "$verb" ${VERBS[DISABLED_UNITS]}; then + comps=$( __get_disabled_units ) + + elif __contains_word "$verb" ${VERBS[STARTABLE_UNITS]}; then + comps=$( __filter_units_by_property CanStart yes \ + $( __get_inactive_units \ + | while read line; do \ + [[ "$line" =~ \.(device|snapshot)$ ]] || echo "$line"; \ + done )) + + elif __contains_word "$verb" ${VERBS[RESTARTABLE_UNITS]}; then + comps=$( __filter_units_by_property CanStart yes \ + $( __get_all_units \ + | while read line; do \ + [[ "$line" =~ \.(device|snapshot|socket|timer)$ ]] || echo "$line"; \ + done )) + + elif __contains_word "$verb" ${VERBS[STOPPABLE_UNITS]}; then + comps=$( __filter_units_by_property CanStop yes \ + $( __get_active_units ) ) + + elif __contains_word "$verb" ${VERBS[RELOADABLE_UNITS]}; then + comps=$( __filter_units_by_property CanReload yes \ + $( __get_active_units ) ) + + elif __contains_word "$verb" ${VERBS[ISOLATABLE_UNITS]}; then + comps=$( __filter_units_by_property AllowIsolate yes \ + $( __get_all_units ) ) + + elif __contains_word "$verb" ${VERBS[FAILED_UNITS]}; then + comps=$( __get_failed_units ) + + elif __contains_word "$verb" ${VERBS[MASKED_UNITS]}; then + comps=$( __get_masked_units ) + + elif __contains_word "$verb" ${VERBS[STANDALONE]} ${VERBS[NAME]}; then + comps='' + + elif __contains_word "$verb" ${VERBS[JOBS]}; then + comps=$( __systemctl list-jobs | { while read a b; do echo "$a"; done; } ) + + elif __contains_word "$verb" ${VERBS[SNAPSHOTS]}; then + comps=$( __systemctl list-units --type snapshot --full --all \ + | { while read a b; do echo "$a"; done; } ) + + elif __contains_word "$verb" ${VERBS[ENVS]}; then + comps=$( __systemctl show-environment \ + | while read line; do echo "${line%%=*}=";done ) + compopt -o nospace + + elif __contains_word "$verb" ${VERBS[FILE]}; then + comps=$( compgen -A file -- "$cur" ) + compopt -o filenames + fi + + COMPREPLY=( $(compgen -W "$comps" -- "$cur") ) + return 0 +} +complete -F _systemctl systemctl + +__get_all_sessions () { systemd-loginctl list-sessions | { while read a b; do echo "$a"; done; } ; } +__get_all_users () { systemd-loginctl list-users | { while read a b; do echo "$b"; done; } ; } +__get_all_seats () { systemd-loginctl list-seats | { while read a b; do echo "$a"; done; } ; } + +_loginctl () { + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + local verb comps + + local -A OPTS=( + [STANDALONE]='--all -a --help -h --no-pager --privileged -P --version' + [ARG]='--host -H --kill-who --property -p --signal -s' + ) + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --signal|-s) + comps=$(compgen -A signal) + ;; + --kill-who) + comps='all leader' + ;; + --host|-H) + comps=$(compgen -A hostname) + ;; + --property|-p) + comps='' + ;; + esac + COMPREPLY=( $(compgen -W "$comps" -- "$cur") ) + return 0 + fi + + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W "${OPTS[*]}" -- "$cur") ) + return 0 + fi + + local -A VERBS=( + [SESSIONS]='session-status show-session activate lock-session unlock-session terminate-session kill-session' + [USERS]='user-status show-user enable-linger disable-linger terminate-user kill-user' + [SEATS]='seat-status show-seat terminate-seat' + [STANDALONE]='list-sessions list-users list-seats flush-devices' + [ATTACH]='attach' + ) + + for ((i=0; $i <= $COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} && + ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG}]}; then + verb=${COMP_WORDS[i]} + break + fi + done + + if [[ -z $verb ]]; then + comps="${VERBS[*]}" + + elif __contains_word "$verb" ${VERBS[SESSIONS]}; then + comps=$( __get_all_sessions ) + + elif __contains_word "$verb" ${VERBS[USERS]}; then + comps=$( __get_all_users ) + + elif __contains_word "$verb" ${VERBS[SEATS]}; then + comps=$( __get_all_seats ) + + elif __contains_word "$verb" ${VERBS[STANDALONE]}; then + comps='' + + elif __contains_word "$verb" ${VERBS[ATTACH]}; then + if [[ $prev = $verb ]]; then + comps=$( __get_all_seats ) + else + comps=$(compgen -A file -- "$cur" ) + compopt -o filenames + fi + fi + + COMPREPLY=( $(compgen -W "$comps" -- "$cur") ) + return 0 +} +complete -F _loginctl loginctl diff --git a/src/systemd.pc.in b/src/systemd.pc.in new file mode 100644 index 000000000..79470f4ed --- /dev/null +++ b/src/systemd.pc.in @@ -0,0 +1,21 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +systemdutildir=@rootlibexecdir@ +systemdsystemunitdir=@systemunitdir@ +systemduserunitdir=@userunitdir@ +systemdsystemconfdir=@pkgsysconfdir@/system +systemduserconfdir=@pkgsysconfdir@/user +systemdsystemunitpath=${systemdsystemconfdir}:/etc/systemd/system:/run/systemd/system:/usr/local/lib/systemd/system:${systemdsystemunitdir}:/usr/lib/systemd/system:/lib/systemd/system +systemduserunitpath=${systemduserconfdir}:/etc/systemd/user:/run/systemd/user:/usr/local/lib/systemd/user:/usr/local/share/systemd/user:${systemduserunitdir}:/usr/lib/systemd/user:/usr/share/systemd/user + +Name: systemd +Description: systemd System and Service Manager +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ diff --git a/src/systemd/Makefile b/src/systemd/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/systemd/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/sd-daemon.h b/src/systemd/sd-daemon.h similarity index 100% rename from src/sd-daemon.h rename to src/systemd/sd-daemon.h diff --git a/src/systemd/sd-id128.h b/src/systemd/sd-id128.h new file mode 100644 index 000000000..af2841eb7 --- /dev/null +++ b/src/systemd/sd-id128.h @@ -0,0 +1,69 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooid128hfoo +#define fooid128hfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef union sd_id128 sd_id128_t; + +union sd_id128 { + uint8_t bytes[16]; + uint64_t qwords[2]; +}; + +char *sd_id128_to_string(sd_id128_t id, char s[33]); + +int sd_id128_from_string(const char s[33], sd_id128_t *ret); + +int sd_id128_randomize(sd_id128_t *ret); + +int sd_id128_get_machine(sd_id128_t *ret); + +int sd_id128_get_boot(sd_id128_t *ret); + +#define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ + ((sd_id128_t) { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ + 0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }}) + +/* Note that SD_ID128_FORMAT_VAL will evaluate the passed argument 16 + * times. It is hence not a good idea to call this macro with an + * expensive function as paramater or an expression with side + * effects */ +#define SD_ID128_FORMAT_STR "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" +#define SD_ID128_FORMAT_VAL(x) (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15] + +static inline bool sd_id128_equal(sd_id128_t a, sd_id128_t b) { + return memcmp(&a, &b, 16) == 0; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h new file mode 100644 index 000000000..f27e461b0 --- /dev/null +++ b/src/systemd/sd-journal.h @@ -0,0 +1,132 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foojournalhfoo +#define foojournalhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Write to daemon */ +int sd_journal_print(int piority, const char *format, ...) __attribute__ ((format (printf, 2, 3))); +int sd_journal_printv(int priority, const char *format, va_list ap); +int sd_journal_send(const char *format, ...) __attribute__((sentinel)); +int sd_journal_sendv(const struct iovec *iov, int n); + +/* Used by the macros below */ +int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) __attribute__ ((format (printf, 5, 6))); +int sd_journal_printv_with_location(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap); +int sd_journal_send_with_location(const char *file, const char *line, const char *func, const char *format, ...) __attribute__((sentinel)); +int sd_journal_sendv_with_location(const char *file, const char *line, const char *func, const struct iovec *iov, int n); + +/* implicitly add code location to messages sent, if this is enabled */ +#ifndef SD_JOURNAL_SUPPRESS_LOCATION + +#define _sd_XSTRINGIFY(x) #x +#define _sd_STRINGIFY(x) _sd_XSTRINGIFY(x) + +#define sd_journal_print(priority, ...) sd_journal_print_with_location(priority, "CODE_FILE=" __FILE__, "CODE_LINE=" _sd_STRINGIFY(__LINE__), __func__, __VA_ARGS__) +#define sd_journal_printv(priority, format, ap) sd_journal_printv_with_location(priority, "CODE_FILE=" __FILE__, "CODE_LINE=" _sd_STRINGIFY(__LINE__), __func__, format, ap) +#define sd_journal_send(...) sd_journal_send_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _sd_STRINGIFY(__LINE__), __func__, __VA_ARGS__) +#define sd_journal_sendv(iovec, n) sd_journal_sendv_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _sd_STRINGIFY(__LINE__), __func__, iovec, n) + +#endif + +int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix); + +/* Browse journal stream */ + +typedef struct sd_journal sd_journal; + +enum { + SD_JOURNAL_LOCAL_ONLY = 1, + SD_JOURNAL_RUNTIME_ONLY = 2, + SD_JOURNAL_SYSTEM_ONLY = 4 +}; + +int sd_journal_open(sd_journal **ret, int flags); +void sd_journal_close(sd_journal *j); + +int sd_journal_previous(sd_journal *j); +int sd_journal_next(sd_journal *j); + +int sd_journal_previous_skip(sd_journal *j, uint64_t skip); +int sd_journal_next_skip(sd_journal *j, uint64_t skip); + +int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret); +int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id); +int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *l); +int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *l); +void sd_journal_restart_data(sd_journal *j); + +int sd_journal_add_match(sd_journal *j, const void *data, size_t size); +void sd_journal_flush_matches(sd_journal *j); + +int sd_journal_seek_head(sd_journal *j); +int sd_journal_seek_tail(sd_journal *j); +int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec); +int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec); +int sd_journal_seek_cursor(sd_journal *j, const char *cursor); + +int sd_journal_get_cursor(sd_journal *j, char **cursor); + +/* int sd_journal_query_unique(sd_journal *j, const char *field); /\* missing *\/ */ +/* int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l); /\* missing *\/ */ +/* void sd_journal_restart_unique(sd_journal *j); /\* missing *\/ */ + +enum { + SD_JOURNAL_NOP, + SD_JOURNAL_APPEND, + SD_JOURNAL_INVALIDATE_ADD, + SD_JOURNAL_INVALIDATE_REMOVE +}; + +int sd_journal_get_fd(sd_journal *j); +int sd_journal_process(sd_journal *j); + +#define SD_JOURNAL_FOREACH(j) \ + if (sd_journal_seek_head(j) >= 0) \ + while (sd_journal_next(j) > 0) + +#define SD_JOURNAL_FOREACH_BACKWARDS(j) \ + if (sd_journal_seek_tail(j) >= 0) \ + while (sd_journal_previous(j) > 0) + +#define SD_JOURNAL_FOREACH_DATA(j, data, l) \ + for (sd_journal_restart_data(j); sd_journal_enumerate_data((j), &(data), &(l)) > 0; ) + +#define SD_JOURNAL_FOREACH_UNIQUE(j, data, l) \ + for (sd_journal_restart_unique(j); sd_journal_enumerate_data((j), &(data), &(l)) > 0; ) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/systemd/sd-login.h b/src/systemd/sd-login.h new file mode 100644 index 000000000..6e99cfc95 --- /dev/null +++ b/src/systemd/sd-login.h @@ -0,0 +1,145 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosdloginhfoo +#define foosdloginhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * A few points: + * + * Instead of returning an empty string array or empty uid array, we + * may return NULL. + * + * Free the data we return with libc free(). + * + * We return error codes as negative errno, kernel-style. 0 or + * positive on success. + * + * These functions access data in /proc, /sys/fs/cgroup and /run. All + * of these are virtual file systems, hence the accesses are + * relatively cheap. + */ + +/* Get session from PID. Note that 'shared' processes of a user are + * not attached to a session, but only attached to a user. This will + * return an error for system processes and 'shared' processes of a + * user. */ +int sd_pid_get_session(pid_t pid, char **session); + +/* Get UID of the owner of the session of the PID (or in case the + * process is a 'shared' user process the UID of that user is + * returned). This will not return the UID of the process, but rather + * the UID of the owner of the cgroup the process is in. This will + * return an error for system processes. */ +int sd_pid_get_owner_uid(pid_t pid, uid_t *uid); + +/* Get systemd unit (i.e. service) name from PID. This will return an + * error for non-service processes. */ +int sd_pid_get_unit(pid_t, char **unit); + +/* Get state from uid. Possible states: offline, lingering, online, active */ +int sd_uid_get_state(uid_t uid, char**state); + +/* Return 1 if uid has session on seat. If require_active is true will + * look for active sessions only. */ +int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat); + +/* Return sessions of user. If require_active is true will look for + * active sessions only. Returns number of sessions as return + * value. If sessions is NULL will just return number of sessions. */ +int sd_uid_get_sessions(uid_t uid, int require_active, char ***sessions); + +/* Return seats of user is on. If require_active is true will look for + * active seats only. Returns number of seats. If seats is NULL will + * just return number of seats.*/ +int sd_uid_get_seats(uid_t uid, int require_active, char ***seats); + +/* Return 1 if the session is a active */ +int sd_session_is_active(const char *session); + +/* Determine user id of session */ +int sd_session_get_uid(const char *session, uid_t *uid); + +/* Determine seat of session */ +int sd_session_get_seat(const char *session, char **seat); + +/* Determine the (PAM) service name this session was registered by. */ +int sd_session_get_service(const char *session, char **service); + +/* Determine the type of this session, i.e. one of "tty", "x11" or "unspecified". */ +int sd_session_get_type(const char *session, char **type); + +/* Determine the class of this session, i.e. one of "user", "greeter" or "lock-screen". */ +int sd_session_get_class(const char *session, char **clazz); + +/* Determine the X11 display of this session. */ +int sd_session_get_display(const char *session, char **display); + +/* Return active session and user of seat */ +int sd_seat_get_active(const char *seat, char **session, uid_t *uid); + +/* Return sessions and users on seat. Returns number of sessions as + * return value. If sessions is NULL returns only the number of + * sessions. */ +int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **uid, unsigned *n_uids); + +/* Return whether the seat is multi-session capable */ +int sd_seat_can_multi_session(const char *seat); + +/* Get all seats, store in *seats. Returns the number of seats. If + * seats is NULL only returns number of seats. */ +int sd_get_seats(char ***seats); + +/* Get all sessions, store in *sessions. Returns the number of + * sessions. If sessions is NULL only returns number of sessions. */ +int sd_get_sessions(char ***sessions); + +/* Get all logged in users, store in *users. Returns the number of + * users. If users is NULL only returns the number of users. */ +int sd_get_uids(uid_t **users); + +/* Monitor object */ +typedef struct sd_login_monitor sd_login_monitor; + +/* Create a new monitor. Category must be NULL, "seat", "session", + * "uid" to get monitor events for the specific category (or all). */ +int sd_login_monitor_new(const char *category, sd_login_monitor** ret); + +/* Destroys the passed monitor. Returns NULL. */ +sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m); + +/* Flushes the monitor */ +int sd_login_monitor_flush(sd_login_monitor *m); + +/* Get FD from monitor */ +int sd_login_monitor_get_fd(sd_login_monitor *m); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h new file mode 100644 index 000000000..c5ac3abd0 --- /dev/null +++ b/src/systemd/sd-messages.h @@ -0,0 +1,41 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosdmessageshfoo +#define foosdmessageshfoo + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SD_MESSAGE_JOURNAL_START SD_ID128_MAKE(f7,73,79,a8,49,0b,40,8b,be,5f,69,40,50,5a,77,7b) +#define SD_MESSAGE_JOURNAL_STOP SD_ID128_MAKE(d9,3f,b3,c9,c2,4d,45,1a,97,ce,a6,15,ce,59,c0,0b) +#define SD_MESSAGE_JOURNAL_DROPPED SD_ID128_MAKE(a5,96,d6,fe,7b,fa,49,94,82,8e,72,30,9e,95,d6,1e) + +#define SD_MESSAGE_COREDUMP SD_ID128_MAKE(fc,2e,22,bc,6e,e6,47,b6,b9,07,29,ab,34,a2,50,b1) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/systemd/sd-readahead.h b/src/systemd/sd-readahead.h new file mode 100644 index 000000000..1f8c5a0ca --- /dev/null +++ b/src/systemd/sd-readahead.h @@ -0,0 +1,73 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosdreadaheadhfoo +#define foosdreadaheadhfoo + +/*** + Copyright 2010 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + Reference implementation of a few boot readahead related + interfaces. These interfaces are trivial to implement. To simplify + porting we provide this reference implementation. Applications are + welcome to reimplement the algorithms described here if they do not + want to include these two source files. + + You may compile this with -DDISABLE_SYSTEMD to disable systemd + support. This makes all calls NOPs. + + Since this is drop-in code we don't want any of our symbols to be + exported in any case. Hence we declare hidden visibility for all of + them. + + You may find an up-to-date version of these source files online: + + http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-readahead.h + http://cgit.freedesktop.org/systemd/systemd/plain/src/readahead/sd-readahead.c + + This should compile on non-Linux systems, too, but all functions + will become NOPs. + + See sd-readahead(7) for more information. +*/ + +/* + Controls ongoing disk read-ahead operations during boot-up. The argument + must be a string, and either "cancel", "done" or "noreplay". + + cancel = terminate read-ahead data collection, drop collected information + done = terminate read-ahead data collection, keep collected information + noreplay = terminate read-ahead replay +*/ +int sd_readahead(const char *action); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/target.c b/src/target.c new file mode 100644 index 000000000..6c1e0c368 --- /dev/null +++ b/src/target.c @@ -0,0 +1,224 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "unit.h" +#include "target.h" +#include "load-fragment.h" +#include "log.h" +#include "dbus-target.h" +#include "special.h" +#include "unit-name.h" + +static const UnitActiveState state_translation_table[_TARGET_STATE_MAX] = { + [TARGET_DEAD] = UNIT_INACTIVE, + [TARGET_ACTIVE] = UNIT_ACTIVE +}; + +static void target_set_state(Target *t, TargetState state) { + TargetState old_state; + assert(t); + + old_state = t->state; + t->state = state; + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(t)->id, + target_state_to_string(old_state), + target_state_to_string(state)); + + unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); +} + +static int target_add_default_dependencies(Target *t) { + static const UnitDependency deps[] = { + UNIT_REQUIRES, + UNIT_REQUIRES_OVERRIDABLE, + UNIT_REQUISITE, + UNIT_REQUISITE_OVERRIDABLE, + UNIT_WANTS, + UNIT_BIND_TO + }; + + Iterator i; + Unit *other; + int r; + unsigned k; + + assert(t); + + /* Imply ordering for requirement dependencies on target + * units. Note that when the user created a contradicting + * ordering manually we won't add anything in here to make + * sure we don't create a loop. */ + + for (k = 0; k < ELEMENTSOF(deps); k++) + SET_FOREACH(other, UNIT(t)->dependencies[deps[k]], i) + if ((r = unit_add_default_target_dependency(other, UNIT(t))) < 0) + return r; + + /* Make sure targets are unloaded on shutdown */ + return unit_add_dependency_by_name(UNIT(t), UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static int target_load(Unit *u) { + Target *t = TARGET(u); + int r; + + assert(t); + + if ((r = unit_load_fragment_and_dropin(u)) < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + if (u->default_dependencies) + if ((r = target_add_default_dependencies(t)) < 0) + return r; + } + + return 0; +} + +static int target_coldplug(Unit *u) { + Target *t = TARGET(u); + + assert(t); + assert(t->state == TARGET_DEAD); + + if (t->deserialized_state != t->state) + target_set_state(t, t->deserialized_state); + + return 0; +} + +static void target_dump(Unit *u, FILE *f, const char *prefix) { + Target *t = TARGET(u); + + assert(t); + assert(f); + + fprintf(f, + "%sTarget State: %s\n", + prefix, target_state_to_string(t->state)); +} + +static int target_start(Unit *u) { + Target *t = TARGET(u); + + assert(t); + assert(t->state == TARGET_DEAD); + + target_set_state(t, TARGET_ACTIVE); + return 0; +} + +static int target_stop(Unit *u) { + Target *t = TARGET(u); + + assert(t); + assert(t->state == TARGET_ACTIVE); + + target_set_state(t, TARGET_DEAD); + return 0; +} + +static int target_serialize(Unit *u, FILE *f, FDSet *fds) { + Target *s = TARGET(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", target_state_to_string(s->state)); + return 0; +} + +static int target_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Target *s = TARGET(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + TargetState state; + + if ((state = target_state_from_string(value)) < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState target_active_state(Unit *u) { + assert(u); + + return state_translation_table[TARGET(u)->state]; +} + +static const char *target_sub_state_to_string(Unit *u) { + assert(u); + + return target_state_to_string(TARGET(u)->state); +} + +static const char* const target_state_table[_TARGET_STATE_MAX] = { + [TARGET_DEAD] = "dead", + [TARGET_ACTIVE] = "active" +}; + +DEFINE_STRING_TABLE_LOOKUP(target_state, TargetState); + +const UnitVTable target_vtable = { + .suffix = ".target", + .object_size = sizeof(Target), + .sections = + "Unit\0" + "Target\0" + "Install\0", + + .load = target_load, + .coldplug = target_coldplug, + + .dump = target_dump, + + .start = target_start, + .stop = target_stop, + + .serialize = target_serialize, + .deserialize_item = target_deserialize_item, + + .active_state = target_active_state, + .sub_state_to_string = target_sub_state_to_string, + + .bus_interface = "org.freedesktop.systemd1.Target", + .bus_message_handler = bus_target_message_handler +}; diff --git a/src/target.h b/src/target.h new file mode 100644 index 000000000..5b97c86fb --- /dev/null +++ b/src/target.h @@ -0,0 +1,47 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef footargethfoo +#define footargethfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Target Target; + +#include "unit.h" + +typedef enum TargetState { + TARGET_DEAD, + TARGET_ACTIVE, + _TARGET_STATE_MAX, + _TARGET_STATE_INVALID = -1 +} TargetState; + +struct Target { + Unit meta; + + TargetState state, deserialized_state; +}; + +extern const UnitVTable target_vtable; + +const char* target_state_to_string(TargetState i); +TargetState target_state_from_string(const char *s); + +#endif diff --git a/src/tcpwrap.c b/src/tcpwrap.c new file mode 100644 index 000000000..0aab1427d --- /dev/null +++ b/src/tcpwrap.c @@ -0,0 +1,68 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#ifdef HAVE_LIBWRAP +#include +#endif + +#include "tcpwrap.h" +#include "log.h" + +bool socket_tcpwrap(int fd, const char *name) { +#ifdef HAVE_LIBWRAP + struct request_info req; + union { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; + struct sockaddr_un un; + struct sockaddr_storage storage; + } sa_union; + socklen_t l = sizeof(sa_union); + + if (getsockname(fd, &sa_union.sa, &l) < 0) + return true; + + if (sa_union.sa.sa_family != AF_INET && + sa_union.sa.sa_family != AF_INET6) + return true; + + request_init(&req, + RQ_DAEMON, name, + RQ_FILE, fd, + NULL); + + fromhost(&req); + + if (!hosts_access(&req)) { + log_warning("Connection refused by tcpwrap."); + return false; + } + + log_debug("Connection accepted by tcpwrap."); +#endif + return true; +} diff --git a/src/tcpwrap.h b/src/tcpwrap.h new file mode 100644 index 000000000..4d4553e8a --- /dev/null +++ b/src/tcpwrap.h @@ -0,0 +1,29 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foolibwraphfoo +#define foolibwraphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +bool socket_tcpwrap(int fd, const char *name); + +#endif diff --git a/src/test-cgroup.c b/src/test-cgroup.c new file mode 100644 index 000000000..eb189374f --- /dev/null +++ b/src/test-cgroup.c @@ -0,0 +1,104 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "cgroup-util.h" +#include "util.h" +#include "log.h" + +int main(int argc, char*argv[]) { + char *path; + char *c, *p; + + assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-a") == 0); + assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-a") == 0); + assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-b") == 0); + assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-b/test-c") == 0); + assert_se(cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, "/test-b", 0) == 0); + + assert_se(cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, getpid(), &path) == 0); + assert_se(streq(path, "/test-b")); + free(path); + + assert_se(cg_attach(SYSTEMD_CGROUP_CONTROLLER, "/test-a", 0) == 0); + + assert_se(cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, getpid(), &path) == 0); + assert_se(path_equal(path, "/test-a")); + free(path); + + assert_se(cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, "/test-b/test-d", 0) == 0); + + assert_se(cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, getpid(), &path) == 0); + assert_se(path_equal(path, "/test-b/test-d")); + free(path); + + assert_se(cg_get_path(SYSTEMD_CGROUP_CONTROLLER, "/test-b/test-d", NULL, &path) == 0); + assert_se(path_equal(path, "/sys/fs/cgroup/systemd/test-b/test-d")); + free(path); + + assert_se(cg_is_empty(SYSTEMD_CGROUP_CONTROLLER, "/test-a", false) > 0); + assert_se(cg_is_empty(SYSTEMD_CGROUP_CONTROLLER, "/test-b", false) > 0); + assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a", false) > 0); + assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", false) == 0); + + assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a", 0, false, false, false, NULL) == 0); + assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", 0, false, false, false, NULL) > 0); + + assert_se(cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", "/test-a", false, false) > 0); + + assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a", false) == 0); + assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", false) > 0); + + assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a", 0, false, false, false, NULL) > 0); + assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", 0, false, false, false, NULL) == 0); + + cg_trim(SYSTEMD_CGROUP_CONTROLLER, "/", false); + + assert_se(cg_delete(SYSTEMD_CGROUP_CONTROLLER, "/test-b") < 0); + assert_se(cg_delete(SYSTEMD_CGROUP_CONTROLLER, "/test-a") >= 0); + + assert_se(cg_split_spec("foobar:/", &c, &p) == 0); + assert(streq(c, "foobar")); + assert(streq(p, "/")); + free(c); + free(p); + + assert_se(cg_split_spec("foobar:", &c, &p) < 0); + assert_se(cg_split_spec("foobar:asdfd", &c, &p) < 0); + assert_se(cg_split_spec(":///", &c, &p) < 0); + assert_se(cg_split_spec(":", &c, &p) < 0); + assert_se(cg_split_spec("", &c, &p) < 0); + assert_se(cg_split_spec("fo/obar:/", &c, &p) < 0); + + assert_se(cg_split_spec("/", &c, &p) >= 0); + assert(c == NULL); + assert(streq(p, "/")); + free(p); + + assert_se(cg_split_spec("foo", &c, &p) >= 0); + assert(streq(c, "foo")); + assert(p == NULL); + free(c); + + return 0; +} diff --git a/src/test-daemon.c b/src/test-daemon.c new file mode 100644 index 000000000..20c5d1517 --- /dev/null +++ b/src/test-daemon.c @@ -0,0 +1,37 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include + +int main(int argc, char*argv[]) { + + sd_notify(0, "STATUS=Starting up"); + sleep(5); + sd_notify(0, + "STATUS=Running\n" + "READY=1"); + sleep(10); + sd_notify(0, "STATUS=Quitting"); + + return 0; +} diff --git a/src/test-engine.c b/src/test-engine.c new file mode 100644 index 000000000..46a2d2cda --- /dev/null +++ b/src/test-engine.c @@ -0,0 +1,99 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "manager.h" + +int main(int argc, char *argv[]) { + Manager *m = NULL; + Unit *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *g = NULL, *h = NULL; + Job *j; + + assert_se(set_unit_path("test") >= 0); + + assert_se(manager_new(MANAGER_SYSTEM, &m) >= 0); + + printf("Load1:\n"); + assert_se(manager_load_unit(m, "a.service", NULL, NULL, &a) >= 0); + assert_se(manager_load_unit(m, "b.service", NULL, NULL, &b) >= 0); + assert_se(manager_load_unit(m, "c.service", NULL, NULL, &c) >= 0); + manager_dump_units(m, stdout, "\t"); + + printf("Test1: (Trivial)\n"); + assert_se(manager_add_job(m, JOB_START, c, JOB_REPLACE, false, NULL, &j) == 0); + manager_dump_jobs(m, stdout, "\t"); + + printf("Load2:\n"); + manager_clear_jobs(m); + assert_se(manager_load_unit(m, "d.service", NULL, NULL, &d) >= 0); + assert_se(manager_load_unit(m, "e.service", NULL, NULL, &e) >= 0); + manager_dump_units(m, stdout, "\t"); + + printf("Test2: (Cyclic Order, Unfixable)\n"); + assert_se(manager_add_job(m, JOB_START, d, JOB_REPLACE, false, NULL, &j) == -ENOEXEC); + manager_dump_jobs(m, stdout, "\t"); + + printf("Test3: (Cyclic Order, Fixable, Garbage Collector)\n"); + assert_se(manager_add_job(m, JOB_START, e, JOB_REPLACE, false, NULL, &j) == 0); + manager_dump_jobs(m, stdout, "\t"); + + printf("Test4: (Identical transaction)\n"); + assert_se(manager_add_job(m, JOB_START, e, JOB_FAIL, false, NULL, &j) == 0); + manager_dump_jobs(m, stdout, "\t"); + + printf("Load3:\n"); + assert_se(manager_load_unit(m, "g.service", NULL, NULL, &g) >= 0); + manager_dump_units(m, stdout, "\t"); + + printf("Test5: (Colliding transaction, fail)\n"); + assert_se(manager_add_job(m, JOB_START, g, JOB_FAIL, false, NULL, &j) == -EEXIST); + + printf("Test6: (Colliding transaction, replace)\n"); + assert_se(manager_add_job(m, JOB_START, g, JOB_REPLACE, false, NULL, &j) == 0); + manager_dump_jobs(m, stdout, "\t"); + + printf("Test7: (Unmergeable job type, fail)\n"); + assert_se(manager_add_job(m, JOB_STOP, g, JOB_FAIL, false, NULL, &j) == -EEXIST); + + printf("Test8: (Mergeable job type, fail)\n"); + assert_se(manager_add_job(m, JOB_RESTART, g, JOB_FAIL, false, NULL, &j) == 0); + manager_dump_jobs(m, stdout, "\t"); + + printf("Test9: (Unmergeable job type, replace)\n"); + assert_se(manager_add_job(m, JOB_STOP, g, JOB_REPLACE, false, NULL, &j) == 0); + manager_dump_jobs(m, stdout, "\t"); + + printf("Load4:\n"); + assert_se(manager_load_unit(m, "h.service", NULL, NULL, &h) >= 0); + manager_dump_units(m, stdout, "\t"); + + printf("Test10: (Unmergeable job type of auxiliary job, fail)\n"); + assert_se(manager_add_job(m, JOB_START, h, JOB_FAIL, false, NULL, &j) == 0); + manager_dump_jobs(m, stdout, "\t"); + + manager_free(m); + + return 0; +} diff --git a/src/test-env-replace.c b/src/test-env-replace.c new file mode 100644 index 000000000..05dbacd7d --- /dev/null +++ b/src/test-env-replace.c @@ -0,0 +1,127 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "util.h" +#include "log.h" +#include "strv.h" + +int main(int argc, char *argv[]) { + + const char *env[] = { + "FOO=BAR BAR", + "BAR=waldo", + NULL + }; + + const char *line[] = { + "FOO$FOO", + "FOO$FOOFOO", + "FOO${FOO}$FOO", + "FOO${FOO}", + "${FOO}", + "$FOO", + "$FOO$FOO", + "${FOO}${BAR}", + "${FOO", + NULL + }; + + char **i, **r, *t, **a, **b; + const char nulstr[] = "fuck\0fuck2\0fuck3\0\0fuck5\0\0xxx"; + + a = strv_parse_nulstr(nulstr, sizeof(nulstr)-1); + + STRV_FOREACH(i, a) + printf("nulstr--%s\n", *i); + + strv_free(a); + + r = replace_env_argv((char**) line, (char**) env); + + STRV_FOREACH(i, r) + printf("%s\n", *i); + + strv_free(r); + + t = normalize_env_assignment("foo=bar"); + printf("%s\n", t); + free(t); + + t = normalize_env_assignment("=bar"); + printf("%s\n", t); + free(t); + + t = normalize_env_assignment("foo="); + printf("%s\n", t); + free(t); + + t = normalize_env_assignment("="); + printf("%s\n", t); + free(t); + + t = normalize_env_assignment(""); + printf("%s\n", t); + free(t); + + t = normalize_env_assignment("a=\"waldo\""); + printf("%s\n", t); + free(t); + + t = normalize_env_assignment("a=\"waldo"); + printf("%s\n", t); + free(t); + + t = normalize_env_assignment("a=waldo\""); + printf("%s\n", t); + free(t); + + t = normalize_env_assignment("a=\'"); + printf("%s\n", t); + free(t); + + t = normalize_env_assignment("a=\'\'"); + printf("%s\n", t); + free(t); + + a = strv_new("FOO=BAR", "WALDO=WALDO", "WALDO=", "PIEP", "SCHLUMPF=SMURF", NULL); + b = strv_new("FOO=KKK", "FOO=", "PIEP=", "SCHLUMPF=SMURFF", "NANANANA=YES", NULL); + + r = strv_env_merge(2, a, b); + strv_free(a); + strv_free(b); + + STRV_FOREACH(i, r) + printf("%s\n", *i); + + printf("CLEANED UP:\n"); + + r = strv_env_clean(r); + + STRV_FOREACH(i, r) + printf("%s\n", *i); + + strv_free(r); + + return 0; +} diff --git a/src/test-hostname.c b/src/test-hostname.c new file mode 100644 index 000000000..0a08416a1 --- /dev/null +++ b/src/test-hostname.c @@ -0,0 +1,37 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "hostname-setup.h" +#include "util.h" + +int main(int argc, char* argv[]) { + int r; + + if ((r = hostname_setup()) < 0) + fprintf(stderr, "hostname: %s\n", strerror(-r)); + + return 0; +} diff --git a/src/test-id128.c b/src/test-id128.c new file mode 100644 index 000000000..617c95568 --- /dev/null +++ b/src/test-id128.c @@ -0,0 +1,52 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include + +#include "util.h" +#include "macro.h" + +#define ID128_WALDI SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10) + +int main(int argc, char *argv[]) { + sd_id128_t id, id2; + char t[33]; + + assert_se(sd_id128_randomize(&id) == 0); + printf("random: %s\n", sd_id128_to_string(id, t)); + + assert_se(sd_id128_from_string(t, &id2) == 0); + assert_se(sd_id128_equal(id, id2)); + + assert_se(sd_id128_get_machine(&id) == 0); + printf("machine: %s\n", sd_id128_to_string(id, t)); + + assert_se(sd_id128_get_boot(&id) == 0); + printf("boot: %s\n", sd_id128_to_string(id, t)); + + printf("waldi: %s\n", sd_id128_to_string(ID128_WALDI, t)); + + printf("waldi2: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(ID128_WALDI)); + + return 0; +} diff --git a/src/test-install.c b/src/test-install.c new file mode 100644 index 000000000..f8e87e0c7 --- /dev/null +++ b/src/test-install.c @@ -0,0 +1,264 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "util.h" +#include "install.h" + +static void dump_changes(UnitFileChange *c, unsigned n) { + unsigned i; + + assert(n == 0 || c); + + for (i = 0; i < n; i++) { + if (c[i].type == UNIT_FILE_UNLINK) + printf("rm '%s'\n", c[i].path); + else if (c[i].type == UNIT_FILE_SYMLINK) + printf("ln -s '%s' '%s'\n", c[i].source, c[i].path); + } +} + +int main(int argc, char* argv[]) { + Hashmap *h; + UnitFileList *p; + Iterator i; + int r; + const char *const files[] = { "avahi-daemon.service", NULL }; + const char *const files2[] = { "/home/lennart/test.service", NULL }; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + + h = hashmap_new(string_hash_func, string_compare_func); + r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h); + assert_se(r == 0); + + HASHMAP_FOREACH(p, h, i) { + UnitFileState s; + + s = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(p->path)); + + assert_se(p->state == s); + + fprintf(stderr, "%s (%s)\n", + p->path, + unit_file_state_to_string(p->state)); + } + + unit_file_list_free(h); + + log_error("enable"); + + r = unit_file_enable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes); + assert_se(r >= 0); + + log_error("enable2"); + + r = unit_file_enable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_ENABLED); + + log_error("disable"); + + changes = NULL; + n_changes = 0; + + r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_DISABLED); + + log_error("mask"); + changes = NULL; + n_changes = 0; + + r = unit_file_mask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes); + assert_se(r >= 0); + log_error("mask2"); + r = unit_file_mask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_MASKED); + + log_error("unmask"); + changes = NULL; + n_changes = 0; + + r = unit_file_unmask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + log_error("unmask2"); + r = unit_file_unmask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_DISABLED); + + log_error("mask"); + changes = NULL; + n_changes = 0; + + r = unit_file_mask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_MASKED); + + log_error("disable"); + changes = NULL; + n_changes = 0; + + r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + log_error("disable2"); + r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_MASKED); + + log_error("umask"); + changes = NULL; + n_changes = 0; + + r = unit_file_unmask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_DISABLED); + + log_error("enable files2"); + changes = NULL; + n_changes = 0; + + r = unit_file_enable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, false, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == UNIT_FILE_ENABLED); + + log_error("disable files2"); + changes = NULL; + n_changes = 0; + + r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == _UNIT_FILE_STATE_INVALID); + + log_error("link files2"); + changes = NULL; + n_changes = 0; + + r = unit_file_link(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, false, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == UNIT_FILE_LINKED); + + log_error("disable files2"); + changes = NULL; + n_changes = 0; + + r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == _UNIT_FILE_STATE_INVALID); + + log_error("link files2"); + changes = NULL; + n_changes = 0; + + r = unit_file_link(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, false, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == UNIT_FILE_LINKED); + + log_error("reenable files2"); + changes = NULL; + n_changes = 0; + + r = unit_file_reenable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, false, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == UNIT_FILE_ENABLED); + + log_error("disable files2"); + changes = NULL; + n_changes = 0; + + r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == _UNIT_FILE_STATE_INVALID); + log_error("preset files"); + changes = NULL; + n_changes = 0; + + r = unit_file_preset(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + unit_file_changes_free(changes, n_changes); + + assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files[0])) == UNIT_FILE_ENABLED); + + return 0; +} diff --git a/src/test-job-type.c b/src/test-job-type.c new file mode 100644 index 000000000..9de21e182 --- /dev/null +++ b/src/test-job-type.c @@ -0,0 +1,84 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "job.h" + +int main(int argc, char*argv[]) { + JobType a, b, c, d, e, f, g; + + for (a = 0; a < _JOB_TYPE_MAX; a++) + for (b = 0; b < _JOB_TYPE_MAX; b++) { + + if (!job_type_is_mergeable(a, b)) + printf("Not mergeable: %s + %s\n", job_type_to_string(a), job_type_to_string(b)); + + for (c = 0; c < _JOB_TYPE_MAX; c++) { + + /* Verify transitivity of mergeability + * of job types */ + assert(!job_type_is_mergeable(a, b) || + !job_type_is_mergeable(b, c) || + job_type_is_mergeable(a, c)); + + d = a; + if (job_type_merge(&d, b) >= 0) { + + printf("%s + %s = %s\n", job_type_to_string(a), job_type_to_string(b), job_type_to_string(d)); + + /* Verify that merged entries can be + * merged with the same entries they + * can be merged with separately */ + assert(!job_type_is_mergeable(a, c) || job_type_is_mergeable(d, c)); + assert(!job_type_is_mergeable(b, c) || job_type_is_mergeable(d, c)); + + /* Verify that if a merged + * with b is not mergeable with + * c then either a or b is not + * mergeable with c either. */ + assert(job_type_is_mergeable(d, c) || !job_type_is_mergeable(a, c) || !job_type_is_mergeable(b, c)); + + e = b; + if (job_type_merge(&e, c) >= 0) { + + /* Verify associativity */ + + f = d; + assert(job_type_merge(&f, c) == 0); + + g = e; + assert(job_type_merge(&g, a) == 0); + + assert(f == g); + + printf("%s + %s + %s = %s\n", job_type_to_string(a), job_type_to_string(b), job_type_to_string(c), job_type_to_string(d)); + } + } + } + } + + + return 0; +} diff --git a/src/test-loopback.c b/src/test-loopback.c new file mode 100644 index 000000000..9784aaf24 --- /dev/null +++ b/src/test-loopback.c @@ -0,0 +1,37 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "loopback-setup.h" +#include "util.h" + +int main(int argc, char* argv[]) { + int r; + + if ((r = loopback_setup()) < 0) + fprintf(stderr, "loopback: %s\n", strerror(-r)); + + return 0; +} diff --git a/src/test-ns.c b/src/test-ns.c new file mode 100644 index 000000000..e2bdfc589 --- /dev/null +++ b/src/test-ns.c @@ -0,0 +1,60 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "namespace.h" +#include "log.h" + +int main(int argc, char *argv[]) { + const char * const writable[] = { + "/home", + NULL + }; + + const char * const readable[] = { + "/", + "/usr", + "/boot", + NULL + }; + + const char * const inaccessible[] = { + "/home/lennart/projects", + NULL + }; + + int r; + + if ((r = setup_namespace((char**) writable, (char**) readable, (char**) inaccessible, true, MS_SHARED)) < 0) { + log_error("Failed to setup namespace: %s", strerror(-r)); + return 1; + } + + execl("/bin/sh", "/bin/sh", NULL); + log_error("execl(): %m"); + + return 1; +} diff --git a/src/test-strv.c b/src/test-strv.c new file mode 100644 index 000000000..1d577dfd3 --- /dev/null +++ b/src/test-strv.c @@ -0,0 +1,66 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "util.h" +#include "specifier.h" + +int main(int argc, char *argv[]) { + const Specifier table[] = { + { 'a', specifier_string, (char*) "AAAA" }, + { 'b', specifier_string, (char*) "BBBB" }, + { 0, NULL, NULL } + }; + + char *w, *state; + size_t l; + const char test[] = "test a b c 'd' e '' '' hhh '' ''"; + + printf("<%s>\n", test); + + FOREACH_WORD_QUOTED(w, l, test, state) { + char *t; + + assert_se(t = strndup(w, l)); + printf("<%s>\n", t); + free(t); + } + + printf("%s\n", default_term_for_tty("/dev/tty23")); + printf("%s\n", default_term_for_tty("/dev/ttyS23")); + printf("%s\n", default_term_for_tty("/dev/tty0")); + printf("%s\n", default_term_for_tty("/dev/pty0")); + printf("%s\n", default_term_for_tty("/dev/pts/0")); + printf("%s\n", default_term_for_tty("/dev/console")); + printf("%s\n", default_term_for_tty("tty23")); + printf("%s\n", default_term_for_tty("ttyS23")); + printf("%s\n", default_term_for_tty("tty0")); + printf("%s\n", default_term_for_tty("pty0")); + printf("%s\n", default_term_for_tty("pts/0")); + printf("%s\n", default_term_for_tty("console")); + + w = specifier_printf("xxx a=%a b=%b yyy", table, NULL); + printf("<%s>\n", w); + free(w); + + return 0; +} diff --git a/src/timedate/.gitignore b/src/timedate/.gitignore new file mode 100644 index 000000000..48757f096 --- /dev/null +++ b/src/timedate/.gitignore @@ -0,0 +1 @@ +org.freedesktop.timedate1.policy diff --git a/src/timedate/Makefile b/src/timedate/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/timedate/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/timedate/org.freedesktop.timedate1.conf b/src/timedate/org.freedesktop.timedate1.conf new file mode 100644 index 000000000..c9c221b64 --- /dev/null +++ b/src/timedate/org.freedesktop.timedate1.conf @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/timedate/org.freedesktop.timedate1.policy.in b/src/timedate/org.freedesktop.timedate1.policy.in new file mode 100644 index 000000000..3d9c2081d --- /dev/null +++ b/src/timedate/org.freedesktop.timedate1.policy.in @@ -0,0 +1,61 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Set system time + <_message>Authentication is required to set the system time. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Set system timezone + <_message>Authentication is required to set the system timezone. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Set RTC to local timezone or UTC + <_message>Authentication is required to control whether + the RTC stores the local or UTC time. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Turn network time synchronization on or off + <_message>Authentication is required to control whether + network time synchronization shall be enabled. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + diff --git a/src/timedate/org.freedesktop.timedate1.service b/src/timedate/org.freedesktop.timedate1.service new file mode 100644 index 000000000..c3120b66c --- /dev/null +++ b/src/timedate/org.freedesktop.timedate1.service @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[D-BUS Service] +Name=org.freedesktop.timedate1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.timedate1.service diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c new file mode 100644 index 000000000..6a7d980c3 --- /dev/null +++ b/src/timedate/timedated.c @@ -0,0 +1,930 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include +#include +#include + +#include "util.h" +#include "strv.h" +#include "dbus-common.h" +#include "polkit.h" +#include "def.h" + +#define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n" +#define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n" + +#define INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + BUS_PEER_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.timedate1\0" + +const char timedate_interface[] _introspect_("timedate1") = INTERFACE; + +typedef struct TZ { + char *zone; + bool local_rtc; + int use_ntp; +} TZ; + +static TZ tz = { + .use_ntp = -1, +}; + +static usec_t remain_until; + +static void free_data(void) { + free(tz.zone); + tz.zone = NULL; + + tz.local_rtc = false; +} + +static bool valid_timezone(const char *name) { + const char *p; + char *t; + bool slash = false; + int r; + struct stat st; + + assert(name); + + if (*name == '/' || *name == 0) + return false; + + for (p = name; *p; p++) { + if (!(*p >= '0' && *p <= '9') && + !(*p >= 'a' && *p <= 'z') && + !(*p >= 'A' && *p <= 'Z') && + !(*p == '-' || *p == '_' || *p == '+' || *p == '/')) + return false; + + if (*p == '/') { + + if (slash) + return false; + + slash = true; + } else + slash = false; + } + + if (slash) + return false; + + t = strappend("/usr/share/zoneinfo/", name); + if (!t) + return false; + + r = stat(t, &st); + free(t); + + if (r < 0) + return false; + + if (!S_ISREG(st.st_mode)) + return false; + + return true; +} + +static void verify_timezone(void) { + char *p, *a = NULL, *b = NULL; + size_t l, q; + int j, k; + + if (!tz.zone) + return; + + p = strappend("/usr/share/zoneinfo/", tz.zone); + if (!p) { + log_error("Out of memory"); + return; + } + + j = read_full_file("/etc/localtime", &a, &l); + k = read_full_file(p, &b, &q); + + free(p); + + if (j < 0 || k < 0 || l != q || memcmp(a, b, l)) { + log_warning("/etc/localtime and /etc/timezone out of sync."); + free(tz.zone); + tz.zone = NULL; + } + + free(a); + free(b); +} + +static int read_data(void) { + int r; + + free_data(); + + r = read_one_line_file("/etc/timezone", &tz.zone); + if (r < 0) { + if (r != -ENOENT) + log_warning("Failed to read /etc/timezone: %s", strerror(-r)); + +#ifdef TARGET_FEDORA + r = parse_env_file("/etc/sysconfig/clock", NEWLINE, + "ZONE", &tz.zone, + NULL); + + if (r < 0 && r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/clock: %s", strerror(-r)); +#endif + } + + if (isempty(tz.zone)) { + free(tz.zone); + tz.zone = NULL; + } + + verify_timezone(); + + tz.local_rtc = hwclock_is_localtime() > 0; + + return 0; +} + +static int write_data_timezone(void) { + int r = 0; + char *p; + + if (!tz.zone) { + if (unlink("/etc/timezone") < 0 && errno != ENOENT) + r = -errno; + + if (unlink("/etc/localtime") < 0 && errno != ENOENT) + r = -errno; + + return r; + } + + p = strappend("/usr/share/zoneinfo/", tz.zone); + if (!p) { + log_error("Out of memory"); + return -ENOMEM; + } + + r = symlink_or_copy_atomic(p, "/etc/localtime"); + free(p); + + if (r < 0) + return r; + + r = write_one_line_file_atomic("/etc/timezone", tz.zone); + if (r < 0) + return r; + + return 0; +} + +static int write_data_local_rtc(void) { + int r; + char *s, *w; + + r = read_full_file("/etc/adjtime", &s, NULL); + if (r < 0) { + if (r != -ENOENT) + return r; + + if (!tz.local_rtc) + return 0; + + w = strdup(NULL_ADJTIME_LOCAL); + if (!w) + return -ENOMEM; + } else { + char *p, *e; + size_t a, b; + + p = strchr(s, '\n'); + if (!p) { + free(s); + return -EIO; + } + + p = strchr(p+1, '\n'); + if (!p) { + free(s); + return -EIO; + } + + p++; + e = strchr(p, '\n'); + if (!e) { + free(s); + return -EIO; + } + + a = p - s; + b = strlen(e); + + w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1); + if (!w) { + free(s); + return -ENOMEM; + } + + *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0; + + if (streq(w, NULL_ADJTIME_UTC)) { + free(w); + + if (unlink("/etc/adjtime") < 0) { + if (errno != ENOENT) + return -errno; + } + + return 0; + } + } + + r = write_one_line_file_atomic("/etc/adjtime", w); + free(w); + + return r; +} + +static int read_ntp(DBusConnection *bus) { + DBusMessage *m = NULL, *reply = NULL; + const char *name = "ntpd.service", *s; + DBusError error; + int r; + + assert(bus); + + dbus_error_init(&error); + + m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitFileState"); + + if (!m) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + + if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) { + /* NTP is not installed. */ + tz.use_ntp = false; + r = 0; + goto finish; + } + + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_STRING, &s, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + tz.use_ntp = + streq(s, "enabled") || + streq(s, "enabled-runtime"); + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int start_ntp(DBusConnection *bus, DBusError *error) { + DBusMessage *m = NULL, *reply = NULL; + const char *name = "ntpd.service", *mode = "replace"; + int r; + + assert(bus); + assert(error); + + m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + tz.use_ntp ? "StartUnit" : "StopUnit"); + if (!m) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(error)); + r = -EIO; + goto finish; + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return r; +} + +static int enable_ntp(DBusConnection *bus, DBusError *error) { + DBusMessage *m = NULL, *reply = NULL; + const char * const names[] = { "ntpd.service", NULL }; + int r; + DBusMessageIter iter; + dbus_bool_t f = FALSE, t = TRUE; + + assert(bus); + assert(error); + + m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles"); + + if (!m) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + dbus_message_iter_init_append(m, &iter); + + r = bus_append_strv_iter(&iter, (char**) names); + if (r < 0) { + log_error("Failed to append unit files."); + goto finish; + } + /* send runtime bool */ + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) { + log_error("Failed to append runtime boolean."); + r = -ENOMEM; + goto finish; + } + + if (tz.use_ntp) { + /* send force bool */ + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) { + log_error("Failed to append force boolean."); + r = -ENOMEM; + goto finish; + } + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(error)); + r = -EIO; + goto finish; + } + + dbus_message_unref(m); + m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Reload"); + if (!m) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + dbus_message_unref(reply); + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(error)); + r = -EIO; + goto finish; + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return r; +} + +static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) { + dbus_bool_t db; + + assert(i); + assert(property); + + db = tz.use_ntp > 0; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db)) + return -ENOMEM; + + return 0; +} + +static const BusProperty bus_timedate_properties[] = { + { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true }, + { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) }, + { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) }, + { NULL, } +}; + +static const BusBoundProperties bps[] = { + { "org.freedesktop.timedate1", bus_timedate_properties, &tz }, + { NULL, } +}; + +static DBusHandlerResult timedate_message_handler( + DBusConnection *connection, + DBusMessage *message, + void *userdata) { + + DBusMessage *reply = NULL, *changed = NULL; + DBusError error; + int r; + + assert(connection); + assert(message); + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) { + const char *z; + dbus_bool_t interactive; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &z, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!valid_timezone(z)) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + + if (!streq_ptr(z, tz.zone)) { + char *t; + + r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + t = strdup(z); + if (!t) + goto oom; + + free(tz.zone); + tz.zone = t; + + /* 1. Write new configuration file */ + r = write_data_timezone(); + if (r < 0) { + log_error("Failed to set timezone: %s", strerror(-r)); + return bus_send_error_reply(connection, message, NULL, r); + } + + if (tz.local_rtc) { + struct timespec ts; + struct tm *tm; + + /* 2. Teach kernel new timezone */ + hwclock_apply_localtime_delta(NULL); + + /* 3. Sync RTC from system clock, with the new delta */ + assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + assert_se(tm = localtime(&ts.tv_sec)); + hwclock_set_time(tm); + } + + log_info("Changed timezone to '%s'.", tz.zone); + + changed = bus_properties_changed_new( + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "Timezone\0"); + if (!changed) + goto oom; + } + + } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) { + dbus_bool_t lrtc; + dbus_bool_t fix_system; + dbus_bool_t interactive; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_BOOLEAN, &lrtc, + DBUS_TYPE_BOOLEAN, &fix_system, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (lrtc != tz.local_rtc) { + struct timespec ts; + + r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + tz.local_rtc = lrtc; + + /* 1. Write new configuration file */ + r = write_data_local_rtc(); + if (r < 0) { + log_error("Failed to set RTC to local/UTC: %s", strerror(-r)); + return bus_send_error_reply(connection, message, NULL, r); + } + + /* 2. Teach kernel new timezone */ + if (tz.local_rtc) + hwclock_apply_localtime_delta(NULL); + else + hwclock_reset_localtime_delta(); + + /* 3. Synchronize clocks */ + assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + + if (fix_system) { + struct tm tm; + + /* Sync system clock from RTC; first, + * initialize the timezone fields of + * struct tm. */ + if (tz.local_rtc) + tm = *localtime(&ts.tv_sec); + else + tm = *gmtime(&ts.tv_sec); + + /* Override the main fields of + * struct tm, but not the timezone + * fields */ + if (hwclock_get_time(&tm) >= 0) { + + /* And set the system clock + * with this */ + if (tz.local_rtc) + ts.tv_sec = mktime(&tm); + else + ts.tv_sec = timegm(&tm); + + clock_settime(CLOCK_REALTIME, &ts); + } + + } else { + struct tm *tm; + + /* Sync RTC from system clock */ + if (tz.local_rtc) + tm = localtime(&ts.tv_sec); + else + tm = gmtime(&ts.tv_sec); + + hwclock_set_time(tm); + } + + log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC"); + + changed = bus_properties_changed_new( + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "LocalRTC\0"); + if (!changed) + goto oom; + } + + } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) { + int64_t utc; + dbus_bool_t relative; + dbus_bool_t interactive; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_INT64, &utc, + DBUS_TYPE_BOOLEAN, &relative, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!relative && utc <= 0) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + + if (!relative || utc != 0) { + struct timespec ts; + struct tm* tm; + + r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (relative) + timespec_store(&ts, now(CLOCK_REALTIME) + utc); + else + timespec_store(&ts, utc); + + /* Set system clock */ + if (clock_settime(CLOCK_REALTIME, &ts) < 0) { + log_error("Failed to set local time: %m"); + return bus_send_error_reply(connection, message, NULL, -errno); + } + + /* Sync down to RTC */ + if (tz.local_rtc) + tm = localtime(&ts.tv_sec); + else + tm = gmtime(&ts.tv_sec); + + hwclock_set_time(tm); + + log_info("Changed local time to %s", ctime(&ts.tv_sec)); + } + } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) { + dbus_bool_t ntp; + dbus_bool_t interactive; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_BOOLEAN, &ntp, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (ntp != !!tz.use_ntp) { + + r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + tz.use_ntp = !!ntp; + + r = enable_ntp(connection, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + r = start_ntp(connection, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled"); + + changed = bus_properties_changed_new( + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "NTP\0"); + if (!changed) + goto oom; + } + + } else + return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + reply = NULL; + + if (changed) { + + if (!dbus_connection_send(connection, changed, NULL)) + goto oom; + + dbus_message_unref(changed); + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + if (changed) + dbus_message_unref(changed); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static int connect_bus(DBusConnection **_bus) { + static const DBusObjectPathVTable timedate_vtable = { + .message_function = timedate_message_handler + }; + DBusError error; + DBusConnection *bus = NULL; + int r; + + assert(_bus); + + dbus_error_init(&error); + + bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + if (!bus) { + log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error)); + r = -ECONNREFUSED; + goto fail; + } + + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) || + !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) { + log_error("Not enough memory"); + r = -ENOMEM; + goto fail; + } + + r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to register name on bus: %s", bus_error_message(&error)); + r = -EEXIST; + goto fail; + } + + if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + log_error("Failed to acquire name."); + r = -EEXIST; + goto fail; + } + + if (_bus) + *_bus = bus; + + return 0; + +fail: + dbus_connection_close(bus); + dbus_connection_unref(bus); + + dbus_error_free(&error); + + return r; +} + +int main(int argc, char *argv[]) { + int r; + DBusConnection *bus = NULL; + bool exiting = false; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc == 2 && streq(argv[1], "--introspect")) { + fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "\n", stdout); + fputs(timedate_interface, stdout); + fputs("\n", stdout); + return 0; + } + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + r = read_data(); + if (r < 0) { + log_error("Failed to read timezone data: %s", strerror(-r)); + goto finish; + } + + r = connect_bus(&bus); + if (r < 0) + goto finish; + + r = read_ntp(bus); + if (r < 0) { + log_error("Failed to determine whether NTP is enabled: %s", strerror(-r)); + goto finish; + } + + remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC; + for (;;) { + + if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC))) + break; + + if (!exiting && remain_until < now(CLOCK_MONOTONIC)) { + exiting = true; + bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1"); + } + } + + r = 0; + +finish: + free_data(); + + if (bus) { + dbus_connection_flush(bus); + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/timer.c b/src/timer.c new file mode 100644 index 000000000..e318fee20 --- /dev/null +++ b/src/timer.c @@ -0,0 +1,520 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" +#include "unit-name.h" +#include "timer.h" +#include "dbus-timer.h" +#include "special.h" +#include "bus-errors.h" + +static const UnitActiveState state_translation_table[_TIMER_STATE_MAX] = { + [TIMER_DEAD] = UNIT_INACTIVE, + [TIMER_WAITING] = UNIT_ACTIVE, + [TIMER_RUNNING] = UNIT_ACTIVE, + [TIMER_ELAPSED] = UNIT_ACTIVE, + [TIMER_FAILED] = UNIT_FAILED +}; + +static void timer_init(Unit *u) { + Timer *t = TIMER(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + t->next_elapse = (usec_t) -1; +} + +static void timer_done(Unit *u) { + Timer *t = TIMER(u); + TimerValue *v; + + assert(t); + + while ((v = t->values)) { + LIST_REMOVE(TimerValue, value, t->values, v); + free(v); + } + + unit_unwatch_timer(u, &t->timer_watch); + + unit_ref_unset(&t->unit); +} + +static int timer_verify(Timer *t) { + assert(t); + + if (UNIT(t)->load_state != UNIT_LOADED) + return 0; + + if (!t->values) { + log_error("%s lacks value setting. Refusing.", UNIT(t)->id); + return -EINVAL; + } + + return 0; +} + +static int timer_add_default_dependencies(Timer *t) { + int r; + + assert(t); + + if (UNIT(t)->manager->running_as == MANAGER_SYSTEM) { + if ((r = unit_add_dependency_by_name(UNIT(t), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0) + return r; + + if ((r = unit_add_two_dependencies_by_name(UNIT(t), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0) + return r; + } + + return unit_add_two_dependencies_by_name(UNIT(t), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static int timer_load(Unit *u) { + Timer *t = TIMER(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + if ((r = unit_load_fragment_and_dropin(u)) < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + + if (!UNIT_DEREF(t->unit)) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + unit_ref_set(&t->unit, x); + } + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(t->unit), true); + if (r < 0) + return r; + + if (UNIT(t)->default_dependencies) + if ((r = timer_add_default_dependencies(t)) < 0) + return r; + } + + return timer_verify(t); +} + +static void timer_dump(Unit *u, FILE *f, const char *prefix) { + Timer *t = TIMER(u); + TimerValue *v; + char + timespan1[FORMAT_TIMESPAN_MAX]; + + fprintf(f, + "%sTimer State: %s\n" + "%sResult: %s\n" + "%sUnit: %s\n", + prefix, timer_state_to_string(t->state), + prefix, timer_result_to_string(t->result), + prefix, UNIT_DEREF(t->unit)->id); + + LIST_FOREACH(value, v, t->values) + fprintf(f, + "%s%s: %s\n", + prefix, + timer_base_to_string(v->base), + strna(format_timespan(timespan1, sizeof(timespan1), v->value))); +} + +static void timer_set_state(Timer *t, TimerState state) { + TimerState old_state; + assert(t); + + old_state = t->state; + t->state = state; + + if (state != TIMER_WAITING) + unit_unwatch_timer(UNIT(t), &t->timer_watch); + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(t)->id, + timer_state_to_string(old_state), + timer_state_to_string(state)); + + unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); +} + +static void timer_enter_waiting(Timer *t, bool initial); + +static int timer_coldplug(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + assert(t->state == TIMER_DEAD); + + if (t->deserialized_state != t->state) { + + if (t->deserialized_state == TIMER_WAITING) + timer_enter_waiting(t, false); + else + timer_set_state(t, t->deserialized_state); + } + + return 0; +} + +static void timer_enter_dead(Timer *t, TimerResult f) { + assert(t); + + if (f != TIMER_SUCCESS) + t->result = f; + + timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD); +} + +static void timer_enter_waiting(Timer *t, bool initial) { + TimerValue *v; + usec_t base = 0, delay, n; + bool found = false; + int r; + + n = now(CLOCK_MONOTONIC); + + LIST_FOREACH(value, v, t->values) { + + if (v->disabled) + continue; + + switch (v->base) { + + case TIMER_ACTIVE: + if (state_translation_table[t->state] == UNIT_ACTIVE) + base = UNIT(t)->inactive_exit_timestamp.monotonic; + else + base = n; + break; + + case TIMER_BOOT: + /* CLOCK_MONOTONIC equals the uptime on Linux */ + base = 0; + break; + + case TIMER_STARTUP: + base = UNIT(t)->manager->startup_timestamp.monotonic; + break; + + case TIMER_UNIT_ACTIVE: + + if (UNIT_DEREF(t->unit)->inactive_exit_timestamp.monotonic <= 0) + continue; + + base = UNIT_DEREF(t->unit)->inactive_exit_timestamp.monotonic; + break; + + case TIMER_UNIT_INACTIVE: + + if (UNIT_DEREF(t->unit)->inactive_enter_timestamp.monotonic <= 0) + continue; + + base = UNIT_DEREF(t->unit)->inactive_enter_timestamp.monotonic; + break; + + default: + assert_not_reached("Unknown timer base"); + } + + v->next_elapse = base + v->value; + + if (!initial && v->next_elapse < n) { + v->disabled = true; + continue; + } + + if (!found) + t->next_elapse = v->next_elapse; + else + t->next_elapse = MIN(t->next_elapse, v->next_elapse); + + found = true; + } + + if (!found) { + timer_set_state(t, TIMER_ELAPSED); + return; + } + + delay = n < t->next_elapse ? t->next_elapse - n : 0; + + if ((r = unit_watch_timer(UNIT(t), delay, &t->timer_watch)) < 0) + goto fail; + + timer_set_state(t, TIMER_WAITING); + return; + +fail: + log_warning("%s failed to enter waiting state: %s", UNIT(t)->id, strerror(-r)); + timer_enter_dead(t, TIMER_FAILURE_RESOURCES); +} + +static void timer_enter_running(Timer *t) { + DBusError error; + int r; + + assert(t); + dbus_error_init(&error); + + /* Don't start job if we are supposed to go down */ + if (UNIT(t)->job && UNIT(t)->job->type == JOB_STOP) + return; + + if ((r = manager_add_job(UNIT(t)->manager, JOB_START, UNIT_DEREF(t->unit), JOB_REPLACE, true, &error, NULL)) < 0) + goto fail; + + timer_set_state(t, TIMER_RUNNING); + return; + +fail: + log_warning("%s failed to queue unit startup job: %s", UNIT(t)->id, bus_error(&error, r)); + timer_enter_dead(t, TIMER_FAILURE_RESOURCES); + + dbus_error_free(&error); +} + +static int timer_start(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + assert(t->state == TIMER_DEAD || t->state == TIMER_FAILED); + + if (UNIT_DEREF(t->unit)->load_state != UNIT_LOADED) + return -ENOENT; + + t->result = TIMER_SUCCESS; + timer_enter_waiting(t, true); + return 0; +} + +static int timer_stop(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + assert(t->state == TIMER_WAITING || t->state == TIMER_RUNNING || t->state == TIMER_ELAPSED); + + timer_enter_dead(t, TIMER_SUCCESS); + return 0; +} + +static int timer_serialize(Unit *u, FILE *f, FDSet *fds) { + Timer *t = TIMER(u); + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", timer_state_to_string(t->state)); + unit_serialize_item(u, f, "result", timer_result_to_string(t->result)); + + return 0; +} + +static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Timer *t = TIMER(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + TimerState state; + + if ((state = timer_state_from_string(value)) < 0) + log_debug("Failed to parse state value %s", value); + else + t->deserialized_state = state; + } else if (streq(key, "result")) { + TimerResult f; + + f = timer_result_from_string(value); + if (f < 0) + log_debug("Failed to parse result value %s", value); + else if (f != TIMER_SUCCESS) + t->result = f; + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState timer_active_state(Unit *u) { + assert(u); + + return state_translation_table[TIMER(u)->state]; +} + +static const char *timer_sub_state_to_string(Unit *u) { + assert(u); + + return timer_state_to_string(TIMER(u)->state); +} + +static void timer_timer_event(Unit *u, uint64_t elapsed, Watch *w) { + Timer *t = TIMER(u); + + assert(t); + assert(elapsed == 1); + + if (t->state != TIMER_WAITING) + return; + + log_debug("Timer elapsed on %s", u->id); + timer_enter_running(t); +} + +void timer_unit_notify(Unit *u, UnitActiveState new_state) { + Iterator i; + Unit *k; + + if (u->type == UNIT_TIMER) + return; + + SET_FOREACH(k, u->dependencies[UNIT_TRIGGERED_BY], i) { + Timer *t; + TimerValue *v; + + if (k->type != UNIT_TIMER) + continue; + + if (k->load_state != UNIT_LOADED) + continue; + + t = TIMER(k); + + /* Reenable all timers that depend on unit state */ + LIST_FOREACH(value, v, t->values) + if (v->base == TIMER_UNIT_ACTIVE || + v->base == TIMER_UNIT_INACTIVE) + v->disabled = false; + + switch (t->state) { + + case TIMER_WAITING: + case TIMER_ELAPSED: + + /* Recalculate sleep time */ + timer_enter_waiting(t, false); + break; + + case TIMER_RUNNING: + + if (UNIT_IS_INACTIVE_OR_FAILED(new_state)) { + log_debug("%s got notified about unit deactivation.", UNIT(t)->id); + timer_enter_waiting(t, false); + } + + break; + + case TIMER_DEAD: + case TIMER_FAILED: + break; + + default: + assert_not_reached("Unknown timer state"); + } + } +} + +static void timer_reset_failed(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + + if (t->state == TIMER_FAILED) + timer_set_state(t, TIMER_DEAD); + + t->result = TIMER_SUCCESS; +} + +static const char* const timer_state_table[_TIMER_STATE_MAX] = { + [TIMER_DEAD] = "dead", + [TIMER_WAITING] = "waiting", + [TIMER_RUNNING] = "running", + [TIMER_ELAPSED] = "elapsed", + [TIMER_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState); + +static const char* const timer_base_table[_TIMER_BASE_MAX] = { + [TIMER_ACTIVE] = "OnActiveSec", + [TIMER_BOOT] = "OnBootSec", + [TIMER_STARTUP] = "OnStartupSec", + [TIMER_UNIT_ACTIVE] = "OnUnitActiveSec", + [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec" +}; + +DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase); + +static const char* const timer_result_table[_TIMER_RESULT_MAX] = { + [TIMER_SUCCESS] = "success", + [TIMER_FAILURE_RESOURCES] = "resources" +}; + +DEFINE_STRING_TABLE_LOOKUP(timer_result, TimerResult); + +const UnitVTable timer_vtable = { + .suffix = ".timer", + .object_size = sizeof(Timer), + .sections = + "Unit\0" + "Timer\0" + "Install\0", + + .init = timer_init, + .done = timer_done, + .load = timer_load, + + .coldplug = timer_coldplug, + + .dump = timer_dump, + + .start = timer_start, + .stop = timer_stop, + + .serialize = timer_serialize, + .deserialize_item = timer_deserialize_item, + + .active_state = timer_active_state, + .sub_state_to_string = timer_sub_state_to_string, + + .timer_event = timer_timer_event, + + .reset_failed = timer_reset_failed, + + .bus_interface = "org.freedesktop.systemd1.Timer", + .bus_message_handler = bus_timer_message_handler, + .bus_invalidating_properties = bus_timer_invalidating_properties +}; diff --git a/src/timer.h b/src/timer.h new file mode 100644 index 000000000..f5c5c64f2 --- /dev/null +++ b/src/timer.h @@ -0,0 +1,93 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef footimerhfoo +#define footimerhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +typedef struct Timer Timer; + +#include "unit.h" + +typedef enum TimerState { + TIMER_DEAD, + TIMER_WAITING, + TIMER_RUNNING, + TIMER_ELAPSED, + TIMER_FAILED, + _TIMER_STATE_MAX, + _TIMER_STATE_INVALID = -1 +} TimerState; + +typedef enum TimerBase { + TIMER_ACTIVE, + TIMER_BOOT, + TIMER_STARTUP, + TIMER_UNIT_ACTIVE, + TIMER_UNIT_INACTIVE, + _TIMER_BASE_MAX, + _TIMER_BASE_INVALID = -1 +} TimerBase; + +typedef struct TimerValue { + usec_t value; + usec_t next_elapse; + + LIST_FIELDS(struct TimerValue, value); + + TimerBase base; + bool disabled; +} TimerValue; + +typedef enum TimerResult { + TIMER_SUCCESS, + TIMER_FAILURE_RESOURCES, + _TIMER_RESULT_MAX, + _TIMER_RESULT_INVALID = -1 +} TimerResult; + +struct Timer { + Unit meta; + + LIST_HEAD(TimerValue, values); + usec_t next_elapse; + + TimerState state, deserialized_state; + UnitRef unit; + + Watch timer_watch; + + TimerResult result; +}; + +void timer_unit_notify(Unit *u, UnitActiveState new_state); + +extern const UnitVTable timer_vtable; + +const char *timer_state_to_string(TimerState i); +TimerState timer_state_from_string(const char *s); + +const char *timer_base_to_string(TimerBase i); +TimerBase timer_base_from_string(const char *s); + +const char* timer_result_to_string(TimerResult i); +TimerResult timer_result_from_string(const char *s); + +#endif diff --git a/src/timestamp.c b/src/timestamp.c new file mode 100644 index 000000000..ce5142946 --- /dev/null +++ b/src/timestamp.c @@ -0,0 +1,39 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#include "util.h" + +int main(int argc, char *argv[]) { + struct dual_timestamp t; + + /* This is mostly useful for stuff like init ram disk scripts + * which want to take a proper timestamp to do minimal bootup + * profiling. */ + + dual_timestamp_get(&t); + printf("%llu %llu\n", + (unsigned long long) t.realtime, + (unsigned long long) t.monotonic); + + return 0; +} diff --git a/src/tmpfiles.c b/src/tmpfiles.c new file mode 100644 index 000000000..873bf233f --- /dev/null +++ b/src/tmpfiles.c @@ -0,0 +1,1314 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering, Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "util.h" +#include "strv.h" +#include "label.h" +#include "set.h" + +/* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates + * them in the file system. This is intended to be used to create + * properly owned directories beneath /tmp, /var/tmp, /run, which are + * volatile and hence need to be recreated on bootup. */ + +typedef enum ItemType { + /* These ones take file names */ + CREATE_FILE = 'f', + TRUNCATE_FILE = 'F', + WRITE_FILE = 'w', + CREATE_DIRECTORY = 'd', + TRUNCATE_DIRECTORY = 'D', + CREATE_FIFO = 'p', + CREATE_SYMLINK = 'L', + CREATE_CHAR_DEVICE = 'c', + CREATE_BLOCK_DEVICE = 'b', + + /* These ones take globs */ + IGNORE_PATH = 'x', + REMOVE_PATH = 'r', + RECURSIVE_REMOVE_PATH = 'R', + RELABEL_PATH = 'z', + RECURSIVE_RELABEL_PATH = 'Z' +} ItemType; + +typedef struct Item { + ItemType type; + + char *path; + char *argument; + uid_t uid; + gid_t gid; + mode_t mode; + usec_t age; + + dev_t major_minor; + + bool uid_set:1; + bool gid_set:1; + bool mode_set:1; + bool age_set:1; +} Item; + +static Hashmap *items = NULL, *globs = NULL; +static Set *unix_sockets = NULL; + +static bool arg_create = false; +static bool arg_clean = false; +static bool arg_remove = false; + +static const char *arg_prefix = NULL; + +#define MAX_DEPTH 256 + +static bool needs_glob(ItemType t) { + return t == IGNORE_PATH || t == REMOVE_PATH || t == RECURSIVE_REMOVE_PATH || t == RELABEL_PATH || t == RECURSIVE_RELABEL_PATH; +} + +static struct Item* find_glob(Hashmap *h, const char *match) { + Item *j; + Iterator i; + + HASHMAP_FOREACH(j, h, i) + if (fnmatch(j->path, match, FNM_PATHNAME|FNM_PERIOD) == 0) + return j; + + return NULL; +} + +static void load_unix_sockets(void) { + FILE *f = NULL; + char line[LINE_MAX]; + + if (unix_sockets) + return; + + /* We maintain a cache of the sockets we found in + * /proc/net/unix to speed things up a little. */ + + unix_sockets = set_new(string_hash_func, string_compare_func); + if (!unix_sockets) + return; + + f = fopen("/proc/net/unix", "re"); + if (!f) + return; + + /* Skip header */ + if (!fgets(line, sizeof(line), f)) + goto fail; + + for (;;) { + char *p, *s; + int k; + + if (!fgets(line, sizeof(line), f)) + break; + + truncate_nl(line); + + p = strchr(line, ':'); + if (!p) + continue; + + if (strlen(p) < 37) + continue; + + p += 37; + p += strspn(p, WHITESPACE); + p += strcspn(p, WHITESPACE); /* skip one more word */ + p += strspn(p, WHITESPACE); + + if (*p != '/') + continue; + + s = strdup(p); + if (!s) + goto fail; + + path_kill_slashes(s); + + k = set_put(unix_sockets, s); + if (k < 0) { + free(s); + + if (k != -EEXIST) + goto fail; + } + } + + fclose(f); + return; + +fail: + set_free_free(unix_sockets); + unix_sockets = NULL; + + if (f) + fclose(f); +} + +static bool unix_socket_alive(const char *fn) { + assert(fn); + + load_unix_sockets(); + + if (unix_sockets) + return !!set_get(unix_sockets, (char*) fn); + + /* We don't know, so assume yes */ + return true; +} + +static int dir_cleanup( + const char *p, + DIR *d, + const struct stat *ds, + usec_t cutoff, + dev_t rootdev, + bool mountpoint, + int maxdepth) +{ + struct dirent *dent; + struct timespec times[2]; + bool deleted = false; + char *sub_path = NULL; + int r = 0; + + while ((dent = readdir(d))) { + struct stat s; + usec_t age; + + if (streq(dent->d_name, ".") || + streq(dent->d_name, "..")) + continue; + + if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) { + + if (errno != ENOENT) { + log_error("stat(%s/%s) failed: %m", p, dent->d_name); + r = -errno; + } + + continue; + } + + /* Stay on the same filesystem */ + if (s.st_dev != rootdev) + continue; + + /* Do not delete read-only files owned by root */ + if (s.st_uid == 0 && !(s.st_mode & S_IWUSR)) + continue; + + free(sub_path); + sub_path = NULL; + + if (asprintf(&sub_path, "%s/%s", p, dent->d_name) < 0) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + /* Is there an item configured for this path? */ + if (hashmap_get(items, sub_path)) + continue; + + if (find_glob(globs, sub_path)) + continue; + + if (S_ISDIR(s.st_mode)) { + + if (mountpoint && + streq(dent->d_name, "lost+found") && + s.st_uid == 0) + continue; + + if (maxdepth <= 0) + log_warning("Reached max depth on %s.", sub_path); + else { + DIR *sub_dir; + int q; + + sub_dir = xopendirat(dirfd(d), dent->d_name, O_NOFOLLOW); + if (sub_dir == NULL) { + if (errno != ENOENT) { + log_error("opendir(%s/%s) failed: %m", p, dent->d_name); + r = -errno; + } + + continue; + } + + q = dir_cleanup(sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1); + closedir(sub_dir); + + if (q < 0) + r = q; + } + + /* Ignore ctime, we change it when deleting */ + age = MAX(timespec_load(&s.st_mtim), + timespec_load(&s.st_atim)); + if (age >= cutoff) + continue; + + log_debug("rmdir '%s'\n", sub_path); + + if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0) { + if (errno != ENOENT && errno != ENOTEMPTY) { + log_error("rmdir(%s): %m", sub_path); + r = -errno; + } + } + + } else { + /* Skip files for which the sticky bit is + * set. These are semantics we define, and are + * unknown elsewhere. See XDG_RUNTIME_DIR + * specification for details. */ + if (s.st_mode & S_ISVTX) + continue; + + if (mountpoint && S_ISREG(s.st_mode)) { + if (streq(dent->d_name, ".journal") && + s.st_uid == 0) + continue; + + if (streq(dent->d_name, "aquota.user") || + streq(dent->d_name, "aquota.group")) + continue; + } + + /* Ignore sockets that are listed in /proc/net/unix */ + if (S_ISSOCK(s.st_mode) && unix_socket_alive(sub_path)) + continue; + + /* Ignore device nodes */ + if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode)) + continue; + + age = MAX3(timespec_load(&s.st_mtim), + timespec_load(&s.st_atim), + timespec_load(&s.st_ctim)); + + if (age >= cutoff) + continue; + + log_debug("unlink '%s'\n", sub_path); + + if (unlinkat(dirfd(d), dent->d_name, 0) < 0) { + if (errno != ENOENT) { + log_error("unlink(%s): %m", sub_path); + r = -errno; + } + } + + deleted = true; + } + } + +finish: + if (deleted) { + /* Restore original directory timestamps */ + times[0] = ds->st_atim; + times[1] = ds->st_mtim; + + if (futimens(dirfd(d), times) < 0) + log_error("utimensat(%s): %m", p); + } + + free(sub_path); + + return r; +} + +static int clean_item(Item *i) { + DIR *d; + struct stat s, ps; + bool mountpoint; + int r; + usec_t cutoff, n; + + assert(i); + + if (i->type != CREATE_DIRECTORY && + i->type != TRUNCATE_DIRECTORY && + i->type != IGNORE_PATH) + return 0; + + if (!i->age_set || i->age <= 0) + return 0; + + n = now(CLOCK_REALTIME); + if (n < i->age) + return 0; + + cutoff = n - i->age; + + d = opendir(i->path); + if (!d) { + if (errno == ENOENT) + return 0; + + log_error("Failed to open directory %s: %m", i->path); + return -errno; + } + + if (fstat(dirfd(d), &s) < 0) { + log_error("stat(%s) failed: %m", i->path); + r = -errno; + goto finish; + } + + if (!S_ISDIR(s.st_mode)) { + log_error("%s is not a directory.", i->path); + r = -ENOTDIR; + goto finish; + } + + if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0) { + log_error("stat(%s/..) failed: %m", i->path); + r = -errno; + goto finish; + } + + mountpoint = s.st_dev != ps.st_dev || + (s.st_dev == ps.st_dev && s.st_ino == ps.st_ino); + + r = dir_cleanup(i->path, d, &s, cutoff, s.st_dev, mountpoint, MAX_DEPTH); + +finish: + if (d) + closedir(d); + + return r; +} + +static int item_set_perms(Item *i, const char *path) { + /* not using i->path directly because it may be a glob */ + if (i->mode_set) + if (chmod(path, i->mode) < 0) { + log_error("chmod(%s) failed: %m", path); + return -errno; + } + + if (i->uid_set || i->gid_set) + if (chown(path, + i->uid_set ? i->uid : (uid_t) -1, + i->gid_set ? i->gid : (gid_t) -1) < 0) { + + log_error("chown(%s) failed: %m", path); + return -errno; + } + + return label_fix(path, false); +} + +static int recursive_relabel_children(Item *i, const char *path) { + DIR *d; + int ret = 0; + + /* This returns the first error we run into, but nevertheless + * tries to go on */ + + d = opendir(path); + if (!d) + return errno == ENOENT ? 0 : -errno; + + for (;;) { + struct dirent buf, *de; + bool is_dir; + int r; + char *entry_path; + + r = readdir_r(d, &buf, &de); + if (r != 0) { + if (ret == 0) + ret = -r; + break; + } + + if (!de) + break; + + if (streq(de->d_name, ".") || streq(de->d_name, "..")) + continue; + + if (asprintf(&entry_path, "%s/%s", path, de->d_name) < 0) { + if (ret == 0) + ret = -ENOMEM; + continue; + } + + if (de->d_type == DT_UNKNOWN) { + struct stat st; + + if (lstat(entry_path, &st) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + free(entry_path); + continue; + } + + is_dir = S_ISDIR(st.st_mode); + + } else + is_dir = de->d_type == DT_DIR; + + r = item_set_perms(i, entry_path); + if (r < 0) { + if (ret == 0 && r != -ENOENT) + ret = r; + free(entry_path); + continue; + } + + if (is_dir) { + r = recursive_relabel_children(i, entry_path); + if (r < 0 && ret == 0) + ret = r; + } + + free(entry_path); + } + + closedir(d); + + return ret; +} + +static int recursive_relabel(Item *i, const char *path) { + int r; + struct stat st; + + r = item_set_perms(i, path); + if (r < 0) + return r; + + if (lstat(path, &st) < 0) + return -errno; + + if (S_ISDIR(st.st_mode)) + r = recursive_relabel_children(i, path); + + return r; +} + +static int glob_item(Item *i, int (*action)(Item *, const char *)) { + int r = 0, k; + glob_t g; + char **fn; + + zero(g); + + errno = 0; + if ((k = glob(i->path, GLOB_NOSORT|GLOB_BRACE, NULL, &g)) != 0) { + + if (k != GLOB_NOMATCH) { + if (errno != 0) + errno = EIO; + + log_error("glob(%s) failed: %m", i->path); + return -errno; + } + } + + STRV_FOREACH(fn, g.gl_pathv) + if ((k = action(i, *fn)) < 0) + r = k; + + globfree(&g); + return r; +} + +static int create_item(Item *i) { + int r; + mode_t u; + struct stat st; + + assert(i); + + switch (i->type) { + + case IGNORE_PATH: + case REMOVE_PATH: + case RECURSIVE_REMOVE_PATH: + return 0; + + case CREATE_FILE: + case TRUNCATE_FILE: + case WRITE_FILE: { + int fd, flags; + + flags = i->type == CREATE_FILE ? O_CREAT|O_APPEND : + i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC : 0; + + u = umask(0); + fd = open(i->path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY|O_NOFOLLOW, i->mode); + umask(u); + + if (fd < 0) { + if (i->type == WRITE_FILE && errno == ENOENT) + break; + + log_error("Failed to create file %s: %m", i->path); + return -errno; + } + + if (i->argument) { + ssize_t n; + size_t l; + struct iovec iovec[2]; + static const char new_line = '\n'; + + l = strlen(i->argument); + + zero(iovec); + iovec[0].iov_base = i->argument; + iovec[0].iov_len = l; + + iovec[1].iov_base = (void*) &new_line; + iovec[1].iov_len = 1; + + n = writev(fd, iovec, 2); + if (n < 0 || (size_t) n != l+1) { + log_error("Failed to write file %s: %s", i->path, n < 0 ? strerror(-n) : "Short"); + close_nointr_nofail(fd); + return n < 0 ? n : -EIO; + } + } + + close_nointr_nofail(fd); + + if (stat(i->path, &st) < 0) { + log_error("stat(%s) failed: %m", i->path); + return -errno; + } + + if (!S_ISREG(st.st_mode)) { + log_error("%s is not a file.", i->path); + return -EEXIST; + } + + r = item_set_perms(i, i->path); + if (r < 0) + return r; + + break; + } + + case TRUNCATE_DIRECTORY: + case CREATE_DIRECTORY: + + u = umask(0); + mkdir_parents(i->path, 0755); + r = mkdir(i->path, i->mode); + umask(u); + + if (r < 0 && errno != EEXIST) { + log_error("Failed to create directory %s: %m", i->path); + return -errno; + } + + if (stat(i->path, &st) < 0) { + log_error("stat(%s) failed: %m", i->path); + return -errno; + } + + if (!S_ISDIR(st.st_mode)) { + log_error("%s is not a directory.", i->path); + return -EEXIST; + } + + r = item_set_perms(i, i->path); + if (r < 0) + return r; + + break; + + case CREATE_FIFO: + + u = umask(0); + r = mkfifo(i->path, i->mode); + umask(u); + + if (r < 0 && errno != EEXIST) { + log_error("Failed to create fifo %s: %m", i->path); + return -errno; + } + + if (stat(i->path, &st) < 0) { + log_error("stat(%s) failed: %m", i->path); + return -errno; + } + + if (!S_ISFIFO(st.st_mode)) { + log_error("%s is not a fifo.", i->path); + return -EEXIST; + } + + r = item_set_perms(i, i->path); + if (r < 0) + return r; + + break; + + case CREATE_SYMLINK: { + char *x; + + r = symlink(i->argument, i->path); + if (r < 0 && errno != EEXIST) { + log_error("symlink(%s, %s) failed: %m", i->argument, i->path); + return -errno; + } + + r = readlink_malloc(i->path, &x); + if (r < 0) { + log_error("readlink(%s) failed: %s", i->path, strerror(-r)); + return -errno; + } + + if (!streq(i->argument, x)) { + free(x); + log_error("%s is not the right symlinks.", i->path); + return -EEXIST; + } + + free(x); + break; + } + + case CREATE_BLOCK_DEVICE: + case CREATE_CHAR_DEVICE: { + + u = umask(0); + r = mknod(i->path, i->mode | (i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR), i->major_minor); + umask(u); + + if (r < 0 && errno != EEXIST) { + log_error("Failed to create device node %s: %m", i->path); + return -errno; + } + + if (stat(i->path, &st) < 0) { + log_error("stat(%s) failed: %m", i->path); + return -errno; + } + + if (i->type == CREATE_BLOCK_DEVICE ? !S_ISBLK(st.st_mode) : !S_ISCHR(st.st_mode)) { + log_error("%s is not a device node.", i->path); + return -EEXIST; + } + + r = item_set_perms(i, i->path); + if (r < 0) + return r; + + break; + } + + case RELABEL_PATH: + + r = glob_item(i, item_set_perms); + if (r < 0) + return 0; + break; + + case RECURSIVE_RELABEL_PATH: + + r = glob_item(i, recursive_relabel); + if (r < 0) + return r; + } + + log_debug("%s created successfully.", i->path); + + return 0; +} + +static int remove_item_instance(Item *i, const char *instance) { + int r; + + assert(i); + + switch (i->type) { + + case CREATE_FILE: + case TRUNCATE_FILE: + case CREATE_DIRECTORY: + case CREATE_FIFO: + case CREATE_SYMLINK: + case CREATE_BLOCK_DEVICE: + case CREATE_CHAR_DEVICE: + case IGNORE_PATH: + case RELABEL_PATH: + case RECURSIVE_RELABEL_PATH: + case WRITE_FILE: + break; + + case REMOVE_PATH: + if (remove(instance) < 0 && errno != ENOENT) { + log_error("remove(%s): %m", instance); + return -errno; + } + + break; + + case TRUNCATE_DIRECTORY: + case RECURSIVE_REMOVE_PATH: + r = rm_rf(instance, false, i->type == RECURSIVE_REMOVE_PATH, false); + if (r < 0 && r != -ENOENT) { + log_error("rm_rf(%s): %s", instance, strerror(-r)); + return r; + } + + break; + } + + return 0; +} + +static int remove_item(Item *i) { + int r = 0; + + assert(i); + + switch (i->type) { + + case CREATE_FILE: + case TRUNCATE_FILE: + case CREATE_DIRECTORY: + case CREATE_FIFO: + case CREATE_SYMLINK: + case CREATE_CHAR_DEVICE: + case CREATE_BLOCK_DEVICE: + case IGNORE_PATH: + case RELABEL_PATH: + case RECURSIVE_RELABEL_PATH: + case WRITE_FILE: + break; + + case REMOVE_PATH: + case TRUNCATE_DIRECTORY: + case RECURSIVE_REMOVE_PATH: + r = glob_item(i, remove_item_instance); + break; + } + + return r; +} + +static int process_item(Item *i) { + int r, q, p; + + assert(i); + + r = arg_create ? create_item(i) : 0; + q = arg_remove ? remove_item(i) : 0; + p = arg_clean ? clean_item(i) : 0; + + if (r < 0) + return r; + + if (q < 0) + return q; + + return p; +} + +static void item_free(Item *i) { + assert(i); + + free(i->path); + free(i->argument); + free(i); +} + +static bool item_equal(Item *a, Item *b) { + assert(a); + assert(b); + + if (!streq_ptr(a->path, b->path)) + return false; + + if (a->type != b->type) + return false; + + if (a->uid_set != b->uid_set || + (a->uid_set && a->uid != b->uid)) + return false; + + if (a->gid_set != b->gid_set || + (a->gid_set && a->gid != b->gid)) + return false; + + if (a->mode_set != b->mode_set || + (a->mode_set && a->mode != b->mode)) + return false; + + if (a->age_set != b->age_set || + (a->age_set && a->age != b->age)) + return false; + + if ((a->type == CREATE_FILE || + a->type == TRUNCATE_FILE || + a->type == WRITE_FILE || + a->type == CREATE_SYMLINK) && + !streq_ptr(a->argument, b->argument)) + return false; + + if ((a->type == CREATE_CHAR_DEVICE || + a->type == CREATE_BLOCK_DEVICE) && + a->major_minor != b->major_minor) + return false; + + return true; +} + +static int parse_line(const char *fname, unsigned line, const char *buffer) { + Item *i, *existing; + char *mode = NULL, *user = NULL, *group = NULL, *age = NULL; + char type; + Hashmap *h; + int r, n = -1; + + assert(fname); + assert(line >= 1); + assert(buffer); + + i = new0(Item, 1); + if (!i) { + log_error("Out of memory"); + return -ENOMEM; + } + + if (sscanf(buffer, + "%c " + "%ms " + "%ms " + "%ms " + "%ms " + "%ms " + "%n", + &type, + &i->path, + &mode, + &user, + &group, + &age, + &n) < 2) { + log_error("[%s:%u] Syntax error.", fname, line); + r = -EIO; + goto finish; + } + + if (n >= 0) { + n += strspn(buffer+n, WHITESPACE); + if (buffer[n] != 0 && (buffer[n] != '-' || buffer[n+1] != 0)) { + i->argument = unquote(buffer+n, "\""); + if (!i->argument) { + log_error("Out of memory"); + return -ENOMEM; + } + } + } + + switch(type) { + + case CREATE_FILE: + case TRUNCATE_FILE: + case CREATE_DIRECTORY: + case TRUNCATE_DIRECTORY: + case CREATE_FIFO: + case IGNORE_PATH: + case REMOVE_PATH: + case RECURSIVE_REMOVE_PATH: + case RELABEL_PATH: + case RECURSIVE_RELABEL_PATH: + break; + + case CREATE_SYMLINK: + if (!i->argument) { + log_error("[%s:%u] Symlink file requires argument.", fname, line); + r = -EBADMSG; + goto finish; + } + break; + + case WRITE_FILE: + if (!i->argument) { + log_error("[%s:%u] Write file requires argument.", fname, line); + r = -EBADMSG; + goto finish; + } + break; + + case CREATE_CHAR_DEVICE: + case CREATE_BLOCK_DEVICE: { + unsigned major, minor; + + if (!i->argument) { + log_error("[%s:%u] Device file requires argument.", fname, line); + r = -EBADMSG; + goto finish; + } + + if (sscanf(i->argument, "%u:%u", &major, &minor) != 2) { + log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i->argument); + r = -EBADMSG; + goto finish; + } + + i->major_minor = makedev(major, minor); + break; + } + + default: + log_error("[%s:%u] Unknown file type '%c'.", fname, line, type); + r = -EBADMSG; + goto finish; + } + + i->type = type; + + if (!path_is_absolute(i->path)) { + log_error("[%s:%u] Path '%s' not absolute.", fname, line, i->path); + r = -EBADMSG; + goto finish; + } + + path_kill_slashes(i->path); + + if (arg_prefix && !path_startswith(i->path, arg_prefix)) { + r = 0; + goto finish; + } + + if (user && !streq(user, "-")) { + const char *u = user; + + r = get_user_creds(&u, &i->uid, NULL, NULL); + if (r < 0) { + log_error("[%s:%u] Unknown user '%s'.", fname, line, user); + goto finish; + } + + i->uid_set = true; + } + + if (group && !streq(group, "-")) { + const char *g = group; + + r = get_group_creds(&g, &i->gid); + if (r < 0) { + log_error("[%s:%u] Unknown group '%s'.", fname, line, group); + goto finish; + } + + i->gid_set = true; + } + + if (mode && !streq(mode, "-")) { + unsigned m; + + if (sscanf(mode, "%o", &m) != 1) { + log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode); + r = -ENOENT; + goto finish; + } + + i->mode = m; + i->mode_set = true; + } else + i->mode = + i->type == CREATE_DIRECTORY || + i->type == TRUNCATE_DIRECTORY ? 0755 : 0644; + + if (age && !streq(age, "-")) { + if (parse_usec(age, &i->age) < 0) { + log_error("[%s:%u] Invalid age '%s'.", fname, line, age); + r = -EBADMSG; + goto finish; + } + + i->age_set = true; + } + + h = needs_glob(i->type) ? globs : items; + + existing = hashmap_get(h, i->path); + if (existing) { + + /* Two identical items are fine */ + if (!item_equal(existing, i)) + log_warning("Two or more conflicting lines for %s configured, ignoring.", i->path); + + r = 0; + goto finish; + } + + r = hashmap_put(h, i->path, i); + if (r < 0) { + log_error("Failed to insert item %s: %s", i->path, strerror(-r)); + goto finish; + } + + i = NULL; + r = 0; + +finish: + free(user); + free(group); + free(mode); + free(age); + + if (i) + item_free(i); + + return r; +} + +static int help(void) { + + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" + "Creates, deletes and cleans up volatile and temporary files and directories.\n\n" + " -h --help Show this help\n" + " --create Create marked files/directories\n" + " --clean Clean up marked directories\n" + " --remove Remove marked files/directories\n" + " --prefix=PATH Only apply rules that apply to paths with the specified prefix\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_CREATE, + ARG_CLEAN, + ARG_REMOVE, + ARG_PREFIX + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "create", no_argument, NULL, ARG_CREATE }, + { "clean", no_argument, NULL, ARG_CLEAN }, + { "remove", no_argument, NULL, ARG_REMOVE }, + { "prefix", required_argument, NULL, ARG_PREFIX }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_CREATE: + arg_create = true; + break; + + case ARG_CLEAN: + arg_clean = true; + break; + + case ARG_REMOVE: + arg_remove = true; + break; + + case ARG_PREFIX: + arg_prefix = optarg; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (!arg_clean && !arg_create && !arg_remove) { + log_error("You need to specify at least one of --clean, --create or --remove."); + return -EINVAL; + } + + return 1; +} + +static int read_config_file(const char *fn, bool ignore_enoent) { + FILE *f; + unsigned v = 0; + int r = 0; + + assert(fn); + + f = fopen(fn, "re"); + if (!f) { + + if (ignore_enoent && errno == ENOENT) + return 0; + + log_error("Failed to open %s: %m", fn); + return -errno; + } + + log_debug("apply: %s\n", fn); + for (;;) { + char line[LINE_MAX], *l; + int k; + + if (!(fgets(line, sizeof(line), f))) + break; + + v++; + + l = strstrip(line); + if (*l == '#' || *l == 0) + continue; + + if ((k = parse_line(fn, v, l)) < 0) + if (r == 0) + r = k; + } + + if (ferror(f)) { + log_error("Failed to read from file %s: %m", fn); + if (r == 0) + r = -EIO; + } + + fclose(f); + + return r; +} + +int main(int argc, char *argv[]) { + int r; + Item *i; + Iterator iterator; + + r = parse_argv(argc, argv); + if (r <= 0) + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + label_init(); + + items = hashmap_new(string_hash_func, string_compare_func); + globs = hashmap_new(string_hash_func, string_compare_func); + + if (!items || !globs) { + log_error("Out of memory"); + r = EXIT_FAILURE; + goto finish; + } + + r = EXIT_SUCCESS; + + if (optind < argc) { + int j; + + for (j = optind; j < argc; j++) + if (read_config_file(argv[j], false) < 0) + r = EXIT_FAILURE; + + } else { + char **files, **f; + + r = conf_files_list(&files, ".conf", + "/etc/tmpfiles.d", + "/run/tmpfiles.d", + "/usr/local/lib/tmpfiles.d", + "/usr/lib/tmpfiles.d", + NULL); + if (r < 0) { + r = EXIT_FAILURE; + log_error("Failed to enumerate tmpfiles.d files: %s", strerror(-r)); + goto finish; + } + + STRV_FOREACH(f, files) { + if (read_config_file(*f, true) < 0) + r = EXIT_FAILURE; + } + + strv_free(files); + } + + HASHMAP_FOREACH(i, globs, iterator) + process_item(i); + + HASHMAP_FOREACH(i, items, iterator) + process_item(i); + +finish: + while ((i = hashmap_steal_first(items))) + item_free(i); + + while ((i = hashmap_steal_first(globs))) + item_free(i); + + hashmap_free(items); + hashmap_free(globs); + + set_free_free(unix_sockets); + + label_finish(); + + return r; +} diff --git a/src/tty-ask-password-agent.c b/src/tty-ask-password-agent.c new file mode 100644 index 000000000..13481b29e --- /dev/null +++ b/src/tty-ask-password-agent.c @@ -0,0 +1,753 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "conf-parser.h" +#include "utmp-wtmp.h" +#include "socket-util.h" +#include "ask-password-api.h" +#include "strv.h" + +static enum { + ACTION_LIST, + ACTION_QUERY, + ACTION_WATCH, + ACTION_WALL +} arg_action = ACTION_QUERY; + +static bool arg_plymouth = false; +static bool arg_console = false; + +static int ask_password_plymouth( + const char *message, + usec_t until, + const char *flag_file, + bool accept_cached, + char ***_passphrases) { + + int fd = -1, notify = -1; + union sockaddr_union sa; + char *packet = NULL; + ssize_t k; + int r, n; + struct pollfd pollfd[2]; + char buffer[LINE_MAX]; + size_t p = 0; + enum { + POLL_SOCKET, + POLL_INOTIFY + }; + + assert(_passphrases); + + if (flag_file) { + if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) { + r = -errno; + goto finish; + } + + if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) { + r = -errno; + goto finish; + } + } + + if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) { + r = -errno; + goto finish; + } + + zero(sa); + sa.sa.sa_family = AF_UNIX; + strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1); + if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) { + log_error("Failed to connect to Plymouth: %m"); + r = -errno; + goto finish; + } + + if (accept_cached) { + packet = strdup("c"); + n = 1; + } else + asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n); + + if (!packet) { + r = -ENOMEM; + goto finish; + } + + if ((k = loop_write(fd, packet, n+1, true)) != n+1) { + r = k < 0 ? (int) k : -EIO; + goto finish; + } + + zero(pollfd); + pollfd[POLL_SOCKET].fd = fd; + pollfd[POLL_SOCKET].events = POLLIN; + pollfd[POLL_INOTIFY].fd = notify; + pollfd[POLL_INOTIFY].events = POLLIN; + + for (;;) { + int sleep_for = -1, j; + + if (until > 0) { + usec_t y; + + y = now(CLOCK_MONOTONIC); + + if (y > until) { + r = -ETIME; + goto finish; + } + + sleep_for = (int) ((until - y) / USEC_PER_MSEC); + } + + if (flag_file) + if (access(flag_file, F_OK) < 0) { + r = -errno; + goto finish; + } + + if ((j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) { + + if (errno == EINTR) + continue; + + r = -errno; + goto finish; + } else if (j == 0) { + r = -ETIME; + goto finish; + } + + if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0) + flush_fd(notify); + + if (pollfd[POLL_SOCKET].revents == 0) + continue; + + if ((k = read(fd, buffer + p, sizeof(buffer) - p)) <= 0) { + r = k < 0 ? -errno : -EIO; + goto finish; + } + + p += k; + + if (p < 1) + continue; + + if (buffer[0] == 5) { + + if (accept_cached) { + /* Hmm, first try with cached + * passwords failed, so let's retry + * with a normal password request */ + free(packet); + packet = NULL; + + if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) { + r = -ENOMEM; + goto finish; + } + + if ((k = loop_write(fd, packet, n+1, true)) != n+1) { + r = k < 0 ? (int) k : -EIO; + goto finish; + } + + accept_cached = false; + p = 0; + continue; + } + + /* No password, because UI not shown */ + r = -ENOENT; + goto finish; + + } else if (buffer[0] == 2 || buffer[0] == 9) { + uint32_t size; + char **l; + + /* One ore more answers */ + if (p < 5) + continue; + + memcpy(&size, buffer+1, sizeof(size)); + size = le32toh(size); + if (size+5 > sizeof(buffer)) { + r = -EIO; + goto finish; + } + + if (p-5 < size) + continue; + + if (!(l = strv_parse_nulstr(buffer + 5, size))) { + r = -ENOMEM; + goto finish; + } + + *_passphrases = l; + break; + + } else { + /* Unknown packet */ + r = -EIO; + goto finish; + } + } + + r = 0; + +finish: + if (notify >= 0) + close_nointr_nofail(notify); + + if (fd >= 0) + close_nointr_nofail(fd); + + free(packet); + + return r; +} + +static int parse_password(const char *filename, char **wall) { + char *socket_name = NULL, *message = NULL, *packet = NULL; + uint64_t not_after = 0; + unsigned pid = 0; + int socket_fd = -1; + bool accept_cached = false; + + const ConfigTableItem items[] = { + { "Ask", "Socket", config_parse_string, 0, &socket_name }, + { "Ask", "NotAfter", config_parse_uint64, 0, ¬_after }, + { "Ask", "Message", config_parse_string, 0, &message }, + { "Ask", "PID", config_parse_unsigned, 0, &pid }, + { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached }, + { NULL, NULL, NULL, 0, NULL } + }; + + FILE *f; + int r; + + assert(filename); + + f = fopen(filename, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + log_error("open(%s): %m", filename); + return -errno; + } + + r = config_parse(filename, f, NULL, config_item_table_lookup, (void*) items, true, NULL); + if (r < 0) { + log_error("Failed to parse password file %s: %s", filename, strerror(-r)); + goto finish; + } + + if (!socket_name) { + log_error("Invalid password file %s", filename); + r = -EBADMSG; + goto finish; + } + + if (not_after > 0) { + if (now(CLOCK_MONOTONIC) > not_after) { + r = 0; + goto finish; + } + } + + if (pid > 0 && + kill(pid, 0) < 0 && + errno == ESRCH) { + r = 0; + goto finish; + } + + if (arg_action == ACTION_LIST) + printf("'%s' (PID %u)\n", message, pid); + else if (arg_action == ACTION_WALL) { + char *_wall; + + if (asprintf(&_wall, + "%s%sPassword entry required for \'%s\' (PID %u).\r\n" + "Please enter password with the systemd-tty-ask-password-agent tool!", + *wall ? *wall : "", + *wall ? "\r\n\r\n" : "", + message, + pid) < 0) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + free(*wall); + *wall = _wall; + } else { + union { + struct sockaddr sa; + struct sockaddr_un un; + } sa; + size_t packet_length = 0; + + assert(arg_action == ACTION_QUERY || + arg_action == ACTION_WATCH); + + if (access(socket_name, W_OK) < 0) { + + if (arg_action == ACTION_QUERY) + log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid); + + r = 0; + goto finish; + } + + if (arg_plymouth) { + char **passwords = NULL; + + if ((r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords)) >= 0) { + char **p; + + packet_length = 1; + STRV_FOREACH(p, passwords) + packet_length += strlen(*p) + 1; + + if (!(packet = new(char, packet_length))) + r = -ENOMEM; + else { + char *d; + + packet[0] = '+'; + d = packet+1; + + STRV_FOREACH(p, passwords) + d = stpcpy(d, *p) + 1; + } + } + + } else { + int tty_fd = -1; + char *password; + + if (arg_console) + if ((tty_fd = acquire_terminal("/dev/console", false, false, false)) < 0) { + r = tty_fd; + goto finish; + } + + r = ask_password_tty(message, not_after, filename, &password); + + if (arg_console) { + close_nointr_nofail(tty_fd); + release_terminal(); + } + + if (r >= 0) { + packet_length = 1+strlen(password)+1; + if (!(packet = new(char, packet_length))) + r = -ENOMEM; + else { + packet[0] = '+'; + strcpy(packet+1, password); + } + + free(password); + } + } + + if (r == -ETIME || r == -ENOENT) { + /* If the query went away, that's OK */ + r = 0; + goto finish; + } + + if (r < 0) { + log_error("Failed to query password: %s", strerror(-r)); + goto finish; + } + + if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) { + log_error("socket(): %m"); + r = -errno; + goto finish; + } + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path)); + + if (sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) { + log_error("Failed to send: %m"); + r = -errno; + goto finish; + } + } + +finish: + fclose(f); + + if (socket_fd >= 0) + close_nointr_nofail(socket_fd); + + free(packet); + free(socket_name); + free(message); + + return r; +} + +static int wall_tty_block(void) { + char *p; + int fd, r; + dev_t devnr; + + r = get_ctty_devnr(0, &devnr); + if (r < 0) + return -r; + + if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0) + return -ENOMEM; + + mkdir_parents(p, 0700); + mkfifo(p, 0600); + + fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + free(p); + + if (fd < 0) + return -errno; + + return fd; +} + +static bool wall_tty_match(const char *path) { + int fd, k; + char *p; + struct stat st; + + if (path_is_absolute(path)) + k = lstat(path, &st); + else { + if (asprintf(&p, "/dev/%s", path) < 0) + return true; + + k = lstat(p, &st); + free(p); + } + + if (k < 0) + return true; + + if (!S_ISCHR(st.st_mode)) + return true; + + /* We use named pipes to ensure that wall messages suggesting + * password entry are not printed over password prompts + * already shown. We use the fact here that opening a pipe in + * non-blocking mode for write-only will succeed only if + * there's some writer behind it. Using pipes has the + * advantage that the block will automatically go away if the + * process dies. */ + + if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) + return true; + + fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + free(p); + + if (fd < 0) + return true; + + /* What, we managed to open the pipe? Then this tty is filtered. */ + close_nointr_nofail(fd); + return false; +} + +static int show_passwords(void) { + DIR *d; + struct dirent *de; + int r = 0; + + if (!(d = opendir("/run/systemd/ask-password"))) { + if (errno == ENOENT) + return 0; + + log_error("opendir(): %m"); + return -errno; + } + + while ((de = readdir(d))) { + char *p; + int q; + char *wall; + + /* We only support /dev on tmpfs, hence we can rely on + * d_type to be reliable */ + + if (de->d_type != DT_REG) + continue; + + if (ignore_file(de->d_name)) + continue; + + if (!startswith(de->d_name, "ask.")) + continue; + + if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + wall = NULL; + if ((q = parse_password(p, &wall)) < 0) + r = q; + + free(p); + + if (wall) { + utmp_wall(wall, wall_tty_match); + free(wall); + } + } + +finish: + if (d) + closedir(d); + + return r; +} + +static int watch_passwords(void) { + enum { + FD_INOTIFY, + FD_SIGNAL, + _FD_MAX + }; + + int notify = -1, signal_fd = -1, tty_block_fd = -1; + struct pollfd pollfd[_FD_MAX]; + sigset_t mask; + int r; + + tty_block_fd = wall_tty_block(); + + mkdir_p("/run/systemd/ask-password", 0755); + + if ((notify = inotify_init1(IN_CLOEXEC)) < 0) { + r = -errno; + goto finish; + } + + if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) { + r = -errno; + goto finish; + } + + assert_se(sigemptyset(&mask) == 0); + sigset_add_many(&mask, SIGINT, SIGTERM, -1); + assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); + + if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) { + log_error("signalfd(): %m"); + r = -errno; + goto finish; + } + + zero(pollfd); + pollfd[FD_INOTIFY].fd = notify; + pollfd[FD_INOTIFY].events = POLLIN; + pollfd[FD_SIGNAL].fd = signal_fd; + pollfd[FD_SIGNAL].events = POLLIN; + + for (;;) { + if ((r = show_passwords()) < 0) + log_error("Failed to show password: %s", strerror(-r)); + + if (poll(pollfd, _FD_MAX, -1) < 0) { + + if (errno == EINTR) + continue; + + r = -errno; + goto finish; + } + + if (pollfd[FD_INOTIFY].revents != 0) + flush_fd(notify); + + if (pollfd[FD_SIGNAL].revents != 0) + break; + } + + r = 0; + +finish: + if (notify >= 0) + close_nointr_nofail(notify); + + if (signal_fd >= 0) + close_nointr_nofail(signal_fd); + + if (tty_block_fd >= 0) + close_nointr_nofail(tty_block_fd); + + return r; +} + +static int help(void) { + + printf("%s [OPTIONS...]\n\n" + "Process system password requests.\n\n" + " -h --help Show this help\n" + " --list Show pending password requests\n" + " --query Process pending password requests\n" + " --watch Continuously process password requests\n" + " --wall Continuously forward password requests to wall\n" + " --plymouth Ask question with Plymouth instead of on TTY\n" + " --console Ask question on /dev/console instead of current TTY\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_LIST = 0x100, + ARG_QUERY, + ARG_WATCH, + ARG_WALL, + ARG_PLYMOUTH, + ARG_CONSOLE + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "list", no_argument, NULL, ARG_LIST }, + { "query", no_argument, NULL, ARG_QUERY }, + { "watch", no_argument, NULL, ARG_WATCH }, + { "wall", no_argument, NULL, ARG_WALL }, + { "plymouth", no_argument, NULL, ARG_PLYMOUTH }, + { "console", no_argument, NULL, ARG_CONSOLE }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_LIST: + arg_action = ACTION_LIST; + break; + + case ARG_QUERY: + arg_action = ACTION_QUERY; + break; + + case ARG_WATCH: + arg_action = ACTION_WATCH; + break; + + case ARG_WALL: + arg_action = ACTION_WALL; + break; + + case ARG_PLYMOUTH: + arg_plymouth = true; + break; + + case ARG_CONSOLE: + arg_console = true; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (optind != argc) { + help(); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r; + + log_parse_environment(); + log_open(); + + umask(0022); + + if ((r = parse_argv(argc, argv)) <= 0) + goto finish; + + if (arg_console) { + setsid(); + release_terminal(); + } + + if (arg_action == ACTION_WATCH || + arg_action == ACTION_WALL) + r = watch_passwords(); + else + r = show_passwords(); + + if (r < 0) + log_error("Error: %s", strerror(-r)); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/udev/.gitignore b/src/udev/.gitignore new file mode 100644 index 000000000..fa3500ba9 --- /dev/null +++ b/src/udev/.gitignore @@ -0,0 +1,40 @@ +*~ +*.o +*.a +*.lo +*.la +.libs +.deps +.dirstamp +Makefile +Makefile.in +/aclocal.m4 +/autom4te.cache +/config.h +/config.h.in +/config.log +/config.status +/config.guess +/config.sub +/libtool +/ltmain.sh +/install-sh +/missing +/configure +/stamp-h1 +/depcomp +/gtk-doc.make +/build-aux +/udev-test-install +/udevd +/udevadm +/test-udev +/test-libudev +/accelerometer +/ata_id +/cdrom_id +/collect +/mtd_probe +/v4l_id +/keymap +/scsi_id diff --git a/src/udev/.vimrc b/src/udev/.vimrc new file mode 100644 index 000000000..366fbdca4 --- /dev/null +++ b/src/udev/.vimrc @@ -0,0 +1,4 @@ +" 'set exrc' in ~/.vimrc will read .vimrc from the current directory +set tabstop=8 +set shiftwidth=8 +set expandtab diff --git a/COPYING b/src/udev/COPYING similarity index 100% rename from COPYING rename to src/udev/COPYING diff --git a/ChangeLog b/src/udev/ChangeLog similarity index 100% rename from ChangeLog rename to src/udev/ChangeLog diff --git a/INSTALL b/src/udev/INSTALL similarity index 100% rename from INSTALL rename to src/udev/INSTALL diff --git a/src/udev/Makefile.am b/src/udev/Makefile.am new file mode 100644 index 000000000..1c7f86b08 --- /dev/null +++ b/src/udev/Makefile.am @@ -0,0 +1,712 @@ +# Copyright (C) 2008-2012 Kay Sievers +# Copyright (C) 2009 Diego Elio 'Flameeyes' Pettenò + +SUBDIRS = . + +ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} + +AM_MAKEFLAGS = --no-print-directory + +LIBUDEV_CURRENT=13 +LIBUDEV_REVISION=2 +LIBUDEV_AGE=13 + +LIBGUDEV_CURRENT=1 +LIBGUDEV_REVISION=1 +LIBGUDEV_AGE=1 + +AM_CPPFLAGS = \ + -include $(top_builddir)/config.h \ + -I$(top_srcdir)/src \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -DPKGLIBEXECDIR=\""$(libexecdir)/udev"\" + +AM_CFLAGS = \ + ${my_CFLAGS} \ + -fvisibility=hidden \ + -ffunction-sections \ + -fdata-sections + +AM_LDFLAGS = \ + -Wl,--gc-sections \ + -Wl,--as-needed + +DISTCHECK_CONFIGURE_FLAGS = \ + --enable-debug \ + --enable-rule_generator \ + --enable-floppy \ + --with-selinux \ + --enable-gtk-doc \ + --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) + +BUILT_SOURCES = +EXTRA_DIST = +CLEANFILES = +INSTALL_EXEC_HOOKS = +INSTALL_DATA_HOOKS = +UNINSTALL_EXEC_HOOKS = +DISTCHECK_HOOKS = +DISTCLEAN_LOCAL_HOOKS = + +udevhomedir = $(libexecdir)/udev +udevhome_SCRIPTS = +dist_udevhome_SCRIPTS = +dist_udevhome_DATA = +dist_man_MANS = + +SED_PROCESS = \ + $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(SED) \ + -e 's,@VERSION\@,$(VERSION),g' \ + -e 's,@prefix\@,$(prefix),g' \ + -e 's,@rootprefix\@,$(rootprefix),g' \ + -e 's,@exec_prefix\@,$(exec_prefix),g' \ + -e 's,@libdir\@,$(libdir),g' \ + -e 's,@includedir\@,$(includedir),g' \ + -e 's,@bindir\@,$(bindir),g' \ + -e 's,@pkglibexecdir\@,$(libexecdir)/udev,g' \ + < $< > $@ || rm $@ + +%.pc: %.pc.in Makefile + $(SED_PROCESS) + +%.rules: %.rules.in Makefile + $(SED_PROCESS) + +%.service: %.service.in Makefile + $(SED_PROCESS) + +%.sh: %.sh.in Makefile + $(SED_PROCESS) + $(AM_V_GEN)chmod +x $@ + +%.pl: %.pl.in Makefile + $(SED_PROCESS) + $(AM_V_GEN)chmod +x $@ + +# ------------------------------------------------------------------------------ +SUBDIRS += src/docs + +include_HEADERS = src/libudev.h +lib_LTLIBRARIES = libudev.la +noinst_LTLIBRARIES = libudev-private.la + +libudev_la_SOURCES =\ + src/libudev-private.h \ + src/libudev.c \ + src/libudev-list.c \ + src/libudev-util.c \ + src/libudev-device.c \ + src/libudev-enumerate.c \ + src/libudev-monitor.c \ + src/libudev-queue.c + +libudev_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -version-info $(LIBUDEV_CURRENT):$(LIBUDEV_REVISION):$(LIBUDEV_AGE) + +libudev_private_la_SOURCES =\ + $(libudev_la_SOURCES) \ + src/libudev-util-private.c \ + src/libudev-device-private.c \ + src/libudev-queue-private.c + +if WITH_SELINUX +libudev_private_la_SOURCES += src/libudev-selinux-private.c +libudev_private_la_LIBADD = $(SELINUX_LIBS) +endif + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = src/libudev.pc +EXTRA_DIST += src/libudev.pc.in +CLEANFILES += src/libudev.pc + +EXTRA_DIST += src/COPYING +# move lib from $(libdir) to $(rootlib_execdir) and update devel link, if needed +libudev-install-move-hook: + if test "$(libdir)" != "$(rootlib_execdir)"; then \ + mkdir -p $(DESTDIR)$(rootlib_execdir) && \ + so_img_name=$$(readlink $(DESTDIR)$(libdir)/libudev.so) && \ + so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \ + ln -sf $$so_img_rel_target_prefix$(rootlib_execdir)/$$so_img_name $(DESTDIR)$(libdir)/libudev.so && \ + mv $(DESTDIR)$(libdir)/libudev.so.* $(DESTDIR)$(rootlib_execdir); \ + fi + +libudev-uninstall-move-hook: + rm -f $(DESTDIR)$(rootlib_execdir)/libudev.so* + +INSTALL_EXEC_HOOKS += libudev-install-move-hook +UNINSTALL_EXEC_HOOKS += libudev-uninstall-move-hook + +# ------------------------------------------------------------------------------ +udev-confdirs: + -mkdir -p $(DESTDIR)$(sysconfdir)/udev/rules.d + -mkdir -p $(DESTDIR)$(libexecdir)/udev/devices + +INSTALL_DATA_HOOKS += udev-confdirs + +udevrulesdir = $(libexecdir)/udev/rules.d +dist_udevrules_DATA = \ + rules/42-usb-hid-pm.rules \ + rules/50-udev-default.rules \ + rules/60-persistent-storage-tape.rules \ + rules/60-persistent-serial.rules \ + rules/60-persistent-input.rules \ + rules/60-persistent-alsa.rules \ + rules/60-persistent-storage.rules \ + rules/75-net-description.rules \ + rules/75-tty-description.rules \ + rules/78-sound-card.rules \ + rules/80-drivers.rules \ + rules/95-udev-late.rules + +udevconfdir = $(sysconfdir)/udev +dist_udevconf_DATA = src/udev.conf + +sharepkgconfigdir = $(datadir)/pkgconfig +sharepkgconfig_DATA = src/udev.pc +EXTRA_DIST += src/udev.pc.in +CLEANFILES += src/udev.pc + +if WITH_SYSTEMD +dist_systemdsystemunit_DATA = \ + src/udev-control.socket \ + src/udev-kernel.socket + +systemdsystemunit_DATA = \ + src/udev.service \ + src/udev-trigger.service \ + src/udev-settle.service + +EXTRA_DIST += \ + src/udev.service.in \ + src/udev-trigger.service.in \ + src/udev-settle.service.in + +CLEANFILES += \ + src/udev.service \ + src/udev-trigger.service \ + src/udev-settle.service + +systemd-install-hook: + mkdir -p $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants + ln -sf ../udev-control.socket $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants/udev-control.socket + ln -sf ../udev-kernel.socket $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants/udev-kernel.socket + mkdir -p $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants + ln -sf ../udev.service $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants/udev.service + ln -sf ../udev-trigger.service $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants/udev-trigger.service + +INSTALL_DATA_HOOKS += systemd-install-hook +endif + +bin_PROGRAMS = \ + udevadm + +pkglibexec_PROGRAMS = \ + udevd + +udev_common_sources = \ + src/udev.h \ + src/udev-event.c \ + src/udev-watch.c \ + src/udev-node.c \ + src/udev-rules.c \ + src/udev-ctrl.c \ + src/udev-builtin.c \ + src/udev-builtin-blkid.c \ + src/udev-builtin-firmware.c \ + src/udev-builtin-hwdb.c \ + src/udev-builtin-input_id.c \ + src/udev-builtin-kmod.c \ + src/udev-builtin-path_id.c \ + src/udev-builtin-usb_id.c + +udev_common_CFLAGS = \ + $(BLKID_CFLAGS) \ + $(KMOD_CFLAGS) + +udev_common_LDADD = \ + libudev-private.la \ + $(BLKID_LIBS) \ + $(KMOD_LIBS) + +udev_common_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DFIRMWARE_PATH="$(FIRMWARE_PATH)" \ + -DUSB_DATABASE=\"$(USB_DATABASE)\" -DPCI_DATABASE=\"$(PCI_DATABASE)\" + +udevd_SOURCES = \ + $(udev_common_sources) \ + src/udevd.c \ + src/sd-daemon.h \ + src/sd-daemon.c +udevd_CFLAGS = $(udev_common_CFLAGS) +udevd_LDADD = $(udev_common_LDADD) +udevd_CPPFLAGS = $(udev_common_CPPFLAGS) + +udevadm_SOURCES = \ + $(udev_common_sources) \ + src/udevadm.c \ + src/udevadm-info.c \ + src/udevadm-control.c \ + src/udevadm-monitor.c \ + src/udevadm-settle.c \ + src/udevadm-trigger.c \ + src/udevadm-test.c \ + src/udevadm-test-builtin.c +udevadm_CFLAGS = $(udev_common_CFLAGS) +udevadm_LDADD = $(udev_common_LDADD) +udevadm_CPPFLAGS = $(udev_common_CPPFLAGS) + +# ------------------------------------------------------------------------------ +if ENABLE_MANPAGES +dist_man_MANS += \ + src/udev.7 \ + src/udevadm.8 \ + src/udevd.8 +endif + +EXTRA_DIST += \ + src/udev.xml \ + src/udevadm.xml \ + src/udevd.xml + +if HAVE_XSLTPROC +dist_noinst_DATA = \ + src/udev.html \ + src/udevadm.html \ + src/udevd.html + +src/%.7 src/%.8 : src/%.xml + $(AM_V_GEN)$(XSLTPROC) -o $@ -nonet http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< + +src/%.html : src/%.xml + $(AM_V_GEN)$(XSLTPROC) -o $@ -nonet http://docbook.sourceforge.net/release/xsl/current/xhtml-1_1/docbook.xsl $< +endif + +# ------------------------------------------------------------------------------ +TESTS = \ + test/udev-test.pl \ + test/rules-test.sh + +check_PROGRAMS = \ + test-libudev \ + test-udev + +test_libudev_SOURCES = src/test-libudev.c +test_libudev_LDADD = libudev.la + +test_udev_SOURCES = \ + $(udev_common_sources) \ + src/test-udev.c +test_udev_CFLAGS = $(udev_common_CFLAGS) +test_udev_LDADD = $(udev_common_LDADD) +test_udev_CPPFLAGS = $(udev_common_CPPFLAGS) +test_udev_DEPENDENCIES = test/sys + +# packed sysfs test tree +test/sys: + $(AM_V_GEN)mkdir -p test && tar -C test/ -xJf $(top_srcdir)/test/sys.tar.xz + +test-sys-distclean: + -rm -rf test/sys +DISTCLEAN_LOCAL_HOOKS += test-sys-distclean + +EXTRA_DIST += test/sys.tar.xz + +# ------------------------------------------------------------------------------ +ata_id_SOURCES = src/ata_id/ata_id.c +ata_id_LDADD = libudev-private.la +pkglibexec_PROGRAMS += ata_id + +# ------------------------------------------------------------------------------ +cdrom_id_SOURCES = src/cdrom_id/cdrom_id.c +cdrom_id_LDADD = libudev-private.la +pkglibexec_PROGRAMS += cdrom_id +dist_udevrules_DATA += src/cdrom_id/60-cdrom_id.rules + +# ------------------------------------------------------------------------------ +collect_SOURCES = src/collect/collect.c +collect_LDADD = libudev-private.la +pkglibexec_PROGRAMS += collect + +# ------------------------------------------------------------------------------ +scsi_id_SOURCES =\ + src/scsi_id/scsi_id.c \ + src/scsi_id/scsi_serial.c \ + src/scsi_id/scsi.h \ + src/scsi_id/scsi_id.h +scsi_id_LDADD = libudev-private.la +pkglibexec_PROGRAMS += scsi_id +dist_man_MANS += src/scsi_id/scsi_id.8 +EXTRA_DIST += src/scsi_id/README + +# ------------------------------------------------------------------------------ +v4l_id_SOURCES = src/v4l_id/v4l_id.c +v4l_id_LDADD = libudev-private.la +pkglibexec_PROGRAMS += v4l_id +dist_udevrules_DATA += src/v4l_id/60-persistent-v4l.rules + +# ------------------------------------------------------------------------------ +accelerometer_SOURCES = src/accelerometer/accelerometer.c +accelerometer_LDADD = libudev-private.la -lm +pkglibexec_PROGRAMS += accelerometer +dist_udevrules_DATA += src/accelerometer/61-accelerometer.rules + +# ------------------------------------------------------------------------------ +if ENABLE_GUDEV +SUBDIRS += src/gudev/docs + +libgudev_includedir=$(includedir)/gudev-1.0/gudev +libgudev_include_HEADERS = \ + src/gudev/gudev.h \ + src/gudev/gudevenums.h \ + src/gudev/gudevenumtypes.h \ + src/gudev/gudevtypes.h \ + src/gudev/gudevclient.h \ + src/gudev/gudevdevice.h \ + src/gudev/gudevenumerator.h + +lib_LTLIBRARIES += libgudev-1.0.la + +pkgconfig_DATA += src/gudev/gudev-1.0.pc +EXTRA_DIST += src/gudev/gudev-1.0.pc.in +CLEANFILES += src/gudev/gudev-1.0.pc + +libgudev_1_0_la_SOURCES = \ + src/gudev/gudevenums.h \ + src/gudev/gudevenumtypes.h \ + src/gudev/gudevenumtypes.h\ + src/gudev/gudevtypes.h \ + src/gudev/gudevclient.h \ + src/gudev/gudevclient.c \ + src/gudev/gudevdevice.h \ + src/gudev/gudevdevice.c \ + src/gudev/gudevenumerator.h \ + src/gudev/gudevenumerator.c \ + src/gudev/gudevprivate.h + +nodist_libgudev_1_0_la_SOURCES = \ + src/gudev/gudevmarshal.h \ + src/gudev/gudevmarshal.c \ + src/gudev/gudevenumtypes.h \ + src/gudev/gudevenumtypes.c +BUILT_SOURCES += $(nodist_libgudev_1_0_la_SOURCES) + +libgudev_1_0_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_builddir)/src\ + -I$(top_srcdir)/src\ + -I$(top_builddir)/src/gudev \ + -I$(top_srcdir)/src/gudev \ + -D_POSIX_PTHREAD_SEMANTICS -D_REENTRANT \ + -D_GUDEV_COMPILATION \ + -DG_LOG_DOMAIN=\"GUdev\" + +libgudev_1_0_la_CFLAGS = \ + -fvisibility=default \ + $(GLIB_CFLAGS) + +libgudev_1_0_la_LIBADD = libudev.la $(GLIB_LIBS) + +libgudev_1_0_la_LDFLAGS = \ + -version-info $(LIBGUDEV_CURRENT):$(LIBGUDEV_REVISION):$(LIBGUDEV_AGE) \ + -export-dynamic -no-undefined \ + -export-symbols-regex '^g_udev_.*' + +EXTRA_DIST += \ + src/gudev/COPYING \ + src/gudev/gudevmarshal.list \ + src/gudev/gudevenumtypes.h.template \ + src/gudev/gudevenumtypes.c.template \ + src/gudev/gjs-example.js \ + src/gudev/seed-example-enum.js \ + src/gudev/seed-example.js + +src/gudev/gudevmarshal.h: src/gudev/gudevmarshal.list + $(AM_V_GEN)glib-genmarshal $< --prefix=g_udev_marshal --header > $@ + +src/gudev/gudevmarshal.c: src/gudev/gudevmarshal.list + $(AM_V_GEN)echo "#include \"gudevmarshal.h\"" > $@ && \ + glib-genmarshal $< --prefix=g_udev_marshal --body >> $@ + +src/gudev/gudevenumtypes.h: src/gudev/gudevenumtypes.h.template src/gudev/gudevenums.h + $(AM_V_GEN)glib-mkenums --template $^ > \ + $@.tmp && mv $@.tmp $@ + +src/gudev/gudevenumtypes.c: src/gudev/gudevenumtypes.c.template src/gudev/gudevenums.h + $(AM_V_GEN)glib-mkenums --template $^ > \ + $@.tmp && mv $@.tmp $@ + +if ENABLE_INTROSPECTION +src/gudev/GUdev-1.0.gir: libgudev-1.0.la $(G_IR_SCANNER) + $(AM_V_GEN)$(G_IR_SCANNER) -v \ + --warn-all \ + --namespace GUdev \ + --nsversion=1.0 \ + --include=GObject-2.0 \ + --library=gudev-1.0 \ + --library-path=$(top_builddir)/src \ + --library-path=$(top_builddir)/src/gudev \ + --output $@ \ + --pkg=glib-2.0 \ + --pkg=gobject-2.0 \ + --pkg-export=gudev-1.0 \ + --c-include=gudev/gudev.h \ + -I$(top_srcdir)/src/\ + -I$(top_builddir)/src/\ + -D_GUDEV_COMPILATION \ + -D_GUDEV_WORK_AROUND_DEV_T_BUG \ + $(top_srcdir)/src/gudev/gudev.h \ + $(top_srcdir)/src/gudev/gudevtypes.h \ + $(top_srcdir)/src/gudev/gudevenums.h \ + $(or $(wildcard $(top_builddir)/src/gudev/gudevenumtypes.h),$(top_srcdir)/src/gudev/gudevenumtypes.h) \ + $(top_srcdir)/src/gudev/gudevclient.h \ + $(top_srcdir)/src/gudev/gudevdevice.h \ + $(top_srcdir)/src/gudev/gudevenumerator.h \ + $(top_srcdir)/src/gudev/gudevclient.c \ + $(top_srcdir)/src/gudev/gudevdevice.c \ + $(top_srcdir)/src/gudev/gudevenumerator.c + +src/gudev/GUdev-1.0.typelib: src/gudev/GUdev-1.0.gir $(G_IR_COMPILER) + $(AM_V_GEN)g-ir-compiler $< -o $@ + +girdir = $(GIRDIR) +gir_DATA = src/gudev/GUdev-1.0.gir + +typelibsdir = $(GIRTYPELIBDIR) +typelibs_DATA = src/gudev/GUdev-1.0.typelib + +CLEANFILES += $(gir_DATA) $(typelibs_DATA) +endif # ENABLE_INTROSPECTION + +# move lib from $(libdir) to $(rootlib_execdir) and update devel link, if needed +libgudev-install-move-hook: + if test "$(libdir)" != "$(rootlib_execdir)"; then \ + mkdir -p $(DESTDIR)$(rootlib_execdir) && \ + so_img_name=$$(readlink $(DESTDIR)$(libdir)/libgudev-1.0.so) && \ + so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \ + ln -sf $$so_img_rel_target_prefix$(rootlib_execdir)/$$so_img_name $(DESTDIR)$(libdir)/libgudev-1.0.so && \ + mv $(DESTDIR)$(libdir)/libgudev-1.0.so.* $(DESTDIR)$(rootlib_execdir); \ + fi + +libgudev-uninstall-move-hook: + rm -f $(DESTDIR)$(rootlib_execdir)/libgudev-1.0.so* + +INSTALL_EXEC_HOOKS += libgudev-install-move-hook +UNINSTALL_EXEC_HOOKS += libgudev-uninstall-move-hook +endif + +# ------------------------------------------------------------------------------ +if ENABLE_KEYMAP +keymap_SOURCES = src/keymap/keymap.c +keymap_CPPFLAGS = $(AM_CPPFLAGS) -I src/keymap +nodist_keymap_SOURCES = \ + src/keymap/keys-from-name.h \ + src/keymap/keys-to-name.h +BUILT_SOURCES += $(nodist_keymap_SOURCES) + +pkglibexec_PROGRAMS += keymap +dist_doc_DATA = src/keymap/README.keymap.txt + +dist_udevrules_DATA += \ + src/keymap/95-keymap.rules \ + src/keymap/95-keyboard-force-release.rules + +dist_udevhome_SCRIPTS += src/keymap/findkeyboards +udevhome_SCRIPTS += src/keymap/keyboard-force-release.sh + +EXTRA_DIST += \ + src/keymap/check-keymaps.sh \ + src/keymap/keyboard-force-release.sh.in + +CLEANFILES += \ + src/keymap/keys.txt \ + src/keymap/keys-from-name.gperf \ + src/keymap/keyboard-force-release.sh + +udevkeymapdir = $(libexecdir)/udev/keymaps +dist_udevkeymap_DATA = \ + src/keymap/keymaps/acer \ + src/keymap/keymaps/acer-aspire_5720 \ + src/keymap/keymaps/acer-aspire_8930 \ + src/keymap/keymaps/acer-aspire_5920g \ + src/keymap/keymaps/acer-aspire_6920 \ + src/keymap/keymaps/acer-travelmate_c300 \ + src/keymap/keymaps/asus \ + src/keymap/keymaps/compaq-e_evo \ + src/keymap/keymaps/dell \ + src/keymap/keymaps/dell-latitude-xt2 \ + src/keymap/keymaps/everex-xt5000 \ + src/keymap/keymaps/fujitsu-amilo_li_2732 \ + src/keymap/keymaps/fujitsu-amilo_pa_2548 \ + src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 \ + src/keymap/keymaps/fujitsu-amilo_pro_v3205 \ + src/keymap/keymaps/fujitsu-amilo_si_1520 \ + src/keymap/keymaps/fujitsu-esprimo_mobile_v5 \ + src/keymap/keymaps/fujitsu-esprimo_mobile_v6 \ + src/keymap/keymaps/genius-slimstar-320 \ + src/keymap/keymaps/hewlett-packard \ + src/keymap/keymaps/hewlett-packard-2510p_2530p \ + src/keymap/keymaps/hewlett-packard-compaq_elitebook \ + src/keymap/keymaps/hewlett-packard-pavilion \ + src/keymap/keymaps/hewlett-packard-presario-2100 \ + src/keymap/keymaps/hewlett-packard-tablet \ + src/keymap/keymaps/hewlett-packard-tx2 \ + src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint \ + src/keymap/keymaps/inventec-symphony_6.0_7.0 \ + src/keymap/keymaps/lenovo-3000 \ + src/keymap/keymaps/lenovo-ideapad \ + src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint \ + src/keymap/keymaps/lenovo-thinkpad_x6_tablet \ + src/keymap/keymaps/lenovo-thinkpad_x200_tablet \ + src/keymap/keymaps/lg-x110 \ + src/keymap/keymaps/logitech-wave \ + src/keymap/keymaps/logitech-wave-cordless \ + src/keymap/keymaps/logitech-wave-pro-cordless \ + src/keymap/keymaps/maxdata-pro_7000 \ + src/keymap/keymaps/medion-fid2060 \ + src/keymap/keymaps/medionnb-a555 \ + src/keymap/keymaps/micro-star \ + src/keymap/keymaps/module-asus-w3j \ + src/keymap/keymaps/module-ibm \ + src/keymap/keymaps/module-lenovo \ + src/keymap/keymaps/module-sony \ + src/keymap/keymaps/module-sony-old \ + src/keymap/keymaps/module-sony-vgn \ + src/keymap/keymaps/olpc-xo \ + src/keymap/keymaps/onkyo \ + src/keymap/keymaps/oqo-model2 \ + src/keymap/keymaps/samsung-other \ + src/keymap/keymaps/samsung-90x3a \ + src/keymap/keymaps/samsung-sq1us \ + src/keymap/keymaps/samsung-sx20s \ + src/keymap/keymaps/toshiba-satellite_a100 \ + src/keymap/keymaps/toshiba-satellite_a110 \ + src/keymap/keymaps/toshiba-satellite_m30x \ + src/keymap/keymaps/zepto-znote + +udevkeymapforcereldir = $(libexecdir)/udev/keymaps/force-release +dist_udevkeymapforcerel_DATA = \ + src/keymap/force-release-maps/dell-touchpad \ + src/keymap/force-release-maps/hp-other \ + src/keymap/force-release-maps/samsung-other \ + src/keymap/force-release-maps/samsung-90x3a \ + src/keymap/force-release-maps/common-volume-keys + +src/keymap/keys.txt: $(INCLUDE_PREFIX)/linux/input.h + $(AM_V_at)mkdir -p src/keymap + $(AM_V_GEN)$(AWK) '/^#define.*KEY_[^ ]+[ \t]+[0-9]/ { if ($$2 != "KEY_MAX") { print $$2 } }' < $< | sed 's/^KEY_COFFEE$$/KEY_SCREENLOCK/' > $@ + +src/keymap/keys-from-name.gperf: src/keymap/keys.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct key { const char* name; unsigned short id; };"; print "%null-strings"; print "%%";} { print $$1 ", " $$1 }' < $< > $@ + +src/keymap/keys-from-name.h: src/keymap/keys-from-name.gperf Makefile + $(AM_V_GEN)$(GPERF) -L ANSI-C -t --ignore-case -N lookup_key -H hash_key_name -p -C < $< > $@ + +src/keymap/keys-to-name.h: src/keymap/keys.txt Makefile + $(AM_V_GEN)$(AWK) 'BEGIN{ print "const char* const key_names[KEY_CNT] = { "} { print "[" $$1 "] = \"" $$1 "\"," } END{print "};"}' < $< > $@ + +keymaps-distcheck-hook: src/keymap/keys.txt + $(top_srcdir)/src/keymap/check-keymaps.sh $(top_srcdir) $^ +DISTCHECK_HOOKS += keymaps-distcheck-hook +endif + +if ENABLE_MTD_PROBE +# ------------------------------------------------------------------------------ +mtd_probe_SOURCES = \ + src/mtd_probe/mtd_probe.c \ + src/mtd_probe/mtd_probe.h \ + src/mtd_probe/probe_smartmedia.c +mtd_probe_CPPFLAGS = $(AM_CPPFLAGS) +dist_udevrules_DATA += src/mtd_probe/75-probe_mtd.rules +pkglibexec_PROGRAMS += mtd_probe +endif + +# ------------------------------------------------------------------------------ +if ENABLE_RULE_GENERATOR +dist_udevhome_SCRIPTS += \ + src/rule_generator/write_cd_rules \ + src/rule_generator/write_net_rules + +dist_udevhome_DATA += \ + src/rule_generator/rule_generator.functions + +dist_udevrules_DATA += \ + src/rule_generator/75-cd-aliases-generator.rules \ + src/rule_generator/75-persistent-net-generator.rules +endif + +# ------------------------------------------------------------------------------ +if ENABLE_FLOPPY +create_floppy_devices_SOURCES = src/floppy/create_floppy_devices.c +create_floppy_devices_LDADD = libudev-private.la +pkglibexec_PROGRAMS += create_floppy_devices +dist_udevrules_DATA += src/floppy/60-floppy.rules +endif + +# ------------------------------------------------------------------------------ +clean-local: + rm -rf udev-test-install + +distclean-local: + rm -rf autom4te.cache + +EXTRA_DIST += \ + $(TESTS) \ + test/rule-syntax-check.py + +CLEANFILES += \ + $(BUILT_SOURCES) + +install-exec-hook: $(INSTALL_EXEC_HOOKS) + +install-data-hook: $(INSTALL_DATA_HOOKS) + +uninstall-hook: $(UNINSTALL_EXEC_HOOKS) + +distcheck-hook: $(DISTCHECK_HOOKS) + +distclean-local: $(DISTCLEAN_LOCAL_HOOKS) + +# ------------------------------------------------------------------------------ +PREVIOUS_VERSION = `expr $(VERSION) - 1` +changelog: + @ head -1 ChangeLog | grep -q "to v$(PREVIOUS_VERSION)" + @ mv ChangeLog ChangeLog.tmp + @ echo "Summary of changes from v$(PREVIOUS_VERSION) to v$(VERSION)" >> ChangeLog + @ echo "============================================" >> ChangeLog + @ echo >> ChangeLog + @ git log --pretty=short $(PREVIOUS_VERSION)..HEAD | git shortlog >> ChangeLog + @ echo >> ChangeLog + @ cat ChangeLog + @ cat ChangeLog.tmp >> ChangeLog + @ rm ChangeLog.tmp + +test-install: + rm -rf $(PWD)/udev-test-install/ + make DESTDIR=$(PWD)/udev-test-install install + tree $(PWD)/udev-test-install/ + +git-release: + head -1 ChangeLog | grep -q "to v$(VERSION)" + head -1 NEWS | grep -q "udev $(VERSION)" + git commit -a -m "release $(VERSION)" + git tag -m "udev $(VERSION)" -s $(VERSION) + git gc --prune=0 + +git-sync: + git push + git push --tags + +tar-sync: + rm -f udev-$(VERSION).tar.sign + xz -d -c udev-$(VERSION).tar.xz | gpg --armor --detach-sign --output udev-$(VERSION).tar.sign + kup put udev-$(VERSION).tar.xz udev-$(VERSION).tar.sign /pub/linux/utils/kernel/hotplug/ + +doc-sync: + for i in src/*.html; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done + for i in src/*.html; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/udev/; done + for i in src/docs/html/*.{html,css,png}; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done + for i in src/docs/html/*.{html,css,png}; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/libudev/; done + for i in src/gudev/docs/html/*.{html,css,png}; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done + for i in src/gudev/docs/html/*.{html,css,png}; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/gudev/; done diff --git a/src/udev/NEWS b/src/udev/NEWS new file mode 100644 index 000000000..f4f6f4e32 --- /dev/null +++ b/src/udev/NEWS @@ -0,0 +1,1735 @@ +udev 182 +======== +Rules files in /etc/udev/rules.s/ with the same name as rules files in +/run/udev/rules.d/ now always have precedence. The stack of files is now: +/usr/lib (package), /run (runtime, auto-generated), /etc (admin), while +the later ones override the earlier ones. In other words: the admin has +always the last say. + +USB auto-suspend is now enabled by default for some built-in USB HID +devices. + +/dev/disk/by-path/ links are no longer created for ATA devices behind +an 'ATA transport class', the logic to extract predictable numbers does +not exist in the kernel at this moment. + +/dev/disk/by-id/scsi-* compatibility links are no longer created for +ATA devices, they have their own ata-* prefix. + +The s390 rule to set mode == 0666 for /dev/z90crypt is is removed from +the udev tree and will be part of s390utils (or alternatively could be +done by the kernel driver itself). + +The udev-acl tool is no longer provided, it will be part of a future +ConsoleKit release. On systemd systems, advanced ConsoleKit and udev-acl +functionality are provided by systemd. + +udev 181 +======== +Require kmod version 5. + +Provide /dev/cdrom symlink for /dev/sr0. + +udev 180 +======== +Fix for ID_PART_ENTRY_* property names, added by the blkid built-in. The +fix is needed for udisk2 to operate properly. + +Fix for skipped rule execution when the kernel has removed the device +node in /dev again, before the event was even started. The fix is needed +to run device-mapper/LVM events properly. + +Fix for the man page installation, which was skipped when xsltproc was not +installed. + +udev 179 +======== +Bugfix for $name resolution, which broke at least some keymap handling. + +udev 178 +======== +Bugfix for the firmware loading behavior with kernel modules which +try to load firmware in the module_init() path. The blocked event +runs into a timout now, which should allow the firmware to be loaded. + +Bugfix for a wrong DEVNAME= export, which breaks at least the udev-acl +tool. + +Bugfix for missing ID_ properties for GPT partitions. + +The RUN+="socket:.." option is deprecated and should not be used. A warning +during rules parsing is printed now. Services which listen to udev events, +need to subscribe to the netlink messages with libudev and not let udev block +in the rules execution until the message is delivered. + +udev 177 +======== +Bugfix for rule_generator instalation. + +udev 176 +======== +The 'devtmpfs' filesystem is required now, udev will not create or delete +device nodes anymore, it only adjusts permissions and ownership of device +nodes and maintains additional symlinks. + +A writable /run directory (ususally tmpfs) is required now for a fully +functional udev, there is no longer a fallback to /dev/.udev. + +The default 'configure' install locations have changed. Packages for systems +with the historic / vs. /usr split need to be adapted, otherwise udev will +be installed in /usr and not work properly. Example configuration options +to install things the traditional way are in INSTALL. + +The default install location of the 'udevadm' tool moved from 'sbin' +to /usr/bin. Some tools expect udevadm in 'sbin', a symlink to udevadm +needs to be manually created if needed, or --bindir=/sbin be specified. + +The expected value of '--libexecdir=' has changed and must no longer contain +the 'udev' directory. + +Kernel modules are now loaded directly by linking udev to 'libkmod'. The +'modprobe' tool is no longer executed by udev. + +The 'blkid' tool is no longer executed from udev rules. Udev links +directly to libblkid now. + +Firmware is loaded natively by udev now, the external 'firmware' binary +is no longer used. + +All built-in tools can be listed and tested with 'udevadm test-builtin'. + +The 'udevadm control --reload-rules' option has been renamed to '--reload'. +It now also reloads the kernel module configuration. + +The systemd socket files use PassCredentials=yes, which is available in +systemd version 38. + +The udev build system only creates a .xz tarball now. + +All tabs in the source code used for indentation are replaced by spaces now. :) + +udev 175 +======== +Bugfixes. + +udev 174 +======== +Bugfixes. + +The udev daemon moved to /lib/udev/udevd. Non-systemd init systems +and non-dracut initramfs image generators need to change the init +scripts. Alternatively the udev build needs to move udevd back to +/sbin or create a symlink in /sbin, which is not done by default. + +The path_id, usb_id, input_id tools are built-in commands now and +the stand-alone tools do not exist anymore. Static lists of file in +initramfs generators need to be updated. For testing, the commands +can still be executed standalone with 'udevadm test-builtin '. + +The fusectl filesystem is no longer mounted directly from udev. +Systemd systems will take care of mounting fusectl and configfs +now. Non-systemd systems need to ship their own rule if they +need these filesystems auto-mounted. + +The long deprecated keys: SYSFS=, ID=, BUS= have been removed. + +The support for 'udevadm trigger --type=failed, and the +RUN{fail_event_on_error} attribute was removed. + +The udev control socket is now created in /run/udev/control +and no longer as an abstract namespace one. + +The rules to create persistent network interface and cdrom link +rules automatically in /etc/udev/rules.d/ have been disabled by +default. Explicit configuration will be required for these use +cases, udev will no longer try to write any persistent system +configuration from a device hotplug path. + +udev 173 +======== +Bugfixes. + +The udev-acl extra is no longer enabled by default now. To enable it, +--enable-udev_acl needs to be given at ./configure time. On systemd +systems, the udev-acl rules prevent it from running as the functionality +has moved to systemd. + +udev 172 +======== +Bugfixes. + +Udev now enables kernel media-presence polling if available. Part +of udisks optical drive tray-handling moved to cdrom_id: The tray +is locked as soon as a media is detected to enable the receiving +of media-eject-request events. Media-eject-request events will +eject the media. + +Libudev enumerate is now able to enumerate a subtree of a given +device. + +The mobile-action-modeswitch modeswitch tool was deleted. The +functionality is provided by usb_modeswitch now. + +udev 171 +======== +Bugfixes. + +The systemd service files require systemd version 28. The systemd +socket activation make it possible now to start 'udevd' and 'udevadm +trigger' in parallel. + +udev 170 +======== +Fix bug in control message handling, which can lead to a failing +udevadm control --exit. Thanks to Jürg Billeter for help tracking +it down. + +udev 169 +======== +Bugfixes. + +We require at least Linux kernel 2.6.32 now. Some platforms might +require a later kernel that supports accept4() and similar, or +need to backport the trivial syscall wiring to the older kernels. + +The hid2hci tool moved to the bluez package and was removed. + +Many of the extras can be --enable/--disabled at ./configure +time. The --disable-extras option was removed. Some extras have +been disabled by default. The current options and their defaults +can be checked with './configure --help'. + +udev 168 +======== +Bugfixes. + +Udev logs a warning now if /run is not writable at udevd +startup. It will still fall back to /dev/.udev, but this is +now considered a bug. + +The running udev daemon can now cleanly shut down with: + udevadm control --exit + +Udev in initramfs should clean the state of the udev database +with: udevadm info --cleanup-db which will remove all state left +behind from events/rules in initramfs. If initramfs uses +--cleanup-db and device-mapper/LVM, the rules in initramfs need +to add OPTIONS+="db_persist" for all dm devices. This will +prevent removal of the udev database for these devices. + +Spawned programs by PROGRAM/IMPORT/RUN now have a hard timeout of +120 seconds per process. If that timeout is reached the spawned +process will be killed. The event timeout can be overwritten with +udev rules. + +If systemd is used, udev gets now activated by netlink data. +Systemd will bind the netlink socket which will buffer all data. +If needed, such setup allows a seemless update of the udev daemon, +where no event can be lost during a udevd update/restart. +Packages need to make sure to: systemctl stop udev.socket udev.service +or 'mask' udev.service during the upgrade to prevent any unwanted +auto-spawning of udevd. +This version of udev conflicts with systemd version below 25. The +unchanged service files will not wirk correctly. + +udev 167 +======== +Bugfixes. + +The udev runtime data moved from /dev/.udev/ to /run/udev/. The +/run mountpoint is supposed to be a tmpfs mounted during early boot, +available and writable to for all tools at any time during bootup, +it replaces /var/run/, which should become a symlink some day. + +If /run does not exist, or is not writable, udev will fall back using +/dev/.udev/. + +On systemd systems with initramfs and LVM used, packagers must +make sure, that the systemd and initramfs versions match. The initramfs +needs to create the /run mountpoint for udev to store the data, and +mount this tmpfs to /run in the rootfs, so the that the udev database +is preserved for the udev version started in the rootfs. + +The command 'udevadm info --convert-db' is gone. The udev daemon +itself, at startup, converts any old database version if necessary. + +The systemd services files have been reorganized. The udev control +socket is bound by systemd and passed to the started udev daemon. +The udev-settle.service is no longer active by default. Services which +can not handle hotplug setups properly need to actively pull it in, to +act like a barrier. Alternatively the settle service can be unconditionally +'systemctl'enabled, and act like a barrier for basic.target. + +The fstab_import callout is no longer built or installed. Udev +should not be used to mount, does not watch changes to fstab, and +should not mirror fstab values in the udev database. + +udev 166 +======== +Bugfixes. + +New and updated keymaps. + +udev 165 +======== +Bugfixes. + +The udev database has changed, After installation of a new udev +version, 'udevadm info --convert-db' should be called, to let the new +udev/libudev version read the already stored data. + +udevadm now supports quoting of property values, and prefixing of +key names: + $ udevadm info --export --export-prefix=MY_ --query=property -n sda + MY_MAJOR='259' + MY_MINOR='0' + MY_DEVNAME='/dev/sda' + MY_DEVTYPE='disk' + ... + +libudev now supports: + udev_device_get_is_initialized() + udev_enumerate_add_match_is_initialized() +to be able to skip devices the kernel has created , but udev has +not already handled. + +libudev now supports: + udev_device_get_usec_since_initialized() +to retrieve the "age" of a udev device record. + +GUdev supports a more generic GUdevEnumerator class, udev TAG +handling, device initialization and timestamp now. + +The counterpart of /sys/dev/{char,block}/$major:$minor, +/dev/{char,block}/$major:$minor symlinks are now unconditionally +created, even when no rule files exist. + +New and updated keymaps. + +udev 164 +======== +Bugfixes. + +GUdev moved from /usr to /. + +udev 163 +======== +Bugfixes. + +udev 162 +======== +Bugfixes. + +Persistent network naming rules are disabled inside of Qemu/KVM now. + +New and updated keymaps. + +Udev gets unconditionally enabled on systemd installations now. There +is no longer the need to to run 'systemctl enable udev.service'. + +udev 161 +======== +Bugfixes. + +udev 160 +======== +Bugfixes. + +udev 159 +======== +Bugfixes. + +New and fixed keymaps. + +Install systemd service files if applicable. + +udev 158 +======== +Bugfixes. + +All distribution specific rules are removed from the udev source tree, +most of them are no longer needed. The Gentoo rules which allow to support +older kernel versions, which are not covered by the default rules anymore +has moved to rules/misc/30-kernel-compat.rules. + +udev 157 +======== +Bugfixes. + +The option --debug-trace and the environemnt variable UDEVD_MAX_CHILDS= +was removed from udevd. + +Udevd now checks the kernel commandline for the following variables: + udev.log-priority= + udev.children-max= + udev.exec-delay= +to help debuging coldplug setups where the loading of a kernel +module crashes the system. + +The subdirectory in the source tree rules/packages has been renamed to +rules/arch, anc contains only architecture specific rules now. + +udev 156 +======== +Bugfixes. + +udev 155 +======== +Bugfixes. + +Now the udev daemon itself, does on startup: + - copy the content of /lib/udev/devices to /dev + - create the standard symlinks like /dev/std{in,out,err}, + /dev/core, /dev/fd, ... + - use static node information provided by kernel modules + and creates these nodes to allow module on-demand loading + - possibly apply permissions to all ststic nodes from udev + rules which are annotated to match a static node + +The default mode for a device node is 0600 now to match the kernel +created devtmpfs defaults. If GROUP= is specified and no MODE= is +given the default will be 0660. + +udev 154 +======== +Bugfixes. + +Udev now gradually starts to pass control over the primary device nodes +and their names to the kernel, and will in the end only manage the +permissions of the node, and possibly create additional symlinks. +As a first step NAME="" will be ignored, and NAME= setings with names +other than the kernel provided name will result in a logged warning. +Kernels that don't provide device names, or devtmpfs is not used, will +still work as they did before, but it is strongly recommended to use +only the same names for the primary device node as the recent kernel +provides for all devices. + +udev 153 +======== +Fix broken firmware loader search path. + +udev 152 +======== +Bugfixes. + +"udevadm trigger" defaults to "change" events now instead of "add" +events. The "udev boot script" might need to add "--action=add" to +the trigger command if not already there, in case the initial coldplug +events are expected as "add" events. + +The option "all_partitons" was removed from udev. This should not be +needed for usual hardware. Udev can not safely make assumptions +about non-existing partition major/minor numbers, and therefore no +longer provide this unreliable and unsafe option. + +The option "ignore_remove" was removed from udev. With devtmpfs +udev passed control over device nodes to the kernel. This option +should not be needed, or can not work as advertised. Neither +udev nor the kernel will remove device nodes which are copied from +the /lib/udev/devices/ directory. + +All "add|change" matches are replaced by "!remove" in the rules and +in the udev logic. All types of events will update possible symlinks +and permissions, only "remove" is handled special now. + +The modem modeswitch extra was removed and the external usb_modeswitch +program should be used instead. + +New and fixed keymaps. + +udev 151 +======== +Bugfixes. + +udev 150 +======== +Bugfixes. + +Kernels with SYSFS_DEPRECATED=y are not supported since a while. Many users +depend on the current sysfs layout and the information not available in the +deprecated layout. All remaining support for the deprecated sysfs layout is +removed now. + +udev 149 +======== +Fix for a possible endless loop in the new input_id program. + +udev 148 +======== +Bugfixes. + +The option "ignore_device" does no longer exist. There is no way to +ignore an event, as libudev events can not be suppressed by rules. +It only prevented RUN keys from being executed, which results in an +inconsistent behavior in current setups. + +BUS=, SYSFS{}=, ID= are long deprecated and should be SUBSYSTEM(S)=, +ATTR(S){}=, KERNEL(S)=. It will cause a warning once for every rule +file from now on. + +The support for the deprecated IDE devices has been removed from the +default set of rules. Distros who still care about non-libata drivers +need to add the rules to the compat rules file. + +The ID_CLASS property on input devices has been replaced by the more accurate +set of flags ID_INPUT_{KEYBOARD,KEY,MOUSE,TOUCHPAD,TABLET,JOYSTICK}. These are +determined by the new "input_id" prober now. Some devices, such as touchpads, +can have several classes. So if you previously had custom udev rules which e. g. +checked for ENV{ID_CLASS}=="kbd", you need to replace this with +ENV{ID_INPUT_KEYBOARD}=="?*". + +udev 147 +======== +Bugfixes. + +To support DEVPATH strings larger than the maximum file name length, the +private udev database format has changed. If some software still reads the +private files in /dev/.udev/, which it shouldn't, now it's time to fix it. +Please do not port anything to the new format again, everything in /dev/.udev +is and always was private to udev, and may and will change any time without +prior notice. + +Multiple devices claiming the same names in /dev are limited to symlinks +only now. Mixing identical symlink names and node names is not supported. +This reduces the amount of data in the database significantly. + +NAME="%k" causes a warning now. It's is and always was completely superfluous. +It will break kernel supplied DEVNAMEs and therefore it needs to be removed +from all rules. + +Most NAME= instructions got removed. Kernel 2.6.31 supplies the needed names +if they are not the default. To support older kernels, the NAME= rules need to +be added to the compat rules file. + +Symlinks to udevadm with the old command names are no longer resolved to +the udevadm commands. + +The udev-acl tool got adopted to changes in ConsoleKit. Version 0.4.1 is +required now. + +The option "last_rule" does no longer exist. Its use breaks too many +things which expect to be run from independent later rules, and is an idication +that something needs to be fixed properly instead. + +The gudev API is no longer marked as experimental, +G_UDEV_API_IS_SUBJECT_TO_CHANGE is no longer needed. The gudev introspection +is enabled by default now. Various projects already depend on introspection +information to bind dynamic languages to the gudev interfaces. + +udev 146 +======== +Bugfixes. + +The udevadm trigger "--retry-failed" option, which is replaced since quite +a while by "--type=failed" is removed. + +The failed tracking was not working at all for a few releases. The RUN +option "ignore_error" is replaced by a "fail_event_on_error" option, and the +default is not to track any failing RUN executions. + +New keymaps, new modem, hid2hci updated. + +udev 145 +======== +Fix possible crash in udevd when worker processes are busy, rules are +changed at the same time, and workers get killed to reload the rules. + +udev 144 +======== +Bugfixes. + +Properties set with ENV{.FOO}="bar" are marked private by starting the +name with a '.'. They will not be stored in the database, and not be +exported with the event. + +Firmware files are looked up in: + /lib/firmware/updates/$(uname -r) + /lib/firmware/updates + /lib/firmware/$(uname -r) + /lib/firmware" +now. + +ATA devices switched the property from ID_BUS=scsi to ID_BUS=ata. +ata_id, instead of scsi_id, is the default tool now for ATA devices. + +udev 143 +======== +Bugfixes. + +The configure options have changed because another library needs to be +installed in a different location. Instead of exec_prefix and udev_prefix, +libdir, rootlibdir and libexecdir are used. The Details are explained in +the README file. + +Event processes now get re-used after they handled an event. This reduces +the number of forks and the pressure on the CPU significantly, because +cloned event processes no longer cause page faults in the main daemon. +After the events have settled, a few worker processes stay around for +future events, all others get cleaned up. + +To be able to use signalfd(), udev depends on kernel version 2.6.25 now. +Also inotify support is mandatory now to run udev. + +The format of the queue exported by the udev damon has changed. There is +no longer a /dev/.udev/queue/ directory. The current event queue can be +accessed with udevadm settle and libudedv. + +Libudev does not have the unstable API header anymore. From now on, +incompatible changes will be handled by bumping the library major version. + +To build udev from the git tree gtk-doc is needed now. The tarballs will +build without it and contain the pre-built documentation. An online copy +is available here: + http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/ + +The tools from the udev-extras repository have been merged into the main +udev repository. Some of the extras have larger external dependencies, and +they can be disabled with the configure switch --disable-extras. + +udev 142 +======== +Bugfixes. + +The program vol_id and the library libvolume_id are removed from the +repository. Libvolume_id is merged with libblkid from the util-linux-ng +package. Persistent disk links for label and uuid depend on the +util-linux-ng version (2.15) of blkid now. Older versions of blkid +can not be used with udev. + +Libudev allows to subscribe to udev events. To prevent unwanted messages +to be delivered, and waking up the subscribing process, a filter can be +installed, to drop messages inside a kernel socket filter. The filters +match on the : properties of the device. + This is part of the ongoing effort to replace HAL, and switch current +users over to directly use libudev. + Libudev is still marked as experimental, and its interface might +eventually change if needed, but no major changes of the currently exported +interface are expected anymore, and a first stable release should happen +soon. + +A too old kernel (2.6.21) or a kernel with CONFIG_SYSFS_DEPRECATED +is not supported since while and udevd will log an error message at +startup. It should still be able to boot-up, but advanced rules and system +services which depend on the information not available in the old sysfs +format will fail to work correctly. + +DVB device naming is supplied by the kernel now. In case older kernels +need to be supported, the old shell script should be added to a compat +rules file. + +udev 141 +======== +Bugfixes. + +The processed udev events get send back to the netlink socket. Libudev +provides access to these events. This is work-in-progress, to replace +the DeviceKit daemon functionality directly with libudev. There are +upcoming kernel changes to allow non-root users to subcribe to these +events. + +udev 140 +======== +Bugfixes. + +"udevadm settle" now optionally accepts a range of events to wait for, +instead of waiting for "all" events. + +udev 139 +======== +Bugfixes. + +The installed watch for block device metadata changes is now removed +during event hadling, because some (broken) tools may be called from udev +rules and (wrongly) open the device with write access. After the finished +event handling the watch is restored. + +udev 138 +======== +Bugfixes. + +Device nodes can be watched for changes with inotify with OPTIONS="watch". +If closed after being opened for writing, a "change" uevent will occur. +/dev/disk/by-{label,uuid}/* symlinks will be automatically updated. + +udev 137 +======== +Bugfixes. + +The udevadm test command has no longer a --force option, nodes and symlinks +are always updated with a test run now. + +The udevd daemon can be started with --resolve-names=never to avoid all user +and group lookups (e.g. in cut-down systems) or --resolve-names=late to +lookup user and groups every time events are handled. + +udev 136 +======== +Bugfixes. + +We are currently merging the Ubuntu rules in the udev default rules, +and get one step closer to provide a common Linux /dev setup, regarding +device names, symlinks, and default device permissions. On udev startup, +we now expect the following groups to be resolvable to their ids with +glibc's getgrnam(): + disk, cdrom, floppy, tape, audio, video, lp, tty, dialout, kmem. +LDAP setups need to make sure, that these groups are always resolvable at +bootup, with only the rootfs mounted, and without network access available. + +Some systems may need to add some new, currently not used groups, or need +to add some users to new groups, but the cost of this change is minimal, +compared to the pain the current, rather random, differences between the +various distributions cause for upstream projects and third-party vendors. + +In general, "normal" users who log into a machine should never be a member +of any such group, but the device-access should be managed by dynamic ACLs, +which get added and removed for the specific users on login/logout and +session activity/inactivity. These groups are only provided for custom setups, +and mainly system services, to allow proper privilege separation. +A video-streaming daemon uid would be a member of "audio" and "video", to get +access to the sound and video devices, but no "normal" user should ever belong +to the "audio" group, because he could listen to the built-in microphone with +any ssh-session established from the other side of the world. + +/dev/serial/by-{id,path}/ now contains links for ttyUSB devices, +which do not depend on the kernel device name. As usual, unique +devices - only a single one per product connected, or a real +USB serial number in the device - are always found with the same +name in the by-id/ directory. +Completely identical devices may overwrite their names in by-id/ +and can only be found reliably in the by-path/ directory. Devices +specified by by-path/ must not change their connection, like the +USB port number they are plugged in, to keep their name. + +To support some advanced features, Linux 2.6.22 is the oldest supported +version now. The kernel config with enabled SYSFS_DEPRECATED is no longer +supported. Older kernels should still work, and devices nodes should be +reliably created, but some rules and libudev will not work correctly because +the old kernels do not provide the expected information or interfaces. + +udev 135 +======== +Bugfixes. + +Fix for a possible segfault while swapping network interface names in udev +versions 131-134. + +udev 134 +======== +Bugfixes. + +The group "video" is part of the default rules now. + +udev 133 +======== +Bugfix for kernels using SYSFS_DEPRECATED* option and finding parent +block devices in some cases. No common distro uses this option anymore, +and we do not get enough testing for this and recent udev versions. If +this option is not needed to run some old distro with a new kernel, +it should be disabled in the kernel config. + +Bugfix for the $links substitution variable, which may crash if no links +are created. This should not happen in usual setups because we always +create /dev/{block,char}/ links. + +The strings of the parsed rules, which are kept in memory, no longer +contain duplicate entries, or duplicate tails of strings. This, and the +new rules parsing/matching code reduces the total in-memory size of +a huge distro rule sets to 0.08 MB, compared to the 1.2MB of udev +version 130. + +The export of DEVTYPE=disk/partition got removed from the default +rules. This value is available from the kernel. The pnp shell script +modprobe hack is removed from the default rules. ACPI devices have _proper_ +modalias support and take care of the same functionality. +Installations which support old kernels, but install current default +udev rules may want to add that to the compat rules file. + +Libvolume_id now always probes for all known filesystems, and does not +stop at the first match. Some filesystems are marked as "exclusive probe", +and if any other filesytem type matches at the same time, libvolume_id +will, by default, not return any probing result. This is intended to prevent +mis-detection with conflicting left-over signatures found from earlier +file system formats. That way, we no longer depend on the probe-order +in case of multiple competing signatures. In some setups the kernel allows +to mount a volume with just the old filesystem signature still in place. +This may damage the new filesystem and cause data-loss, just by mounting +it. Because volume_id can not decide which one the correct signature is, +the wrong signatures need to be removed manually from the volume, or the +volume needs to be reformatted, to enable filesystem detection and possible +auto-mounting. + +udev 132 +======== +Fix segfault if compiled without optimization and dbg() does not get +compiled out and uses variables which are not available. + +udev 131 +======== +Bugfixes. (And maybe new bugs. :)) + +The rule matching engine got converted from a rule list to a token +array which reduced the in-memory rules representation of a full +featured distros with thousends of udev rules from 1.2MB to 0.12 MB. +Limits like 5 ENV and ATTR matches, and one single instance for most +other keys per rule are gone. + +The NAME assignment is no longer special cased. If later rules assign +a NAME value again, the former value will be overwritten. As usual +for most other keys, the NAME value can be protected by doing a final +assignment with NAME:="". + +All udev code now uses libudev, which is also exported. The library +is still under development, marked as experimental, and its interface +may change as long as the DeviceKit integration is not finished. + +Many thanks to Alan Jenkins for his continuous help, and finding and +optimizing some of the computing expensive parts. + +udev 130 +======== +Bugfixes. + +Kernel devices and device nodes are connected now by reverse indizes in +/sys and /dev. A device number retrieved by a stat() or similar, the +kernel device directory can be found by looking up: + /sys/dev/{block,char}/: +and the device node of the same device by looking up: + /dev/{block,char}/: + +udev 129 +======== +Fix recently introduced bug, which caused a compilation without large +file support, where vol_id does not recognize raid signatures at the end +of a volume. + +Firewire disks now create both, by-id/scsi-* and by-id/ieee-* links. +Seems some kernel versions prevent the creation of the ieee-* links, +so people used the scsi-* link which disappeared now. + +More libudev work. Almost all udevadm functionality comes from libudev +now. + +udevadm trigger has a new option --type, which allows to trigger events +for "devices", for "subsystems", or "failed" devices. The old option +--retry-failed" still works, but is no longer mentioned in the man page. + +udev 128 +======== +Bugfixes. + +The udevadm info --device-id-of-file= output has changed to use +the obvious format. Possible current users should use the --export +option which is not affected. + +The old udev commands symlinks to udevadm are not installed, if +these symlinks are used, a warning is printed. + +udev 127 +======== +Bugfixes. + +Optical drive's media is no longer probed for raid signatures, +reading the end of the device causes some devices to malfunction. +Also the offset of the last session found is used now to probe +for the filesystem. + +The volume_id library got a major version number update to 1, +some deprecated functions are removed. + +A shared library "libudev" gets installed now to provide access +to udev device information. DeviceKit, the successor of HAL, will +need this library to access the udev database and search sysfs for +devices. +The library is currently in an experimental state, also the API is +expected to change, as long as the DeviceKit integration is not +finished. + +udev 126 +======== +We use ./configure now. See INSTALL for details. Current +options are: + --prefix= + "/usr" - prefix for man pages, include files + --exec-prefix= + "" - the root filesystem, prefix for libs and binaries + --sysconfdir= + "/etc" + --with-libdir-name= + "lib" - directory name for libraries, not a path name + multilib 64bit systems may use "lib64" instead of "lib" + --enable-debug + compile-in verbose debug messages + --disable-logging + disable all logging and compile-out all log strings + --with-selinux + link against SELInux libraries, to set the expected context + for created files + +In the default rules, the group "disk" gets permissions 0660 instead +of 0640. One small step closer to unify distro rules. Some day, all +distros hopefully end up with the same set of rules. + +No symlinks to udevadm are installed anymore, if they are still needed, +they should be provided by the package. + +udev 125 +======== +Bugfixes. + +Default udev rules, which are not supposed to be edited by the user, should +be placed in /lib/udev/rules.d/ now, to make it clear that they are private to +the udev package and will be replaced with an update. Udev will pick up rule +files from: + /lib/udev/rules.d/ - default installed rules + /etc/udev/rules.d/ - user rules + on-the-fly generated rules + /dev/.udev/rules.d/ - temporary non-persistent rules created after bootup +It does not matter in which directory a rule file lives, all files are sorted +in lexical order. + +To help creating /dev/root, we have now: + $ udevadm info --export --export-prefix="ROOT_" --device-id-of-file=/ + ROOT_MAJOR=8 + ROOT_MINOR=5 +In case the current --device-id-of-file is already used, please switch to +the --export format version, it saves the output parsing and the old +format will be changed to use ':' as a separator, like the format in the +sysfs 'dev' file. + +udev 124 +======== +Fix cdrom_id to properly recognize blank media. + +udev 123 +======== +Bugfixes. + +Tape drive id-data is queried from /dev/bsg/* instead of the tape +nodes. This avoids rewinding tapes on open(). + +udev 122 +======== +Bugfixes. + +The symlinks udevcontrol and udevtrigger are no longer installed by +the Makefile. + +The scsi_id program does not depend on sysfs anymore. It can speak +SGv4 now, so /dev/bsg/* device nodes can be used, to query SCSI device +data, which should solve some old problems with tape devices, where +we better do not open all tape device nodes to identify the device. + +udev 121 +======== +Many bugfixes. + +The cdrom_id program is replaced by an advanced version, which can +detect most common device types, and also properties of the inserted +media. This is part of moving some basic functionality from HAL into +udev (and the kernel). + +udev 120 +======== +Bugfixes. + +The last WAIT_FOR_SYSFS rule is removed from the default rules. + +The symlinks to udevadm for the debugging tools: udevmonitor and +udevtest are no longer created. + +The symlinks to the udevadm man page for the old tool names are +no longer created. + +Abstract namespace sockets paths in RUN+="socket:@" rules, +should be prefixed with '@' to indicate that the path is not a +real file. + +udev 119 +======== +Bugfixes. + +udev 118 +======== +Bugfixes. + +Udevstart is removed from the tree, it did not get installed for +a long time now, and is long replaced by trigger and settle. + +udev 117 +======== +Bugfixes. + +All udev tools are merged into a single binary called udevadm. +The old names of the tools are built-in commands in udevadm now. +Symlinks to udevadm, with the names of the old tools, provide +the same functionality as the standalone tools. There is also +only a single udevadm.8 man page left for all tools. + +Tools like mkinitramfs should be checked, if they need to include +udevadm in the list of files. + +udev 116 +======== +Bugfixes. + +udev 115 +======== +Bugfixes. + +The etc/udev/rules.d/ directory now contains a default set of basic +udev rules. This initial version is the result of a rules file merge +of Fedora and openSUSE. For these both distros only a few specific +rules are left in their own file, named after the distro. Rules which +are optionally installed, because they are only valid for a specific +architecture, or rules for subsystems which are not always used are +in etc/udev/packages/. + +udev 114 +======== +Bugfixes. + +Dynamic rules can be created in /dev/.udev/rules.d/ to trigger +actions by dynamically created rules. + +SYMLINK=="" matches agains the entries in the list of +currently defined symlinks. The links are not created in the +filesystem at that point in time, but the values can be matched. + +RUN{ignore_error}+="" will ignore any exit code from the +program and not record as a failed event. + +udev 113 +======== +Bugfixes. + +Final merge of patches/features from the Ubuntu package. + +udev 112 +======== +Bugfixes. + +Control characters in filesystem label strings are no longer silenty +removed, but hex-encoded, to be able to uniquely identify the device +by its symlink in /dev/disk/by-label/. +If libvolume_id is used by mount(8), LABEL= will work as expected, +if slashes or other characters are used in the label string. + +To test the existence of a file, TEST=="" and TEST!="" +can be specified now. The TEST key accepts an optional mode mask +TEST{0100}=="". + +Scsi_id now supports a mode without expecting scsi-specific sysfs +entries to allow the extraction of cciss-device persistent properties. + +udev 111 +======== +Bugfixes. + +In the future, we may see uuid's which are just simple character +strings (see the DDF Raid Specification). For that reason vol_id now +exports ID_FS_UUID_SAFE, just like ID_FS_LABEL_SAFE. For things like +the creation of symlinks, the *_SAFE values ensure, that no control +or whitespace characters are used in the filename. + +Possible users of libvolume_id, please use the volume_id_get_* functions. +The public struct will go away in a future release of the library. + +udev 110 +======== +Bugfixes. + +Removal of useless extras/eventrecorder.sh. + +udev 109 +======== +Bugfixes. + +udev 108 +======== +Bugfixes. + +The directory multiplexer for dev.d/ and hotplug.d are finally removed +from the udev package. + +udev 107 +======== +Bugfixes. + +Symlinks can have priorities now, the priority is assigned to the device +and specified with OPTIONS="link_priority=100". Devices with higher +priorities overwrite the symlinks of devices with lower priorities. +If the device that currently owns the link, goes away, the symlink +will be removed, and recreated, pointing to the next device with the +highest actual priority. This should make /dev/disk/by-{label,uuid,id} +more reliable, if multiple devices contain the same metadata and overwrite +these symlinks. + +The dasd_id program is removed from the udev tree, and dasdinfo, with the +needed rules, are part of the s390-tools now. + +Please add KERNEL=="[0-9]*:[0-9]*" to the scsi wait-for-sysfs rule, +we may get the scsi sysfs mess fixed some day, and this will only catch +the devices we are looking for. + +USB serial numbers for storage devices have the target:lun now appended, +to make it possibble to distinguish broken multi-lun devices with all +the same SCSI identifiers. + +Note: The extra "run_directory" which searches and executes stuff in +/etc/hotplug.d/ and /etc/dev.d/ is long deprecated, and will be removed +with the next release. Make sure, that you don't use it anymore, or +provides your own implementation of that inefficient stuff. +We are tired of reports about a "slow udev", because these directories +contain stuff, that runs with _every_ event, instead of using rules, +that run programs only for the matching events. + +udev 106 +======== +Bugfixes. + +udev 105 +======== +Bugfixes. + +DRIVER== will match only for devices that actually have a real +driver. DRIVERS== must be used, if parent devices should be +included in the match. + +Libvolume_id's "linux_raid" detection needed another fix. + +udev 104 +======== +Bugfixes. + +udev 103 +======== +Add additional check to volume_id detection of via_raid, cause +some company decided to put a matching pattern all over the empty +storage area of their music players. + +udev 102 +======== +Fix path_id for SAS devices. + +udev 101 +======== +The udev daemon can be started with --debug-trace now, which will +execute all events serialized to get a chance to catch a possible +action that crashes the box. + +A warning is logged, if PHYSDEV* keys, the "device" link, or a parent +device attribute like $attr{../file} is used, only WAIT_FOR_SYSFS rules +are excluded from the warning. Referencing parent attributes directly +may break when something in the kernel driver model changes. Udev will +just find the attribute by walking up the parent chain. + +Udevtrigger now sorts the list of devices depending on the device +dependency, so a "usb" device is triggered after the parent "pci" +device. + +udev 100 +======== +Revert persistent-storage ata-serial '_' '-' replacement. + +udev 099 +======== +Bugfixes. + +Udevtrigger can now filter the list of devices to be triggered. Matches +for subsystems or sysfs attributes can be specified. + +The entries in /dev/.udev/queue and /dev/.udev/failed have changed to +zero-sized files to avoid pointing to /sys and confuse broken tools which +scan the /dev directory. To retry failed events, udevtrigger --retry-failed +should be used now. + +The rules and scripts to create udev rules for persistent network +devices and optical drives are in the extras/rules_generator directory +now. If you use something similar, please consider replacing your own +version with this, to share the support effort. The rule_generator +installs its own rules into /etc/udev/rules.d. + +The cdrom_id tool installs its own rule now in /etc/udev/rules.d, cause +the rule_generator depends on cdrom_id to be called in an earlier rule. + +udev 098 +======== +Bugfixes. + +Renaming of some key names (the old names still work): +BUS -> SUBSYSTEMS, ID -> KERNELS, SYSFS -> ATTRS, DRIVER -> DRIVERS. +(The behavior of the key DRIVER will change soon in one of the next +releases, to match only the event device, please switch to DRIVERS +instead. If DRIVER is used, it will behave like DRIVERS, but an error +is logged. +With the new key names, we have a more consistent and simpler scheme. +We can match the properties of the event device only, with: KERNEL, +SUBSYSTEM, ATTR, DRIVER. Or include all the parent devices in the match, +with: KERNELS, SUBSYSTEMS, ATTRS, DRIVERS. ID, BUS, SYSFS, DRIVER are no +longer mentioned in the man page and should be switched in the rule +files. + +ATTR{file}="value" can be used now, to write to a sysfs file of the +event device. Instead of: + ..., SYSFS{type}=="0|7|14", RUN+="/bin/sh -c 'echo 60 > /sys$$DEVPATH/timeout'" +we now can do: + ..., ATTR{type}=="0|7|14", ATTR{timeout}="60" + +All the PHYSDEV* keys are deprecated and will be removed from a +future kernel: + PHYDEVPATH - is the path of a parent device and should not be + needed at all. + PHYSDEVBUS - is just a SUBSYSTEM value of a parent, and can be + matched with SUBSYSTEMS== + PHYSDEVDRIVER - for bus devices it is available as ENV{DRIVER}. + Newer kernels will have DRIVER in the environment, + for older kernels udev puts in. Class device will + no longer carry this property of a parent and + DRIVERS== can be used to match such a parent value. +Note that ENV{DRIVER} is only available for a few bus devices, where +the driver is already bound at device event time. On coldplug, the +events for a lot devices are already bound to a driver, and they will have +that value set. But on hotplug, at the time the kernel creates the device, +it can't know what driver may claim the device after that, therefore +in most cases it will be empty. + +Failed events should now be re-triggered with: + udevtrigger --retry-failed. +Please switch to this command, so we keep the details of the /dev/.udev/failed/ +files private to the udev tools. We may need to switch the current symlink +target, cause some obviously broken tools try to scan all files in /dev +including /dev/.udev/, find the links to /sys and end up stat()'ing sysfs files +million times. This takes ages on slow boxes. + +The udevinfo attribute walk (-a) now works with giving a device node +name (-n) instead of a devpath (-p). The query now always works, also when +no database file was created by udev. + +The built-in /etc/passwd /etc/group parser is removed, we always depend on +getpwnam() and getgrnam() now. One of the next releases will depend on +fnmatch() and may use getopt_long(). + +udev 097 +======== +Bugfixes and small improvements. + +udev 096 +======== +Fix path_id for recent kernels. + +udev 095 +======== +%e is finally gone. + +Added support for swapping network interface names, by temporarily +renaming the device and wait for the target name to become free. + +udev 094 +======== +The built-in MODALIAS key and substitution is removed. + +udev 093 +======== +The binary firmware helper is replaced by the usual simple +shell script. Udevsend is removed from the tree. + +udev 092 +======== +Bugfix release. + +udev 091 +======== +Some more keys require the correct use of '==' and '=' depending +on the kind of operation beeing an assignment or a match. Rules +with invalid operations are skipped and logged to syslog. Please +test with udevtest if the parsing of your rules throws errors and +fix possibly broken rules. + +udev 090 +======== +Provide "udevsettle" to wait for all current udev events to finish. +It also watches the current kernel netlink queue by comparing the +even sequence number to make sure that there are no current pending +events that have not already arrived in the daemon. + +udev 089 +======== +Fix rule to skip persistent rules for removable IDE devices, which +also skipped optical IDE drives. + +All *_id program are installed in /lib/udev/ by default now. + +No binary is stripped anymore as this should be done in the +packaging process and not at build time. + +libvolume_id is provided as a shared library now and vol_id is +linked against it. Also one of the next HAL versions will require +this library, and the HAL build process will also require the +header file to be installed. The copy of the same code in HAL will +be removed to have only a single copy left on the system. + +udev 088 +======== +Add persistent links for SCSI tapes. The rules file is renamed +to 60-persistent-storage.rules. + +Create persistent path for usb devices. Can be used for all sorts +of devices that can't be distinguished by other properties like +multiple identical keyboards and mice connected to the same box. + +Provide "udevtrigger" program to request events on coldplug. The +shell script is much too slow with thousends of devices. + +udev 087 +======== +Fix persistent disk rules to exclude removable IDE drives. + +Warn if %e, $modalias or MODALIAS is used. + +udev 086 +======== +Fix queue export, which wasn't correct for subsequent add/remove +events for the same device. + +udev 085 +======== +Fix cramfs detection on big endian. + +Make WAIT_FOR_SYSFS usable in "normal" rules and silent if the whole +device goes away. + +udev 084 +======== +If BUS== and SYSFS{}== have been used in the same rule, the sysfs +attributes were only checked at the parent device that matched the +by BUS requested subsystem. Fix it to also look at the device we +received the event for. + +Build variable CROSS has changed to CROSS_COMPILE to match the kernel +build name. + +udev 083 +======== +Fix a bug where NAME="" would prevent RUN from beeing executed. + +RUN="/bin/program" does not longer automatically add the subsystem +as the first parameter. This is from the days of /sbin/hotplug +which is dead now and it's just confusing to need to add a space at +the end of the program name to prevent this. +If you use rules that need the subsystem as the first parameter, +like the old "udev_run_hotlugd" and "udev_run_devd", add the subsystem +to the key like RUN+="/bin/program $env{SUBSYSTEM}". + +udev 082 +======== +The udev man page has moved to udev(7) as it does not describe a command +anymore. The programs udev, udevstart and udevsend are no longer installed +by default and must be copied manually, if they should be installed or +included in a package. + +Fix a bug where "ignore_device" could run earlier collected RUN keys before +the ignore rule was applied. + +More preparation for future sysfs changes. usb_id and scsi_id no longer +depend on a magic order of devices in the /devices chain. Specific devices +should be requested by their subsytem. + +This will always find the scsi parent device without depending on a specific +path position: + dev = sysfs_device_get(devpath); + dev_usb = sysfs_device_get_parent_with_subsystem(dev, "scsi"); + +The "device" link in the current sysfs layout will be automatically +_resolved_ as a parent and in the new sysfs layout it will just _be_ the +parent in the devpath. If a device is requested by it's symlink, like all +class devices in the new sysfs layout will look like, it gets automatically +resolved and substituted with the real devpath and not the symlink path. + +Note: +A similar logic must be applied to _all_ sysfs users, including +scripts, that search along parent devices in sysfs. The explicit use of +the "device" link must be avoided. With the future sysfs layout all +DEVPATH's will start with /devices/ and have a "subsystem" symlink poiting +back to the "class" or the "bus". The layout of the parent devices in +/devices is not necessarily expected to be stable across kernel releases and +searching for parents by their subsystem should make sysfs users tolerant +for changed parent chains. + +udev 081 +======== +Prepare udev to work with the experimental kernel patch, that moves +/sys/class devices to /sys/devices and /sys/block to /sys/class/block. + +Clarify BUS, ID, $id usage and fix $id behavior. This prepares for +moving the class devices to /sys/devices. + +Thanks again to Marco for help finding a hopefully nice compromise +to make %b simpler and working again. + +udev 080 +======== +Complete removal of libsysfs, replaced by simple helper functions +which are much simpler and a bit faster. The udev daemon operatesentirely +on event parameters and does not use sysfs for simple rules anymore. +Please report any new bugs/problems, that may be caused by this big +change. They will be fixed immediately. + +The enumeration format character '%e' is deprecated and will be +removed sometimes from a future udev version. It never worked correctly +outside of udevstart, so we can't use it with the new parallel +coldplug. A simple enumeration is as useless as the devfs naming +scheme, just get rid of both if you still use it. + +MODALIAS and $modalias is not needed and will be removed from one of +the next udev versions, replace it in all rules with ENV{MODALIAS} or +the sysfs "modalias" value. + +Thanks a lot to Marco for all his help on finding and fixing bugs. + +udev 079 +======== +Let scsi_id request libata drive serial numbers from page 0x80. + +Renamed etc/udev/persistent.rules to persistent-disk.rules and +added /dev/disk/by-name/* for device mapper device names. + +Removed %e from the man page. It never worked reliably outside +of udevstart and udevstart is no longer recommended to use. + +udev 078 +======== +Symlinks are now exported to the event environment. Hopefully it's no +longer needed to run udevinfo from an event process, like it was +mentioned on the hotplug list: + UDEV [1134776873.702967] add@/block/sdb + ... + DEVNAME=/dev/sdb + DEVLINKS=/dev/disk/by-id/usb-IBM_Memory_Key_0218B301030027E8 /dev/disk/by-path/usb-0218B301030027E8:0:0:0 + +udev 077 +======== +Fix a problem if udevsend is used as the hotplug handler and tries to use +syslog, which causes a "vc" event loop. 2.6.15 will make udevsend obsolete +and this kind of problems will hopefully go away soon. + +udev 076 +======== +All built-in logic to work around bad sysfs timing is removed with this +version. The need to wait for sysfs files is almost fixed with a kernel +version that doesn't work with this udev version anyway. Until we fix +the timing of the "bus" link creation, the former integrated logic should +be emulated by a rule placed before all other rules: + ACTION=="add", DEVPATH=="/devices/*", ENV{PHYSDEVBUS}=="?*", WAIT_FOR_SYSFS="bus" + +The option "udev_db" does no longer exist. All udev state will be in +/$udev_root/.udev/ now, there is no longer an option to set this +to anything else. +If the init script or something else used this value, just depend on +this hardcoded path. But remember _all_content_ of this directory is +still private to udev and can change at any time. + +Default location for rule sripts and helper programs is now: /lib/udev/. +Everything that is not useful on the commandline should go into this +directory. Some of the helpers in the extras folder are installed there +now. The rules need to be changed, to find the helpers there. + +Also /lib/udev/devices is recommended as a directory where packages or +the user can place real device nodes, which get copied over to /dev at +every boot. This should replace the various solutions with custom config +files. + +Udevsend does no longer start the udev daemon. This must be done with +the init script that prepares /dev on tmpfs and creates the initial nodes, +before starting the daemon. + +udev 075 +======== +Silent a too verbose error logging for the old hotplug.d/ dev.d/ +emulation. + +The copy of klibc is removed. A systemwide installed version of klibc +should be used to build a klibc udev now. + +udev 074 +======== +NAME="" will not create any nodes, but execute RUN keys. To completely +ignore an event the OPTION "ignore_device" should be used. + +After removal of the reorder queue, events with a TIMEOUT can be executed +without any queuing now. + +udev 073 +======== +Fixed bug in udevd, if inotify is not available. We depend on netlink +uevents now, kernels without that event source will not work with that +version of udev anymore. + +udev 072 +======== +The rule parsing happens now in the daemon once at startup, all udev +event processes inherit the already parsed rules from the daemon. +It is shipped with SUSE10.0 and reduces heavily the system load at +startup. The option to save precompiled rules and let the udev process +pick the them up is removed, as it's no longer needed. + +Kernel 2.6.15 will have symlinks at /class/input pointing to the real +device. Libsysfs is changed to "translate" the requested link into the +real device path, as it would happen with the hotplug event. Otherwise +device removal and the udev database will not work. + +Using 'make STRIPCMD=' will leave the binaries unstripped for debugging +and packaging. + +A few improvements for vol_id, the filesytem probing code. + +udev 071 +======== +Fix a stupid typo in extras/run_directory for "make install". + +scsi_id creates the temporary devnode now in /dev for usage with a +non-writable /tmp directory. + +The uevent kernel socket buffer can carry app. 50.000 events now, +let's see who can break this again. :) + +The upcoming kernel will have a new input driver core integration. +Some class devices are now symlinks to the real device. libsysfs +needs a fix for this to work correctly. Udevstart of older udev +versions will _not_ create these devices! + +udev 070 +======== +Fix a 'install' target in the Makefile, that prevents EXTRAS from +beeing installed. + +udev 069 +======== +A bunch of mostly trivial bugfixes. From now on no node name or +symlink name can contain any character than plain whitelisted ascii +characters or validated utf8 byte-streams. This is needed for the +/dev/disk/by-label/* links, because we import untrusted data and +export it to the filesystem. + +udev 068 +======== +More bugfixes. If udevd was started from the kernel, we don't +have stdin/stdout/stderr, which broke the forked tools in some +situations. + +udev 067 +======== +Bugfix. udevstart event ordering was broken for a long time. +The new run_program() uncovered it, because /dev/null was not +available while we try to run external programs. +Now udevstart should create it before we run anything. + +udev 066 +======== +Minor bugfixes and some distro rules updates. If you don't have the +persistent disk rules in /dev/disk/by-*/* on your distro, just +grab it from here. :) + +udev 065 +======== +We can use socket communication now to pass events from udev to +other programs: + RUN+="socket:/org/freedesktop/hal/udev_event" +will pass the whole udev event to the HAL daemon without the need +for a forked helper. (See ChangeLog for udevmonitor, as an example) + +udev 064 +======== +Mostly bugfixes and see ChangeLog. + +The test for the existence of an environment value should be +switched from: + ENV{KEY}=="*" to ENV{KEY}=="?*" +because "*" will not fail anymore, if the key does not exist or +is empty. + +udev 063 +======== +Bugfixes and a few tweaks described in the ChangeLog. + +udev 062 +======== +Mostly a Bugfix release. + +Added WAIT_FOR_SYSFS="" to be able to fight against the sysfs +timing with custom rules. + +udev 061 +======== +We changed the internal rule storage format. Our large rule files took +2 MB of RAM, with the change we are down to 99kB. + +If the device-node has been created with default name and no symlink or +options are to remenber, it is not longer stored in the udevdb. HAL will +need to be updated to work correctly with that change. + +To overrride optimization flags, OPTFLAGS may be used now. + +udev 060 +======== +Bugfix release. + +udev 059 +======== +Major changes happened with this release. The goal is to take over the +complete kernel-event handling and provide a more efficient way to dispatch +kernel events. Replacing most of the current shell script logic and the +kernel forked helper with a netlink-daemon and a rule-based event handling. + +o udevd listens to netlink events now. The first valid netlink event + will make udevd ignore any message from udevsend that contains a + SEQNUM, to avoid duplicate events. The forked events can be disabled + with: + echo "" > /proc/sys/kernel/hotplug + For full support, the broken input-subsytem needs to be fixed, not to + bypass the driver core. + +o /etc/dev.d/ + /etc/hotplug.d/ directory multiplexing is completely + removed from udev itself and must be emulated by calling small + helper binaries provided in the extras folder: + make EXTRAS=extras/run_directory/ + will build udev_run_devd and udev_run_hotplugd, which can be called + from a rule if needed: + RUN+="/sbin/udev_run_hotplugd" + The recommended way to handle this is to convert all the calls from + the directories to explicit udev rules and get completely rid of the + multiplexing. (To catch a ttyUSB event, you now no longer need to + fork and exit 300 tty script instances you are not interested in, it + is just one rule that matches exactly the device.) + +o udev handles now _all_ events not just events for class and block + devices, this way it is possible to control the complete event + behavior with udev rules. Especially useful for rules like: + ACTION="add", DEVPATH="/devices/*", MODALIAS=="?*", RUN+="/sbin/modprobe $modalias" + +o As used in the modalias rule, udev supports now textual + substitution placeholder along with the usual format chars. This + needs to be documented, for now it's only visible in udev_rules_parse.c. + +o The rule keys support now more operations. This is documented in the + man page. It is possible to add values to list-keys like the SYMLINK + and RUN list with KEY+="value" and to clear the list by assigning KEY="". + Also "final"-assignments are supported by using KEY:="value", which will + prevent changing the key by any later rule. + +o kernel 2.6.12 has the "detached_state" attribute removed from + sysfs, which was used to recognize sysfs population. We switched that + to wait for the "bus" link, which is only available in kernels after 2.6.11. + Running this udev version on older kernels may cause a short delay for + some events. + +o To provide infrastructure for persistent device naming, the id programs: + scsi_id, vol_id (former udev_volume_id), and ata_id (new) are able now + to export the probed data in environment key format: + pim:~ # /sbin/ata_id --export /dev/hda + ID_MODEL=HTS726060M9AT00 + ID_SERIAL=MRH401M4G6UM9B + ID_REVISION=MH4OA6BA + + The following rules: + KERNEL="hd*[!0-9]", IMPORT="/sbin/ata_id --export $tempnode" + KERNEL="hd*[!0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_MODEL}_$env{ID_SERIAL}" + + Will create: + kay@pim:~> tree /dev/disk + /dev/disk + |-- by-id + | |-- HTS726060M9AT00_MRH401M4G6UM9B -> ../../hda + | `-- IBM-Memory_Key -> ../../sda + |-- by-label + | |-- swap -> ../../hda1 + | |-- date -> ../../sda1 + | `-- home -> ../../hda3 + `-- by-uuid + |-- 2E08712B0870F2E7 -> ../../hda3 + |-- 9352cfef-7687-47bc-a2a3-34cf136f72e1 -> ../../hda1 + |-- E845-7A89 -> ../../sda1 + `-- b2a61681-3812-4f13-a4ff-920d70604299 -> ../../hda2 + + The IMPORT= operation will import these keys in the environment and make + it available for later PROGRAM= and RUN= executed programs. The keys are + also stored in the udevdb and can be queried from there with one of the + next udev versions. + +o A few binaries are silently added to the repository, which can be used + to replay kernel events from initramfs instead of using coldplug. udevd + can be instructed now to queue-up events while the stored events from + initramfs are filled into the udevd-queue. This code is still under + development and there is no documentation now besides the code itself. + The additional binaries get compiled, but are not installed by default. + +o There is also a temporary fix for a performance problem where too many + events happen in parallel and every event needs to parse the rules. + udev can now read precompiled rules stored on disk. This is likely to be + replaced by a more elegant solution in a future udev version. + +udev 058 +======== +With kernel version 2.6.12, the sysfs file "detached_state" was removed. +Fix for libsysfs not to expect this file was added. + +udev 057 +======== +All rules are applied now, but only the first matching rule with a NAME-key +will be applied. All later rules with NAME-key are completely ignored. This +way system supplied symlinks or permissions gets applied to user-defined +naming rules. + +Note: +Please check your rules setup, if you may need to add OPTIONS="last_rule" +to some rules, to keep the old behavior. + +The rules are read on "remove"-events too. That makes is possible to match +with keys that are available on remove (KERNEL, SUBSYSTEM, ID, ENV, ...) to +instruct udev to ignore an event (OPTIONS="ignore_device"). +The new ACTION-key may be used to let a rule act only at a "remove"-event. + +The new RUN-key supports rule-based execution of programs after device-node +handling. This is meant as a general replacement for the dev.d/-directories +to give fine grained control over the execution of programs. + +The %s{}-sysfs format char replacement values are searched at any of the +devices in the device chain now, not only at the class-device. + +We support log priority levels now. The value udev_log in udev.conf is used +to determine what is printed to syslog. This makes it possible to +run a version with compiled-in debug messages in a production environment +which is sometimes needed to find a bug. +It is still possible to supress the inclusion of _any_ syslog usage with +USE_LOG=false to create the smallest possible binaries if needed. +The configured udev_log value can be overridden with the environment variable +UDEV_LOG. + +udev 056 +======== +Possible use of a system-wide klibc: + make USE_KLIBC=true KLCC=/usr/bin/klcc all +will link against an external klibc and our own version will be ignored. + +udev 055 +======== +We support an unlimited count of symlinks now. + +If USE_STATIC=true is passed to a glibc build, we link statically and use +a built-in userdb parser to resolve user and group names. + +The PLACE= key is gone. It can be replaced by an ID= for a long time, because +we walk up the chain of physical devices to find a match. + +The KEY="" format supports '=', '==', '!=,' , '+=' now. This makes it +easy to skip certain attribute matches without composing rules with weird +character class negations like: + KERNEL="[!s][!c][!d]*" +this can now be replaced with: + KERNEL!="scd*" +The current simple '=' is still supported, and should work as it does today, +but existing rules should be converted if possible, to be better readable. + +We have new ENV{}== key now, to match against a maximum of 5 environment +variables. + +udevstart is its own binary again, because we don't need co carry this araound +with every forked event. diff --git a/src/udev/README b/src/udev/README new file mode 100644 index 000000000..38459c6b2 --- /dev/null +++ b/src/udev/README @@ -0,0 +1,101 @@ +udev - Linux userspace device management + +Integrating udev in the system has complex dependencies and may differ from +distribution to distribution. A system may not be able to boot up or work +reliably without a properly installed udev version. The upstream udev project +does not recommend replacing a distro's udev installation with the upstream +version. + +The upstream udev project's set of default rules may require a most recent +kernel release to work properly. + +Tools and rules shipped by udev are not public API and may change at any time. +Never call any private tool in /usr/lib/udev from any external application; it +might just go away in the next release. Access to udev information is only offered +by udevadm and libudev. Tools and rules in /usr/lib/udev and the entire contents +of the /run/udev directory are private to udev and do change whenever needed. + +Requirements: + - Version 2.6.34 of the Linux kernel with sysfs, procfs, signalfd, inotify, + unix domain sockets, networking and hotplug enabled + + - Some architectures might need a later kernel, that supports accept4(), + or need to backport the accept4() syscall wiring in the kernel. + + - These options are required: + CONFIG_DEVTMPFS=y + CONFIG_HOTPLUG=y + CONFIG_INOTIFY_USER=y + CONFIG_NET=y + CONFIG_PROC_FS=y + CONFIG_SIGNALFD=y + CONFIG_SYSFS=y + CONFIG_SYSFS_DEPRECATED*=n + CONFIG_UEVENT_HELPER_PATH="" + + - These options might be needed: + CONFIG_BLK_DEV_BSG=y (SCSI devices) + CONFIG_TMPFS_POSIX_ACL=y (user ACLs for device nodes) + + - The /dev directory needs the 'devtmpfs' filesystem mounted. + Udev only manages the permissions and ownership of the + kernel-provided device nodes, and possibly creates additional symlinks. + + - Udev requires /run to be writable, which is usually done by mounting a + 'tmpfs' filesystem. + + - This version of udev does not work properly with the CONFIG_SYSFS_DEPRECATED* + option enabled. + + - The deprecated hotplug helper /sbin/hotplug should be disabled in the + kernel configuration, it is not needed today, and may render the system + unusable because the kernel may create too many processes in parallel + so that the system runs out-of-memory. + + - The proc filesystem must be mounted on /proc, and the sysfs filesystem must + be mounted at /sys. No other locations are supported by a standard + udev installation. + + - The default rule sset requires the following group names resolvable at udev startup: + disk, cdrom, floppy, tape, audio, video, lp, tty, dialout, and kmem. + Especially in LDAP setups, it is required that getgrnam() be able to resolve + these group names with only the rootfs mounted and while no network is + available. + + - Some udev extras have external dependencies like: + libglib2, usbutils, pciutils, and gperf. + All these extras can be disabled with configure options. + +Setup: + - The udev daemon should be started to handle device events sent by the kernel. + During bootup, the events for already existing devices can be replayed, so + that they are configured by udev. The systemd service files contain the + needed commands to start the udev daemon and the coldplug sequence. + + - Restarting the daemon never applies any rules to existing devices. + + - New/changed rule files are picked up automatically; there is usually no + daemon restart or signal needed. + +Operation: + - Based on events the kernel sends out on device creation/removal, udev + creates/removes device nodes and symlinks in the /dev directory. + + - All kernel events are matched against a set of specified rules, which + possibly hook into the event processing and load required kernel + modules to set up devices. For all devices, the kernel exports a major/minor + number; if needed, udev creates a device node with the default kernel + device name. If specified, udev applies permissions/ownership to the device + node, creates additional symlinks pointing to the node, and executes + programs to handle the device. + + - The events udev handles, and the information udev merges into its device + database, can be accessed with libudev: + http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/ + http://www.kernel.org/pub/linux/utils/kernel/hotplug/gudev/ + +For more details about udev and udev rules, see the udev man pages: + http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev/ + +Please direct any comment/question to the linux-hotplug mailing list at: + linux-hotplug@vger.kernel.org diff --git a/src/udev/TODO b/src/udev/TODO new file mode 100644 index 000000000..8b8b9c8f8 --- /dev/null +++ b/src/udev/TODO @@ -0,0 +1,22 @@ + - find a way to tell udev to not cancel firmware + requests in initramfs + + - scsi_id -> sg3_utils? + + - make gtk-doc optional like kmod + + - move /usr/lib/udev/devices/ to tmpfiles + + - trigger --subsystem-match=usb/usb_device + + - kill rules_generator + + - have a $attrs{} ? + + - remove RUN+="socket:" + + - libudev.so.1 + - symbol versioning + - return object with *_unref() + - udev_monitor_from_socket() + - udev_queue_get_failed_list_entry() diff --git a/src/udev/autogen.sh b/src/udev/autogen.sh new file mode 100755 index 000000000..55ee03afd --- /dev/null +++ b/src/udev/autogen.sh @@ -0,0 +1,44 @@ +#!/bin/sh -e + +if [ -f .git/hooks/pre-commit.sample -a ! -f .git/hooks/pre-commit ] ; then + cp -p .git/hooks/pre-commit.sample .git/hooks/pre-commit && \ + chmod +x .git/hooks/pre-commit && \ + echo "Activated pre-commit hook." +fi + +gtkdocize +autoreconf --install --symlink + +libdir() { + echo $(cd $1/$(gcc -print-multi-os-directory); pwd) +} + +args="$args \ +--prefix=/usr \ +--sysconfdir=/etc \ +--libdir=$(libdir /usr/lib) \ +--with-selinux \ +--enable-gtk-doc" + +if [ -L /bin ]; then +args="$args \ +--libexecdir=/usr/lib \ +--with-systemdsystemunitdir=/usr/lib/systemd/system \ +" +else +args="$args \ +--with-rootprefix= \ +---with-rootlibdir=$(libdir /lib) \ +--bindir=/sbin \ +--libexecdir=/lib \ +--with-systemdsystemunitdir=/lib/systemd/system \ +" +fi + +echo +echo "----------------------------------------------------------------" +echo "Initialized build system. For a common configuration please run:" +echo "----------------------------------------------------------------" +echo +echo "./configure CFLAGS='-g -O1' $args" +echo diff --git a/src/udev/configure.ac b/src/udev/configure.ac new file mode 100644 index 000000000..b31b62f28 --- /dev/null +++ b/src/udev/configure.ac @@ -0,0 +1,242 @@ +AC_PREREQ(2.60) +AC_INIT([udev], + [182], + [linux-hotplug@vger.kernel.org], + [udev], + [http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html]) +AC_CONFIG_SRCDIR([src/udevd.c]) +AC_CONFIG_AUX_DIR([build-aux]) +AM_INIT_AUTOMAKE([check-news foreign 1.11 -Wall -Wno-portability silent-rules tar-pax no-dist-gzip dist-xz subdir-objects]) +AC_USE_SYSTEM_EXTENSIONS +AC_SYS_LARGEFILE +AC_CONFIG_MACRO_DIR([m4]) +AM_SILENT_RULES([yes]) +LT_INIT([disable-static]) +AC_PROG_AWK +AC_PROG_SED +AC_PROG_MKDIR_P +GTK_DOC_CHECK(1.10) +AC_PREFIX_DEFAULT([/usr]) + +AC_PATH_PROG([XSLTPROC], [xsltproc]) +AM_CONDITIONAL(HAVE_XSLTPROC, test x"$XSLTPROC" != x) + +AC_SEARCH_LIBS([clock_gettime], [rt], [], [AC_MSG_ERROR([POSIX RT library not found])]) + +PKG_CHECK_MODULES(BLKID, blkid >= 2.20) + +PKG_CHECK_MODULES(KMOD, libkmod >= 5) + +AC_ARG_WITH([rootprefix], + AS_HELP_STRING([--with-rootprefix=DIR], [rootfs directory prefix for config files and kernel modules]), + [], [with_rootprefix=${ac_default_prefix}]) +AC_SUBST([rootprefix], [$with_rootprefix]) + +AC_ARG_WITH([rootlibdir], + AS_HELP_STRING([--with-rootlibdir=DIR], [rootfs directory to install shared libraries]), + [], [with_rootlibdir=$libdir]) +AC_SUBST([rootlib_execdir], [$with_rootlibdir]) + +AC_ARG_WITH([selinux], + AS_HELP_STRING([--with-selinux], [enable SELinux support]), + [], [with_selinux=no]) +AS_IF([test "x$with_selinux" = "xyes"], [ + LIBS_save=$LIBS + AC_CHECK_LIB(selinux, getprevcon, + [], + AC_MSG_ERROR([SELinux selected but libselinux not found])) + LIBS=$LIBS_save + SELINUX_LIBS="-lselinux -lsepol" + AC_DEFINE(WITH_SELINUX, [1] ,[SELinux support.]) +]) +AC_SUBST([SELINUX_LIBS]) +AM_CONDITIONAL(WITH_SELINUX, [test "x$with_selinux" = "xyes"]) + +AC_ARG_ENABLE([debug], + AS_HELP_STRING([--enable-debug], [enable debug messages @<:@default=disabled@:>@]), + [], [enable_debug=no]) +AS_IF([test "x$enable_debug" = "xyes"], [ AC_DEFINE(ENABLE_DEBUG, [1], [Debug messages.]) ]) + +AC_ARG_ENABLE([logging], + AS_HELP_STRING([--disable-logging], [disable system logging @<:@default=enabled@:>@]), + [], enable_logging=yes) +AS_IF([test "x$enable_logging" = "xyes"], [ AC_DEFINE(ENABLE_LOGGING, [1], [System logging.]) ]) + +AC_ARG_ENABLE([manpages], + AS_HELP_STRING([--disable-manpages], [disable man pages @<:@default=enabled@:>@]), + [], enable_manpages=yes) +AM_CONDITIONAL([ENABLE_MANPAGES], [test "x$enable_manpages" = "xyes"]) + +if test "x$cross_compiling" = "xno" ; then + AC_CHECK_FILES([/usr/share/pci.ids], [pciids=/usr/share/pci.ids]) + AC_CHECK_FILES([/usr/share/hwdata/pci.ids], [pciids=/usr/share/hwdata/pci.ids]) + AC_CHECK_FILES([/usr/share/misc/pci.ids], [pciids=/usr/share/misc/pci.ids]) +fi + +AC_ARG_WITH(usb-ids-path, + [AS_HELP_STRING([--with-usb-ids-path=DIR], [Path to usb.ids file])], + [USB_DATABASE=${withval}], + [if test -n "$usbids" ; then + USB_DATABASE="$usbids" + else + PKG_CHECK_MODULES(USBUTILS, usbutils >= 0.82) + AC_SUBST([USB_DATABASE], [$($PKG_CONFIG --variable=usbids usbutils)]) + fi]) +AC_MSG_CHECKING([for USB database location]) +AC_MSG_RESULT([$USB_DATABASE]) +AC_SUBST(USB_DATABASE) + +AC_ARG_WITH(pci-ids-path, + [AS_HELP_STRING([--with-pci-ids-path=DIR], [Path to pci.ids file])], + [PCI_DATABASE=${withval}], + [if test -n "$pciids" ; then + PCI_DATABASE="$pciids" + else + AC_MSG_ERROR([pci.ids not found, try --with-pci-ids-path=]) + fi]) +AC_MSG_CHECKING([for PCI database location]) +AC_MSG_RESULT([$PCI_DATABASE]) +AC_SUBST(PCI_DATABASE) + +AC_ARG_WITH(firmware-path, + AS_HELP_STRING([--with-firmware-path=DIR[[[:DIR[...]]]]], + [Firmware search path (default=ROOTPREFIX/lib/firmware/updates:ROOTPREFIX/lib/firmware)]), + [], [with_firmware_path="$rootprefix/lib/firmware/updates:$rootprefix/lib/firmware"]) +OLD_IFS=$IFS +IFS=: +for i in $with_firmware_path; do + if test "x${FIRMWARE_PATH}" = "x"; then + FIRMWARE_PATH="\\\"${i}/\\\"" + else + FIRMWARE_PATH="${FIRMWARE_PATH}, \\\"${i}/\\\"" + fi +done +IFS=$OLD_IFS +AC_SUBST([FIRMWARE_PATH], [$FIRMWARE_PATH]) + +AC_ARG_WITH([systemdsystemunitdir], + AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]), + [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)]) +AS_IF([test "x$with_systemdsystemunitdir" != "xno"], [ AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) ]) +AM_CONDITIONAL(WITH_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != "xno" ]) + +# ------------------------------------------------------------------------------ +# GUdev - libudev gobject interface +# ------------------------------------------------------------------------------ +AC_ARG_ENABLE([gudev], + AS_HELP_STRING([--disable-gudev], [disable Gobject libudev support @<:@default=enabled@:>@]), + [], [enable_gudev=yes]) +AS_IF([test "x$enable_gudev" = "xyes"], [ PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.22.0 gobject-2.0 >= 2.22.0]) ]) + +AC_ARG_ENABLE([introspection], + AS_HELP_STRING([--disable-introspection], [disable GObject introspection @<:@default=enabled@:>@]), + [], [enable_introspection=yes]) +AS_IF([test "x$enable_introspection" = "xyes"], [ + PKG_CHECK_MODULES([INTROSPECTION], [gobject-introspection-1.0 >= 0.6.2]) + AC_DEFINE([ENABLE_INTROSPECTION], [1], [enable GObject introspection support]) + AC_SUBST([G_IR_SCANNER], [$($PKG_CONFIG --variable=g_ir_scanner gobject-introspection-1.0)]) + AC_SUBST([G_IR_COMPILER], [$($PKG_CONFIG --variable=g_ir_compiler gobject-introspection-1.0)]) + AC_SUBST([G_IR_GENERATE], [$($PKG_CONFIG --variable=g_ir_generate gobject-introspection-1.0)]) + AC_SUBST([GIRDIR], [$($PKG_CONFIG --define-variable=datadir=${datadir} --variable=girdir gobject-introspection-1.0)]) + AC_SUBST([GIRTYPELIBDIR], [$($PKG_CONFIG --define-variable=libdir=${libdir} --variable=typelibdir gobject-introspection-1.0)]) +]) +AM_CONDITIONAL([ENABLE_INTROSPECTION], [test "x$enable_introspection" = "xyes"]) +AM_CONDITIONAL([ENABLE_GUDEV], [test "x$enable_gudev" = "xyes"]) + +# ------------------------------------------------------------------------------ +# keymap - map custom hardware's multimedia keys +# ------------------------------------------------------------------------------ +AC_ARG_ENABLE([keymap], + AS_HELP_STRING([--disable-keymap], [disable keymap fixup support @<:@default=enabled@:>@]), + [], [enable_keymap=yes]) +AS_IF([test "x$enable_keymap" = "xyes"], [ + AC_PATH_PROG([GPERF], [gperf]) + if test -z "$GPERF"; then + AC_MSG_ERROR([gperf is needed]) + fi + + AC_CHECK_HEADER([linux/input.h], [:], AC_MSG_ERROR([kernel headers not found])) + AC_SUBST([INCLUDE_PREFIX], [$(echo '#include ' | eval $ac_cpp -E - | sed -n '/linux\/input.h/ {s:.*"\(.*\)/linux/input.h".*:\1:; p; q}')]) +]) +AM_CONDITIONAL([ENABLE_KEYMAP], [test "x$enable_keymap" = "xyes"]) + +# ------------------------------------------------------------------------------ +# mtd_probe - autoloads FTL module for mtd devices +# ------------------------------------------------------------------------------ +AC_ARG_ENABLE([mtd_probe], + AS_HELP_STRING([--disable-mtd_probe], [disable MTD support @<:@default=enabled@:>@]), + [], [enable_mtd_probe=yes]) +AM_CONDITIONAL([ENABLE_MTD_PROBE], [test "x$enable_mtd_probe" = "xyes"]) + +# ------------------------------------------------------------------------------ +# rule_generator - persistent network and optical device rule generator +# ------------------------------------------------------------------------------ +AC_ARG_ENABLE([rule_generator], + AS_HELP_STRING([--enable-rule_generator], [enable persistent network + cdrom links support @<:@default=disabled@:>@]), + [], [enable_rule_generator=no]) +AM_CONDITIONAL([ENABLE_RULE_GENERATOR], [test "x$enable_rule_generator" = "xyes"]) + +# ------------------------------------------------------------------------------ +# create_floppy_devices - historical floppy kernel device nodes (/dev/fd0h1440, ...) +# ------------------------------------------------------------------------------ +AC_ARG_ENABLE([floppy], + AS_HELP_STRING([--enable-floppy], [enable legacy floppy support @<:@default=disabled@:>@]), + [], [enable_floppy=no]) +AM_CONDITIONAL([ENABLE_FLOPPY], [test "x$enable_floppy" = "xyes"]) + +my_CFLAGS="-Wall \ +-Wmissing-declarations -Wmissing-prototypes \ +-Wnested-externs -Wpointer-arith \ +-Wpointer-arith -Wsign-compare -Wchar-subscripts \ +-Wstrict-prototypes -Wshadow \ +-Wformat-security -Wtype-limits" +AC_SUBST([my_CFLAGS]) + +AC_CONFIG_HEADERS(config.h) +AC_CONFIG_FILES([ + Makefile + src/docs/Makefile + src/docs/version.xml + src/gudev/docs/Makefile + src/gudev/docs/version.xml +]) + +AC_OUTPUT +AC_MSG_RESULT([ + $PACKAGE $VERSION + ======== + + prefix: ${prefix} + rootprefix: ${rootprefix} + sysconfdir: ${sysconfdir} + bindir: ${bindir} + libdir: ${libdir} + rootlibdir: ${rootlib_execdir} + libexecdir: ${libexecdir} + datarootdir: ${datarootdir} + mandir: ${mandir} + includedir: ${includedir} + include_prefix: ${INCLUDE_PREFIX} + systemdsystemunitdir: ${systemdsystemunitdir} + firmware path: ${FIRMWARE_PATH} + usb.ids: ${USB_DATABASE} + pci.ids: ${PCI_DATABASE} + + compiler: ${CC} + cflags: ${CFLAGS} + ldflags: ${LDFLAGS} + xsltproc: ${XSLTPROC} + gperf: ${GPERF} + + logging: ${enable_logging} + debug: ${enable_debug} + selinux: ${with_selinux} + + man pages ${enable_manpages} + gudev: ${enable_gudev} + gintrospection: ${enable_introspection} + keymap: ${enable_keymap} + mtd_probe: ${enable_mtd_probe} + rule_generator: ${enable_rule_generator} + floppy: ${enable_floppy} +]) diff --git a/src/udev/m4/.gitignore b/src/udev/m4/.gitignore new file mode 100644 index 000000000..0ca2c0372 --- /dev/null +++ b/src/udev/m4/.gitignore @@ -0,0 +1,4 @@ +libtool.m4 +lt*m4 +gtk-doc.m4 + diff --git a/rules/42-usb-hid-pm.rules b/src/udev/rules/42-usb-hid-pm.rules similarity index 100% rename from rules/42-usb-hid-pm.rules rename to src/udev/rules/42-usb-hid-pm.rules diff --git a/rules/50-udev-default.rules b/src/udev/rules/50-udev-default.rules similarity index 100% rename from rules/50-udev-default.rules rename to src/udev/rules/50-udev-default.rules diff --git a/rules/60-persistent-alsa.rules b/src/udev/rules/60-persistent-alsa.rules similarity index 100% rename from rules/60-persistent-alsa.rules rename to src/udev/rules/60-persistent-alsa.rules diff --git a/rules/60-persistent-input.rules b/src/udev/rules/60-persistent-input.rules similarity index 100% rename from rules/60-persistent-input.rules rename to src/udev/rules/60-persistent-input.rules diff --git a/rules/60-persistent-serial.rules b/src/udev/rules/60-persistent-serial.rules similarity index 100% rename from rules/60-persistent-serial.rules rename to src/udev/rules/60-persistent-serial.rules diff --git a/rules/60-persistent-storage-tape.rules b/src/udev/rules/60-persistent-storage-tape.rules similarity index 100% rename from rules/60-persistent-storage-tape.rules rename to src/udev/rules/60-persistent-storage-tape.rules diff --git a/rules/60-persistent-storage.rules b/src/udev/rules/60-persistent-storage.rules similarity index 100% rename from rules/60-persistent-storage.rules rename to src/udev/rules/60-persistent-storage.rules diff --git a/rules/75-net-description.rules b/src/udev/rules/75-net-description.rules similarity index 100% rename from rules/75-net-description.rules rename to src/udev/rules/75-net-description.rules diff --git a/rules/75-tty-description.rules b/src/udev/rules/75-tty-description.rules similarity index 100% rename from rules/75-tty-description.rules rename to src/udev/rules/75-tty-description.rules diff --git a/rules/78-sound-card.rules b/src/udev/rules/78-sound-card.rules similarity index 100% rename from rules/78-sound-card.rules rename to src/udev/rules/78-sound-card.rules diff --git a/rules/80-drivers.rules b/src/udev/rules/80-drivers.rules similarity index 100% rename from rules/80-drivers.rules rename to src/udev/rules/80-drivers.rules diff --git a/rules/95-udev-late.rules b/src/udev/rules/95-udev-late.rules similarity index 100% rename from rules/95-udev-late.rules rename to src/udev/rules/95-udev-late.rules diff --git a/src/udev/src/.gitignore b/src/udev/src/.gitignore new file mode 100644 index 000000000..beb8604bc --- /dev/null +++ b/src/udev/src/.gitignore @@ -0,0 +1,5 @@ +*.[78] +*.html +udev.pc +libudev.pc +udev*.service diff --git a/src/COPYING b/src/udev/src/COPYING similarity index 100% rename from src/COPYING rename to src/udev/src/COPYING diff --git a/src/accelerometer/61-accelerometer.rules b/src/udev/src/accelerometer/61-accelerometer.rules similarity index 100% rename from src/accelerometer/61-accelerometer.rules rename to src/udev/src/accelerometer/61-accelerometer.rules diff --git a/src/accelerometer/accelerometer.c b/src/udev/src/accelerometer/accelerometer.c similarity index 100% rename from src/accelerometer/accelerometer.c rename to src/udev/src/accelerometer/accelerometer.c diff --git a/src/ata_id/ata_id.c b/src/udev/src/ata_id/ata_id.c similarity index 100% rename from src/ata_id/ata_id.c rename to src/udev/src/ata_id/ata_id.c diff --git a/src/cdrom_id/60-cdrom_id.rules b/src/udev/src/cdrom_id/60-cdrom_id.rules similarity index 100% rename from src/cdrom_id/60-cdrom_id.rules rename to src/udev/src/cdrom_id/60-cdrom_id.rules diff --git a/src/cdrom_id/cdrom_id.c b/src/udev/src/cdrom_id/cdrom_id.c similarity index 100% rename from src/cdrom_id/cdrom_id.c rename to src/udev/src/cdrom_id/cdrom_id.c diff --git a/src/collect/collect.c b/src/udev/src/collect/collect.c similarity index 100% rename from src/collect/collect.c rename to src/udev/src/collect/collect.c diff --git a/src/docs/.gitignore b/src/udev/src/docs/.gitignore similarity index 100% rename from src/docs/.gitignore rename to src/udev/src/docs/.gitignore diff --git a/src/docs/Makefile.am b/src/udev/src/docs/Makefile.am similarity index 100% rename from src/docs/Makefile.am rename to src/udev/src/docs/Makefile.am diff --git a/src/docs/libudev-docs.xml b/src/udev/src/docs/libudev-docs.xml similarity index 100% rename from src/docs/libudev-docs.xml rename to src/udev/src/docs/libudev-docs.xml diff --git a/src/docs/libudev-sections.txt b/src/udev/src/docs/libudev-sections.txt similarity index 100% rename from src/docs/libudev-sections.txt rename to src/udev/src/docs/libudev-sections.txt diff --git a/src/docs/libudev.types b/src/udev/src/docs/libudev.types similarity index 100% rename from src/docs/libudev.types rename to src/udev/src/docs/libudev.types diff --git a/src/docs/version.xml.in b/src/udev/src/docs/version.xml.in similarity index 100% rename from src/docs/version.xml.in rename to src/udev/src/docs/version.xml.in diff --git a/src/floppy/60-floppy.rules b/src/udev/src/floppy/60-floppy.rules similarity index 100% rename from src/floppy/60-floppy.rules rename to src/udev/src/floppy/60-floppy.rules diff --git a/src/floppy/create_floppy_devices.c b/src/udev/src/floppy/create_floppy_devices.c similarity index 100% rename from src/floppy/create_floppy_devices.c rename to src/udev/src/floppy/create_floppy_devices.c diff --git a/src/gudev/.gitignore b/src/udev/src/gudev/.gitignore similarity index 100% rename from src/gudev/.gitignore rename to src/udev/src/gudev/.gitignore diff --git a/src/gudev/COPYING b/src/udev/src/gudev/COPYING similarity index 100% rename from src/gudev/COPYING rename to src/udev/src/gudev/COPYING diff --git a/src/gudev/docs/.gitignore b/src/udev/src/gudev/docs/.gitignore similarity index 100% rename from src/gudev/docs/.gitignore rename to src/udev/src/gudev/docs/.gitignore diff --git a/src/gudev/docs/Makefile.am b/src/udev/src/gudev/docs/Makefile.am similarity index 100% rename from src/gudev/docs/Makefile.am rename to src/udev/src/gudev/docs/Makefile.am diff --git a/src/gudev/docs/gudev-docs.xml b/src/udev/src/gudev/docs/gudev-docs.xml similarity index 100% rename from src/gudev/docs/gudev-docs.xml rename to src/udev/src/gudev/docs/gudev-docs.xml diff --git a/src/gudev/docs/gudev-sections.txt b/src/udev/src/gudev/docs/gudev-sections.txt similarity index 100% rename from src/gudev/docs/gudev-sections.txt rename to src/udev/src/gudev/docs/gudev-sections.txt diff --git a/src/gudev/docs/gudev.types b/src/udev/src/gudev/docs/gudev.types similarity index 100% rename from src/gudev/docs/gudev.types rename to src/udev/src/gudev/docs/gudev.types diff --git a/src/gudev/docs/version.xml.in b/src/udev/src/gudev/docs/version.xml.in similarity index 100% rename from src/gudev/docs/version.xml.in rename to src/udev/src/gudev/docs/version.xml.in diff --git a/src/gudev/gjs-example.js b/src/udev/src/gudev/gjs-example.js similarity index 100% rename from src/gudev/gjs-example.js rename to src/udev/src/gudev/gjs-example.js diff --git a/src/gudev/gudev-1.0.pc.in b/src/udev/src/gudev/gudev-1.0.pc.in similarity index 100% rename from src/gudev/gudev-1.0.pc.in rename to src/udev/src/gudev/gudev-1.0.pc.in diff --git a/src/gudev/gudev.h b/src/udev/src/gudev/gudev.h similarity index 100% rename from src/gudev/gudev.h rename to src/udev/src/gudev/gudev.h diff --git a/src/gudev/gudevclient.c b/src/udev/src/gudev/gudevclient.c similarity index 100% rename from src/gudev/gudevclient.c rename to src/udev/src/gudev/gudevclient.c diff --git a/src/gudev/gudevclient.h b/src/udev/src/gudev/gudevclient.h similarity index 100% rename from src/gudev/gudevclient.h rename to src/udev/src/gudev/gudevclient.h diff --git a/src/gudev/gudevdevice.c b/src/udev/src/gudev/gudevdevice.c similarity index 100% rename from src/gudev/gudevdevice.c rename to src/udev/src/gudev/gudevdevice.c diff --git a/src/gudev/gudevdevice.h b/src/udev/src/gudev/gudevdevice.h similarity index 100% rename from src/gudev/gudevdevice.h rename to src/udev/src/gudev/gudevdevice.h diff --git a/src/gudev/gudevenumerator.c b/src/udev/src/gudev/gudevenumerator.c similarity index 100% rename from src/gudev/gudevenumerator.c rename to src/udev/src/gudev/gudevenumerator.c diff --git a/src/gudev/gudevenumerator.h b/src/udev/src/gudev/gudevenumerator.h similarity index 100% rename from src/gudev/gudevenumerator.h rename to src/udev/src/gudev/gudevenumerator.h diff --git a/src/gudev/gudevenums.h b/src/udev/src/gudev/gudevenums.h similarity index 100% rename from src/gudev/gudevenums.h rename to src/udev/src/gudev/gudevenums.h diff --git a/src/gudev/gudevenumtypes.c.template b/src/udev/src/gudev/gudevenumtypes.c.template similarity index 100% rename from src/gudev/gudevenumtypes.c.template rename to src/udev/src/gudev/gudevenumtypes.c.template diff --git a/src/gudev/gudevenumtypes.h.template b/src/udev/src/gudev/gudevenumtypes.h.template similarity index 100% rename from src/gudev/gudevenumtypes.h.template rename to src/udev/src/gudev/gudevenumtypes.h.template diff --git a/src/gudev/gudevmarshal.list b/src/udev/src/gudev/gudevmarshal.list similarity index 100% rename from src/gudev/gudevmarshal.list rename to src/udev/src/gudev/gudevmarshal.list diff --git a/src/gudev/gudevprivate.h b/src/udev/src/gudev/gudevprivate.h similarity index 100% rename from src/gudev/gudevprivate.h rename to src/udev/src/gudev/gudevprivate.h diff --git a/src/gudev/gudevtypes.h b/src/udev/src/gudev/gudevtypes.h similarity index 100% rename from src/gudev/gudevtypes.h rename to src/udev/src/gudev/gudevtypes.h diff --git a/src/gudev/seed-example-enum.js b/src/udev/src/gudev/seed-example-enum.js similarity index 100% rename from src/gudev/seed-example-enum.js rename to src/udev/src/gudev/seed-example-enum.js diff --git a/src/gudev/seed-example.js b/src/udev/src/gudev/seed-example.js similarity index 100% rename from src/gudev/seed-example.js rename to src/udev/src/gudev/seed-example.js diff --git a/src/keymap/.gitignore b/src/udev/src/keymap/.gitignore similarity index 100% rename from src/keymap/.gitignore rename to src/udev/src/keymap/.gitignore diff --git a/src/keymap/95-keyboard-force-release.rules b/src/udev/src/keymap/95-keyboard-force-release.rules similarity index 100% rename from src/keymap/95-keyboard-force-release.rules rename to src/udev/src/keymap/95-keyboard-force-release.rules diff --git a/src/keymap/95-keymap.rules b/src/udev/src/keymap/95-keymap.rules similarity index 100% rename from src/keymap/95-keymap.rules rename to src/udev/src/keymap/95-keymap.rules diff --git a/src/keymap/README.keymap.txt b/src/udev/src/keymap/README.keymap.txt similarity index 100% rename from src/keymap/README.keymap.txt rename to src/udev/src/keymap/README.keymap.txt diff --git a/src/keymap/check-keymaps.sh b/src/udev/src/keymap/check-keymaps.sh similarity index 100% rename from src/keymap/check-keymaps.sh rename to src/udev/src/keymap/check-keymaps.sh diff --git a/src/keymap/findkeyboards b/src/udev/src/keymap/findkeyboards similarity index 100% rename from src/keymap/findkeyboards rename to src/udev/src/keymap/findkeyboards diff --git a/src/keymap/force-release-maps/common-volume-keys b/src/udev/src/keymap/force-release-maps/common-volume-keys similarity index 100% rename from src/keymap/force-release-maps/common-volume-keys rename to src/udev/src/keymap/force-release-maps/common-volume-keys diff --git a/src/keymap/force-release-maps/dell-touchpad b/src/udev/src/keymap/force-release-maps/dell-touchpad similarity index 100% rename from src/keymap/force-release-maps/dell-touchpad rename to src/udev/src/keymap/force-release-maps/dell-touchpad diff --git a/src/keymap/force-release-maps/hp-other b/src/udev/src/keymap/force-release-maps/hp-other similarity index 100% rename from src/keymap/force-release-maps/hp-other rename to src/udev/src/keymap/force-release-maps/hp-other diff --git a/src/keymap/force-release-maps/samsung-90x3a b/src/udev/src/keymap/force-release-maps/samsung-90x3a similarity index 100% rename from src/keymap/force-release-maps/samsung-90x3a rename to src/udev/src/keymap/force-release-maps/samsung-90x3a diff --git a/src/keymap/force-release-maps/samsung-other b/src/udev/src/keymap/force-release-maps/samsung-other similarity index 100% rename from src/keymap/force-release-maps/samsung-other rename to src/udev/src/keymap/force-release-maps/samsung-other diff --git a/src/keymap/keyboard-force-release.sh.in b/src/udev/src/keymap/keyboard-force-release.sh.in similarity index 100% rename from src/keymap/keyboard-force-release.sh.in rename to src/udev/src/keymap/keyboard-force-release.sh.in diff --git a/src/keymap/keymap.c b/src/udev/src/keymap/keymap.c similarity index 100% rename from src/keymap/keymap.c rename to src/udev/src/keymap/keymap.c diff --git a/src/keymap/keymaps/acer b/src/udev/src/keymap/keymaps/acer similarity index 100% rename from src/keymap/keymaps/acer rename to src/udev/src/keymap/keymaps/acer diff --git a/src/keymap/keymaps/acer-aspire_5720 b/src/udev/src/keymap/keymaps/acer-aspire_5720 similarity index 100% rename from src/keymap/keymaps/acer-aspire_5720 rename to src/udev/src/keymap/keymaps/acer-aspire_5720 diff --git a/src/keymap/keymaps/acer-aspire_5920g b/src/udev/src/keymap/keymaps/acer-aspire_5920g similarity index 100% rename from src/keymap/keymaps/acer-aspire_5920g rename to src/udev/src/keymap/keymaps/acer-aspire_5920g diff --git a/src/keymap/keymaps/acer-aspire_6920 b/src/udev/src/keymap/keymaps/acer-aspire_6920 similarity index 100% rename from src/keymap/keymaps/acer-aspire_6920 rename to src/udev/src/keymap/keymaps/acer-aspire_6920 diff --git a/src/keymap/keymaps/acer-aspire_8930 b/src/udev/src/keymap/keymaps/acer-aspire_8930 similarity index 100% rename from src/keymap/keymaps/acer-aspire_8930 rename to src/udev/src/keymap/keymaps/acer-aspire_8930 diff --git a/src/keymap/keymaps/acer-travelmate_c300 b/src/udev/src/keymap/keymaps/acer-travelmate_c300 similarity index 100% rename from src/keymap/keymaps/acer-travelmate_c300 rename to src/udev/src/keymap/keymaps/acer-travelmate_c300 diff --git a/src/keymap/keymaps/asus b/src/udev/src/keymap/keymaps/asus similarity index 100% rename from src/keymap/keymaps/asus rename to src/udev/src/keymap/keymaps/asus diff --git a/src/keymap/keymaps/compaq-e_evo b/src/udev/src/keymap/keymaps/compaq-e_evo similarity index 100% rename from src/keymap/keymaps/compaq-e_evo rename to src/udev/src/keymap/keymaps/compaq-e_evo diff --git a/src/keymap/keymaps/dell b/src/udev/src/keymap/keymaps/dell similarity index 100% rename from src/keymap/keymaps/dell rename to src/udev/src/keymap/keymaps/dell diff --git a/src/keymap/keymaps/dell-latitude-xt2 b/src/udev/src/keymap/keymaps/dell-latitude-xt2 similarity index 100% rename from src/keymap/keymaps/dell-latitude-xt2 rename to src/udev/src/keymap/keymaps/dell-latitude-xt2 diff --git a/src/keymap/keymaps/everex-xt5000 b/src/udev/src/keymap/keymaps/everex-xt5000 similarity index 100% rename from src/keymap/keymaps/everex-xt5000 rename to src/udev/src/keymap/keymaps/everex-xt5000 diff --git a/src/keymap/keymaps/fujitsu-amilo_li_2732 b/src/udev/src/keymap/keymaps/fujitsu-amilo_li_2732 similarity index 100% rename from src/keymap/keymaps/fujitsu-amilo_li_2732 rename to src/udev/src/keymap/keymaps/fujitsu-amilo_li_2732 diff --git a/src/keymap/keymaps/fujitsu-amilo_pa_2548 b/src/udev/src/keymap/keymaps/fujitsu-amilo_pa_2548 similarity index 100% rename from src/keymap/keymaps/fujitsu-amilo_pa_2548 rename to src/udev/src/keymap/keymaps/fujitsu-amilo_pa_2548 diff --git a/src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 b/src/udev/src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 similarity index 100% rename from src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 rename to src/udev/src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 diff --git a/src/keymap/keymaps/fujitsu-amilo_pro_v3205 b/src/udev/src/keymap/keymaps/fujitsu-amilo_pro_v3205 similarity index 100% rename from src/keymap/keymaps/fujitsu-amilo_pro_v3205 rename to src/udev/src/keymap/keymaps/fujitsu-amilo_pro_v3205 diff --git a/src/keymap/keymaps/fujitsu-amilo_si_1520 b/src/udev/src/keymap/keymaps/fujitsu-amilo_si_1520 similarity index 100% rename from src/keymap/keymaps/fujitsu-amilo_si_1520 rename to src/udev/src/keymap/keymaps/fujitsu-amilo_si_1520 diff --git a/src/keymap/keymaps/fujitsu-esprimo_mobile_v5 b/src/udev/src/keymap/keymaps/fujitsu-esprimo_mobile_v5 similarity index 100% rename from src/keymap/keymaps/fujitsu-esprimo_mobile_v5 rename to src/udev/src/keymap/keymaps/fujitsu-esprimo_mobile_v5 diff --git a/src/keymap/keymaps/fujitsu-esprimo_mobile_v6 b/src/udev/src/keymap/keymaps/fujitsu-esprimo_mobile_v6 similarity index 100% rename from src/keymap/keymaps/fujitsu-esprimo_mobile_v6 rename to src/udev/src/keymap/keymaps/fujitsu-esprimo_mobile_v6 diff --git a/src/keymap/keymaps/genius-slimstar-320 b/src/udev/src/keymap/keymaps/genius-slimstar-320 similarity index 100% rename from src/keymap/keymaps/genius-slimstar-320 rename to src/udev/src/keymap/keymaps/genius-slimstar-320 diff --git a/src/keymap/keymaps/hewlett-packard b/src/udev/src/keymap/keymaps/hewlett-packard similarity index 100% rename from src/keymap/keymaps/hewlett-packard rename to src/udev/src/keymap/keymaps/hewlett-packard diff --git a/src/keymap/keymaps/hewlett-packard-2510p_2530p b/src/udev/src/keymap/keymaps/hewlett-packard-2510p_2530p similarity index 100% rename from src/keymap/keymaps/hewlett-packard-2510p_2530p rename to src/udev/src/keymap/keymaps/hewlett-packard-2510p_2530p diff --git a/src/keymap/keymaps/hewlett-packard-compaq_elitebook b/src/udev/src/keymap/keymaps/hewlett-packard-compaq_elitebook similarity index 100% rename from src/keymap/keymaps/hewlett-packard-compaq_elitebook rename to src/udev/src/keymap/keymaps/hewlett-packard-compaq_elitebook diff --git a/src/keymap/keymaps/hewlett-packard-pavilion b/src/udev/src/keymap/keymaps/hewlett-packard-pavilion similarity index 100% rename from src/keymap/keymaps/hewlett-packard-pavilion rename to src/udev/src/keymap/keymaps/hewlett-packard-pavilion diff --git a/src/keymap/keymaps/hewlett-packard-presario-2100 b/src/udev/src/keymap/keymaps/hewlett-packard-presario-2100 similarity index 100% rename from src/keymap/keymaps/hewlett-packard-presario-2100 rename to src/udev/src/keymap/keymaps/hewlett-packard-presario-2100 diff --git a/src/keymap/keymaps/hewlett-packard-tablet b/src/udev/src/keymap/keymaps/hewlett-packard-tablet similarity index 100% rename from src/keymap/keymaps/hewlett-packard-tablet rename to src/udev/src/keymap/keymaps/hewlett-packard-tablet diff --git a/src/keymap/keymaps/hewlett-packard-tx2 b/src/udev/src/keymap/keymaps/hewlett-packard-tx2 similarity index 100% rename from src/keymap/keymaps/hewlett-packard-tx2 rename to src/udev/src/keymap/keymaps/hewlett-packard-tx2 diff --git a/src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint b/src/udev/src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint similarity index 100% rename from src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint rename to src/udev/src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint diff --git a/src/keymap/keymaps/inventec-symphony_6.0_7.0 b/src/udev/src/keymap/keymaps/inventec-symphony_6.0_7.0 similarity index 100% rename from src/keymap/keymaps/inventec-symphony_6.0_7.0 rename to src/udev/src/keymap/keymaps/inventec-symphony_6.0_7.0 diff --git a/src/keymap/keymaps/lenovo-3000 b/src/udev/src/keymap/keymaps/lenovo-3000 similarity index 100% rename from src/keymap/keymaps/lenovo-3000 rename to src/udev/src/keymap/keymaps/lenovo-3000 diff --git a/src/keymap/keymaps/lenovo-ideapad b/src/udev/src/keymap/keymaps/lenovo-ideapad similarity index 100% rename from src/keymap/keymaps/lenovo-ideapad rename to src/udev/src/keymap/keymaps/lenovo-ideapad diff --git a/src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint b/src/udev/src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint similarity index 100% rename from src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint rename to src/udev/src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint diff --git a/src/keymap/keymaps/lenovo-thinkpad_x200_tablet b/src/udev/src/keymap/keymaps/lenovo-thinkpad_x200_tablet similarity index 100% rename from src/keymap/keymaps/lenovo-thinkpad_x200_tablet rename to src/udev/src/keymap/keymaps/lenovo-thinkpad_x200_tablet diff --git a/src/keymap/keymaps/lenovo-thinkpad_x6_tablet b/src/udev/src/keymap/keymaps/lenovo-thinkpad_x6_tablet similarity index 100% rename from src/keymap/keymaps/lenovo-thinkpad_x6_tablet rename to src/udev/src/keymap/keymaps/lenovo-thinkpad_x6_tablet diff --git a/src/keymap/keymaps/lg-x110 b/src/udev/src/keymap/keymaps/lg-x110 similarity index 100% rename from src/keymap/keymaps/lg-x110 rename to src/udev/src/keymap/keymaps/lg-x110 diff --git a/src/keymap/keymaps/logitech-wave b/src/udev/src/keymap/keymaps/logitech-wave similarity index 100% rename from src/keymap/keymaps/logitech-wave rename to src/udev/src/keymap/keymaps/logitech-wave diff --git a/src/keymap/keymaps/logitech-wave-cordless b/src/udev/src/keymap/keymaps/logitech-wave-cordless similarity index 100% rename from src/keymap/keymaps/logitech-wave-cordless rename to src/udev/src/keymap/keymaps/logitech-wave-cordless diff --git a/src/keymap/keymaps/logitech-wave-pro-cordless b/src/udev/src/keymap/keymaps/logitech-wave-pro-cordless similarity index 100% rename from src/keymap/keymaps/logitech-wave-pro-cordless rename to src/udev/src/keymap/keymaps/logitech-wave-pro-cordless diff --git a/src/keymap/keymaps/maxdata-pro_7000 b/src/udev/src/keymap/keymaps/maxdata-pro_7000 similarity index 100% rename from src/keymap/keymaps/maxdata-pro_7000 rename to src/udev/src/keymap/keymaps/maxdata-pro_7000 diff --git a/src/keymap/keymaps/medion-fid2060 b/src/udev/src/keymap/keymaps/medion-fid2060 similarity index 100% rename from src/keymap/keymaps/medion-fid2060 rename to src/udev/src/keymap/keymaps/medion-fid2060 diff --git a/src/keymap/keymaps/medionnb-a555 b/src/udev/src/keymap/keymaps/medionnb-a555 similarity index 100% rename from src/keymap/keymaps/medionnb-a555 rename to src/udev/src/keymap/keymaps/medionnb-a555 diff --git a/src/keymap/keymaps/micro-star b/src/udev/src/keymap/keymaps/micro-star similarity index 100% rename from src/keymap/keymaps/micro-star rename to src/udev/src/keymap/keymaps/micro-star diff --git a/src/keymap/keymaps/module-asus-w3j b/src/udev/src/keymap/keymaps/module-asus-w3j similarity index 100% rename from src/keymap/keymaps/module-asus-w3j rename to src/udev/src/keymap/keymaps/module-asus-w3j diff --git a/src/keymap/keymaps/module-ibm b/src/udev/src/keymap/keymaps/module-ibm similarity index 100% rename from src/keymap/keymaps/module-ibm rename to src/udev/src/keymap/keymaps/module-ibm diff --git a/src/keymap/keymaps/module-lenovo b/src/udev/src/keymap/keymaps/module-lenovo similarity index 100% rename from src/keymap/keymaps/module-lenovo rename to src/udev/src/keymap/keymaps/module-lenovo diff --git a/src/keymap/keymaps/module-sony b/src/udev/src/keymap/keymaps/module-sony similarity index 100% rename from src/keymap/keymaps/module-sony rename to src/udev/src/keymap/keymaps/module-sony diff --git a/src/keymap/keymaps/module-sony-old b/src/udev/src/keymap/keymaps/module-sony-old similarity index 100% rename from src/keymap/keymaps/module-sony-old rename to src/udev/src/keymap/keymaps/module-sony-old diff --git a/src/keymap/keymaps/module-sony-vgn b/src/udev/src/keymap/keymaps/module-sony-vgn similarity index 100% rename from src/keymap/keymaps/module-sony-vgn rename to src/udev/src/keymap/keymaps/module-sony-vgn diff --git a/src/keymap/keymaps/olpc-xo b/src/udev/src/keymap/keymaps/olpc-xo similarity index 100% rename from src/keymap/keymaps/olpc-xo rename to src/udev/src/keymap/keymaps/olpc-xo diff --git a/src/keymap/keymaps/onkyo b/src/udev/src/keymap/keymaps/onkyo similarity index 100% rename from src/keymap/keymaps/onkyo rename to src/udev/src/keymap/keymaps/onkyo diff --git a/src/keymap/keymaps/oqo-model2 b/src/udev/src/keymap/keymaps/oqo-model2 similarity index 100% rename from src/keymap/keymaps/oqo-model2 rename to src/udev/src/keymap/keymaps/oqo-model2 diff --git a/src/keymap/keymaps/samsung-90x3a b/src/udev/src/keymap/keymaps/samsung-90x3a similarity index 100% rename from src/keymap/keymaps/samsung-90x3a rename to src/udev/src/keymap/keymaps/samsung-90x3a diff --git a/src/keymap/keymaps/samsung-other b/src/udev/src/keymap/keymaps/samsung-other similarity index 100% rename from src/keymap/keymaps/samsung-other rename to src/udev/src/keymap/keymaps/samsung-other diff --git a/src/keymap/keymaps/samsung-sq1us b/src/udev/src/keymap/keymaps/samsung-sq1us similarity index 100% rename from src/keymap/keymaps/samsung-sq1us rename to src/udev/src/keymap/keymaps/samsung-sq1us diff --git a/src/keymap/keymaps/samsung-sx20s b/src/udev/src/keymap/keymaps/samsung-sx20s similarity index 100% rename from src/keymap/keymaps/samsung-sx20s rename to src/udev/src/keymap/keymaps/samsung-sx20s diff --git a/src/keymap/keymaps/toshiba-satellite_a100 b/src/udev/src/keymap/keymaps/toshiba-satellite_a100 similarity index 100% rename from src/keymap/keymaps/toshiba-satellite_a100 rename to src/udev/src/keymap/keymaps/toshiba-satellite_a100 diff --git a/src/keymap/keymaps/toshiba-satellite_a110 b/src/udev/src/keymap/keymaps/toshiba-satellite_a110 similarity index 100% rename from src/keymap/keymaps/toshiba-satellite_a110 rename to src/udev/src/keymap/keymaps/toshiba-satellite_a110 diff --git a/src/keymap/keymaps/toshiba-satellite_m30x b/src/udev/src/keymap/keymaps/toshiba-satellite_m30x similarity index 100% rename from src/keymap/keymaps/toshiba-satellite_m30x rename to src/udev/src/keymap/keymaps/toshiba-satellite_m30x diff --git a/src/keymap/keymaps/zepto-znote b/src/udev/src/keymap/keymaps/zepto-znote similarity index 100% rename from src/keymap/keymaps/zepto-znote rename to src/udev/src/keymap/keymaps/zepto-znote diff --git a/src/libudev-device-private.c b/src/udev/src/libudev-device-private.c similarity index 100% rename from src/libudev-device-private.c rename to src/udev/src/libudev-device-private.c diff --git a/src/libudev-device.c b/src/udev/src/libudev-device.c similarity index 100% rename from src/libudev-device.c rename to src/udev/src/libudev-device.c diff --git a/src/libudev-enumerate.c b/src/udev/src/libudev-enumerate.c similarity index 100% rename from src/libudev-enumerate.c rename to src/udev/src/libudev-enumerate.c diff --git a/src/libudev-list.c b/src/udev/src/libudev-list.c similarity index 100% rename from src/libudev-list.c rename to src/udev/src/libudev-list.c diff --git a/src/libudev-monitor.c b/src/udev/src/libudev-monitor.c similarity index 100% rename from src/libudev-monitor.c rename to src/udev/src/libudev-monitor.c diff --git a/src/libudev-private.h b/src/udev/src/libudev-private.h similarity index 100% rename from src/libudev-private.h rename to src/udev/src/libudev-private.h diff --git a/src/libudev-queue-private.c b/src/udev/src/libudev-queue-private.c similarity index 100% rename from src/libudev-queue-private.c rename to src/udev/src/libudev-queue-private.c diff --git a/src/libudev-queue.c b/src/udev/src/libudev-queue.c similarity index 100% rename from src/libudev-queue.c rename to src/udev/src/libudev-queue.c diff --git a/src/libudev-selinux-private.c b/src/udev/src/libudev-selinux-private.c similarity index 100% rename from src/libudev-selinux-private.c rename to src/udev/src/libudev-selinux-private.c diff --git a/src/libudev-util-private.c b/src/udev/src/libudev-util-private.c similarity index 100% rename from src/libudev-util-private.c rename to src/udev/src/libudev-util-private.c diff --git a/src/libudev-util.c b/src/udev/src/libudev-util.c similarity index 100% rename from src/libudev-util.c rename to src/udev/src/libudev-util.c diff --git a/src/libudev.c b/src/udev/src/libudev.c similarity index 100% rename from src/libudev.c rename to src/udev/src/libudev.c diff --git a/src/libudev.h b/src/udev/src/libudev.h similarity index 100% rename from src/libudev.h rename to src/udev/src/libudev.h diff --git a/src/libudev.pc.in b/src/udev/src/libudev.pc.in similarity index 100% rename from src/libudev.pc.in rename to src/udev/src/libudev.pc.in diff --git a/src/mtd_probe/75-probe_mtd.rules b/src/udev/src/mtd_probe/75-probe_mtd.rules similarity index 100% rename from src/mtd_probe/75-probe_mtd.rules rename to src/udev/src/mtd_probe/75-probe_mtd.rules diff --git a/src/mtd_probe/mtd_probe.c b/src/udev/src/mtd_probe/mtd_probe.c similarity index 100% rename from src/mtd_probe/mtd_probe.c rename to src/udev/src/mtd_probe/mtd_probe.c diff --git a/src/mtd_probe/mtd_probe.h b/src/udev/src/mtd_probe/mtd_probe.h similarity index 100% rename from src/mtd_probe/mtd_probe.h rename to src/udev/src/mtd_probe/mtd_probe.h diff --git a/src/mtd_probe/probe_smartmedia.c b/src/udev/src/mtd_probe/probe_smartmedia.c similarity index 100% rename from src/mtd_probe/probe_smartmedia.c rename to src/udev/src/mtd_probe/probe_smartmedia.c diff --git a/src/rule_generator/75-cd-aliases-generator.rules b/src/udev/src/rule_generator/75-cd-aliases-generator.rules similarity index 100% rename from src/rule_generator/75-cd-aliases-generator.rules rename to src/udev/src/rule_generator/75-cd-aliases-generator.rules diff --git a/src/rule_generator/75-persistent-net-generator.rules b/src/udev/src/rule_generator/75-persistent-net-generator.rules similarity index 100% rename from src/rule_generator/75-persistent-net-generator.rules rename to src/udev/src/rule_generator/75-persistent-net-generator.rules diff --git a/src/rule_generator/rule_generator.functions b/src/udev/src/rule_generator/rule_generator.functions similarity index 100% rename from src/rule_generator/rule_generator.functions rename to src/udev/src/rule_generator/rule_generator.functions diff --git a/src/rule_generator/write_cd_rules b/src/udev/src/rule_generator/write_cd_rules similarity index 100% rename from src/rule_generator/write_cd_rules rename to src/udev/src/rule_generator/write_cd_rules diff --git a/src/rule_generator/write_net_rules b/src/udev/src/rule_generator/write_net_rules similarity index 100% rename from src/rule_generator/write_net_rules rename to src/udev/src/rule_generator/write_net_rules diff --git a/src/scsi_id/.gitignore b/src/udev/src/scsi_id/.gitignore similarity index 100% rename from src/scsi_id/.gitignore rename to src/udev/src/scsi_id/.gitignore diff --git a/src/scsi_id/README b/src/udev/src/scsi_id/README similarity index 100% rename from src/scsi_id/README rename to src/udev/src/scsi_id/README diff --git a/src/scsi_id/scsi.h b/src/udev/src/scsi_id/scsi.h similarity index 100% rename from src/scsi_id/scsi.h rename to src/udev/src/scsi_id/scsi.h diff --git a/src/scsi_id/scsi_id.8 b/src/udev/src/scsi_id/scsi_id.8 similarity index 100% rename from src/scsi_id/scsi_id.8 rename to src/udev/src/scsi_id/scsi_id.8 diff --git a/src/scsi_id/scsi_id.c b/src/udev/src/scsi_id/scsi_id.c similarity index 100% rename from src/scsi_id/scsi_id.c rename to src/udev/src/scsi_id/scsi_id.c diff --git a/src/scsi_id/scsi_id.h b/src/udev/src/scsi_id/scsi_id.h similarity index 100% rename from src/scsi_id/scsi_id.h rename to src/udev/src/scsi_id/scsi_id.h diff --git a/src/scsi_id/scsi_serial.c b/src/udev/src/scsi_id/scsi_serial.c similarity index 100% rename from src/scsi_id/scsi_serial.c rename to src/udev/src/scsi_id/scsi_serial.c diff --git a/src/udev/src/sd-daemon.c b/src/udev/src/sd-daemon.c new file mode 100644 index 000000000..763e079b4 --- /dev/null +++ b/src/udev/src/sd-daemon.c @@ -0,0 +1,530 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + Copyright 2010 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#ifdef __BIONIC__ +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) +#include +#endif + +#include "sd-daemon.h" + +#if (__GNUC__ >= 4) +#ifdef SD_EXPORT_SYMBOLS +/* Export symbols */ +#define _sd_export_ __attribute__ ((visibility("default"))) +#else +/* Don't export the symbols */ +#define _sd_export_ __attribute__ ((visibility("hidden"))) +#endif +#else +#define _sd_export_ +#endif + +_sd_export_ int sd_listen_fds(int unset_environment) { + +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) + return 0; +#else + int r, fd; + const char *e; + char *p = NULL; + unsigned long l; + + if (!(e = getenv("LISTEN_PID"))) { + r = 0; + goto finish; + } + + errno = 0; + l = strtoul(e, &p, 10); + + if (errno != 0) { + r = -errno; + goto finish; + } + + if (!p || *p || l <= 0) { + r = -EINVAL; + goto finish; + } + + /* Is this for us? */ + if (getpid() != (pid_t) l) { + r = 0; + goto finish; + } + + if (!(e = getenv("LISTEN_FDS"))) { + r = 0; + goto finish; + } + + errno = 0; + l = strtoul(e, &p, 10); + + if (errno != 0) { + r = -errno; + goto finish; + } + + if (!p || *p) { + r = -EINVAL; + goto finish; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) { + int flags; + + if ((flags = fcntl(fd, F_GETFD)) < 0) { + r = -errno; + goto finish; + } + + if (flags & FD_CLOEXEC) + continue; + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) { + r = -errno; + goto finish; + } + } + + r = (int) l; + +finish: + if (unset_environment) { + unsetenv("LISTEN_PID"); + unsetenv("LISTEN_FDS"); + } + + return r; +#endif +} + +_sd_export_ int sd_is_fifo(int fd, const char *path) { + struct stat st_fd; + + if (fd < 0) + return -EINVAL; + + memset(&st_fd, 0, sizeof(st_fd)); + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISFIFO(st_fd.st_mode)) + return 0; + + if (path) { + struct stat st_path; + + memset(&st_path, 0, sizeof(st_path)); + if (stat(path, &st_path) < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + return -errno; + } + + return + st_path.st_dev == st_fd.st_dev && + st_path.st_ino == st_fd.st_ino; + } + + return 1; +} + +_sd_export_ int sd_is_special(int fd, const char *path) { + struct stat st_fd; + + if (fd < 0) + return -EINVAL; + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISREG(st_fd.st_mode) && !S_ISCHR(st_fd.st_mode)) + return 0; + + if (path) { + struct stat st_path; + + if (stat(path, &st_path) < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + return -errno; + } + + if (S_ISREG(st_fd.st_mode) && S_ISREG(st_path.st_mode)) + return + st_path.st_dev == st_fd.st_dev && + st_path.st_ino == st_fd.st_ino; + else if (S_ISCHR(st_fd.st_mode) && S_ISCHR(st_path.st_mode)) + return st_path.st_rdev == st_fd.st_rdev; + else + return 0; + } + + return 1; +} + +static int sd_is_socket_internal(int fd, int type, int listening) { + struct stat st_fd; + + if (fd < 0 || type < 0) + return -EINVAL; + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISSOCK(st_fd.st_mode)) + return 0; + + if (type != 0) { + int other_type = 0; + socklen_t l = sizeof(other_type); + + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0) + return -errno; + + if (l != sizeof(other_type)) + return -EINVAL; + + if (other_type != type) + return 0; + } + + if (listening >= 0) { + int accepting = 0; + socklen_t l = sizeof(accepting); + + if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0) + return -errno; + + if (l != sizeof(accepting)) + return -EINVAL; + + if (!accepting != !listening) + return 0; + } + + return 1; +} + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + struct sockaddr_un un; + struct sockaddr_storage storage; +}; + +_sd_export_ int sd_is_socket(int fd, int family, int type, int listening) { + int r; + + if (family < 0) + return -EINVAL; + + if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) + return r; + + if (family > 0) { + union sockaddr_union sockaddr; + socklen_t l; + + memset(&sockaddr, 0, sizeof(sockaddr)); + l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + return sockaddr.sa.sa_family == family; + } + + return 1; +} + +_sd_export_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) { + union sockaddr_union sockaddr; + socklen_t l; + int r; + + if (family != 0 && family != AF_INET && family != AF_INET6) + return -EINVAL; + + if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) + return r; + + memset(&sockaddr, 0, sizeof(sockaddr)); + l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + if (sockaddr.sa.sa_family != AF_INET && + sockaddr.sa.sa_family != AF_INET6) + return 0; + + if (family > 0) + if (sockaddr.sa.sa_family != family) + return 0; + + if (port > 0) { + if (sockaddr.sa.sa_family == AF_INET) { + if (l < sizeof(struct sockaddr_in)) + return -EINVAL; + + return htons(port) == sockaddr.in4.sin_port; + } else { + if (l < sizeof(struct sockaddr_in6)) + return -EINVAL; + + return htons(port) == sockaddr.in6.sin6_port; + } + } + + return 1; +} + +_sd_export_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) { + union sockaddr_union sockaddr; + socklen_t l; + int r; + + if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) + return r; + + memset(&sockaddr, 0, sizeof(sockaddr)); + l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + if (sockaddr.sa.sa_family != AF_UNIX) + return 0; + + if (path) { + if (length <= 0) + length = strlen(path); + + if (length <= 0) + /* Unnamed socket */ + return l == offsetof(struct sockaddr_un, sun_path); + + if (path[0]) + /* Normal path socket */ + return + (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) && + memcmp(path, sockaddr.un.sun_path, length+1) == 0; + else + /* Abstract namespace socket */ + return + (l == offsetof(struct sockaddr_un, sun_path) + length) && + memcmp(path, sockaddr.un.sun_path, length) == 0; + } + + return 1; +} + +_sd_export_ int sd_is_mq(int fd, const char *path) { +#if !defined(__linux__) + return 0; +#else + struct mq_attr attr; + + if (fd < 0) + return -EINVAL; + + if (mq_getattr(fd, &attr) < 0) + return -errno; + + if (path) { + char fpath[PATH_MAX]; + struct stat a, b; + + if (path[0] != '/') + return -EINVAL; + + if (fstat(fd, &a) < 0) + return -errno; + + strncpy(stpcpy(fpath, "/dev/mqueue"), path, sizeof(fpath) - 12); + fpath[sizeof(fpath)-1] = 0; + + if (stat(fpath, &b) < 0) + return -errno; + + if (a.st_dev != b.st_dev || + a.st_ino != b.st_ino) + return 0; + } + + return 1; +#endif +} + +_sd_export_ int sd_notify(int unset_environment, const char *state) { +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) || !defined(SOCK_CLOEXEC) + return 0; +#else + int fd = -1, r; + struct msghdr msghdr; + struct iovec iovec; + union sockaddr_union sockaddr; + const char *e; + + if (!state) { + r = -EINVAL; + goto finish; + } + + if (!(e = getenv("NOTIFY_SOCKET"))) + return 0; + + /* Must be an abstract socket, or an absolute path */ + if ((e[0] != '@' && e[0] != '/') || e[1] == 0) { + r = -EINVAL; + goto finish; + } + + if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) { + r = -errno; + goto finish; + } + + memset(&sockaddr, 0, sizeof(sockaddr)); + sockaddr.sa.sa_family = AF_UNIX; + strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path)); + + if (sockaddr.un.sun_path[0] == '@') + sockaddr.un.sun_path[0] = 0; + + memset(&iovec, 0, sizeof(iovec)); + iovec.iov_base = (char*) state; + iovec.iov_len = strlen(state); + + memset(&msghdr, 0, sizeof(msghdr)); + msghdr.msg_name = &sockaddr; + msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e); + + if (msghdr.msg_namelen > sizeof(struct sockaddr_un)) + msghdr.msg_namelen = sizeof(struct sockaddr_un); + + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + + if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) { + r = -errno; + goto finish; + } + + r = 1; + +finish: + if (unset_environment) + unsetenv("NOTIFY_SOCKET"); + + if (fd >= 0) + close(fd); + + return r; +#endif +} + +_sd_export_ int sd_notifyf(int unset_environment, const char *format, ...) { +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) + return 0; +#else + va_list ap; + char *p = NULL; + int r; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0 || !p) + return -ENOMEM; + + r = sd_notify(unset_environment, p); + free(p); + + return r; +#endif +} + +_sd_export_ int sd_booted(void) { +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) + return 0; +#else + + struct stat a, b; + + /* We simply test whether the systemd cgroup hierarchy is + * mounted */ + + if (lstat("/sys/fs/cgroup", &a) < 0) + return 0; + + if (lstat("/sys/fs/cgroup/systemd", &b) < 0) + return 0; + + return a.st_dev != b.st_dev; +#endif +} diff --git a/src/udev/src/sd-daemon.h b/src/udev/src/sd-daemon.h new file mode 100644 index 000000000..fe51159ee --- /dev/null +++ b/src/udev/src/sd-daemon.h @@ -0,0 +1,282 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosddaemonhfoo +#define foosddaemonhfoo + +/*** + Copyright 2010 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + Reference implementation of a few systemd related interfaces for + writing daemons. These interfaces are trivial to implement. To + simplify porting we provide this reference implementation. + Applications are welcome to reimplement the algorithms described + here if they do not want to include these two source files. + + The following functionality is provided: + + - Support for logging with log levels on stderr + - File descriptor passing for socket-based activation + - Daemon startup and status notification + - Detection of systemd boots + + You may compile this with -DDISABLE_SYSTEMD to disable systemd + support. This makes all those calls NOPs that are directly related to + systemd (i.e. only sd_is_xxx() will stay useful). + + Since this is drop-in code we don't want any of our symbols to be + exported in any case. Hence we declare hidden visibility for all of + them. + + You may find an up-to-date version of these source files online: + + http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h + http://cgit.freedesktop.org/systemd/systemd/plain/src/sd-daemon.c + + This should compile on non-Linux systems, too, but with the + exception of the sd_is_xxx() calls all functions will become NOPs. + + See sd-daemon(7) for more information. +*/ + +#ifndef _sd_printf_attr_ +#if __GNUC__ >= 4 +#define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b))) +#else +#define _sd_printf_attr_(a,b) +#endif +#endif + +/* + Log levels for usage on stderr: + + fprintf(stderr, SD_NOTICE "Hello World!\n"); + + This is similar to printk() usage in the kernel. +*/ +#define SD_EMERG "<0>" /* system is unusable */ +#define SD_ALERT "<1>" /* action must be taken immediately */ +#define SD_CRIT "<2>" /* critical conditions */ +#define SD_ERR "<3>" /* error conditions */ +#define SD_WARNING "<4>" /* warning conditions */ +#define SD_NOTICE "<5>" /* normal but significant condition */ +#define SD_INFO "<6>" /* informational */ +#define SD_DEBUG "<7>" /* debug-level messages */ + +/* The first passed file descriptor is fd 3 */ +#define SD_LISTEN_FDS_START 3 + +/* + Returns how many file descriptors have been passed, or a negative + errno code on failure. Optionally, removes the $LISTEN_FDS and + $LISTEN_PID file descriptors from the environment (recommended, but + problematic in threaded environments). If r is the return value of + this function you'll find the file descriptors passed as fds + SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative + errno style error code on failure. This function call ensures that + the FD_CLOEXEC flag is set for the passed file descriptors, to make + sure they are not passed on to child processes. If FD_CLOEXEC shall + not be set, the caller needs to unset it after this call for all file + descriptors that are used. + + See sd_listen_fds(3) for more information. +*/ +int sd_listen_fds(int unset_environment); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a FIFO in the file system stored under the + specified path, 0 otherwise. If path is NULL a path name check will + not be done and the call only verifies if the file descriptor + refers to a FIFO. Returns a negative errno style error code on + failure. + + See sd_is_fifo(3) for more information. +*/ +int sd_is_fifo(int fd, const char *path); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a special character device on the file + system stored under the specified path, 0 otherwise. + If path is NULL a path name check will not be done and the call + only verifies if the file descriptor refers to a special character. + Returns a negative errno style error code on failure. + + See sd_is_special(3) for more information. +*/ +int sd_is_special(int fd, const char *path); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a socket of the specified family (AF_INET, + ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If + family is 0 a socket family check will not be done. If type is 0 a + socket type check will not be done and the call only verifies if + the file descriptor refers to a socket. If listening is > 0 it is + verified that the socket is in listening mode. (i.e. listen() has + been called) If listening is == 0 it is verified that the socket is + not in listening mode. If listening is < 0 no listening mode check + is done. Returns a negative errno style error code on failure. + + See sd_is_socket(3) for more information. +*/ +int sd_is_socket(int fd, int family, int type, int listening); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is an Internet socket, of the specified family + (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM, + SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version + check is not done. If type is 0 a socket type check will not be + done. If port is 0 a socket port check will not be done. The + listening flag is used the same way as in sd_is_socket(). Returns a + negative errno style error code on failure. + + See sd_is_socket_inet(3) for more information. +*/ +int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is an AF_UNIX socket of the specified type + (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0 + a socket type check will not be done. If path is NULL a socket path + check will not be done. For normal AF_UNIX sockets set length to + 0. For abstract namespace sockets set length to the length of the + socket name (including the initial 0 byte), and pass the full + socket path in path (including the initial 0 byte). The listening + flag is used the same way as in sd_is_socket(). Returns a negative + errno style error code on failure. + + See sd_is_socket_unix(3) for more information. +*/ +int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a POSIX Message Queue of the specified name, + 0 otherwise. If path is NULL a message queue name check is not + done. Returns a negative errno style error code on failure. +*/ +int sd_is_mq(int fd, const char *path); + +/* + Informs systemd about changed daemon state. This takes a number of + newline separated environment-style variable assignments in a + string. The following variables are known: + + READY=1 Tells systemd that daemon startup is finished (only + relevant for services of Type=notify). The passed + argument is a boolean "1" or "0". Since there is + little value in signaling non-readiness the only + value daemons should send is "READY=1". + + STATUS=... Passes a single-line status string back to systemd + that describes the daemon state. This is free-from + and can be used for various purposes: general state + feedback, fsck-like programs could pass completion + percentages and failing programs could pass a human + readable error message. Example: "STATUS=Completed + 66% of file system check..." + + ERRNO=... If a daemon fails, the errno-style error code, + formatted as string. Example: "ERRNO=2" for ENOENT. + + BUSERROR=... If a daemon fails, the D-Bus error-style error + code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut" + + MAINPID=... The main pid of a daemon, in case systemd did not + fork off the process itself. Example: "MAINPID=4711" + + WATCHDOG=1 Tells systemd to update the watchdog timestamp. + Services using this feature should do this in + regular intervals. A watchdog framework can use the + timestamps to detect failed services. + + Daemons can choose to send additional variables. However, it is + recommended to prefix variable names not listed above with X_. + + Returns a negative errno-style error code on failure. Returns > 0 + if systemd could be notified, 0 if it couldn't possibly because + systemd is not running. + + Example: When a daemon finished starting up, it could issue this + call to notify systemd about it: + + sd_notify(0, "READY=1"); + + See sd_notifyf() for more complete examples. + + See sd_notify(3) for more information. +*/ +int sd_notify(int unset_environment, const char *state); + +/* + Similar to sd_notify() but takes a format string. + + Example 1: A daemon could send the following after initialization: + + sd_notifyf(0, "READY=1\n" + "STATUS=Processing requests...\n" + "MAINPID=%lu", + (unsigned long) getpid()); + + Example 2: A daemon could send the following shortly before + exiting, on failure: + + sd_notifyf(0, "STATUS=Failed to start up: %s\n" + "ERRNO=%i", + strerror(errno), + errno); + + See sd_notifyf(3) for more information. +*/ +int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3); + +/* + Returns > 0 if the system was booted with systemd. Returns < 0 on + error. Returns 0 if the system was not booted with systemd. Note + that all of the functions above handle non-systemd boots just + fine. You should NOT protect them with a call to this function. Also + note that this function checks whether the system, not the user + session is controlled by systemd. However the functions above work + for both user and system services. + + See sd_booted(3) for more information. +*/ +int sd_booted(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/test-libudev.c b/src/udev/src/test-libudev.c similarity index 100% rename from src/test-libudev.c rename to src/udev/src/test-libudev.c diff --git a/src/test-udev.c b/src/udev/src/test-udev.c similarity index 100% rename from src/test-udev.c rename to src/udev/src/test-udev.c diff --git a/src/udev-builtin-blkid.c b/src/udev/src/udev-builtin-blkid.c similarity index 100% rename from src/udev-builtin-blkid.c rename to src/udev/src/udev-builtin-blkid.c diff --git a/src/udev-builtin-firmware.c b/src/udev/src/udev-builtin-firmware.c similarity index 100% rename from src/udev-builtin-firmware.c rename to src/udev/src/udev-builtin-firmware.c diff --git a/src/udev-builtin-hwdb.c b/src/udev/src/udev-builtin-hwdb.c similarity index 100% rename from src/udev-builtin-hwdb.c rename to src/udev/src/udev-builtin-hwdb.c diff --git a/src/udev-builtin-input_id.c b/src/udev/src/udev-builtin-input_id.c similarity index 100% rename from src/udev-builtin-input_id.c rename to src/udev/src/udev-builtin-input_id.c diff --git a/src/udev-builtin-kmod.c b/src/udev/src/udev-builtin-kmod.c similarity index 100% rename from src/udev-builtin-kmod.c rename to src/udev/src/udev-builtin-kmod.c diff --git a/src/udev-builtin-path_id.c b/src/udev/src/udev-builtin-path_id.c similarity index 100% rename from src/udev-builtin-path_id.c rename to src/udev/src/udev-builtin-path_id.c diff --git a/src/udev-builtin-usb_id.c b/src/udev/src/udev-builtin-usb_id.c similarity index 100% rename from src/udev-builtin-usb_id.c rename to src/udev/src/udev-builtin-usb_id.c diff --git a/src/udev-builtin.c b/src/udev/src/udev-builtin.c similarity index 100% rename from src/udev-builtin.c rename to src/udev/src/udev-builtin.c diff --git a/src/udev-control.socket b/src/udev/src/udev-control.socket similarity index 100% rename from src/udev-control.socket rename to src/udev/src/udev-control.socket diff --git a/src/udev-ctrl.c b/src/udev/src/udev-ctrl.c similarity index 100% rename from src/udev-ctrl.c rename to src/udev/src/udev-ctrl.c diff --git a/src/udev-event.c b/src/udev/src/udev-event.c similarity index 100% rename from src/udev-event.c rename to src/udev/src/udev-event.c diff --git a/src/udev-kernel.socket b/src/udev/src/udev-kernel.socket similarity index 100% rename from src/udev-kernel.socket rename to src/udev/src/udev-kernel.socket diff --git a/src/udev-node.c b/src/udev/src/udev-node.c similarity index 100% rename from src/udev-node.c rename to src/udev/src/udev-node.c diff --git a/src/udev-rules.c b/src/udev/src/udev-rules.c similarity index 100% rename from src/udev-rules.c rename to src/udev/src/udev-rules.c diff --git a/src/udev-settle.service.in b/src/udev/src/udev-settle.service.in similarity index 100% rename from src/udev-settle.service.in rename to src/udev/src/udev-settle.service.in diff --git a/src/udev-trigger.service.in b/src/udev/src/udev-trigger.service.in similarity index 100% rename from src/udev-trigger.service.in rename to src/udev/src/udev-trigger.service.in diff --git a/src/udev-watch.c b/src/udev/src/udev-watch.c similarity index 100% rename from src/udev-watch.c rename to src/udev/src/udev-watch.c diff --git a/src/udev.conf b/src/udev/src/udev.conf similarity index 100% rename from src/udev.conf rename to src/udev/src/udev.conf diff --git a/src/udev.h b/src/udev/src/udev.h similarity index 100% rename from src/udev.h rename to src/udev/src/udev.h diff --git a/src/udev.pc.in b/src/udev/src/udev.pc.in similarity index 100% rename from src/udev.pc.in rename to src/udev/src/udev.pc.in diff --git a/src/udev.service.in b/src/udev/src/udev.service.in similarity index 100% rename from src/udev.service.in rename to src/udev/src/udev.service.in diff --git a/src/udev.xml b/src/udev/src/udev.xml similarity index 100% rename from src/udev.xml rename to src/udev/src/udev.xml diff --git a/src/udevadm-control.c b/src/udev/src/udevadm-control.c similarity index 100% rename from src/udevadm-control.c rename to src/udev/src/udevadm-control.c diff --git a/src/udevadm-info.c b/src/udev/src/udevadm-info.c similarity index 100% rename from src/udevadm-info.c rename to src/udev/src/udevadm-info.c diff --git a/src/udevadm-monitor.c b/src/udev/src/udevadm-monitor.c similarity index 100% rename from src/udevadm-monitor.c rename to src/udev/src/udevadm-monitor.c diff --git a/src/udevadm-settle.c b/src/udev/src/udevadm-settle.c similarity index 100% rename from src/udevadm-settle.c rename to src/udev/src/udevadm-settle.c diff --git a/src/udevadm-test-builtin.c b/src/udev/src/udevadm-test-builtin.c similarity index 100% rename from src/udevadm-test-builtin.c rename to src/udev/src/udevadm-test-builtin.c diff --git a/src/udevadm-test.c b/src/udev/src/udevadm-test.c similarity index 100% rename from src/udevadm-test.c rename to src/udev/src/udevadm-test.c diff --git a/src/udevadm-trigger.c b/src/udev/src/udevadm-trigger.c similarity index 100% rename from src/udevadm-trigger.c rename to src/udev/src/udevadm-trigger.c diff --git a/src/udevadm.c b/src/udev/src/udevadm.c similarity index 100% rename from src/udevadm.c rename to src/udev/src/udevadm.c diff --git a/src/udevadm.xml b/src/udev/src/udevadm.xml similarity index 100% rename from src/udevadm.xml rename to src/udev/src/udevadm.xml diff --git a/src/udevd.c b/src/udev/src/udevd.c similarity index 100% rename from src/udevd.c rename to src/udev/src/udevd.c diff --git a/src/udevd.xml b/src/udev/src/udevd.xml similarity index 100% rename from src/udevd.xml rename to src/udev/src/udevd.xml diff --git a/src/v4l_id/60-persistent-v4l.rules b/src/udev/src/v4l_id/60-persistent-v4l.rules similarity index 100% rename from src/v4l_id/60-persistent-v4l.rules rename to src/udev/src/v4l_id/60-persistent-v4l.rules diff --git a/src/v4l_id/v4l_id.c b/src/udev/src/v4l_id/v4l_id.c similarity index 100% rename from src/v4l_id/v4l_id.c rename to src/udev/src/v4l_id/v4l_id.c diff --git a/test/.gitignore b/src/udev/test/.gitignore similarity index 100% rename from test/.gitignore rename to src/udev/test/.gitignore diff --git a/test/rule-syntax-check.py b/src/udev/test/rule-syntax-check.py similarity index 100% rename from test/rule-syntax-check.py rename to src/udev/test/rule-syntax-check.py diff --git a/test/rules-test.sh b/src/udev/test/rules-test.sh similarity index 100% rename from test/rules-test.sh rename to src/udev/test/rules-test.sh diff --git a/test/sys.tar.xz b/src/udev/test/sys.tar.xz similarity index 100% rename from test/sys.tar.xz rename to src/udev/test/sys.tar.xz diff --git a/test/udev-test.pl b/src/udev/test/udev-test.pl similarity index 100% rename from test/udev-test.pl rename to src/udev/test/udev-test.pl diff --git a/src/umount.c b/src/umount.c new file mode 100644 index 000000000..0a63d23a0 --- /dev/null +++ b/src/umount.c @@ -0,0 +1,644 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 ProFUSION embedded systems + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "list.h" +#include "mount-setup.h" +#include "umount.h" +#include "util.h" + +typedef struct MountPoint { + char *path; + dev_t devnum; + bool skip_ro; + LIST_FIELDS (struct MountPoint, mount_point); +} MountPoint; + +static void mount_point_free(MountPoint **head, MountPoint *m) { + assert(head); + assert(m); + + LIST_REMOVE(MountPoint, mount_point, *head, m); + + free(m->path); + free(m); +} + +static void mount_points_list_free(MountPoint **head) { + assert(head); + + while (*head) + mount_point_free(head, *head); +} + +static int mount_points_list_get(MountPoint **head) { + FILE *proc_self_mountinfo; + char *path, *p; + unsigned int i; + int r; + + assert(head); + + if (!(proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"))) + return -errno; + + for (i = 1;; i++) { + int k; + MountPoint *m; + char *root; + bool skip_ro; + + path = p = NULL; + + if ((k = fscanf(proc_self_mountinfo, + "%*s " /* (1) mount id */ + "%*s " /* (2) parent id */ + "%*s " /* (3) major:minor */ + "%ms " /* (4) root */ + "%ms " /* (5) mount point */ + "%*s" /* (6) mount options */ + "%*[^-]" /* (7) optional fields */ + "- " /* (8) separator */ + "%*s " /* (9) file system type */ + "%*s" /* (10) mount source */ + "%*s" /* (11) mount options 2 */ + "%*[^\n]", /* some rubbish at the end */ + &root, + &path)) != 2) { + if (k == EOF) + break; + + log_warning("Failed to parse /proc/self/mountinfo:%u.", i); + + free(path); + continue; + } + + /* If we encounter a bind mount, don't try to remount + * the source dir too early */ + skip_ro = !streq(root, "/"); + free(root); + + p = cunescape(path); + free(path); + + if (!p) { + r = -ENOMEM; + goto finish; + } + + if (mount_point_is_api(p) || mount_point_ignore(p)) { + free(p); + continue; + } + + if (!(m = new0(MountPoint, 1))) { + free(p); + r = -ENOMEM; + goto finish; + } + + m->path = p; + m->skip_ro = skip_ro; + LIST_PREPEND(MountPoint, mount_point, *head, m); + } + + r = 0; + +finish: + fclose(proc_self_mountinfo); + + return r; +} + +static int swap_list_get(MountPoint **head) { + FILE *proc_swaps; + unsigned int i; + int r; + + assert(head); + + if (!(proc_swaps = fopen("/proc/swaps", "re"))) + return (errno == ENOENT) ? 0 : -errno; + + (void) fscanf(proc_swaps, "%*s %*s %*s %*s %*s\n"); + + for (i = 2;; i++) { + MountPoint *swap; + char *dev = NULL, *d; + int k; + + if ((k = fscanf(proc_swaps, + "%ms " /* device/file */ + "%*s " /* type of swap */ + "%*s " /* swap size */ + "%*s " /* used */ + "%*s\n", /* priority */ + &dev)) != 1) { + + if (k == EOF) + break; + + log_warning("Failed to parse /proc/swaps:%u.", i); + + free(dev); + continue; + } + + if (endswith(dev, "(deleted)")) { + free(dev); + continue; + } + + d = cunescape(dev); + free(dev); + + if (!d) { + r = -ENOMEM; + goto finish; + } + + if (!(swap = new0(MountPoint, 1))) { + free(d); + r = -ENOMEM; + goto finish; + } + + swap->path = d; + LIST_PREPEND(MountPoint, mount_point, *head, swap); + } + + r = 0; + +finish: + fclose(proc_swaps); + + return r; +} + +static int loopback_list_get(MountPoint **head) { + int r; + struct udev *udev; + struct udev_enumerate *e = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + + assert(head); + + if (!(udev = udev_new())) { + r = -ENOMEM; + goto finish; + } + + if (!(e = udev_enumerate_new(udev))) { + r = -ENOMEM; + goto finish; + } + + if (udev_enumerate_add_match_subsystem(e, "block") < 0 || + udev_enumerate_add_match_sysname(e, "loop*") < 0) { + r = -EIO; + goto finish; + } + + if (udev_enumerate_scan_devices(e) < 0) { + r = -EIO; + goto finish; + } + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + MountPoint *lb; + struct udev_device *d; + char *loop; + const char *dn; + + if (!(d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)))) { + r = -ENOMEM; + goto finish; + } + + if (!(dn = udev_device_get_devnode(d))) { + udev_device_unref(d); + continue; + } + + loop = strdup(dn); + udev_device_unref(d); + + if (!loop) { + r = -ENOMEM; + goto finish; + } + + if (!(lb = new0(MountPoint, 1))) { + free(loop); + r = -ENOMEM; + goto finish; + } + + lb->path = loop; + LIST_PREPEND(MountPoint, mount_point, *head, lb); + } + + r = 0; + +finish: + if (e) + udev_enumerate_unref(e); + + if (udev) + udev_unref(udev); + + return r; +} + +static int dm_list_get(MountPoint **head) { + int r; + struct udev *udev; + struct udev_enumerate *e = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + + assert(head); + + if (!(udev = udev_new())) { + r = -ENOMEM; + goto finish; + } + + if (!(e = udev_enumerate_new(udev))) { + r = -ENOMEM; + goto finish; + } + + if (udev_enumerate_add_match_subsystem(e, "block") < 0 || + udev_enumerate_add_match_sysname(e, "dm-*") < 0) { + r = -EIO; + goto finish; + } + + if (udev_enumerate_scan_devices(e) < 0) { + r = -EIO; + goto finish; + } + + first = udev_enumerate_get_list_entry(e); + + udev_list_entry_foreach(item, first) { + MountPoint *m; + struct udev_device *d; + dev_t devnum; + char *node; + const char *dn; + + if (!(d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)))) { + r = -ENOMEM; + goto finish; + } + + devnum = udev_device_get_devnum(d); + dn = udev_device_get_devnode(d); + + if (major(devnum) == 0 || !dn) { + udev_device_unref(d); + continue; + } + + node = strdup(dn); + udev_device_unref(d); + + if (!node) { + r = -ENOMEM; + goto finish; + } + + if (!(m = new(MountPoint, 1))) { + free(node); + r = -ENOMEM; + goto finish; + } + + m->path = node; + m->devnum = devnum; + LIST_PREPEND(MountPoint, mount_point, *head, m); + } + + r = 0; + +finish: + if (e) + udev_enumerate_unref(e); + + if (udev) + udev_unref(udev); + + return r; +} + +static int delete_loopback(const char *device) { + int fd, r; + + if ((fd = open(device, O_RDONLY|O_CLOEXEC)) < 0) + return errno == ENOENT ? 0 : -errno; + + r = ioctl(fd, LOOP_CLR_FD, 0); + close_nointr_nofail(fd); + + if (r >= 0) + return 1; + + /* ENXIO: not bound, so no error */ + if (errno == ENXIO) + return 0; + + return -errno; +} + +static int delete_dm(dev_t devnum) { + int fd, r; + struct dm_ioctl dm; + + assert(major(devnum) != 0); + + if ((fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC)) < 0) + return -errno; + + zero(dm); + dm.version[0] = DM_VERSION_MAJOR; + dm.version[1] = DM_VERSION_MINOR; + dm.version[2] = DM_VERSION_PATCHLEVEL; + + dm.data_size = sizeof(dm); + dm.dev = devnum; + + r = ioctl(fd, DM_DEV_REMOVE, &dm); + close_nointr_nofail(fd); + + return r >= 0 ? 0 : -errno; +} + +static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_error) { + MountPoint *m, *n; + int n_failed = 0; + + assert(head); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + if (path_equal(m->path, "/") +#ifndef HAVE_SPLIT_USR + || path_equal(m->path, "/usr") +#endif + ) { + n_failed++; + continue; + } + + /* Trying to umount. Forcing to umount if busy (only for NFS mounts) */ + if (umount2(m->path, MNT_FORCE) == 0) { + log_info("Unmounted %s.", m->path); + if (changed) + *changed = true; + + mount_point_free(head, m); + } else if (log_error) { + log_warning("Could not unmount %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +static int mount_points_list_remount_read_only(MountPoint **head, bool *changed) { + MountPoint *m, *n; + int n_failed = 0; + + assert(head); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + + if (m->skip_ro) { + n_failed++; + continue; + } + + /* Trying to remount read-only */ + if (mount(NULL, m->path, NULL, MS_MGC_VAL|MS_REMOUNT|MS_RDONLY, NULL) == 0) { + if (changed) + *changed = true; + + mount_point_free(head, m); + } else { + log_warning("Could not remount as read-only %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +static int swap_points_list_off(MountPoint **head, bool *changed) { + MountPoint *m, *n; + int n_failed = 0; + + assert(head); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + if (swapoff(m->path) == 0) { + if (changed) + *changed = true; + + mount_point_free(head, m); + } else { + log_warning("Could not deactivate swap %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +static int loopback_points_list_detach(MountPoint **head, bool *changed) { + MountPoint *m, *n; + int n_failed = 0, k; + struct stat root_st; + + assert(head); + + k = lstat("/", &root_st); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + int r; + struct stat loopback_st; + + if (k >= 0 && + major(root_st.st_dev) != 0 && + lstat(m->path, &loopback_st) >= 0 && + root_st.st_dev == loopback_st.st_rdev) { + n_failed ++; + continue; + } + + if ((r = delete_loopback(m->path)) >= 0) { + + if (r > 0 && changed) + *changed = true; + + mount_point_free(head, m); + } else { + log_warning("Could not delete loopback %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +static int dm_points_list_detach(MountPoint **head, bool *changed) { + MountPoint *m, *n; + int n_failed = 0, k; + struct stat root_st; + + assert(head); + + k = lstat("/", &root_st); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + int r; + + if (k >= 0 && + major(root_st.st_dev) != 0 && + root_st.st_dev == m->devnum) { + n_failed ++; + continue; + } + + if ((r = delete_dm(m->devnum)) >= 0) { + + if (r > 0 && changed) + *changed = true; + + mount_point_free(head, m); + } else { + log_warning("Could not delete dm %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +int umount_all(bool *changed) { + int r; + bool umount_changed; + + LIST_HEAD(MountPoint, mp_list_head); + + LIST_HEAD_INIT(MountPoint, mp_list_head); + + r = mount_points_list_get(&mp_list_head); + if (r < 0) + goto end; + + /* retry umount, until nothing can be umounted anymore */ + do { + umount_changed = false; + + mount_points_list_umount(&mp_list_head, &umount_changed, false); + if (umount_changed) + *changed = true; + + } while (umount_changed); + + /* umount one more time with logging enabled */ + r = mount_points_list_umount(&mp_list_head, &umount_changed, true); + if (r <= 0) + goto end; + + r = mount_points_list_remount_read_only(&mp_list_head, changed); + + end: + mount_points_list_free(&mp_list_head); + + return r; +} + +int swapoff_all(bool *changed) { + int r; + LIST_HEAD(MountPoint, swap_list_head); + + LIST_HEAD_INIT(MountPoint, swap_list_head); + + r = swap_list_get(&swap_list_head); + if (r < 0) + goto end; + + r = swap_points_list_off(&swap_list_head, changed); + + end: + mount_points_list_free(&swap_list_head); + + return r; +} + +int loopback_detach_all(bool *changed) { + int r; + LIST_HEAD(MountPoint, loopback_list_head); + + LIST_HEAD_INIT(MountPoint, loopback_list_head); + + r = loopback_list_get(&loopback_list_head); + if (r < 0) + goto end; + + r = loopback_points_list_detach(&loopback_list_head, changed); + + end: + mount_points_list_free(&loopback_list_head); + + return r; +} + +int dm_detach_all(bool *changed) { + int r; + LIST_HEAD(MountPoint, dm_list_head); + + LIST_HEAD_INIT(MountPoint, dm_list_head); + + r = dm_list_get(&dm_list_head); + if (r < 0) + goto end; + + r = dm_points_list_detach(&dm_list_head, changed); + + end: + mount_points_list_free(&dm_list_head); + + return r; +} diff --git a/src/umount.h b/src/umount.h new file mode 100644 index 000000000..acdf09acf --- /dev/null +++ b/src/umount.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooumounthfoo +#define fooumounthfoo + +/*** + This file is part of systemd. + + Copyright 2010 ProFUSION embedded systems + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +int umount_all(bool *changed); + +int swapoff_all(bool *changed); + +int loopback_detach_all(bool *changed); + +int dm_detach_all(bool *changed); + +#endif diff --git a/src/unit-name.c b/src/unit-name.c new file mode 100644 index 000000000..1cbb80456 --- /dev/null +++ b/src/unit-name.c @@ -0,0 +1,448 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "util.h" +#include "unit-name.h" + +#define VALID_CHARS \ + "0123456789" \ + "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + ":-_.\\" + +bool unit_name_is_valid_no_type(const char *n, bool template_ok) { + const char *e, *i, *at; + + /* Valid formats: + * + * string@instance.suffix + * string.suffix + */ + + assert(n); + + if (strlen(n) >= UNIT_NAME_MAX) + return false; + + e = strrchr(n, '.'); + if (!e || e == n) + return false; + + for (i = n, at = NULL; i < e; i++) { + + if (*i == '@' && !at) + at = i; + + if (!strchr("@" VALID_CHARS, *i)) + return false; + } + + if (at) { + if (at == n) + return false; + + if (!template_ok && at+1 == e) + return false; + } + + return true; +} + +bool unit_instance_is_valid(const char *i) { + assert(i); + + /* The max length depends on the length of the string, so we + * don't really check this here. */ + + if (i[0] == 0) + return false; + + /* We allow additional @ in the instance string, we do not + * allow them in the prefix! */ + + for (; *i; i++) + if (!strchr("@" VALID_CHARS, *i)) + return false; + + return true; +} + +bool unit_prefix_is_valid(const char *p) { + + /* We don't allow additional @ in the instance string */ + + if (p[0] == 0) + return false; + + for (; *p; p++) + if (!strchr(VALID_CHARS, *p)) + return false; + + return true; +} + +int unit_name_to_instance(const char *n, char **instance) { + const char *p, *d; + char *i; + + assert(n); + assert(instance); + + /* Everything past the first @ and before the last . is the instance */ + if (!(p = strchr(n, '@'))) { + *instance = NULL; + return 0; + } + + assert_se(d = strrchr(n, '.')); + assert(p < d); + + if (!(i = strndup(p+1, d-p-1))) + return -ENOMEM; + + *instance = i; + return 0; +} + +char *unit_name_to_prefix_and_instance(const char *n) { + const char *d; + + assert(n); + + assert_se(d = strrchr(n, '.')); + + return strndup(n, d - n); +} + +char *unit_name_to_prefix(const char *n) { + const char *p; + + if ((p = strchr(n, '@'))) + return strndup(n, p - n); + + return unit_name_to_prefix_and_instance(n); +} + +char *unit_name_change_suffix(const char *n, const char *suffix) { + char *e, *r; + size_t a, b; + + assert(n); + assert(unit_name_is_valid_no_type(n, true)); + assert(suffix); + + assert_se(e = strrchr(n, '.')); + a = e - n; + b = strlen(suffix); + + if (!(r = new(char, a + b + 1))) + return NULL; + + memcpy(r, n, a); + memcpy(r+a, suffix, b+1); + + return r; +} + +char *unit_name_build(const char *prefix, const char *instance, const char *suffix) { + assert(prefix); + assert(unit_prefix_is_valid(prefix)); + assert(!instance || unit_instance_is_valid(instance)); + assert(suffix); + + if (!instance) + return strappend(prefix, suffix); + + return join(prefix, "@", instance, suffix, NULL); +} + +static char* do_escape(const char *f, char *t) { + assert(f); + assert(t); + + for (; *f; f++) { + if (*f == '/') + *(t++) = '-'; + else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f)) { + *(t++) = '\\'; + *(t++) = 'x'; + *(t++) = hexchar(*f >> 4); + *(t++) = hexchar(*f); + } else + *(t++) = *f; + } + + return t; +} + +char *unit_name_build_escape(const char *prefix, const char *instance, const char *suffix) { + char *r, *t; + size_t a, b, c; + + assert(prefix); + assert(suffix); + + /* Takes a arbitrary string for prefix and instance plus a + * suffix and makes a nice string suitable as unit name of it, + * escaping all weird chars on the way. + * + * / becomes ., and all chars not allowed in a unit name get + * escaped as \xFF, including \ and ., of course. This + * escaping is hence reversible. + * + * This is primarily useful to make nice unit names from + * strings, but is actually useful for any kind of string. + */ + + a = strlen(prefix); + c = strlen(suffix); + + if (instance) { + b = strlen(instance); + + if (!(r = new(char, a*4 + 1 + b*4 + c + 1))) + return NULL; + + t = do_escape(prefix, r); + *(t++) = '@'; + t = do_escape(instance, t); + } else { + + if (!(r = new(char, a*4 + c + 1))) + return NULL; + + t = do_escape(prefix, r); + } + + strcpy(t, suffix); + return r; +} + +char *unit_name_escape(const char *f) { + char *r, *t; + + if (!(r = new(char, strlen(f)*4+1))) + return NULL; + + t = do_escape(f, r); + *t = 0; + + return r; + +} + +char *unit_name_unescape(const char *f) { + char *r, *t; + + assert(f); + + if (!(r = strdup(f))) + return NULL; + + for (t = r; *f; f++) { + if (*f == '-') + *(t++) = '/'; + else if (*f == '\\') { + int a, b; + + if (f[1] != 'x' || + (a = unhexchar(f[2])) < 0 || + (b = unhexchar(f[3])) < 0) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + } else { + *(t++) = (char) ((a << 4) | b); + f += 3; + } + } else + *(t++) = *f; + } + + *t = 0; + + return r; +} + +bool unit_name_is_template(const char *n) { + const char *p; + + assert(n); + + if (!(p = strchr(n, '@'))) + return false; + + return p[1] == '.'; +} + +char *unit_name_replace_instance(const char *f, const char *i) { + const char *p, *e; + char *r, *k; + size_t a; + + assert(f); + + p = strchr(f, '@'); + assert_se(e = strrchr(f, '.')); + + a = p - f; + + if (p) { + size_t b; + + b = strlen(i); + + if (!(r = new(char, a + 1 + b + strlen(e) + 1))) + return NULL; + + k = mempcpy(r, f, a + 1); + k = mempcpy(k, i, b); + } else { + + if (!(r = new(char, a + strlen(e) + 1))) + return NULL; + + k = mempcpy(r, f, a); + } + + strcpy(k, e); + return r; +} + +char *unit_name_template(const char *f) { + const char *p, *e; + char *r; + size_t a; + + if (!(p = strchr(f, '@'))) + return strdup(f); + + assert_se(e = strrchr(f, '.')); + a = p - f + 1; + + if (!(r = new(char, a + strlen(e) + 1))) + return NULL; + + strcpy(mempcpy(r, f, a), e); + return r; + +} + +char *unit_name_from_path(const char *path, const char *suffix) { + char *p, *r; + + assert(path); + assert(suffix); + + if (!(p = strdup(path))) + return NULL; + + path_kill_slashes(p); + + path = p[0] == '/' ? p + 1 : p; + + if (path[0] == 0) { + free(p); + return strappend("-", suffix); + } + + r = unit_name_build_escape(path, NULL, suffix); + free(p); + + return r; +} + +char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix) { + char *p, *r; + + assert(path); + assert(suffix); + + if (!(p = strdup(path))) + return NULL; + + path_kill_slashes(p); + + path = p[0] == '/' ? p + 1 : p; + + if (path[0] == 0) { + free(p); + return unit_name_build_escape(prefix, "-", suffix); + } + + r = unit_name_build_escape(prefix, path, suffix); + free(p); + + return r; +} + +char *unit_name_to_path(const char *name) { + char *w, *e; + + assert(name); + + if (!(w = unit_name_to_prefix(name))) + return NULL; + + e = unit_name_unescape(w); + free(w); + + if (!e) + return NULL; + + if (e[0] != '/') { + w = strappend("/", e); + free(e); + + if (!w) + return NULL; + + e = w; + } + + return e; +} + +char *unit_name_path_unescape(const char *f) { + char *e; + + assert(f); + + if (!(e = unit_name_unescape(f))) + return NULL; + + if (e[0] != '/') { + char *w; + + w = strappend("/", e); + free(e); + + if (!w) + return NULL; + + e = w; + } + + return e; +} diff --git a/src/unit-name.h b/src/unit-name.h new file mode 100644 index 000000000..e369910ae --- /dev/null +++ b/src/unit-name.h @@ -0,0 +1,57 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foounitnamehfoo +#define foounitnamehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include + +#define UNIT_NAME_MAX 256 + +int unit_name_to_instance(const char *n, char **instance); +char* unit_name_to_prefix(const char *n); +char* unit_name_to_prefix_and_instance(const char *n); + +bool unit_name_is_valid_no_type(const char *n, bool template_ok); +bool unit_prefix_is_valid(const char *p); +bool unit_instance_is_valid(const char *i); + +char *unit_name_change_suffix(const char *n, const char *suffix); + +char *unit_name_build(const char *prefix, const char *instance, const char *suffix); +char *unit_name_build_escape(const char *prefix, const char *instance, const char *suffix); + +char *unit_name_escape(const char *f); +char *unit_name_unescape(const char *f); + +char *unit_name_path_unescape(const char *f); + +bool unit_name_is_template(const char *n); + +char *unit_name_replace_instance(const char *f, const char *i); + +char *unit_name_template(const char *f); + +char *unit_name_from_path(const char *path, const char *suffix); +char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix); +char *unit_name_to_path(const char *name); + +#endif diff --git a/src/unit.c b/src/unit.c new file mode 100644 index 000000000..9e33701c8 --- /dev/null +++ b/src/unit.c @@ -0,0 +1,2676 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "set.h" +#include "unit.h" +#include "macro.h" +#include "strv.h" +#include "load-fragment.h" +#include "load-dropin.h" +#include "log.h" +#include "unit-name.h" +#include "specifier.h" +#include "dbus-unit.h" +#include "special.h" +#include "cgroup-util.h" +#include "missing.h" +#include "cgroup-attr.h" + +const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { + [UNIT_SERVICE] = &service_vtable, + [UNIT_TIMER] = &timer_vtable, + [UNIT_SOCKET] = &socket_vtable, + [UNIT_TARGET] = &target_vtable, + [UNIT_DEVICE] = &device_vtable, + [UNIT_MOUNT] = &mount_vtable, + [UNIT_AUTOMOUNT] = &automount_vtable, + [UNIT_SNAPSHOT] = &snapshot_vtable, + [UNIT_SWAP] = &swap_vtable, + [UNIT_PATH] = &path_vtable +}; + +Unit *unit_new(Manager *m, size_t size) { + Unit *u; + + assert(m); + assert(size >= sizeof(Unit)); + + u = malloc0(size); + if (!u) + return NULL; + + u->names = set_new(string_hash_func, string_compare_func); + if (!u->names) { + free(u); + return NULL; + } + + u->manager = m; + u->type = _UNIT_TYPE_INVALID; + u->deserialized_job = _JOB_TYPE_INVALID; + u->default_dependencies = true; + u->unit_file_state = _UNIT_FILE_STATE_INVALID; + + return u; +} + +bool unit_has_name(Unit *u, const char *name) { + assert(u); + assert(name); + + return !!set_get(u->names, (char*) name); +} + +int unit_add_name(Unit *u, const char *text) { + UnitType t; + char *s, *i = NULL; + int r; + + assert(u); + assert(text); + + if (unit_name_is_template(text)) { + if (!u->instance) + return -EINVAL; + + s = unit_name_replace_instance(text, u->instance); + } else + s = strdup(text); + + if (!s) + return -ENOMEM; + + if (!unit_name_is_valid(s, false)) { + r = -EINVAL; + goto fail; + } + + assert_se((t = unit_name_to_type(s)) >= 0); + + if (u->type != _UNIT_TYPE_INVALID && t != u->type) { + r = -EINVAL; + goto fail; + } + + if ((r = unit_name_to_instance(s, &i)) < 0) + goto fail; + + if (i && unit_vtable[t]->no_instances) { + r = -EINVAL; + goto fail; + } + + /* Ensure that this unit is either instanced or not instanced, + * but not both. */ + if (u->type != _UNIT_TYPE_INVALID && !u->instance != !i) { + r = -EINVAL; + goto fail; + } + + if (unit_vtable[t]->no_alias && + !set_isempty(u->names) && + !set_get(u->names, s)) { + r = -EEXIST; + goto fail; + } + + if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) { + r = -E2BIG; + goto fail; + } + + if ((r = set_put(u->names, s)) < 0) { + if (r == -EEXIST) + r = 0; + goto fail; + } + + if ((r = hashmap_put(u->manager->units, s, u)) < 0) { + set_remove(u->names, s); + goto fail; + } + + if (u->type == _UNIT_TYPE_INVALID) { + + u->type = t; + u->id = s; + u->instance = i; + + LIST_PREPEND(Unit, units_by_type, u->manager->units_by_type[t], u); + + if (UNIT_VTABLE(u)->init) + UNIT_VTABLE(u)->init(u); + } else + free(i); + + unit_add_to_dbus_queue(u); + return 0; + +fail: + free(s); + free(i); + + return r; +} + +int unit_choose_id(Unit *u, const char *name) { + char *s, *t = NULL, *i; + int r; + + assert(u); + assert(name); + + if (unit_name_is_template(name)) { + + if (!u->instance) + return -EINVAL; + + if (!(t = unit_name_replace_instance(name, u->instance))) + return -ENOMEM; + + name = t; + } + + /* Selects one of the names of this unit as the id */ + s = set_get(u->names, (char*) name); + free(t); + + if (!s) + return -ENOENT; + + if ((r = unit_name_to_instance(s, &i)) < 0) + return r; + + u->id = s; + + free(u->instance); + u->instance = i; + + unit_add_to_dbus_queue(u); + + return 0; +} + +int unit_set_description(Unit *u, const char *description) { + char *s; + + assert(u); + + if (!(s = strdup(description))) + return -ENOMEM; + + free(u->description); + u->description = s; + + unit_add_to_dbus_queue(u); + return 0; +} + +bool unit_check_gc(Unit *u) { + assert(u); + + if (u->load_state == UNIT_STUB) + return true; + + if (UNIT_VTABLE(u)->no_gc) + return true; + + if (u->no_gc) + return true; + + if (u->job) + return true; + + if (unit_active_state(u) != UNIT_INACTIVE) + return true; + + if (UNIT_VTABLE(u)->check_gc) + if (UNIT_VTABLE(u)->check_gc(u)) + return true; + + return false; +} + +void unit_add_to_load_queue(Unit *u) { + assert(u); + assert(u->type != _UNIT_TYPE_INVALID); + + if (u->load_state != UNIT_STUB || u->in_load_queue) + return; + + LIST_PREPEND(Unit, load_queue, u->manager->load_queue, u); + u->in_load_queue = true; +} + +void unit_add_to_cleanup_queue(Unit *u) { + assert(u); + + if (u->in_cleanup_queue) + return; + + LIST_PREPEND(Unit, cleanup_queue, u->manager->cleanup_queue, u); + u->in_cleanup_queue = true; +} + +void unit_add_to_gc_queue(Unit *u) { + assert(u); + + if (u->in_gc_queue || u->in_cleanup_queue) + return; + + if (unit_check_gc(u)) + return; + + LIST_PREPEND(Unit, gc_queue, u->manager->gc_queue, u); + u->in_gc_queue = true; + + u->manager->n_in_gc_queue ++; + + if (u->manager->gc_queue_timestamp <= 0) + u->manager->gc_queue_timestamp = now(CLOCK_MONOTONIC); +} + +void unit_add_to_dbus_queue(Unit *u) { + assert(u); + assert(u->type != _UNIT_TYPE_INVALID); + + if (u->load_state == UNIT_STUB || u->in_dbus_queue) + return; + + /* Shortcut things if nobody cares */ + if (!bus_has_subscriber(u->manager)) { + u->sent_dbus_new_signal = true; + return; + } + + LIST_PREPEND(Unit, dbus_queue, u->manager->dbus_unit_queue, u); + u->in_dbus_queue = true; +} + +static void bidi_set_free(Unit *u, Set *s) { + Iterator i; + Unit *other; + + assert(u); + + /* Frees the set and makes sure we are dropped from the + * inverse pointers */ + + SET_FOREACH(other, s, i) { + UnitDependency d; + + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) + set_remove(other->dependencies[d], u); + + unit_add_to_gc_queue(other); + } + + set_free(s); +} + +void unit_free(Unit *u) { + UnitDependency d; + Iterator i; + char *t; + + assert(u); + + bus_unit_send_removed_signal(u); + + if (u->load_state != UNIT_STUB) + if (UNIT_VTABLE(u)->done) + UNIT_VTABLE(u)->done(u); + + SET_FOREACH(t, u->names, i) + hashmap_remove_value(u->manager->units, t, u); + + if (u->job) + job_free(u->job); + + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) + bidi_set_free(u, u->dependencies[d]); + + if (u->type != _UNIT_TYPE_INVALID) + LIST_REMOVE(Unit, units_by_type, u->manager->units_by_type[u->type], u); + + if (u->in_load_queue) + LIST_REMOVE(Unit, load_queue, u->manager->load_queue, u); + + if (u->in_dbus_queue) + LIST_REMOVE(Unit, dbus_queue, u->manager->dbus_unit_queue, u); + + if (u->in_cleanup_queue) + LIST_REMOVE(Unit, cleanup_queue, u->manager->cleanup_queue, u); + + if (u->in_gc_queue) { + LIST_REMOVE(Unit, gc_queue, u->manager->gc_queue, u); + u->manager->n_in_gc_queue--; + } + + cgroup_bonding_free_list(u->cgroup_bondings, u->manager->n_reloading <= 0); + cgroup_attribute_free_list(u->cgroup_attributes); + + free(u->description); + free(u->fragment_path); + free(u->instance); + + set_free_free(u->names); + + condition_free_list(u->conditions); + + while (u->refs) + unit_ref_unset(u->refs); + + free(u); +} + +UnitActiveState unit_active_state(Unit *u) { + assert(u); + + if (u->load_state == UNIT_MERGED) + return unit_active_state(unit_follow_merge(u)); + + /* After a reload it might happen that a unit is not correctly + * loaded but still has a process around. That's why we won't + * shortcut failed loading to UNIT_INACTIVE_FAILED. */ + + return UNIT_VTABLE(u)->active_state(u); +} + +const char* unit_sub_state_to_string(Unit *u) { + assert(u); + + return UNIT_VTABLE(u)->sub_state_to_string(u); +} + +static void complete_move(Set **s, Set **other) { + assert(s); + assert(other); + + if (!*other) + return; + + if (*s) + set_move(*s, *other); + else { + *s = *other; + *other = NULL; + } +} + +static void merge_names(Unit *u, Unit *other) { + char *t; + Iterator i; + + assert(u); + assert(other); + + complete_move(&u->names, &other->names); + + set_free_free(other->names); + other->names = NULL; + other->id = NULL; + + SET_FOREACH(t, u->names, i) + assert_se(hashmap_replace(u->manager->units, t, u) == 0); +} + +static void merge_dependencies(Unit *u, Unit *other, UnitDependency d) { + Iterator i; + Unit *back; + int r; + + assert(u); + assert(other); + assert(d < _UNIT_DEPENDENCY_MAX); + + /* Fix backwards pointers */ + SET_FOREACH(back, other->dependencies[d], i) { + UnitDependency k; + + for (k = 0; k < _UNIT_DEPENDENCY_MAX; k++) + if ((r = set_remove_and_put(back->dependencies[k], other, u)) < 0) { + + if (r == -EEXIST) + set_remove(back->dependencies[k], other); + else + assert(r == -ENOENT); + } + } + + complete_move(&u->dependencies[d], &other->dependencies[d]); + + set_free(other->dependencies[d]); + other->dependencies[d] = NULL; +} + +int unit_merge(Unit *u, Unit *other) { + UnitDependency d; + + assert(u); + assert(other); + assert(u->manager == other->manager); + assert(u->type != _UNIT_TYPE_INVALID); + + other = unit_follow_merge(other); + + if (other == u) + return 0; + + if (u->type != other->type) + return -EINVAL; + + if (!u->instance != !other->instance) + return -EINVAL; + + if (other->load_state != UNIT_STUB && + other->load_state != UNIT_ERROR) + return -EEXIST; + + if (other->job) + return -EEXIST; + + if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) + return -EEXIST; + + /* Merge names */ + merge_names(u, other); + + /* Redirect all references */ + while (other->refs) + unit_ref_set(other->refs, u); + + /* Merge dependencies */ + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) + merge_dependencies(u, other, d); + + other->load_state = UNIT_MERGED; + other->merged_into = u; + + /* If there is still some data attached to the other node, we + * don't need it anymore, and can free it. */ + if (other->load_state != UNIT_STUB) + if (UNIT_VTABLE(other)->done) + UNIT_VTABLE(other)->done(other); + + unit_add_to_dbus_queue(u); + unit_add_to_cleanup_queue(other); + + return 0; +} + +int unit_merge_by_name(Unit *u, const char *name) { + Unit *other; + int r; + char *s = NULL; + + assert(u); + assert(name); + + if (unit_name_is_template(name)) { + if (!u->instance) + return -EINVAL; + + if (!(s = unit_name_replace_instance(name, u->instance))) + return -ENOMEM; + + name = s; + } + + if (!(other = manager_get_unit(u->manager, name))) + r = unit_add_name(u, name); + else + r = unit_merge(u, other); + + free(s); + return r; +} + +Unit* unit_follow_merge(Unit *u) { + assert(u); + + while (u->load_state == UNIT_MERGED) + assert_se(u = u->merged_into); + + return u; +} + +int unit_add_exec_dependencies(Unit *u, ExecContext *c) { + int r; + + assert(u); + assert(c); + + if (c->std_output != EXEC_OUTPUT_KMSG && + c->std_output != EXEC_OUTPUT_SYSLOG && + c->std_output != EXEC_OUTPUT_JOURNAL && + c->std_output != EXEC_OUTPUT_KMSG_AND_CONSOLE && + c->std_output != EXEC_OUTPUT_SYSLOG_AND_CONSOLE && + c->std_output != EXEC_OUTPUT_JOURNAL_AND_CONSOLE && + c->std_error != EXEC_OUTPUT_KMSG && + c->std_error != EXEC_OUTPUT_SYSLOG && + c->std_error != EXEC_OUTPUT_JOURNAL && + c->std_error != EXEC_OUTPUT_KMSG_AND_CONSOLE && + c->std_error != EXEC_OUTPUT_JOURNAL_AND_CONSOLE && + c->std_error != EXEC_OUTPUT_SYSLOG_AND_CONSOLE) + return 0; + + /* If syslog or kernel logging is requested, make sure our own + * logging daemon is run first. */ + + if (u->manager->running_as == MANAGER_SYSTEM) + if ((r = unit_add_two_dependencies_by_name(u, UNIT_REQUIRES, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true)) < 0) + return r; + + return 0; +} + +const char *unit_description(Unit *u) { + assert(u); + + if (u->description) + return u->description; + + return strna(u->id); +} + +void unit_dump(Unit *u, FILE *f, const char *prefix) { + char *t; + UnitDependency d; + Iterator i; + char *p2; + const char *prefix2; + char + timestamp1[FORMAT_TIMESTAMP_MAX], + timestamp2[FORMAT_TIMESTAMP_MAX], + timestamp3[FORMAT_TIMESTAMP_MAX], + timestamp4[FORMAT_TIMESTAMP_MAX], + timespan[FORMAT_TIMESPAN_MAX]; + Unit *following; + + assert(u); + assert(u->type >= 0); + + if (!prefix) + prefix = ""; + p2 = strappend(prefix, "\t"); + prefix2 = p2 ? p2 : prefix; + + fprintf(f, + "%s-> Unit %s:\n" + "%s\tDescription: %s\n" + "%s\tInstance: %s\n" + "%s\tUnit Load State: %s\n" + "%s\tUnit Active State: %s\n" + "%s\tInactive Exit Timestamp: %s\n" + "%s\tActive Enter Timestamp: %s\n" + "%s\tActive Exit Timestamp: %s\n" + "%s\tInactive Enter Timestamp: %s\n" + "%s\tGC Check Good: %s\n" + "%s\tNeed Daemon Reload: %s\n", + prefix, u->id, + prefix, unit_description(u), + prefix, strna(u->instance), + prefix, unit_load_state_to_string(u->load_state), + prefix, unit_active_state_to_string(unit_active_state(u)), + prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->inactive_exit_timestamp.realtime)), + prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->active_enter_timestamp.realtime)), + prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->active_exit_timestamp.realtime)), + prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->inactive_enter_timestamp.realtime)), + prefix, yes_no(unit_check_gc(u)), + prefix, yes_no(unit_need_daemon_reload(u))); + + SET_FOREACH(t, u->names, i) + fprintf(f, "%s\tName: %s\n", prefix, t); + + if ((following = unit_following(u))) + fprintf(f, "%s\tFollowing: %s\n", prefix, following->id); + + if (u->fragment_path) + fprintf(f, "%s\tFragment Path: %s\n", prefix, u->fragment_path); + + if (u->job_timeout > 0) + fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout)); + + condition_dump_list(u->conditions, f, prefix); + + if (dual_timestamp_is_set(&u->condition_timestamp)) + fprintf(f, + "%s\tCondition Timestamp: %s\n" + "%s\tCondition Result: %s\n", + prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->condition_timestamp.realtime)), + prefix, yes_no(u->condition_result)); + + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { + Unit *other; + + SET_FOREACH(other, u->dependencies[d], i) + fprintf(f, "%s\t%s: %s\n", prefix, unit_dependency_to_string(d), other->id); + } + + if (u->load_state == UNIT_LOADED) { + CGroupBonding *b; + CGroupAttribute *a; + + fprintf(f, + "%s\tStopWhenUnneeded: %s\n" + "%s\tRefuseManualStart: %s\n" + "%s\tRefuseManualStop: %s\n" + "%s\tDefaultDependencies: %s\n" + "%s\tOnFailureIsolate: %s\n" + "%s\tIgnoreOnIsolate: %s\n" + "%s\tIgnoreOnSnapshot: %s\n", + prefix, yes_no(u->stop_when_unneeded), + prefix, yes_no(u->refuse_manual_start), + prefix, yes_no(u->refuse_manual_stop), + prefix, yes_no(u->default_dependencies), + prefix, yes_no(u->on_failure_isolate), + prefix, yes_no(u->ignore_on_isolate), + prefix, yes_no(u->ignore_on_snapshot)); + + LIST_FOREACH(by_unit, b, u->cgroup_bondings) + fprintf(f, "%s\tControlGroup: %s:%s\n", + prefix, b->controller, b->path); + + LIST_FOREACH(by_unit, a, u->cgroup_attributes) { + char *v = NULL; + + if (a->map_callback) + a->map_callback(a->controller, a->name, a->value, &v); + + fprintf(f, "%s\tControlGroupAttribute: %s %s \"%s\"\n", + prefix, a->controller, a->name, v ? v : a->value); + + free(v); + } + + if (UNIT_VTABLE(u)->dump) + UNIT_VTABLE(u)->dump(u, f, prefix2); + + } else if (u->load_state == UNIT_MERGED) + fprintf(f, + "%s\tMerged into: %s\n", + prefix, u->merged_into->id); + else if (u->load_state == UNIT_ERROR) + fprintf(f, "%s\tLoad Error Code: %s\n", prefix, strerror(-u->load_error)); + + + if (u->job) + job_dump(u->job, f, prefix2); + + free(p2); +} + +/* Common implementation for multiple backends */ +int unit_load_fragment_and_dropin(Unit *u) { + int r; + + assert(u); + + /* Load a .service file */ + if ((r = unit_load_fragment(u)) < 0) + return r; + + if (u->load_state == UNIT_STUB) + return -ENOENT; + + /* Load drop-in directory data */ + if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) + return r; + + return 0; +} + +/* Common implementation for multiple backends */ +int unit_load_fragment_and_dropin_optional(Unit *u) { + int r; + + assert(u); + + /* Same as unit_load_fragment_and_dropin(), but whether + * something can be loaded or not doesn't matter. */ + + /* Load a .service file */ + if ((r = unit_load_fragment(u)) < 0) + return r; + + if (u->load_state == UNIT_STUB) + u->load_state = UNIT_LOADED; + + /* Load drop-in directory data */ + if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) + return r; + + return 0; +} + +int unit_add_default_target_dependency(Unit *u, Unit *target) { + assert(u); + assert(target); + + if (target->type != UNIT_TARGET) + return 0; + + /* Only add the dependency if both units are loaded, so that + * that loop check below is reliable */ + if (u->load_state != UNIT_LOADED || + target->load_state != UNIT_LOADED) + return 0; + + /* If either side wants no automatic dependencies, then let's + * skip this */ + if (!u->default_dependencies || + !target->default_dependencies) + return 0; + + /* Don't create loops */ + if (set_get(target->dependencies[UNIT_BEFORE], u)) + return 0; + + return unit_add_dependency(target, UNIT_AFTER, u, true); +} + +static int unit_add_default_dependencies(Unit *u) { + static const UnitDependency deps[] = { + UNIT_REQUIRED_BY, + UNIT_REQUIRED_BY_OVERRIDABLE, + UNIT_WANTED_BY, + UNIT_BOUND_BY + }; + + Unit *target; + Iterator i; + int r; + unsigned k; + + assert(u); + + for (k = 0; k < ELEMENTSOF(deps); k++) + SET_FOREACH(target, u->dependencies[deps[k]], i) + if ((r = unit_add_default_target_dependency(u, target)) < 0) + return r; + + return 0; +} + +int unit_load(Unit *u) { + int r; + + assert(u); + + if (u->in_load_queue) { + LIST_REMOVE(Unit, load_queue, u->manager->load_queue, u); + u->in_load_queue = false; + } + + if (u->type == _UNIT_TYPE_INVALID) + return -EINVAL; + + if (u->load_state != UNIT_STUB) + return 0; + + if (UNIT_VTABLE(u)->load) + if ((r = UNIT_VTABLE(u)->load(u)) < 0) + goto fail; + + if (u->load_state == UNIT_STUB) { + r = -ENOENT; + goto fail; + } + + if (u->load_state == UNIT_LOADED && + u->default_dependencies) + if ((r = unit_add_default_dependencies(u)) < 0) + goto fail; + + if (u->on_failure_isolate && + set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { + + log_error("More than one OnFailure= dependencies specified for %s but OnFailureIsolate= enabled. Refusing.", + u->id); + + r = -EINVAL; + goto fail; + } + + assert((u->load_state != UNIT_MERGED) == !u->merged_into); + + unit_add_to_dbus_queue(unit_follow_merge(u)); + unit_add_to_gc_queue(u); + + return 0; + +fail: + u->load_state = UNIT_ERROR; + u->load_error = r; + unit_add_to_dbus_queue(u); + unit_add_to_gc_queue(u); + + log_debug("Failed to load configuration for %s: %s", u->id, strerror(-r)); + + return r; +} + +bool unit_condition_test(Unit *u) { + assert(u); + + dual_timestamp_get(&u->condition_timestamp); + u->condition_result = condition_test_list(u->conditions); + + return u->condition_result; +} + +/* Errors: + * -EBADR: This unit type does not support starting. + * -EALREADY: Unit is already started. + * -EAGAIN: An operation is already in progress. Retry later. + * -ECANCELED: Too many requests for now. + */ +int unit_start(Unit *u) { + UnitActiveState state; + Unit *following; + + assert(u); + + if (u->load_state != UNIT_LOADED) + return -EINVAL; + + /* If this is already started, then this will succeed. Note + * that this will even succeed if this unit is not startable + * by the user. This is relied on to detect when we need to + * wait for units and when waiting is finished. */ + state = unit_active_state(u); + if (UNIT_IS_ACTIVE_OR_RELOADING(state)) + return -EALREADY; + + /* If the conditions failed, don't do anything at all. If we + * already are activating this call might still be useful to + * speed up activation in case there is some hold-off time, + * but we don't want to recheck the condition in that case. */ + if (state != UNIT_ACTIVATING && + !unit_condition_test(u)) { + log_debug("Starting of %s requested but condition failed. Ignoring.", u->id); + return -EALREADY; + } + + /* Forward to the main object, if we aren't it. */ + if ((following = unit_following(u))) { + log_debug("Redirecting start request from %s to %s.", u->id, following->id); + return unit_start(following); + } + + /* If it is stopped, but we cannot start it, then fail */ + if (!UNIT_VTABLE(u)->start) + return -EBADR; + + /* We don't suppress calls to ->start() here when we are + * already starting, to allow this request to be used as a + * "hurry up" call, for example when the unit is in some "auto + * restart" state where it waits for a holdoff timer to elapse + * before it will start again. */ + + unit_add_to_dbus_queue(u); + + unit_status_printf(u, NULL, "Starting %s...", unit_description(u)); + return UNIT_VTABLE(u)->start(u); +} + +bool unit_can_start(Unit *u) { + assert(u); + + return !!UNIT_VTABLE(u)->start; +} + +bool unit_can_isolate(Unit *u) { + assert(u); + + return unit_can_start(u) && + u->allow_isolate; +} + +/* Errors: + * -EBADR: This unit type does not support stopping. + * -EALREADY: Unit is already stopped. + * -EAGAIN: An operation is already in progress. Retry later. + */ +int unit_stop(Unit *u) { + UnitActiveState state; + Unit *following; + + assert(u); + + state = unit_active_state(u); + if (UNIT_IS_INACTIVE_OR_FAILED(state)) + return -EALREADY; + + if ((following = unit_following(u))) { + log_debug("Redirecting stop request from %s to %s.", u->id, following->id); + return unit_stop(following); + } + + if (!UNIT_VTABLE(u)->stop) + return -EBADR; + + unit_add_to_dbus_queue(u); + + unit_status_printf(u, NULL, "Stopping %s...", unit_description(u)); + return UNIT_VTABLE(u)->stop(u); +} + +/* Errors: + * -EBADR: This unit type does not support reloading. + * -ENOEXEC: Unit is not started. + * -EAGAIN: An operation is already in progress. Retry later. + */ +int unit_reload(Unit *u) { + UnitActiveState state; + Unit *following; + + assert(u); + + if (u->load_state != UNIT_LOADED) + return -EINVAL; + + if (!unit_can_reload(u)) + return -EBADR; + + state = unit_active_state(u); + if (state == UNIT_RELOADING) + return -EALREADY; + + if (state != UNIT_ACTIVE) + return -ENOEXEC; + + if ((following = unit_following(u))) { + log_debug("Redirecting reload request from %s to %s.", u->id, following->id); + return unit_reload(following); + } + + unit_add_to_dbus_queue(u); + return UNIT_VTABLE(u)->reload(u); +} + +bool unit_can_reload(Unit *u) { + assert(u); + + if (!UNIT_VTABLE(u)->reload) + return false; + + if (!UNIT_VTABLE(u)->can_reload) + return true; + + return UNIT_VTABLE(u)->can_reload(u); +} + +static void unit_check_unneeded(Unit *u) { + Iterator i; + Unit *other; + + assert(u); + + /* If this service shall be shut down when unneeded then do + * so. */ + + if (!u->stop_when_unneeded) + return; + + if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) + return; + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i) + if (unit_pending_active(other)) + return; + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i) + if (unit_pending_active(other)) + return; + + SET_FOREACH(other, u->dependencies[UNIT_WANTED_BY], i) + if (unit_pending_active(other)) + return; + + SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) + if (unit_pending_active(other)) + return; + + log_info("Service %s is not needed anymore. Stopping.", u->id); + + /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */ + manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, true, NULL, NULL); +} + +static void retroactively_start_dependencies(Unit *u) { + Iterator i; + Unit *other; + + assert(u); + assert(UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))); + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) + if (!set_get(u->dependencies[UNIT_AFTER], other) && + !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, true, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_BIND_TO], i) + if (!set_get(u->dependencies[UNIT_AFTER], other) && + !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, true, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRES_OVERRIDABLE], i) + if (!set_get(u->dependencies[UNIT_AFTER], other) && + !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_START, other, JOB_FAIL, false, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], i) + if (!set_get(u->dependencies[UNIT_AFTER], other) && + !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, true, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) + if (!set_get(u->dependencies[UNIT_AFTER], other) && + !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_START, other, JOB_FAIL, false, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_CONFLICTS], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, true, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, true, NULL, NULL); +} + +static void retroactively_stop_dependencies(Unit *u) { + Iterator i; + Unit *other; + + assert(u); + assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); + + /* Pull down units which are bound to us recursively if enabled */ + SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, true, NULL, NULL); +} + +static void check_unneeded_dependencies(Unit *u) { + Iterator i; + Unit *other; + + assert(u); + assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); + + /* Garbage collect services that might not be needed anymore, if enabled */ + SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); + SET_FOREACH(other, u->dependencies[UNIT_REQUIRES_OVERRIDABLE], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); + SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); + SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); + SET_FOREACH(other, u->dependencies[UNIT_REQUISITE_OVERRIDABLE], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); + SET_FOREACH(other, u->dependencies[UNIT_BIND_TO], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); +} + +void unit_trigger_on_failure(Unit *u) { + Unit *other; + Iterator i; + + assert(u); + + if (set_size(u->dependencies[UNIT_ON_FAILURE]) <= 0) + return; + + log_info("Triggering OnFailure= dependencies of %s.", u->id); + + SET_FOREACH(other, u->dependencies[UNIT_ON_FAILURE], i) { + int r; + + if ((r = manager_add_job(u->manager, JOB_START, other, u->on_failure_isolate ? JOB_ISOLATE : JOB_REPLACE, true, NULL, NULL)) < 0) + log_error("Failed to enqueue OnFailure= job: %s", strerror(-r)); + } +} + +void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) { + bool unexpected; + + assert(u); + assert(os < _UNIT_ACTIVE_STATE_MAX); + assert(ns < _UNIT_ACTIVE_STATE_MAX); + + /* Note that this is called for all low-level state changes, + * even if they might map to the same high-level + * UnitActiveState! That means that ns == os is OK an expected + * behaviour here. For example: if a mount point is remounted + * this function will be called too! */ + + if (u->manager->n_reloading <= 0) { + dual_timestamp ts; + + dual_timestamp_get(&ts); + + if (UNIT_IS_INACTIVE_OR_FAILED(os) && !UNIT_IS_INACTIVE_OR_FAILED(ns)) + u->inactive_exit_timestamp = ts; + else if (!UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_INACTIVE_OR_FAILED(ns)) + u->inactive_enter_timestamp = ts; + + if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns)) + u->active_enter_timestamp = ts; + else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns)) + u->active_exit_timestamp = ts; + + timer_unit_notify(u, ns); + path_unit_notify(u, ns); + } + + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + cgroup_bonding_trim_list(u->cgroup_bondings, true); + + if (u->job) { + unexpected = false; + + if (u->job->state == JOB_WAITING) + + /* So we reached a different state for this + * job. Let's see if we can run it now if it + * failed previously due to EAGAIN. */ + job_add_to_run_queue(u->job); + + /* Let's check whether this state change constitutes a + * finished job, or maybe contradicts a running job and + * hence needs to invalidate jobs. */ + + switch (u->job->type) { + + case JOB_START: + case JOB_VERIFY_ACTIVE: + + if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) + job_finish_and_invalidate(u->job, JOB_DONE); + else if (u->job->state == JOB_RUNNING && ns != UNIT_ACTIVATING) { + unexpected = true; + + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE); + } + + break; + + case JOB_RELOAD: + case JOB_RELOAD_OR_START: + + if (u->job->state == JOB_RUNNING) { + if (ns == UNIT_ACTIVE) + job_finish_and_invalidate(u->job, reload_success ? JOB_DONE : JOB_FAILED); + else if (ns != UNIT_ACTIVATING && ns != UNIT_RELOADING) { + unexpected = true; + + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE); + } + } + + break; + + case JOB_STOP: + case JOB_RESTART: + case JOB_TRY_RESTART: + + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + job_finish_and_invalidate(u->job, JOB_DONE); + else if (u->job->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) { + unexpected = true; + job_finish_and_invalidate(u->job, JOB_FAILED); + } + + break; + + default: + assert_not_reached("Job type unknown"); + } + + } else + unexpected = true; + + if (u->manager->n_reloading <= 0) { + + /* If this state change happened without being + * requested by a job, then let's retroactively start + * or stop dependencies. We skip that step when + * deserializing, since we don't want to create any + * additional jobs just because something is already + * activated. */ + + if (unexpected) { + if (UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_ACTIVE_OR_ACTIVATING(ns)) + retroactively_start_dependencies(u); + else if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) + retroactively_stop_dependencies(u); + } + + /* stop unneeded units regardless if going down was expected or not */ + if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) + check_unneeded_dependencies(u); + + if (ns != os && ns == UNIT_FAILED) { + log_notice("Unit %s entered failed state.", u->id); + unit_trigger_on_failure(u); + } + } + + /* Some names are special */ + if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { + + if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) + /* The bus just might have become available, + * hence try to connect to it, if we aren't + * yet connected. */ + bus_init(u->manager, true); + + if (u->type == UNIT_SERVICE && + !UNIT_IS_ACTIVE_OR_RELOADING(os) && + u->manager->n_reloading <= 0) { + /* Write audit record if we have just finished starting up */ + manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, true); + u->in_audit = true; + } + + if (!UNIT_IS_ACTIVE_OR_RELOADING(os)) + manager_send_unit_plymouth(u->manager, u); + + } else { + + /* We don't care about D-Bus here, since we'll get an + * asynchronous notification for it anyway. */ + + if (u->type == UNIT_SERVICE && + UNIT_IS_INACTIVE_OR_FAILED(ns) && + !UNIT_IS_INACTIVE_OR_FAILED(os) && + u->manager->n_reloading <= 0) { + + /* Hmm, if there was no start record written + * write it now, so that we always have a nice + * pair */ + if (!u->in_audit) { + manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); + + if (ns == UNIT_INACTIVE) + manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, true); + } else + /* Write audit record if we have just finished shutting down */ + manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); + + u->in_audit = false; + } + } + + manager_recheck_journal(u->manager); + + /* Maybe we finished startup and are now ready for being + * stopped because unneeded? */ + unit_check_unneeded(u); + + unit_add_to_dbus_queue(u); + unit_add_to_gc_queue(u); +} + +int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w) { + struct epoll_event ev; + + assert(u); + assert(fd >= 0); + assert(w); + assert(w->type == WATCH_INVALID || (w->type == WATCH_FD && w->fd == fd && w->data.unit == u)); + + zero(ev); + ev.data.ptr = w; + ev.events = events; + + if (epoll_ctl(u->manager->epoll_fd, + w->type == WATCH_INVALID ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, + fd, + &ev) < 0) + return -errno; + + w->fd = fd; + w->type = WATCH_FD; + w->data.unit = u; + + return 0; +} + +void unit_unwatch_fd(Unit *u, Watch *w) { + assert(u); + assert(w); + + if (w->type == WATCH_INVALID) + return; + + assert(w->type == WATCH_FD); + assert(w->data.unit == u); + assert_se(epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); + + w->fd = -1; + w->type = WATCH_INVALID; + w->data.unit = NULL; +} + +int unit_watch_pid(Unit *u, pid_t pid) { + assert(u); + assert(pid >= 1); + + /* Watch a specific PID. We only support one unit watching + * each PID for now. */ + + return hashmap_put(u->manager->watch_pids, LONG_TO_PTR(pid), u); +} + +void unit_unwatch_pid(Unit *u, pid_t pid) { + assert(u); + assert(pid >= 1); + + hashmap_remove_value(u->manager->watch_pids, LONG_TO_PTR(pid), u); +} + +int unit_watch_timer(Unit *u, usec_t delay, Watch *w) { + struct itimerspec its; + int flags, fd; + bool ours; + + assert(u); + assert(w); + assert(w->type == WATCH_INVALID || (w->type == WATCH_UNIT_TIMER && w->data.unit == u)); + + /* This will try to reuse the old timer if there is one */ + + if (w->type == WATCH_UNIT_TIMER) { + assert(w->data.unit == u); + assert(w->fd >= 0); + + ours = false; + fd = w->fd; + } else if (w->type == WATCH_INVALID) { + + ours = true; + if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) + return -errno; + } else + assert_not_reached("Invalid watch type"); + + zero(its); + + if (delay <= 0) { + /* Set absolute time in the past, but not 0, since we + * don't want to disarm the timer */ + its.it_value.tv_sec = 0; + its.it_value.tv_nsec = 1; + + flags = TFD_TIMER_ABSTIME; + } else { + timespec_store(&its.it_value, delay); + flags = 0; + } + + /* This will also flush the elapse counter */ + if (timerfd_settime(fd, flags, &its, NULL) < 0) + goto fail; + + if (w->type == WATCH_INVALID) { + struct epoll_event ev; + + zero(ev); + ev.data.ptr = w; + ev.events = EPOLLIN; + + if (epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) + goto fail; + } + + w->type = WATCH_UNIT_TIMER; + w->fd = fd; + w->data.unit = u; + + return 0; + +fail: + if (ours) + close_nointr_nofail(fd); + + return -errno; +} + +void unit_unwatch_timer(Unit *u, Watch *w) { + assert(u); + assert(w); + + if (w->type == WATCH_INVALID) + return; + + assert(w->type == WATCH_UNIT_TIMER); + assert(w->data.unit == u); + assert(w->fd >= 0); + + assert_se(epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); + close_nointr_nofail(w->fd); + + w->fd = -1; + w->type = WATCH_INVALID; + w->data.unit = NULL; +} + +bool unit_job_is_applicable(Unit *u, JobType j) { + assert(u); + assert(j >= 0 && j < _JOB_TYPE_MAX); + + switch (j) { + + case JOB_VERIFY_ACTIVE: + case JOB_START: + case JOB_STOP: + return true; + + case JOB_RESTART: + case JOB_TRY_RESTART: + return unit_can_start(u); + + case JOB_RELOAD: + return unit_can_reload(u); + + case JOB_RELOAD_OR_START: + return unit_can_reload(u) && unit_can_start(u); + + default: + assert_not_reached("Invalid job type"); + } +} + +int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference) { + + static const UnitDependency inverse_table[_UNIT_DEPENDENCY_MAX] = { + [UNIT_REQUIRES] = UNIT_REQUIRED_BY, + [UNIT_REQUIRES_OVERRIDABLE] = UNIT_REQUIRED_BY_OVERRIDABLE, + [UNIT_WANTS] = UNIT_WANTED_BY, + [UNIT_REQUISITE] = UNIT_REQUIRED_BY, + [UNIT_REQUISITE_OVERRIDABLE] = UNIT_REQUIRED_BY_OVERRIDABLE, + [UNIT_BIND_TO] = UNIT_BOUND_BY, + [UNIT_REQUIRED_BY] = _UNIT_DEPENDENCY_INVALID, + [UNIT_REQUIRED_BY_OVERRIDABLE] = _UNIT_DEPENDENCY_INVALID, + [UNIT_WANTED_BY] = _UNIT_DEPENDENCY_INVALID, + [UNIT_BOUND_BY] = UNIT_BIND_TO, + [UNIT_CONFLICTS] = UNIT_CONFLICTED_BY, + [UNIT_CONFLICTED_BY] = UNIT_CONFLICTS, + [UNIT_BEFORE] = UNIT_AFTER, + [UNIT_AFTER] = UNIT_BEFORE, + [UNIT_ON_FAILURE] = _UNIT_DEPENDENCY_INVALID, + [UNIT_REFERENCES] = UNIT_REFERENCED_BY, + [UNIT_REFERENCED_BY] = UNIT_REFERENCES, + [UNIT_TRIGGERS] = UNIT_TRIGGERED_BY, + [UNIT_TRIGGERED_BY] = UNIT_TRIGGERS, + [UNIT_PROPAGATE_RELOAD_TO] = UNIT_PROPAGATE_RELOAD_FROM, + [UNIT_PROPAGATE_RELOAD_FROM] = UNIT_PROPAGATE_RELOAD_TO + }; + int r, q = 0, v = 0, w = 0; + + assert(u); + assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX); + assert(other); + + u = unit_follow_merge(u); + other = unit_follow_merge(other); + + /* We won't allow dependencies on ourselves. We will not + * consider them an error however. */ + if (u == other) + return 0; + + if ((r = set_ensure_allocated(&u->dependencies[d], trivial_hash_func, trivial_compare_func)) < 0) + return r; + + if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) + if ((r = set_ensure_allocated(&other->dependencies[inverse_table[d]], trivial_hash_func, trivial_compare_func)) < 0) + return r; + + if (add_reference) + if ((r = set_ensure_allocated(&u->dependencies[UNIT_REFERENCES], trivial_hash_func, trivial_compare_func)) < 0 || + (r = set_ensure_allocated(&other->dependencies[UNIT_REFERENCED_BY], trivial_hash_func, trivial_compare_func)) < 0) + return r; + + if ((q = set_put(u->dependencies[d], other)) < 0) + return q; + + if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) + if ((v = set_put(other->dependencies[inverse_table[d]], u)) < 0) { + r = v; + goto fail; + } + + if (add_reference) { + if ((w = set_put(u->dependencies[UNIT_REFERENCES], other)) < 0) { + r = w; + goto fail; + } + + if ((r = set_put(other->dependencies[UNIT_REFERENCED_BY], u)) < 0) + goto fail; + } + + unit_add_to_dbus_queue(u); + return 0; + +fail: + if (q > 0) + set_remove(u->dependencies[d], other); + + if (v > 0) + set_remove(other->dependencies[inverse_table[d]], u); + + if (w > 0) + set_remove(u->dependencies[UNIT_REFERENCES], other); + + return r; +} + +int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference) { + int r; + + assert(u); + + if ((r = unit_add_dependency(u, d, other, add_reference)) < 0) + return r; + + if ((r = unit_add_dependency(u, e, other, add_reference)) < 0) + return r; + + return 0; +} + +static const char *resolve_template(Unit *u, const char *name, const char*path, char **p) { + char *s; + + assert(u); + assert(name || path); + + if (!name) + name = file_name_from_path(path); + + if (!unit_name_is_template(name)) { + *p = NULL; + return name; + } + + if (u->instance) + s = unit_name_replace_instance(name, u->instance); + else { + char *i; + + if (!(i = unit_name_to_prefix(u->id))) + return NULL; + + s = unit_name_replace_instance(name, i); + free(i); + } + + if (!s) + return NULL; + + *p = s; + return s; +} + +int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { + Unit *other; + int r; + char *s; + + assert(u); + assert(name || path); + + if (!(name = resolve_template(u, name, path, &s))) + return -ENOMEM; + + if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) + goto finish; + + r = unit_add_dependency(u, d, other, add_reference); + +finish: + free(s); + return r; +} + +int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { + Unit *other; + int r; + char *s; + + assert(u); + assert(name || path); + + if (!(name = resolve_template(u, name, path, &s))) + return -ENOMEM; + + if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) + goto finish; + + r = unit_add_two_dependencies(u, d, e, other, add_reference); + +finish: + free(s); + return r; +} + +int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { + Unit *other; + int r; + char *s; + + assert(u); + assert(name || path); + + if (!(name = resolve_template(u, name, path, &s))) + return -ENOMEM; + + if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) + goto finish; + + r = unit_add_dependency(other, d, u, add_reference); + +finish: + free(s); + return r; +} + +int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { + Unit *other; + int r; + char *s; + + assert(u); + assert(name || path); + + if (!(name = resolve_template(u, name, path, &s))) + return -ENOMEM; + + if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) + goto finish; + + if ((r = unit_add_two_dependencies(other, d, e, u, add_reference)) < 0) + goto finish; + +finish: + free(s); + return r; +} + +int set_unit_path(const char *p) { + char *cwd, *c; + int r; + + /* This is mostly for debug purposes */ + + if (path_is_absolute(p)) { + if (!(c = strdup(p))) + return -ENOMEM; + } else { + if (!(cwd = get_current_dir_name())) + return -errno; + + r = asprintf(&c, "%s/%s", cwd, p); + free(cwd); + + if (r < 0) + return -ENOMEM; + } + + if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0) { + r = -errno; + free(c); + return r; + } + + return 0; +} + +char *unit_dbus_path(Unit *u) { + char *p, *e; + + assert(u); + + if (!u->id) + return NULL; + + if (!(e = bus_path_escape(u->id))) + return NULL; + + p = strappend("/org/freedesktop/systemd1/unit/", e); + free(e); + + return p; +} + +int unit_add_cgroup(Unit *u, CGroupBonding *b) { + int r; + + assert(u); + assert(b); + + assert(b->path); + + if (!b->controller) { + if (!(b->controller = strdup(SYSTEMD_CGROUP_CONTROLLER))) + return -ENOMEM; + + b->ours = true; + } + + /* Ensure this hasn't been added yet */ + assert(!b->unit); + + if (streq(b->controller, SYSTEMD_CGROUP_CONTROLLER)) { + CGroupBonding *l; + + l = hashmap_get(u->manager->cgroup_bondings, b->path); + LIST_PREPEND(CGroupBonding, by_path, l, b); + + if ((r = hashmap_replace(u->manager->cgroup_bondings, b->path, l)) < 0) { + LIST_REMOVE(CGroupBonding, by_path, l, b); + return r; + } + } + + LIST_PREPEND(CGroupBonding, by_unit, u->cgroup_bondings, b); + b->unit = u; + + return 0; +} + +static char *default_cgroup_path(Unit *u) { + char *p; + + assert(u); + + if (u->instance) { + char *t; + + t = unit_name_template(u->id); + if (!t) + return NULL; + + p = join(u->manager->cgroup_hierarchy, "/", t, "/", u->instance, NULL); + free(t); + } else + p = join(u->manager->cgroup_hierarchy, "/", u->id, NULL); + + return p; +} + +int unit_add_cgroup_from_text(Unit *u, const char *name) { + char *controller = NULL, *path = NULL; + CGroupBonding *b = NULL; + bool ours = false; + int r; + + assert(u); + assert(name); + + if ((r = cg_split_spec(name, &controller, &path)) < 0) + return r; + + if (!path) { + path = default_cgroup_path(u); + ours = true; + } + + if (!controller) { + controller = strdup(SYSTEMD_CGROUP_CONTROLLER); + ours = true; + } + + if (!path || !controller) { + free(path); + free(controller); + + return -ENOMEM; + } + + if (cgroup_bonding_find_list(u->cgroup_bondings, controller)) { + r = -EEXIST; + goto fail; + } + + if (!(b = new0(CGroupBonding, 1))) { + r = -ENOMEM; + goto fail; + } + + b->controller = controller; + b->path = path; + b->ours = ours; + b->essential = streq(controller, SYSTEMD_CGROUP_CONTROLLER); + + if ((r = unit_add_cgroup(u, b)) < 0) + goto fail; + + return 0; + +fail: + free(path); + free(controller); + free(b); + + return r; +} + +static int unit_add_one_default_cgroup(Unit *u, const char *controller) { + CGroupBonding *b = NULL; + int r = -ENOMEM; + + assert(u); + + if (!controller) + controller = SYSTEMD_CGROUP_CONTROLLER; + + if (cgroup_bonding_find_list(u->cgroup_bondings, controller)) + return 0; + + if (!(b = new0(CGroupBonding, 1))) + return -ENOMEM; + + if (!(b->controller = strdup(controller))) + goto fail; + + if (!(b->path = default_cgroup_path(u))) + goto fail; + + b->ours = true; + b->essential = streq(controller, SYSTEMD_CGROUP_CONTROLLER); + + if ((r = unit_add_cgroup(u, b)) < 0) + goto fail; + + return 0; + +fail: + free(b->path); + free(b->controller); + free(b); + + return r; +} + +int unit_add_default_cgroups(Unit *u) { + CGroupAttribute *a; + char **c; + int r; + + assert(u); + + /* Adds in the default cgroups, if they weren't specified + * otherwise. */ + + if (!u->manager->cgroup_hierarchy) + return 0; + + if ((r = unit_add_one_default_cgroup(u, NULL)) < 0) + return r; + + STRV_FOREACH(c, u->manager->default_controllers) + unit_add_one_default_cgroup(u, *c); + + LIST_FOREACH(by_unit, a, u->cgroup_attributes) + unit_add_one_default_cgroup(u, a->controller); + + return 0; +} + +CGroupBonding* unit_get_default_cgroup(Unit *u) { + assert(u); + + return cgroup_bonding_find_list(u->cgroup_bondings, SYSTEMD_CGROUP_CONTROLLER); +} + +int unit_add_cgroup_attribute(Unit *u, const char *controller, const char *name, const char *value, CGroupAttributeMapCallback map_callback) { + int r; + char *c = NULL; + CGroupAttribute *a; + + assert(u); + assert(name); + assert(value); + + if (!controller) { + const char *dot; + + dot = strchr(name, '.'); + if (!dot) + return -EINVAL; + + c = strndup(name, dot - name); + if (!c) + return -ENOMEM; + + controller = c; + } + + if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { + r = -EINVAL; + goto finish; + } + + a = new0(CGroupAttribute, 1); + if (!a) { + r = -ENOMEM; + goto finish; + } + + if (c) { + a->controller = c; + c = NULL; + } else + a->controller = strdup(controller); + + a->name = strdup(name); + a->value = strdup(value); + + if (!a->controller || !a->name || !a->value) { + free(a->controller); + free(a->name); + free(a->value); + free(a); + + return -ENOMEM; + } + + a->map_callback = map_callback; + + LIST_PREPEND(CGroupAttribute, by_unit, u->cgroup_attributes, a); + + r = 0; + +finish: + free(c); + return r; +} + +int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { + char *t; + int r; + + assert(u); + assert(type); + assert(_found); + + if (!(t = unit_name_change_suffix(u->id, type))) + return -ENOMEM; + + assert(!unit_has_name(u, t)); + + r = manager_load_unit(u->manager, t, NULL, NULL, _found); + free(t); + + assert(r < 0 || *_found != u); + + return r; +} + +int unit_get_related_unit(Unit *u, const char *type, Unit **_found) { + Unit *found; + char *t; + + assert(u); + assert(type); + assert(_found); + + if (!(t = unit_name_change_suffix(u->id, type))) + return -ENOMEM; + + assert(!unit_has_name(u, t)); + + found = manager_get_unit(u->manager, t); + free(t); + + if (!found) + return -ENOENT; + + *_found = found; + return 0; +} + +static char *specifier_prefix_and_instance(char specifier, void *data, void *userdata) { + Unit *u = userdata; + assert(u); + + return unit_name_to_prefix_and_instance(u->id); +} + +static char *specifier_prefix(char specifier, void *data, void *userdata) { + Unit *u = userdata; + assert(u); + + return unit_name_to_prefix(u->id); +} + +static char *specifier_prefix_unescaped(char specifier, void *data, void *userdata) { + Unit *u = userdata; + char *p, *r; + + assert(u); + + if (!(p = unit_name_to_prefix(u->id))) + return NULL; + + r = unit_name_unescape(p); + free(p); + + return r; +} + +static char *specifier_instance_unescaped(char specifier, void *data, void *userdata) { + Unit *u = userdata; + assert(u); + + if (u->instance) + return unit_name_unescape(u->instance); + + return strdup(""); +} + +static char *specifier_filename(char specifier, void *data, void *userdata) { + Unit *u = userdata; + assert(u); + + if (u->instance) + return unit_name_path_unescape(u->instance); + + return unit_name_to_path(u->instance); +} + +static char *specifier_cgroup(char specifier, void *data, void *userdata) { + Unit *u = userdata; + assert(u); + + return default_cgroup_path(u); +} + +static char *specifier_cgroup_root(char specifier, void *data, void *userdata) { + Unit *u = userdata; + char *p; + assert(u); + + if (specifier == 'r') + return strdup(u->manager->cgroup_hierarchy); + + if (parent_of_path(u->manager->cgroup_hierarchy, &p) < 0) + return strdup(""); + + if (streq(p, "/")) { + free(p); + return strdup(""); + } + + return p; +} + +static char *specifier_runtime(char specifier, void *data, void *userdata) { + Unit *u = userdata; + assert(u); + + if (u->manager->running_as == MANAGER_USER) { + const char *e; + + e = getenv("XDG_RUNTIME_DIR"); + if (e) + return strdup(e); + } + + return strdup("/run"); +} + +char *unit_name_printf(Unit *u, const char* format) { + + /* + * This will use the passed string as format string and + * replace the following specifiers: + * + * %n: the full id of the unit (foo@bar.waldo) + * %N: the id of the unit without the suffix (foo@bar) + * %p: the prefix (foo) + * %i: the instance (bar) + */ + + const Specifier table[] = { + { 'n', specifier_string, u->id }, + { 'N', specifier_prefix_and_instance, NULL }, + { 'p', specifier_prefix, NULL }, + { 'i', specifier_string, u->instance }, + { 0, NULL, NULL } + }; + + assert(u); + assert(format); + + return specifier_printf(format, table, u); +} + +char *unit_full_printf(Unit *u, const char *format) { + + /* This is similar to unit_name_printf() but also supports + * unescaping. Also, adds a couple of additional codes: + * + * %c cgroup path of unit + * %r root cgroup path of this systemd instance (e.g. "/user/lennart/shared/systemd-4711") + * %R parent of root cgroup path (e.g. "/usr/lennart/shared") + * %t the runtime directory to place sockets in (e.g. "/run" or $XDG_RUNTIME_DIR) + */ + + const Specifier table[] = { + { 'n', specifier_string, u->id }, + { 'N', specifier_prefix_and_instance, NULL }, + { 'p', specifier_prefix, NULL }, + { 'P', specifier_prefix_unescaped, NULL }, + { 'i', specifier_string, u->instance }, + { 'I', specifier_instance_unescaped, NULL }, + { 'f', specifier_filename, NULL }, + { 'c', specifier_cgroup, NULL }, + { 'r', specifier_cgroup_root, NULL }, + { 'R', specifier_cgroup_root, NULL }, + { 't', specifier_runtime, NULL }, + { 0, NULL, NULL } + }; + + assert(u); + assert(format); + + return specifier_printf(format, table, u); +} + +char **unit_full_printf_strv(Unit *u, char **l) { + size_t n; + char **r, **i, **j; + + /* Applies unit_full_printf to every entry in l */ + + assert(u); + + n = strv_length(l); + if (!(r = new(char*, n+1))) + return NULL; + + for (i = l, j = r; *i; i++, j++) + if (!(*j = unit_full_printf(u, *i))) + goto fail; + + *j = NULL; + return r; + +fail: + for (j--; j >= r; j--) + free(*j); + + free(r); + + return NULL; +} + +int unit_watch_bus_name(Unit *u, const char *name) { + assert(u); + assert(name); + + /* Watch a specific name on the bus. We only support one unit + * watching each name for now. */ + + return hashmap_put(u->manager->watch_bus, name, u); +} + +void unit_unwatch_bus_name(Unit *u, const char *name) { + assert(u); + assert(name); + + hashmap_remove_value(u->manager->watch_bus, name, u); +} + +bool unit_can_serialize(Unit *u) { + assert(u); + + return UNIT_VTABLE(u)->serialize && UNIT_VTABLE(u)->deserialize_item; +} + +int unit_serialize(Unit *u, FILE *f, FDSet *fds) { + int r; + + assert(u); + assert(f); + assert(fds); + + if (!unit_can_serialize(u)) + return 0; + + if ((r = UNIT_VTABLE(u)->serialize(u, f, fds)) < 0) + return r; + + if (u->job) + unit_serialize_item(u, f, "job", job_type_to_string(u->job->type)); + + dual_timestamp_serialize(f, "inactive-exit-timestamp", &u->inactive_exit_timestamp); + dual_timestamp_serialize(f, "active-enter-timestamp", &u->active_enter_timestamp); + dual_timestamp_serialize(f, "active-exit-timestamp", &u->active_exit_timestamp); + dual_timestamp_serialize(f, "inactive-enter-timestamp", &u->inactive_enter_timestamp); + dual_timestamp_serialize(f, "condition-timestamp", &u->condition_timestamp); + + if (dual_timestamp_is_set(&u->condition_timestamp)) + unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result)); + + /* End marker */ + fputc('\n', f); + return 0; +} + +void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *format, ...) { + va_list ap; + + assert(u); + assert(f); + assert(key); + assert(format); + + fputs(key, f); + fputc('=', f); + + va_start(ap, format); + vfprintf(f, format, ap); + va_end(ap); + + fputc('\n', f); +} + +void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) { + assert(u); + assert(f); + assert(key); + assert(value); + + fprintf(f, "%s=%s\n", key, value); +} + +int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { + int r; + + assert(u); + assert(f); + assert(fds); + + if (!unit_can_serialize(u)) + return 0; + + for (;;) { + char line[LINE_MAX], *l, *v; + size_t k; + + if (!fgets(line, sizeof(line), f)) { + if (feof(f)) + return 0; + return -errno; + } + + char_array_0(line); + l = strstrip(line); + + /* End marker */ + if (l[0] == 0) + return 0; + + k = strcspn(l, "="); + + if (l[k] == '=') { + l[k] = 0; + v = l+k+1; + } else + v = l+k; + + if (streq(l, "job")) { + JobType type; + + if ((type = job_type_from_string(v)) < 0) + log_debug("Failed to parse job type value %s", v); + else + u->deserialized_job = type; + + continue; + } else if (streq(l, "inactive-exit-timestamp")) { + dual_timestamp_deserialize(v, &u->inactive_exit_timestamp); + continue; + } else if (streq(l, "active-enter-timestamp")) { + dual_timestamp_deserialize(v, &u->active_enter_timestamp); + continue; + } else if (streq(l, "active-exit-timestamp")) { + dual_timestamp_deserialize(v, &u->active_exit_timestamp); + continue; + } else if (streq(l, "inactive-enter-timestamp")) { + dual_timestamp_deserialize(v, &u->inactive_enter_timestamp); + continue; + } else if (streq(l, "condition-timestamp")) { + dual_timestamp_deserialize(v, &u->condition_timestamp); + continue; + } else if (streq(l, "condition-result")) { + int b; + + if ((b = parse_boolean(v)) < 0) + log_debug("Failed to parse condition result value %s", v); + else + u->condition_result = b; + + continue; + } + + if ((r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds)) < 0) + return r; + } +} + +int unit_add_node_link(Unit *u, const char *what, bool wants) { + Unit *device; + char *e; + int r; + + assert(u); + + if (!what) + return 0; + + /* Adds in links to the device node that this unit is based on */ + + if (!is_device_path(what)) + return 0; + + if (!(e = unit_name_build_escape(what+1, NULL, ".device"))) + return -ENOMEM; + + r = manager_load_unit(u->manager, e, NULL, NULL, &device); + free(e); + + if (r < 0) + return r; + + if ((r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_BIND_TO, device, true)) < 0) + return r; + + if (wants) + if ((r = unit_add_dependency(device, UNIT_WANTS, u, false)) < 0) + return r; + + return 0; +} + +int unit_coldplug(Unit *u) { + int r; + + assert(u); + + if (UNIT_VTABLE(u)->coldplug) + if ((r = UNIT_VTABLE(u)->coldplug(u)) < 0) + return r; + + if (u->deserialized_job >= 0) { + if ((r = manager_add_job(u->manager, u->deserialized_job, u, JOB_IGNORE_REQUIREMENTS, false, NULL, NULL)) < 0) + return r; + + u->deserialized_job = _JOB_TYPE_INVALID; + } + + return 0; +} + +void unit_status_printf(Unit *u, const char *status, const char *format, ...) { + va_list ap; + + assert(u); + assert(format); + + if (!UNIT_VTABLE(u)->show_status) + return; + + if (!manager_get_show_status(u->manager)) + return; + + if (!manager_is_booting_or_shutting_down(u->manager)) + return; + + va_start(ap, format); + status_vprintf(status, true, format, ap); + va_end(ap); +} + +bool unit_need_daemon_reload(Unit *u) { + assert(u); + + if (u->fragment_path) { + struct stat st; + + zero(st); + if (stat(u->fragment_path, &st) < 0) + /* What, cannot access this anymore? */ + return true; + + if (u->fragment_mtime > 0 && + timespec_load(&st.st_mtim) != u->fragment_mtime) + return true; + } + + if (UNIT_VTABLE(u)->need_daemon_reload) + return UNIT_VTABLE(u)->need_daemon_reload(u); + + return false; +} + +void unit_reset_failed(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->reset_failed) + UNIT_VTABLE(u)->reset_failed(u); +} + +Unit *unit_following(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->following) + return UNIT_VTABLE(u)->following(u); + + return NULL; +} + +bool unit_pending_inactive(Unit *u) { + assert(u); + + /* Returns true if the unit is inactive or going down */ + + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))) + return true; + + if (u->job && u->job->type == JOB_STOP) + return true; + + return false; +} + +bool unit_pending_active(Unit *u) { + assert(u); + + /* Returns true if the unit is active or going up */ + + if (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) + return true; + + if (u->job && + (u->job->type == JOB_START || + u->job->type == JOB_RELOAD_OR_START || + u->job->type == JOB_RESTART)) + return true; + + return false; +} + +UnitType unit_name_to_type(const char *n) { + UnitType t; + + assert(n); + + for (t = 0; t < _UNIT_TYPE_MAX; t++) + if (endswith(n, unit_vtable[t]->suffix)) + return t; + + return _UNIT_TYPE_INVALID; +} + +bool unit_name_is_valid(const char *n, bool template_ok) { + UnitType t; + + t = unit_name_to_type(n); + if (t < 0 || t >= _UNIT_TYPE_MAX) + return false; + + return unit_name_is_valid_no_type(n, template_ok); +} + +int unit_kill(Unit *u, KillWho w, KillMode m, int signo, DBusError *error) { + assert(u); + assert(w >= 0 && w < _KILL_WHO_MAX); + assert(m >= 0 && m < _KILL_MODE_MAX); + assert(signo > 0); + assert(signo < _NSIG); + + if (m == KILL_NONE) + return 0; + + if (!UNIT_VTABLE(u)->kill) + return -ENOTSUP; + + return UNIT_VTABLE(u)->kill(u, w, m, signo, error); +} + +int unit_following_set(Unit *u, Set **s) { + assert(u); + assert(s); + + if (UNIT_VTABLE(u)->following_set) + return UNIT_VTABLE(u)->following_set(u, s); + + *s = NULL; + return 0; +} + +UnitFileState unit_get_unit_file_state(Unit *u) { + assert(u); + + if (u->unit_file_state < 0 && u->fragment_path) + u->unit_file_state = unit_file_get_state( + u->manager->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, + NULL, file_name_from_path(u->fragment_path)); + + return u->unit_file_state; +} + +Unit* unit_ref_set(UnitRef *ref, Unit *u) { + assert(ref); + assert(u); + + if (ref->unit) + unit_ref_unset(ref); + + ref->unit = u; + LIST_PREPEND(UnitRef, refs, u->refs, ref); + return u; +} + +void unit_ref_unset(UnitRef *ref) { + assert(ref); + + if (!ref->unit) + return; + + LIST_REMOVE(UnitRef, refs, ref->unit->refs, ref); + ref->unit = NULL; +} + +static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { + [UNIT_STUB] = "stub", + [UNIT_LOADED] = "loaded", + [UNIT_ERROR] = "error", + [UNIT_MERGED] = "merged", + [UNIT_MASKED] = "masked" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); + +static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { + [UNIT_ACTIVE] = "active", + [UNIT_RELOADING] = "reloading", + [UNIT_INACTIVE] = "inactive", + [UNIT_FAILED] = "failed", + [UNIT_ACTIVATING] = "activating", + [UNIT_DEACTIVATING] = "deactivating" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); + +static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = { + [UNIT_REQUIRES] = "Requires", + [UNIT_REQUIRES_OVERRIDABLE] = "RequiresOverridable", + [UNIT_WANTS] = "Wants", + [UNIT_REQUISITE] = "Requisite", + [UNIT_REQUISITE_OVERRIDABLE] = "RequisiteOverridable", + [UNIT_REQUIRED_BY] = "RequiredBy", + [UNIT_REQUIRED_BY_OVERRIDABLE] = "RequiredByOverridable", + [UNIT_BIND_TO] = "BindTo", + [UNIT_WANTED_BY] = "WantedBy", + [UNIT_CONFLICTS] = "Conflicts", + [UNIT_CONFLICTED_BY] = "ConflictedBy", + [UNIT_BOUND_BY] = "BoundBy", + [UNIT_BEFORE] = "Before", + [UNIT_AFTER] = "After", + [UNIT_REFERENCES] = "References", + [UNIT_REFERENCED_BY] = "ReferencedBy", + [UNIT_ON_FAILURE] = "OnFailure", + [UNIT_TRIGGERS] = "Triggers", + [UNIT_TRIGGERED_BY] = "TriggeredBy", + [UNIT_PROPAGATE_RELOAD_TO] = "PropagateReloadTo", + [UNIT_PROPAGATE_RELOAD_FROM] = "PropagateReloadFrom" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency); diff --git a/src/unit.h b/src/unit.h new file mode 100644 index 000000000..756f465da --- /dev/null +++ b/src/unit.h @@ -0,0 +1,562 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foounithfoo +#define foounithfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include + +typedef struct Unit Unit; +typedef struct UnitVTable UnitVTable; +typedef enum UnitType UnitType; +typedef enum UnitLoadState UnitLoadState; +typedef enum UnitActiveState UnitActiveState; +typedef enum UnitDependency UnitDependency; +typedef struct UnitRef UnitRef; + +#include "set.h" +#include "util.h" +#include "list.h" +#include "socket-util.h" +#include "execute.h" +#include "condition.h" +#include "install.h" + +enum UnitType { + UNIT_SERVICE = 0, + UNIT_SOCKET, + UNIT_TARGET, + UNIT_DEVICE, + UNIT_MOUNT, + UNIT_AUTOMOUNT, + UNIT_SNAPSHOT, + UNIT_TIMER, + UNIT_SWAP, + UNIT_PATH, + _UNIT_TYPE_MAX, + _UNIT_TYPE_INVALID = -1 +}; + +enum UnitLoadState { + UNIT_STUB, + UNIT_LOADED, + UNIT_ERROR, + UNIT_MERGED, + UNIT_MASKED, + _UNIT_LOAD_STATE_MAX, + _UNIT_LOAD_STATE_INVALID = -1 +}; + +enum UnitActiveState { + UNIT_ACTIVE, + UNIT_RELOADING, + UNIT_INACTIVE, + UNIT_FAILED, + UNIT_ACTIVATING, + UNIT_DEACTIVATING, + _UNIT_ACTIVE_STATE_MAX, + _UNIT_ACTIVE_STATE_INVALID = -1 +}; + +static inline bool UNIT_IS_ACTIVE_OR_RELOADING(UnitActiveState t) { + return t == UNIT_ACTIVE || t == UNIT_RELOADING; +} + +static inline bool UNIT_IS_ACTIVE_OR_ACTIVATING(UnitActiveState t) { + return t == UNIT_ACTIVE || t == UNIT_ACTIVATING || t == UNIT_RELOADING; +} + +static inline bool UNIT_IS_INACTIVE_OR_DEACTIVATING(UnitActiveState t) { + return t == UNIT_INACTIVE || t == UNIT_FAILED || t == UNIT_DEACTIVATING; +} + +static inline bool UNIT_IS_INACTIVE_OR_FAILED(UnitActiveState t) { + return t == UNIT_INACTIVE || t == UNIT_FAILED; +} + +enum UnitDependency { + /* Positive dependencies */ + UNIT_REQUIRES, + UNIT_REQUIRES_OVERRIDABLE, + UNIT_REQUISITE, + UNIT_REQUISITE_OVERRIDABLE, + UNIT_WANTS, + UNIT_BIND_TO, + + /* Inverse of the above */ + UNIT_REQUIRED_BY, /* inverse of 'requires' and 'requisite' is 'required_by' */ + UNIT_REQUIRED_BY_OVERRIDABLE, /* inverse of 'requires_overridable' and 'requisite_overridable' is 'soft_required_by' */ + UNIT_WANTED_BY, /* inverse of 'wants' */ + UNIT_BOUND_BY, /* inverse of 'bind_to' */ + + /* Negative dependencies */ + UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicted_by' */ + UNIT_CONFLICTED_BY, + + /* Order */ + UNIT_BEFORE, /* inverse of 'before' is 'after' and vice versa */ + UNIT_AFTER, + + /* On Failure */ + UNIT_ON_FAILURE, + + /* Triggers (i.e. a socket triggers a service) */ + UNIT_TRIGGERS, + UNIT_TRIGGERED_BY, + + /* Propagate reloads */ + UNIT_PROPAGATE_RELOAD_TO, + UNIT_PROPAGATE_RELOAD_FROM, + + /* Reference information for GC logic */ + UNIT_REFERENCES, /* Inverse of 'references' is 'referenced_by' */ + UNIT_REFERENCED_BY, + + _UNIT_DEPENDENCY_MAX, + _UNIT_DEPENDENCY_INVALID = -1 +}; + +#include "manager.h" +#include "job.h" +#include "cgroup.h" +#include "cgroup-attr.h" + +struct Unit { + Manager *manager; + + UnitType type; + UnitLoadState load_state; + Unit *merged_into; + + char *id; /* One name is special because we use it for identification. Points to an entry in the names set */ + char *instance; + + Set *names; + Set *dependencies[_UNIT_DEPENDENCY_MAX]; + + char *description; + + char *fragment_path; /* if loaded from a config file this is the primary path to it */ + usec_t fragment_mtime; + + /* If there is something to do with this unit, then this is + * the job for it */ + Job *job; + + usec_t job_timeout; + + /* References to this */ + LIST_HEAD(UnitRef, refs); + + /* Conditions to check */ + LIST_HEAD(Condition, conditions); + + dual_timestamp condition_timestamp; + + dual_timestamp inactive_exit_timestamp; + dual_timestamp active_enter_timestamp; + dual_timestamp active_exit_timestamp; + dual_timestamp inactive_enter_timestamp; + + /* Counterparts in the cgroup filesystem */ + CGroupBonding *cgroup_bondings; + CGroupAttribute *cgroup_attributes; + + /* Per type list */ + LIST_FIELDS(Unit, units_by_type); + + /* Load queue */ + LIST_FIELDS(Unit, load_queue); + + /* D-Bus queue */ + LIST_FIELDS(Unit, dbus_queue); + + /* Cleanup queue */ + LIST_FIELDS(Unit, cleanup_queue); + + /* GC queue */ + LIST_FIELDS(Unit, gc_queue); + + /* Used during GC sweeps */ + unsigned gc_marker; + + /* When deserializing, temporarily store the job type for this + * unit here, if there was a job scheduled */ + int deserialized_job; /* This is actually of type JobType */ + + /* Error code when we didn't manage to load the unit (negative) */ + int load_error; + + /* Cached unit file state */ + UnitFileState unit_file_state; + + /* Garbage collect us we nobody wants or requires us anymore */ + bool stop_when_unneeded; + + /* Create default dependencies */ + bool default_dependencies; + + /* Refuse manual starting, allow starting only indirectly via dependency. */ + bool refuse_manual_start; + + /* Don't allow the user to stop this unit manually, allow stopping only indirectly via dependency. */ + bool refuse_manual_stop; + + /* Allow isolation requests */ + bool allow_isolate; + + /* Isolate OnFailure unit */ + bool on_failure_isolate; + + /* Ignore this unit when isolating */ + bool ignore_on_isolate; + + /* Ignore this unit when snapshotting */ + bool ignore_on_snapshot; + + /* Did the last condition check suceed? */ + bool condition_result; + + bool in_load_queue:1; + bool in_dbus_queue:1; + bool in_cleanup_queue:1; + bool in_gc_queue:1; + + bool sent_dbus_new_signal:1; + + bool no_gc:1; + + bool in_audit:1; +}; + +struct UnitRef { + /* Keeps tracks of references to a unit. This is useful so + * that we can merge two units if necessary and correct all + * references to them */ + + Unit* unit; + LIST_FIELDS(UnitRef, refs); +}; + +#include "service.h" +#include "timer.h" +#include "socket.h" +#include "target.h" +#include "device.h" +#include "mount.h" +#include "automount.h" +#include "snapshot.h" +#include "swap.h" +#include "path.h" + +struct UnitVTable { + const char *suffix; + + /* How much memory does an object of this unit type need */ + size_t object_size; + + /* Config file sections this unit type understands, separated + * by NUL chars */ + const char *sections; + + /* This should reset all type-specific variables. This should + * not allocate memory, and is called with zero-initialized + * data. It should hence only initialize variables that need + * to be set != 0. */ + void (*init)(Unit *u); + + /* This should free all type-specific variables. It should be + * idempotent. */ + void (*done)(Unit *u); + + /* Actually load data from disk. This may fail, and should set + * load_state to UNIT_LOADED, UNIT_MERGED or leave it at + * UNIT_STUB if no configuration could be found. */ + int (*load)(Unit *u); + + /* If a a lot of units got created via enumerate(), this is + * where to actually set the state and call unit_notify(). */ + int (*coldplug)(Unit *u); + + void (*dump)(Unit *u, FILE *f, const char *prefix); + + int (*start)(Unit *u); + int (*stop)(Unit *u); + int (*reload)(Unit *u); + + int (*kill)(Unit *u, KillWho w, KillMode m, int signo, DBusError *error); + + bool (*can_reload)(Unit *u); + + /* Write all data that cannot be restored from other sources + * away using unit_serialize_item() */ + int (*serialize)(Unit *u, FILE *f, FDSet *fds); + + /* Restore one item from the serialization */ + int (*deserialize_item)(Unit *u, const char *key, const char *data, FDSet *fds); + + /* Boils down the more complex internal state of this unit to + * a simpler one that the engine can understand */ + UnitActiveState (*active_state)(Unit *u); + + /* Returns the substate specific to this unit type as + * string. This is purely information so that we can give the + * user a more fine grained explanation in which actual state a + * unit is in. */ + const char* (*sub_state_to_string)(Unit *u); + + /* Return true when there is reason to keep this entry around + * even nothing references it and it isn't active in any + * way */ + bool (*check_gc)(Unit *u); + + /* Return true when this unit is suitable for snapshotting */ + bool (*check_snapshot)(Unit *u); + + void (*fd_event)(Unit *u, int fd, uint32_t events, Watch *w); + void (*sigchld_event)(Unit *u, pid_t pid, int code, int status); + void (*timer_event)(Unit *u, uint64_t n_elapsed, Watch *w); + + /* Check whether unit needs a daemon reload */ + bool (*need_daemon_reload)(Unit *u); + + /* Reset failed state if we are in failed state */ + void (*reset_failed)(Unit *u); + + /* Called whenever any of the cgroups this unit watches for + * ran empty */ + void (*cgroup_notify_empty)(Unit *u); + + /* Called whenever a process of this unit sends us a message */ + void (*notify_message)(Unit *u, pid_t pid, char **tags); + + /* Called whenever a name thus Unit registered for comes or + * goes away. */ + void (*bus_name_owner_change)(Unit *u, const char *name, const char *old_owner, const char *new_owner); + + /* Called whenever a bus PID lookup finishes */ + void (*bus_query_pid_done)(Unit *u, const char *name, pid_t pid); + + /* Called for each message received on the bus */ + DBusHandlerResult (*bus_message_handler)(Unit *u, DBusConnection *c, DBusMessage *message); + + /* Return the unit this unit is following */ + Unit *(*following)(Unit *u); + + /* Return the set of units that are following each other */ + int (*following_set)(Unit *u, Set **s); + + /* This is called for each unit type and should be used to + * enumerate existing devices and load them. However, + * everything that is loaded here should still stay in + * inactive state. It is the job of the coldplug() call above + * to put the units into the initial state. */ + int (*enumerate)(Manager *m); + + /* Type specific cleanups. */ + void (*shutdown)(Manager *m); + + /* When sending out PropertiesChanged signal, which properties + * shall be invalidated? This is a NUL separated list of + * strings, to minimize relocations a little. */ + const char *bus_invalidating_properties; + + /* The interface name */ + const char *bus_interface; + + /* Can units of this type have multiple names? */ + bool no_alias:1; + + /* Instances make no sense for this type */ + bool no_instances:1; + + /* Exclude from automatic gc */ + bool no_gc:1; + + /* Show status updates on the console */ + bool show_status:1; +}; + +extern const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX]; + +#define UNIT_VTABLE(u) unit_vtable[(u)->type] + +/* For casting a unit into the various unit types */ +#define DEFINE_CAST(UPPERCASE, MixedCase) \ + static inline MixedCase* UPPERCASE(Unit *u) { \ + if (_unlikely_(!u || u->type != UNIT_##UPPERCASE)) \ + return NULL; \ + \ + return (MixedCase*) u; \ + } + +/* For casting the various unit types into a unit */ +#define UNIT(u) (&(u)->meta) + +DEFINE_CAST(SOCKET, Socket); +DEFINE_CAST(TIMER, Timer); +DEFINE_CAST(SERVICE, Service); +DEFINE_CAST(TARGET, Target); +DEFINE_CAST(DEVICE, Device); +DEFINE_CAST(MOUNT, Mount); +DEFINE_CAST(AUTOMOUNT, Automount); +DEFINE_CAST(SNAPSHOT, Snapshot); +DEFINE_CAST(SWAP, Swap); +DEFINE_CAST(PATH, Path); + +Unit *unit_new(Manager *m, size_t size); +void unit_free(Unit *u); + +int unit_add_name(Unit *u, const char *name); + +int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference); +int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference); + +int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *filename, bool add_reference); +int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference); + +int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *filename, bool add_reference); +int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference); + +int unit_add_exec_dependencies(Unit *u, ExecContext *c); + +int unit_add_cgroup(Unit *u, CGroupBonding *b); +int unit_add_cgroup_from_text(Unit *u, const char *name); +int unit_add_default_cgroups(Unit *u); +CGroupBonding* unit_get_default_cgroup(Unit *u); +int unit_add_cgroup_attribute(Unit *u, const char *controller, const char *name, const char *value, CGroupAttributeMapCallback map_callback); + +int unit_choose_id(Unit *u, const char *name); +int unit_set_description(Unit *u, const char *description); + +bool unit_check_gc(Unit *u); + +void unit_add_to_load_queue(Unit *u); +void unit_add_to_dbus_queue(Unit *u); +void unit_add_to_cleanup_queue(Unit *u); +void unit_add_to_gc_queue(Unit *u); + +int unit_merge(Unit *u, Unit *other); +int unit_merge_by_name(Unit *u, const char *other); + +Unit *unit_follow_merge(Unit *u); + +int unit_load_fragment_and_dropin(Unit *u); +int unit_load_fragment_and_dropin_optional(Unit *u); +int unit_load(Unit *unit); + +const char *unit_description(Unit *u); + +bool unit_has_name(Unit *u, const char *name); + +UnitActiveState unit_active_state(Unit *u); + +const char* unit_sub_state_to_string(Unit *u); + +void unit_dump(Unit *u, FILE *f, const char *prefix); + +bool unit_can_reload(Unit *u); +bool unit_can_start(Unit *u); +bool unit_can_isolate(Unit *u); + +int unit_start(Unit *u); +int unit_stop(Unit *u); +int unit_reload(Unit *u); + +int unit_kill(Unit *u, KillWho w, KillMode m, int signo, DBusError *error); + +void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success); + +int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w); +void unit_unwatch_fd(Unit *u, Watch *w); + +int unit_watch_pid(Unit *u, pid_t pid); +void unit_unwatch_pid(Unit *u, pid_t pid); + +int unit_watch_timer(Unit *u, usec_t delay, Watch *w); +void unit_unwatch_timer(Unit *u, Watch *w); + +int unit_watch_bus_name(Unit *u, const char *name); +void unit_unwatch_bus_name(Unit *u, const char *name); + +bool unit_job_is_applicable(Unit *u, JobType j); + +int set_unit_path(const char *p); + +char *unit_dbus_path(Unit *u); + +int unit_load_related_unit(Unit *u, const char *type, Unit **_found); +int unit_get_related_unit(Unit *u, const char *type, Unit **_found); + +char *unit_name_printf(Unit *u, const char* text); +char *unit_full_printf(Unit *u, const char *text); +char **unit_full_printf_strv(Unit *u, char **l); + +bool unit_can_serialize(Unit *u); +int unit_serialize(Unit *u, FILE *f, FDSet *fds); +void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *value, ...) _printf_attr_(4,5); +void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value); +int unit_deserialize(Unit *u, FILE *f, FDSet *fds); + +int unit_add_node_link(Unit *u, const char *what, bool wants); + +int unit_coldplug(Unit *u); + +void unit_status_printf(Unit *u, const char *status, const char *format, ...); + +bool unit_need_daemon_reload(Unit *u); + +void unit_reset_failed(Unit *u); + +Unit *unit_following(Unit *u); + +bool unit_pending_inactive(Unit *u); +bool unit_pending_active(Unit *u); + +int unit_add_default_target_dependency(Unit *u, Unit *target); + +int unit_following_set(Unit *u, Set **s); + +UnitType unit_name_to_type(const char *n); +bool unit_name_is_valid(const char *n, bool template_ok); + +void unit_trigger_on_failure(Unit *u); + +bool unit_condition_test(Unit *u); + +UnitFileState unit_get_unit_file_state(Unit *u); + +Unit* unit_ref_set(UnitRef *ref, Unit *u); +void unit_ref_unset(UnitRef *ref); + +#define UNIT_DEREF(ref) ((ref).unit) + +const char *unit_load_state_to_string(UnitLoadState i); +UnitLoadState unit_load_state_from_string(const char *s); + +const char *unit_active_state_to_string(UnitActiveState i); +UnitActiveState unit_active_state_from_string(const char *s); + +const char *unit_dependency_to_string(UnitDependency i); +UnitDependency unit_dependency_from_string(const char *s); + +#endif diff --git a/src/update-utmp.c b/src/update-utmp.c new file mode 100644 index 000000000..0d177d616 --- /dev/null +++ b/src/update-utmp.c @@ -0,0 +1,423 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_AUDIT +#include +#endif + +#include "log.h" +#include "macro.h" +#include "util.h" +#include "special.h" +#include "utmp-wtmp.h" +#include "dbus-common.h" + +typedef struct Context { + DBusConnection *bus; +#ifdef HAVE_AUDIT + int audit_fd; +#endif +} Context; + +static usec_t get_startup_time(Context *c) { + const char + *interface = "org.freedesktop.systemd1.Manager", + *property = "StartupTimestamp"; + + DBusError error; + usec_t t = 0; + DBusMessage *m = NULL, *reply = NULL; + DBusMessageIter iter, sub; + + dbus_error_init(&error); + + assert(c); + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.DBus.Properties", + "Get"))) { + log_error("Could not allocate message."); + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) { + log_error("Failed to send command: %s", bus_error_message(&error)); + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) { + log_error("Failed to parse reply."); + goto finish; + } + + dbus_message_iter_get_basic(&sub, &t); + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return t; +} + +static int get_current_runlevel(Context *c) { + static const struct { + const int runlevel; + const char *special; + } table[] = { + /* The first target of this list that is active or has + * a job scheduled wins. We prefer runlevels 5 and 3 + * here over the others, since these are the main + * runlevels used on Fedora. It might make sense to + * change the order on some distributions. */ + { '5', SPECIAL_RUNLEVEL5_TARGET }, + { '3', SPECIAL_RUNLEVEL3_TARGET }, + { '4', SPECIAL_RUNLEVEL4_TARGET }, + { '2', SPECIAL_RUNLEVEL2_TARGET }, + { 'S', SPECIAL_RESCUE_TARGET }, + }; + const char + *interface = "org.freedesktop.systemd1.Unit", + *property = "ActiveState"; + + DBusMessage *m = NULL, *reply = NULL; + int r = 0; + unsigned i; + DBusError error; + + assert(c); + + dbus_error_init(&error); + + for (i = 0; i < ELEMENTSOF(table); i++) { + const char *path = NULL, *state; + DBusMessageIter iter, sub; + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &table[i].special, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) { + dbus_error_free(&error); + continue; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + dbus_message_unref(m); + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "Get"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + dbus_message_unref(reply); + if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) { + log_error("Failed to send command: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_get_basic(&sub, &state); + + if (streq(state, "active") || streq(state, "reloading")) + r = table[i].runlevel; + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + + if (r) + break; + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int on_reboot(Context *c) { + int r = 0, q; + usec_t t; + + assert(c); + + /* We finished start-up, so let's write the utmp + * record and send the audit msg */ + +#ifdef HAVE_AUDIT + if (c->audit_fd >= 0) + if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "init", NULL, NULL, NULL, 1) < 0) { + log_error("Failed to send audit message: %m"); + r = -errno; + } +#endif + + /* If this call fails it will return 0, which + * utmp_put_reboot() will then fix to the current time */ + t = get_startup_time(c); + + if ((q = utmp_put_reboot(t)) < 0) { + log_error("Failed to write utmp record: %s", strerror(-q)); + r = q; + } + + return r; +} + +static int on_shutdown(Context *c) { + int r = 0, q; + + assert(c); + + /* We started shut-down, so let's write the utmp + * record and send the audit msg */ + +#ifdef HAVE_AUDIT + if (c->audit_fd >= 0) + if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "init", NULL, NULL, NULL, 1) < 0) { + log_error("Failed to send audit message: %m"); + r = -errno; + } +#endif + + if ((q = utmp_put_shutdown()) < 0) { + log_error("Failed to write utmp record: %s", strerror(-q)); + r = q; + } + + return r; +} + +static int on_runlevel(Context *c) { + int r = 0, q, previous, runlevel; + + assert(c); + + /* We finished changing runlevel, so let's write the + * utmp record and send the audit msg */ + + /* First, get last runlevel */ + if ((q = utmp_get_runlevel(&previous, NULL)) < 0) { + + if (q != -ESRCH && q != -ENOENT) { + log_error("Failed to get current runlevel: %s", strerror(-q)); + return q; + } + + /* Hmm, we didn't find any runlevel, that means we + * have been rebooted */ + r = on_reboot(c); + previous = 0; + } + + /* Secondly, get new runlevel */ + if ((runlevel = get_current_runlevel(c)) < 0) + return runlevel; + + if (previous == runlevel) + return 0; + +#ifdef HAVE_AUDIT + if (c->audit_fd >= 0) { + char *s = NULL; + + if (asprintf(&s, "old-level=%c new-level=%c", + previous > 0 ? previous : 'N', + runlevel > 0 ? runlevel : 'N') < 0) + return -ENOMEM; + + if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, NULL, NULL, NULL, 1) < 0) { + log_error("Failed to send audit message: %m"); + r = -errno; + } + + free(s); + } +#endif + + if ((q = utmp_put_runlevel(runlevel, previous)) < 0) { + log_error("Failed to write utmp record: %s", strerror(-q)); + r = q; + } + + return r; +} + +int main(int argc, char *argv[]) { + int r; + DBusError error; + Context c; + + dbus_error_init(&error); + + zero(c); +#ifdef HAVE_AUDIT + c.audit_fd = -1; +#endif + + if (getppid() != 1) { + log_error("This program should be invoked by init only."); + return EXIT_FAILURE; + } + + if (argc != 2) { + log_error("This program requires one argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + +#ifdef HAVE_AUDIT + if ((c.audit_fd = audit_open()) < 0 && + /* If the kernel lacks netlink or audit support, + * don't worry about it. */ + errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT) + log_error("Failed to connect to audit log: %m"); +#endif + + if (bus_connect(DBUS_BUS_SYSTEM, &c.bus, NULL, &error) < 0) { + log_error("Failed to get D-Bus connection: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + log_debug("systemd-update-utmp running as pid %lu", (unsigned long) getpid()); + + if (streq(argv[1], "reboot")) + r = on_reboot(&c); + else if (streq(argv[1], "shutdown")) + r = on_shutdown(&c); + else if (streq(argv[1], "runlevel")) + r = on_runlevel(&c); + else { + log_error("Unknown command %s", argv[1]); + r = -EINVAL; + } + + log_debug("systemd-update-utmp stopped as pid %lu", (unsigned long) getpid()); + +finish: +#ifdef HAVE_AUDIT + if (c.audit_fd >= 0) + audit_close(c.audit_fd); +#endif + + if (c.bus) { + dbus_connection_flush(c.bus); + dbus_connection_close(c.bus); + dbus_connection_unref(c.bus); + } + + dbus_error_free(&error); + dbus_shutdown(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/user.conf b/src/user.conf new file mode 100644 index 000000000..9508a02ee --- /dev/null +++ b/src/user.conf @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# See systemd.conf(5) for details + +[Manager] +#LogLevel=info +#LogTarget=console +#LogColor=yes +#LogLocation=no +#DefaultControllers=cpu +#DefaultStandardOutput=inherit +#DefaultStandardError=inherit diff --git a/src/utf8.c b/src/utf8.c new file mode 100644 index 000000000..11619dce2 --- /dev/null +++ b/src/utf8.c @@ -0,0 +1,214 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +/* This file is based on the GLIB utf8 validation functions. The + * original license text follows. */ + +/* gutf8.c - Operations on UTF-8 strings. + * + * Copyright (C) 1999 Tom Tromey + * Copyright (C) 2000 Red Hat, Inc. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include + +#include "utf8.h" + +#define FILTER_CHAR '_' + +static inline bool is_unicode_valid(uint32_t ch) { + + if (ch >= 0x110000) /* End of unicode space */ + return false; + if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */ + return false; + if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */ + return false; + if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */ + return false; + + return true; +} + +static inline bool is_continuation_char(uint8_t ch) { + if ((ch & 0xc0) != 0x80) /* 10xxxxxx */ + return false; + return true; +} + +static inline void merge_continuation_char(uint32_t *u_ch, uint8_t ch) { + *u_ch <<= 6; + *u_ch |= ch & 0x3f; +} + +static char* utf8_validate(const char *str, char *output) { + uint32_t val = 0; + uint32_t min = 0; + const uint8_t *p, *last; + int size; + uint8_t *o; + + assert(str); + + o = (uint8_t*) output; + for (p = (const uint8_t*) str; *p; p++) { + if (*p < 128) { + if (o) + *o = *p; + } else { + last = p; + + if ((*p & 0xe0) == 0xc0) { /* 110xxxxx two-char seq. */ + size = 2; + min = 128; + val = (uint32_t) (*p & 0x1e); + goto ONE_REMAINING; + } else if ((*p & 0xf0) == 0xe0) { /* 1110xxxx three-char seq.*/ + size = 3; + min = (1 << 11); + val = (uint32_t) (*p & 0x0f); + goto TWO_REMAINING; + } else if ((*p & 0xf8) == 0xf0) { /* 11110xxx four-char seq */ + size = 4; + min = (1 << 16); + val = (uint32_t) (*p & 0x07); + } else + goto error; + + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + + TWO_REMAINING: + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + + ONE_REMAINING: + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + + if (val < min) + goto error; + + if (!is_unicode_valid(val)) + goto error; + + if (o) { + memcpy(o, last, (size_t) size); + o += size; + } + + continue; + + error: + if (o) { + *o = FILTER_CHAR; + p = last; /* We retry at the next character */ + } else + goto failure; + } + + if (o) + o++; + } + + if (o) { + *o = '\0'; + return output; + } + + return (char*) str; + +failure: + return NULL; +} + +char* utf8_is_valid (const char *str) { + return utf8_validate(str, NULL); +} + +char* utf8_filter (const char *str) { + char *new_str; + + assert(str); + + new_str = malloc(strlen(str) + 1); + if (!new_str) + return NULL; + + return utf8_validate(str, new_str); +} + +char *ascii_is_valid(const char *str) { + const char *p; + + assert(str); + + for (p = str; *p; p++) + if ((unsigned char) *p >= 128) + return NULL; + + return (char*) str; +} + +char *ascii_filter(const char *str) { + char *r, *s, *d; + size_t l; + + assert(str); + + l = strlen(str); + r = malloc(l + 1); + if (!r) + return NULL; + + for (s = r, d = r; *s; s++) + if ((unsigned char) *s < 128) + *(d++) = *s; + + *d = 0; + + return r; +} diff --git a/src/utf8.h b/src/utf8.h new file mode 100644 index 000000000..9a72bec08 --- /dev/null +++ b/src/utf8.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooutf8hfoo +#define fooutf8hfoo + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "macro.h" + +char *utf8_is_valid(const char *s) _pure_; +char *ascii_is_valid(const char *s) _pure_; + +char *utf8_filter(const char *s); +char *ascii_filter(const char *s); + +#endif diff --git a/src/util.c b/src/util.c new file mode 100644 index 000000000..dfc1dc6b8 --- /dev/null +++ b/src/util.c @@ -0,0 +1,6256 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "macro.h" +#include "util.h" +#include "ioprio.h" +#include "missing.h" +#include "log.h" +#include "strv.h" +#include "label.h" +#include "exit-status.h" +#include "hashmap.h" + +int saved_argc = 0; +char **saved_argv = NULL; + +size_t page_size(void) { + static __thread size_t pgsz = 0; + long r; + + if (_likely_(pgsz > 0)) + return pgsz; + + assert_se((r = sysconf(_SC_PAGESIZE)) > 0); + + pgsz = (size_t) r; + + return pgsz; +} + +bool streq_ptr(const char *a, const char *b) { + + /* Like streq(), but tries to make sense of NULL pointers */ + + if (a && b) + return streq(a, b); + + if (!a && !b) + return true; + + return false; +} + +usec_t now(clockid_t clock_id) { + struct timespec ts; + + assert_se(clock_gettime(clock_id, &ts) == 0); + + return timespec_load(&ts); +} + +dual_timestamp* dual_timestamp_get(dual_timestamp *ts) { + assert(ts); + + ts->realtime = now(CLOCK_REALTIME); + ts->monotonic = now(CLOCK_MONOTONIC); + + return ts; +} + +dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { + int64_t delta; + assert(ts); + + ts->realtime = u; + + if (u == 0) + ts->monotonic = 0; + else { + delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u; + + ts->monotonic = now(CLOCK_MONOTONIC); + + if ((int64_t) ts->monotonic > delta) + ts->monotonic -= delta; + else + ts->monotonic = 0; + } + + return ts; +} + +usec_t timespec_load(const struct timespec *ts) { + assert(ts); + + return + (usec_t) ts->tv_sec * USEC_PER_SEC + + (usec_t) ts->tv_nsec / NSEC_PER_USEC; +} + +struct timespec *timespec_store(struct timespec *ts, usec_t u) { + assert(ts); + + ts->tv_sec = (time_t) (u / USEC_PER_SEC); + ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC); + + return ts; +} + +usec_t timeval_load(const struct timeval *tv) { + assert(tv); + + return + (usec_t) tv->tv_sec * USEC_PER_SEC + + (usec_t) tv->tv_usec; +} + +struct timeval *timeval_store(struct timeval *tv, usec_t u) { + assert(tv); + + tv->tv_sec = (time_t) (u / USEC_PER_SEC); + tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC); + + return tv; +} + +bool endswith(const char *s, const char *postfix) { + size_t sl, pl; + + assert(s); + assert(postfix); + + sl = strlen(s); + pl = strlen(postfix); + + if (pl == 0) + return true; + + if (sl < pl) + return false; + + return memcmp(s + sl - pl, postfix, pl) == 0; +} + +bool startswith(const char *s, const char *prefix) { + size_t sl, pl; + + assert(s); + assert(prefix); + + sl = strlen(s); + pl = strlen(prefix); + + if (pl == 0) + return true; + + if (sl < pl) + return false; + + return memcmp(s, prefix, pl) == 0; +} + +bool startswith_no_case(const char *s, const char *prefix) { + size_t sl, pl; + unsigned i; + + assert(s); + assert(prefix); + + sl = strlen(s); + pl = strlen(prefix); + + if (pl == 0) + return true; + + if (sl < pl) + return false; + + for(i = 0; i < pl; ++i) { + if (tolower(s[i]) != tolower(prefix[i])) + return false; + } + + return true; +} + +bool first_word(const char *s, const char *word) { + size_t sl, wl; + + assert(s); + assert(word); + + sl = strlen(s); + wl = strlen(word); + + if (sl < wl) + return false; + + if (wl == 0) + return true; + + if (memcmp(s, word, wl) != 0) + return false; + + return s[wl] == 0 || + strchr(WHITESPACE, s[wl]); +} + +int close_nointr(int fd) { + assert(fd >= 0); + + for (;;) { + int r; + + r = close(fd); + if (r >= 0) + return r; + + if (errno != EINTR) + return -errno; + } +} + +void close_nointr_nofail(int fd) { + int saved_errno = errno; + + /* like close_nointr() but cannot fail, and guarantees errno + * is unchanged */ + + assert_se(close_nointr(fd) == 0); + + errno = saved_errno; +} + +void close_many(const int fds[], unsigned n_fd) { + unsigned i; + + for (i = 0; i < n_fd; i++) + close_nointr_nofail(fds[i]); +} + +int parse_boolean(const char *v) { + assert(v); + + if (streq(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || !strcasecmp(v, "on")) + return 1; + else if (streq(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || !strcasecmp(v, "off")) + return 0; + + return -EINVAL; +} + +int parse_pid(const char *s, pid_t* ret_pid) { + unsigned long ul = 0; + pid_t pid; + int r; + + assert(s); + assert(ret_pid); + + if ((r = safe_atolu(s, &ul)) < 0) + return r; + + pid = (pid_t) ul; + + if ((unsigned long) pid != ul) + return -ERANGE; + + if (pid <= 0) + return -ERANGE; + + *ret_pid = pid; + return 0; +} + +int parse_uid(const char *s, uid_t* ret_uid) { + unsigned long ul = 0; + uid_t uid; + int r; + + assert(s); + assert(ret_uid); + + if ((r = safe_atolu(s, &ul)) < 0) + return r; + + uid = (uid_t) ul; + + if ((unsigned long) uid != ul) + return -ERANGE; + + *ret_uid = uid; + return 0; +} + +int safe_atou(const char *s, unsigned *ret_u) { + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret_u); + + errno = 0; + l = strtoul(s, &x, 0); + + if (!x || *x || errno) + return errno ? -errno : -EINVAL; + + if ((unsigned long) (unsigned) l != l) + return -ERANGE; + + *ret_u = (unsigned) l; + return 0; +} + +int safe_atoi(const char *s, int *ret_i) { + char *x = NULL; + long l; + + assert(s); + assert(ret_i); + + errno = 0; + l = strtol(s, &x, 0); + + if (!x || *x || errno) + return errno ? -errno : -EINVAL; + + if ((long) (int) l != l) + return -ERANGE; + + *ret_i = (int) l; + return 0; +} + +int safe_atollu(const char *s, long long unsigned *ret_llu) { + char *x = NULL; + unsigned long long l; + + assert(s); + assert(ret_llu); + + errno = 0; + l = strtoull(s, &x, 0); + + if (!x || *x || errno) + return errno ? -errno : -EINVAL; + + *ret_llu = l; + return 0; +} + +int safe_atolli(const char *s, long long int *ret_lli) { + char *x = NULL; + long long l; + + assert(s); + assert(ret_lli); + + errno = 0; + l = strtoll(s, &x, 0); + + if (!x || *x || errno) + return errno ? -errno : -EINVAL; + + *ret_lli = l; + return 0; +} + +/* Split a string into words. */ +char *split(const char *c, size_t *l, const char *separator, char **state) { + char *current; + + current = *state ? *state : (char*) c; + + if (!*current || *c == 0) + return NULL; + + current += strspn(current, separator); + *l = strcspn(current, separator); + *state = current+*l; + + return (char*) current; +} + +/* Split a string into words, but consider strings enclosed in '' and + * "" as words even if they include spaces. */ +char *split_quoted(const char *c, size_t *l, char **state) { + char *current, *e; + bool escaped = false; + + current = *state ? *state : (char*) c; + + if (!*current || *c == 0) + return NULL; + + current += strspn(current, WHITESPACE); + + if (*current == '\'') { + current ++; + + for (e = current; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + else if (*e == '\'') + break; + } + + *l = e-current; + *state = *e == 0 ? e : e+1; + } else if (*current == '\"') { + current ++; + + for (e = current; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + else if (*e == '\"') + break; + } + + *l = e-current; + *state = *e == 0 ? e : e+1; + } else { + for (e = current; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + else if (strchr(WHITESPACE, *e)) + break; + } + *l = e-current; + *state = e; + } + + return (char*) current; +} + +char **split_path_and_make_absolute(const char *p) { + char **l; + assert(p); + + if (!(l = strv_split(p, ":"))) + return NULL; + + if (!strv_path_make_absolute_cwd(l)) { + strv_free(l); + return NULL; + } + + return l; +} + +int get_parent_of_pid(pid_t pid, pid_t *_ppid) { + int r; + FILE *f; + char fn[PATH_MAX], line[LINE_MAX], *p; + long unsigned ppid; + + assert(pid > 0); + assert(_ppid); + + assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%lu/stat", (unsigned long) pid) < (int) (sizeof(fn)-1)); + char_array_0(fn); + + if (!(f = fopen(fn, "re"))) + return -errno; + + if (!(fgets(line, sizeof(line), f))) { + r = feof(f) ? -EIO : -errno; + fclose(f); + return r; + } + + fclose(f); + + /* Let's skip the pid and comm fields. The latter is enclosed + * in () but does not escape any () in its value, so let's + * skip over it manually */ + + if (!(p = strrchr(line, ')'))) + return -EIO; + + p++; + + if (sscanf(p, " " + "%*c " /* state */ + "%lu ", /* ppid */ + &ppid) != 1) + return -EIO; + + if ((long unsigned) (pid_t) ppid != ppid) + return -ERANGE; + + *_ppid = (pid_t) ppid; + + return 0; +} + +int get_starttime_of_pid(pid_t pid, unsigned long long *st) { + int r; + FILE *f; + char fn[PATH_MAX], line[LINE_MAX], *p; + + assert(pid > 0); + assert(st); + + assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%lu/stat", (unsigned long) pid) < (int) (sizeof(fn)-1)); + char_array_0(fn); + + if (!(f = fopen(fn, "re"))) + return -errno; + + if (!(fgets(line, sizeof(line), f))) { + r = feof(f) ? -EIO : -errno; + fclose(f); + return r; + } + + fclose(f); + + /* Let's skip the pid and comm fields. The latter is enclosed + * in () but does not escape any () in its value, so let's + * skip over it manually */ + + if (!(p = strrchr(line, ')'))) + return -EIO; + + p++; + + if (sscanf(p, " " + "%*c " /* state */ + "%*d " /* ppid */ + "%*d " /* pgrp */ + "%*d " /* session */ + "%*d " /* tty_nr */ + "%*d " /* tpgid */ + "%*u " /* flags */ + "%*u " /* minflt */ + "%*u " /* cminflt */ + "%*u " /* majflt */ + "%*u " /* cmajflt */ + "%*u " /* utime */ + "%*u " /* stime */ + "%*d " /* cutime */ + "%*d " /* cstime */ + "%*d " /* priority */ + "%*d " /* nice */ + "%*d " /* num_threads */ + "%*d " /* itrealvalue */ + "%llu " /* starttime */, + st) != 1) + return -EIO; + + return 0; +} + +int write_one_line_file(const char *fn, const char *line) { + FILE *f; + int r; + + assert(fn); + assert(line); + + if (!(f = fopen(fn, "we"))) + return -errno; + + errno = 0; + if (fputs(line, f) < 0) { + r = -errno; + goto finish; + } + + if (!endswith(line, "\n")) + fputc('\n', f); + + fflush(f); + + if (ferror(f)) { + if (errno != 0) + r = -errno; + else + r = -EIO; + } else + r = 0; + +finish: + fclose(f); + return r; +} + +int fchmod_umask(int fd, mode_t m) { + mode_t u; + int r; + + u = umask(0777); + r = fchmod(fd, m & (~u)) < 0 ? -errno : 0; + umask(u); + + return r; +} + +int write_one_line_file_atomic(const char *fn, const char *line) { + FILE *f; + int r; + char *p; + + assert(fn); + assert(line); + + r = fopen_temporary(fn, &f, &p); + if (r < 0) + return r; + + fchmod_umask(fileno(f), 0644); + + errno = 0; + if (fputs(line, f) < 0) { + r = -errno; + goto finish; + } + + if (!endswith(line, "\n")) + fputc('\n', f); + + fflush(f); + + if (ferror(f)) { + if (errno != 0) + r = -errno; + else + r = -EIO; + } else { + if (rename(p, fn) < 0) + r = -errno; + else + r = 0; + } + +finish: + if (r < 0) + unlink(p); + + fclose(f); + free(p); + + return r; +} + +int read_one_line_file(const char *fn, char **line) { + FILE *f; + int r; + char t[LINE_MAX], *c; + + assert(fn); + assert(line); + + f = fopen(fn, "re"); + if (!f) + return -errno; + + if (!fgets(t, sizeof(t), f)) { + + if (ferror(f)) { + r = -errno; + goto finish; + } + + t[0] = 0; + } + + c = strdup(t); + if (!c) { + r = -ENOMEM; + goto finish; + } + + truncate_nl(c); + + *line = c; + r = 0; + +finish: + fclose(f); + return r; +} + +int read_full_file(const char *fn, char **contents, size_t *size) { + FILE *f; + int r; + size_t n, l; + char *buf = NULL; + struct stat st; + + if (!(f = fopen(fn, "re"))) + return -errno; + + if (fstat(fileno(f), &st) < 0) { + r = -errno; + goto finish; + } + + /* Safety check */ + if (st.st_size > 4*1024*1024) { + r = -E2BIG; + goto finish; + } + + n = st.st_size > 0 ? st.st_size : LINE_MAX; + l = 0; + + for (;;) { + char *t; + size_t k; + + if (!(t = realloc(buf, n+1))) { + r = -ENOMEM; + goto finish; + } + + buf = t; + k = fread(buf + l, 1, n - l, f); + + if (k <= 0) { + if (ferror(f)) { + r = -errno; + goto finish; + } + + break; + } + + l += k; + n *= 2; + + /* Safety check */ + if (n > 4*1024*1024) { + r = -E2BIG; + goto finish; + } + } + + buf[l] = 0; + *contents = buf; + buf = NULL; + + if (size) + *size = l; + + r = 0; + +finish: + fclose(f); + free(buf); + + return r; +} + +int parse_env_file( + const char *fname, + const char *separator, ...) { + + int r = 0; + char *contents = NULL, *p; + + assert(fname); + assert(separator); + + if ((r = read_full_file(fname, &contents, NULL)) < 0) + return r; + + p = contents; + for (;;) { + const char *key = NULL; + + p += strspn(p, separator); + p += strspn(p, WHITESPACE); + + if (!*p) + break; + + if (!strchr(COMMENTS, *p)) { + va_list ap; + char **value; + + va_start(ap, separator); + while ((key = va_arg(ap, char *))) { + size_t n; + char *v; + + value = va_arg(ap, char **); + + n = strlen(key); + if (strncmp(p, key, n) != 0 || + p[n] != '=') + continue; + + p += n + 1; + n = strcspn(p, separator); + + if (n >= 2 && + strchr(QUOTES, p[0]) && + p[n-1] == p[0]) + v = strndup(p+1, n-2); + else + v = strndup(p, n); + + if (!v) { + r = -ENOMEM; + va_end(ap); + goto fail; + } + + if (v[0] == '\0') { + /* return empty value strings as NULL */ + free(v); + v = NULL; + } + + free(*value); + *value = v; + + p += n; + + r ++; + break; + } + va_end(ap); + } + + if (!key) + p += strcspn(p, separator); + } + +fail: + free(contents); + return r; +} + +int load_env_file( + const char *fname, + char ***rl) { + + FILE *f; + char **m = NULL; + int r; + + assert(fname); + assert(rl); + + if (!(f = fopen(fname, "re"))) + return -errno; + + while (!feof(f)) { + char l[LINE_MAX], *p, *u; + char **t; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + r = -errno; + goto finish; + } + + p = strstrip(l); + + if (!*p) + continue; + + if (strchr(COMMENTS, *p)) + continue; + + if (!(u = normalize_env_assignment(p))) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + t = strv_append(m, u); + free(u); + + if (!t) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + + strv_free(m); + m = t; + } + + r = 0; + + *rl = m; + m = NULL; + +finish: + if (f) + fclose(f); + + strv_free(m); + + return r; +} + +int write_env_file(const char *fname, char **l) { + char **i, *p; + FILE *f; + int r; + + r = fopen_temporary(fname, &f, &p); + if (r < 0) + return r; + + fchmod_umask(fileno(f), 0644); + + errno = 0; + STRV_FOREACH(i, l) { + fputs(*i, f); + fputc('\n', f); + } + + fflush(f); + + if (ferror(f)) { + if (errno != 0) + r = -errno; + else + r = -EIO; + } else { + if (rename(p, fname) < 0) + r = -errno; + else + r = 0; + } + + if (r < 0) + unlink(p); + + fclose(f); + free(p); + + return r; +} + +char *truncate_nl(char *s) { + assert(s); + + s[strcspn(s, NEWLINE)] = 0; + return s; +} + +int get_process_comm(pid_t pid, char **name) { + int r; + + assert(name); + + if (pid == 0) + r = read_one_line_file("/proc/self/comm", name); + else { + char *p; + if (asprintf(&p, "/proc/%lu/comm", (unsigned long) pid) < 0) + return -ENOMEM; + + r = read_one_line_file(p, name); + free(p); + } + + return r; +} + +int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { + char *r, *k; + int c; + bool space = false; + size_t left; + FILE *f; + + assert(max_length > 0); + assert(line); + + if (pid == 0) + f = fopen("/proc/self/cmdline", "re"); + else { + char *p; + if (asprintf(&p, "/proc/%lu/cmdline", (unsigned long) pid) < 0) + return -ENOMEM; + + f = fopen(p, "re"); + free(p); + } + + if (!f) + return -errno; + + r = new(char, max_length); + if (!r) { + fclose(f); + return -ENOMEM; + } + + k = r; + left = max_length; + while ((c = getc(f)) != EOF) { + + if (isprint(c)) { + if (space) { + if (left <= 4) + break; + + *(k++) = ' '; + left--; + space = false; + } + + if (left <= 4) + break; + + *(k++) = (char) c; + left--; + } else + space = true; + } + + if (left <= 4) { + size_t n = MIN(left-1, 3U); + memcpy(k, "...", n); + k[n] = 0; + } else + *k = 0; + + fclose(f); + + /* Kernel threads have no argv[] */ + if (r[0] == 0) { + char *t; + int h; + + free(r); + + if (!comm_fallback) + return -ENOENT; + + h = get_process_comm(pid, &t); + if (h < 0) + return h; + + r = join("[", t, "]", NULL); + free(t); + + if (!r) + return -ENOMEM; + } + + *line = r; + return 0; +} + +int is_kernel_thread(pid_t pid) { + char *p; + size_t count; + char c; + bool eof; + FILE *f; + + if (pid == 0) + return 0; + + if (asprintf(&p, "/proc/%lu/cmdline", (unsigned long) pid) < 0) + return -ENOMEM; + + f = fopen(p, "re"); + free(p); + + if (!f) + return -errno; + + count = fread(&c, 1, 1, f); + eof = feof(f); + fclose(f); + + /* Kernel threads have an empty cmdline */ + + if (count <= 0) + return eof ? 1 : -errno; + + return 0; +} + +int get_process_exe(pid_t pid, char **name) { + int r; + + assert(name); + + if (pid == 0) + r = readlink_malloc("/proc/self/exe", name); + else { + char *p; + if (asprintf(&p, "/proc/%lu/exe", (unsigned long) pid) < 0) + return -ENOMEM; + + r = readlink_malloc(p, name); + free(p); + } + + return r; +} + +int get_process_uid(pid_t pid, uid_t *uid) { + char *p; + FILE *f; + int r; + + assert(uid); + + if (pid == 0) + return getuid(); + + if (asprintf(&p, "/proc/%lu/status", (unsigned long) pid) < 0) + return -ENOMEM; + + f = fopen(p, "re"); + free(p); + + if (!f) + return -errno; + + while (!feof(f)) { + char line[LINE_MAX], *l; + + if (!fgets(line, sizeof(line), f)) { + if (feof(f)) + break; + + r = -errno; + goto finish; + } + + l = strstrip(line); + + if (startswith(l, "Uid:")) { + l += 4; + l += strspn(l, WHITESPACE); + + l[strcspn(l, WHITESPACE)] = 0; + + r = parse_uid(l, uid); + goto finish; + } + } + + r = -EIO; + +finish: + fclose(f); + + return r; +} + +char *strnappend(const char *s, const char *suffix, size_t b) { + size_t a; + char *r; + + if (!s && !suffix) + return strdup(""); + + if (!s) + return strndup(suffix, b); + + if (!suffix) + return strdup(s); + + assert(s); + assert(suffix); + + a = strlen(s); + + if (!(r = new(char, a+b+1))) + return NULL; + + memcpy(r, s, a); + memcpy(r+a, suffix, b); + r[a+b] = 0; + + return r; +} + +char *strappend(const char *s, const char *suffix) { + return strnappend(s, suffix, suffix ? strlen(suffix) : 0); +} + +int readlink_malloc(const char *p, char **r) { + size_t l = 100; + + assert(p); + assert(r); + + for (;;) { + char *c; + ssize_t n; + + if (!(c = new(char, l))) + return -ENOMEM; + + if ((n = readlink(p, c, l-1)) < 0) { + int ret = -errno; + free(c); + return ret; + } + + if ((size_t) n < l-1) { + c[n] = 0; + *r = c; + return 0; + } + + free(c); + l *= 2; + } +} + +int readlink_and_make_absolute(const char *p, char **r) { + char *target, *k; + int j; + + assert(p); + assert(r); + + if ((j = readlink_malloc(p, &target)) < 0) + return j; + + k = file_in_same_dir(p, target); + free(target); + + if (!k) + return -ENOMEM; + + *r = k; + return 0; +} + +int readlink_and_canonicalize(const char *p, char **r) { + char *t, *s; + int j; + + assert(p); + assert(r); + + j = readlink_and_make_absolute(p, &t); + if (j < 0) + return j; + + s = canonicalize_file_name(t); + if (s) { + free(t); + *r = s; + } else + *r = t; + + path_kill_slashes(*r); + + return 0; +} + +int parent_of_path(const char *path, char **_r) { + const char *e, *a = NULL, *b = NULL, *p; + char *r; + bool slash = false; + + assert(path); + assert(_r); + + if (!*path) + return -EINVAL; + + for (e = path; *e; e++) { + + if (!slash && *e == '/') { + a = b; + b = e; + slash = true; + } else if (slash && *e != '/') + slash = false; + } + + if (*(e-1) == '/') + p = a; + else + p = b; + + if (!p) + return -EINVAL; + + if (p == path) + r = strdup("/"); + else + r = strndup(path, p-path); + + if (!r) + return -ENOMEM; + + *_r = r; + return 0; +} + + +char *file_name_from_path(const char *p) { + char *r; + + assert(p); + + if ((r = strrchr(p, '/'))) + return r + 1; + + return (char*) p; +} + +bool path_is_absolute(const char *p) { + assert(p); + + return p[0] == '/'; +} + +bool is_path(const char *p) { + + return !!strchr(p, '/'); +} + +char *path_make_absolute(const char *p, const char *prefix) { + assert(p); + + /* Makes every item in the list an absolute path by prepending + * the prefix, if specified and necessary */ + + if (path_is_absolute(p) || !prefix) + return strdup(p); + + return join(prefix, "/", p, NULL); +} + +char *path_make_absolute_cwd(const char *p) { + char *cwd, *r; + + assert(p); + + /* Similar to path_make_absolute(), but prefixes with the + * current working directory. */ + + if (path_is_absolute(p)) + return strdup(p); + + if (!(cwd = get_current_dir_name())) + return NULL; + + r = path_make_absolute(p, cwd); + free(cwd); + + return r; +} + +char **strv_path_make_absolute_cwd(char **l) { + char **s; + + /* Goes through every item in the string list and makes it + * absolute. This works in place and won't rollback any + * changes on failure. */ + + STRV_FOREACH(s, l) { + char *t; + + if (!(t = path_make_absolute_cwd(*s))) + return NULL; + + free(*s); + *s = t; + } + + return l; +} + +char **strv_path_canonicalize(char **l) { + char **s; + unsigned k = 0; + bool enomem = false; + + if (strv_isempty(l)) + return l; + + /* Goes through every item in the string list and canonicalize + * the path. This works in place and won't rollback any + * changes on failure. */ + + STRV_FOREACH(s, l) { + char *t, *u; + + t = path_make_absolute_cwd(*s); + free(*s); + + if (!t) { + enomem = true; + continue; + } + + errno = 0; + u = canonicalize_file_name(t); + free(t); + + if (!u) { + if (errno == ENOMEM || !errno) + enomem = true; + + continue; + } + + l[k++] = u; + } + + l[k] = NULL; + + if (enomem) + return NULL; + + return l; +} + +char **strv_path_remove_empty(char **l) { + char **f, **t; + + if (!l) + return NULL; + + for (f = t = l; *f; f++) { + + if (dir_is_empty(*f) > 0) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + +int reset_all_signal_handlers(void) { + int sig; + + for (sig = 1; sig < _NSIG; sig++) { + struct sigaction sa; + + if (sig == SIGKILL || sig == SIGSTOP) + continue; + + zero(sa); + sa.sa_handler = SIG_DFL; + sa.sa_flags = SA_RESTART; + + /* On Linux the first two RT signals are reserved by + * glibc, and sigaction() will return EINVAL for them. */ + if ((sigaction(sig, &sa, NULL) < 0)) + if (errno != EINVAL) + return -errno; + } + + return 0; +} + +char *strstrip(char *s) { + char *e; + + /* Drops trailing whitespace. Modifies the string in + * place. Returns pointer to first non-space character */ + + s += strspn(s, WHITESPACE); + + for (e = strchr(s, 0); e > s; e --) + if (!strchr(WHITESPACE, e[-1])) + break; + + *e = 0; + + return s; +} + +char *delete_chars(char *s, const char *bad) { + char *f, *t; + + /* Drops all whitespace, regardless where in the string */ + + for (f = s, t = s; *f; f++) { + if (strchr(bad, *f)) + continue; + + *(t++) = *f; + } + + *t = 0; + + return s; +} + +bool in_charset(const char *s, const char* charset) { + const char *i; + + assert(s); + assert(charset); + + for (i = s; *i; i++) + if (!strchr(charset, *i)) + return false; + + return true; +} + +char *file_in_same_dir(const char *path, const char *filename) { + char *e, *r; + size_t k; + + assert(path); + assert(filename); + + /* This removes the last component of path and appends + * filename, unless the latter is absolute anyway or the + * former isn't */ + + if (path_is_absolute(filename)) + return strdup(filename); + + if (!(e = strrchr(path, '/'))) + return strdup(filename); + + k = strlen(filename); + if (!(r = new(char, e-path+1+k+1))) + return NULL; + + memcpy(r, path, e-path+1); + memcpy(r+(e-path)+1, filename, k+1); + + return r; +} + +int safe_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid) { + struct stat st; + + if (label_mkdir(path, mode) >= 0) + if (chmod_and_chown(path, mode, uid, gid) < 0) + return -errno; + + if (lstat(path, &st) < 0) + return -errno; + + if ((st.st_mode & 0777) != mode || + st.st_uid != uid || + st.st_gid != gid || + !S_ISDIR(st.st_mode)) { + errno = EEXIST; + return -errno; + } + + return 0; +} + + +int mkdir_parents(const char *path, mode_t mode) { + const char *p, *e; + + assert(path); + + /* Creates every parent directory in the path except the last + * component. */ + + p = path + strspn(path, "/"); + for (;;) { + int r; + char *t; + + e = p + strcspn(p, "/"); + p = e + strspn(e, "/"); + + /* Is this the last component? If so, then we're + * done */ + if (*p == 0) + return 0; + + if (!(t = strndup(path, e - path))) + return -ENOMEM; + + r = label_mkdir(t, mode); + free(t); + + if (r < 0 && errno != EEXIST) + return -errno; + } +} + +int mkdir_p(const char *path, mode_t mode) { + int r; + + /* Like mkdir -p */ + + if ((r = mkdir_parents(path, mode)) < 0) + return r; + + if (label_mkdir(path, mode) < 0 && errno != EEXIST) + return -errno; + + return 0; +} + +int rmdir_parents(const char *path, const char *stop) { + size_t l; + int r = 0; + + assert(path); + assert(stop); + + l = strlen(path); + + /* Skip trailing slashes */ + while (l > 0 && path[l-1] == '/') + l--; + + while (l > 0) { + char *t; + + /* Skip last component */ + while (l > 0 && path[l-1] != '/') + l--; + + /* Skip trailing slashes */ + while (l > 0 && path[l-1] == '/') + l--; + + if (l <= 0) + break; + + if (!(t = strndup(path, l))) + return -ENOMEM; + + if (path_startswith(stop, t)) { + free(t); + return 0; + } + + r = rmdir(t); + free(t); + + if (r < 0) + if (errno != ENOENT) + return -errno; + } + + return 0; +} + + +char hexchar(int x) { + static const char table[16] = "0123456789abcdef"; + + return table[x & 15]; +} + +int unhexchar(char c) { + + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return -1; +} + +char octchar(int x) { + return '0' + (x & 7); +} + +int unoctchar(char c) { + + if (c >= '0' && c <= '7') + return c - '0'; + + return -1; +} + +char decchar(int x) { + return '0' + (x % 10); +} + +int undecchar(char c) { + + if (c >= '0' && c <= '9') + return c - '0'; + + return -1; +} + +char *cescape(const char *s) { + char *r, *t; + const char *f; + + assert(s); + + /* Does C style string escaping. */ + + if (!(r = new(char, strlen(s)*4 + 1))) + return NULL; + + for (f = s, t = r; *f; f++) + + switch (*f) { + + case '\a': + *(t++) = '\\'; + *(t++) = 'a'; + break; + case '\b': + *(t++) = '\\'; + *(t++) = 'b'; + break; + case '\f': + *(t++) = '\\'; + *(t++) = 'f'; + break; + case '\n': + *(t++) = '\\'; + *(t++) = 'n'; + break; + case '\r': + *(t++) = '\\'; + *(t++) = 'r'; + break; + case '\t': + *(t++) = '\\'; + *(t++) = 't'; + break; + case '\v': + *(t++) = '\\'; + *(t++) = 'v'; + break; + case '\\': + *(t++) = '\\'; + *(t++) = '\\'; + break; + case '"': + *(t++) = '\\'; + *(t++) = '"'; + break; + case '\'': + *(t++) = '\\'; + *(t++) = '\''; + break; + + default: + /* For special chars we prefer octal over + * hexadecimal encoding, simply because glib's + * g_strescape() does the same */ + if ((*f < ' ') || (*f >= 127)) { + *(t++) = '\\'; + *(t++) = octchar((unsigned char) *f >> 6); + *(t++) = octchar((unsigned char) *f >> 3); + *(t++) = octchar((unsigned char) *f); + } else + *(t++) = *f; + break; + } + + *t = 0; + + return r; +} + +char *cunescape_length(const char *s, size_t length) { + char *r, *t; + const char *f; + + assert(s); + + /* Undoes C style string escaping */ + + r = new(char, length+1); + if (!r) + return r; + + for (f = s, t = r; f < s + length; f++) { + + if (*f != '\\') { + *(t++) = *f; + continue; + } + + f++; + + switch (*f) { + + case 'a': + *(t++) = '\a'; + break; + case 'b': + *(t++) = '\b'; + break; + case 'f': + *(t++) = '\f'; + break; + case 'n': + *(t++) = '\n'; + break; + case 'r': + *(t++) = '\r'; + break; + case 't': + *(t++) = '\t'; + break; + case 'v': + *(t++) = '\v'; + break; + case '\\': + *(t++) = '\\'; + break; + case '"': + *(t++) = '"'; + break; + case '\'': + *(t++) = '\''; + break; + + case 's': + /* This is an extension of the XDG syntax files */ + *(t++) = ' '; + break; + + case 'x': { + /* hexadecimal encoding */ + int a, b; + + a = unhexchar(f[1]); + b = unhexchar(f[2]); + + if (a < 0 || b < 0) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + *(t++) = 'x'; + } else { + *(t++) = (char) ((a << 4) | b); + f += 2; + } + + break; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { + /* octal encoding */ + int a, b, c; + + a = unoctchar(f[0]); + b = unoctchar(f[1]); + c = unoctchar(f[2]); + + if (a < 0 || b < 0 || c < 0) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + *(t++) = f[0]; + } else { + *(t++) = (char) ((a << 6) | (b << 3) | c); + f += 2; + } + + break; + } + + case 0: + /* premature end of string.*/ + *(t++) = '\\'; + goto finish; + + default: + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + *(t++) = *f; + break; + } + } + +finish: + *t = 0; + return r; +} + +char *cunescape(const char *s) { + return cunescape_length(s, strlen(s)); +} + +char *xescape(const char *s, const char *bad) { + char *r, *t; + const char *f; + + /* Escapes all chars in bad, in addition to \ and all special + * chars, in \xFF style escaping. May be reversed with + * cunescape. */ + + if (!(r = new(char, strlen(s)*4+1))) + return NULL; + + for (f = s, t = r; *f; f++) { + + if ((*f < ' ') || (*f >= 127) || + (*f == '\\') || strchr(bad, *f)) { + *(t++) = '\\'; + *(t++) = 'x'; + *(t++) = hexchar(*f >> 4); + *(t++) = hexchar(*f); + } else + *(t++) = *f; + } + + *t = 0; + + return r; +} + +char *bus_path_escape(const char *s) { + char *r, *t; + const char *f; + + assert(s); + + /* Escapes all chars that D-Bus' object path cannot deal + * with. Can be reverse with bus_path_unescape() */ + + if (!(r = new(char, strlen(s)*3+1))) + return NULL; + + for (f = s, t = r; *f; f++) { + + if (!(*f >= 'A' && *f <= 'Z') && + !(*f >= 'a' && *f <= 'z') && + !(*f >= '0' && *f <= '9')) { + *(t++) = '_'; + *(t++) = hexchar(*f >> 4); + *(t++) = hexchar(*f); + } else + *(t++) = *f; + } + + *t = 0; + + return r; +} + +char *bus_path_unescape(const char *f) { + char *r, *t; + + assert(f); + + if (!(r = strdup(f))) + return NULL; + + for (t = r; *f; f++) { + + if (*f == '_') { + int a, b; + + if ((a = unhexchar(f[1])) < 0 || + (b = unhexchar(f[2])) < 0) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '_'; + } else { + *(t++) = (char) ((a << 4) | b); + f += 2; + } + } else + *(t++) = *f; + } + + *t = 0; + + return r; +} + +char *path_kill_slashes(char *path) { + char *f, *t; + bool slash = false; + + /* Removes redundant inner and trailing slashes. Modifies the + * passed string in-place. + * + * ///foo///bar/ becomes /foo/bar + */ + + for (f = path, t = path; *f; f++) { + + if (*f == '/') { + slash = true; + continue; + } + + if (slash) { + slash = false; + *(t++) = '/'; + } + + *(t++) = *f; + } + + /* Special rule, if we are talking of the root directory, a + trailing slash is good */ + + if (t == path && slash) + *(t++) = '/'; + + *t = 0; + return path; +} + +bool path_startswith(const char *path, const char *prefix) { + assert(path); + assert(prefix); + + if ((path[0] == '/') != (prefix[0] == '/')) + return false; + + for (;;) { + size_t a, b; + + path += strspn(path, "/"); + prefix += strspn(prefix, "/"); + + if (*prefix == 0) + return true; + + if (*path == 0) + return false; + + a = strcspn(path, "/"); + b = strcspn(prefix, "/"); + + if (a != b) + return false; + + if (memcmp(path, prefix, a) != 0) + return false; + + path += a; + prefix += b; + } +} + +bool path_equal(const char *a, const char *b) { + assert(a); + assert(b); + + if ((a[0] == '/') != (b[0] == '/')) + return false; + + for (;;) { + size_t j, k; + + a += strspn(a, "/"); + b += strspn(b, "/"); + + if (*a == 0 && *b == 0) + return true; + + if (*a == 0 || *b == 0) + return false; + + j = strcspn(a, "/"); + k = strcspn(b, "/"); + + if (j != k) + return false; + + if (memcmp(a, b, j) != 0) + return false; + + a += j; + b += k; + } +} + +char *ascii_strlower(char *t) { + char *p; + + assert(t); + + for (p = t; *p; p++) + if (*p >= 'A' && *p <= 'Z') + *p = *p - 'A' + 'a'; + + return t; +} + +bool ignore_file(const char *filename) { + assert(filename); + + return + filename[0] == '.' || + streq(filename, "lost+found") || + streq(filename, "aquota.user") || + streq(filename, "aquota.group") || + endswith(filename, "~") || + endswith(filename, ".rpmnew") || + endswith(filename, ".rpmsave") || + endswith(filename, ".rpmorig") || + endswith(filename, ".dpkg-old") || + endswith(filename, ".dpkg-new") || + endswith(filename, ".swp"); +} + +int fd_nonblock(int fd, bool nonblock) { + int flags; + + assert(fd >= 0); + + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) + return -errno; + + if (nonblock) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if (fcntl(fd, F_SETFL, flags) < 0) + return -errno; + + return 0; +} + +int fd_cloexec(int fd, bool cloexec) { + int flags; + + assert(fd >= 0); + + if ((flags = fcntl(fd, F_GETFD, 0)) < 0) + return -errno; + + if (cloexec) + flags |= FD_CLOEXEC; + else + flags &= ~FD_CLOEXEC; + + if (fcntl(fd, F_SETFD, flags) < 0) + return -errno; + + return 0; +} + +static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) { + unsigned i; + + assert(n_fdset == 0 || fdset); + + for (i = 0; i < n_fdset; i++) + if (fdset[i] == fd) + return true; + + return false; +} + +int close_all_fds(const int except[], unsigned n_except) { + DIR *d; + struct dirent *de; + int r = 0; + + assert(n_except == 0 || except); + + d = opendir("/proc/self/fd"); + if (!d) { + int fd; + struct rlimit rl; + + /* When /proc isn't available (for example in chroots) + * the fallback is brute forcing through the fd + * table */ + + assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0); + for (fd = 3; fd < (int) rl.rlim_max; fd ++) { + + if (fd_in_set(fd, except, n_except)) + continue; + + if (close_nointr(fd) < 0) + if (errno != EBADF && r == 0) + r = -errno; + } + + return r; + } + + while ((de = readdir(d))) { + int fd = -1; + + if (ignore_file(de->d_name)) + continue; + + if (safe_atoi(de->d_name, &fd) < 0) + /* Let's better ignore this, just in case */ + continue; + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + if (fd_in_set(fd, except, n_except)) + continue; + + if (close_nointr(fd) < 0) { + /* Valgrind has its own FD and doesn't want to have it closed */ + if (errno != EBADF && r == 0) + r = -errno; + } + } + + closedir(d); + return r; +} + +bool chars_intersect(const char *a, const char *b) { + const char *p; + + /* Returns true if any of the chars in a are in b. */ + for (p = a; *p; p++) + if (strchr(b, *p)) + return true; + + return false; +} + +char *format_timestamp(char *buf, size_t l, usec_t t) { + struct tm tm; + time_t sec; + + assert(buf); + assert(l > 0); + + if (t <= 0) + return NULL; + + sec = (time_t) (t / USEC_PER_SEC); + + if (strftime(buf, l, "%a, %d %b %Y %H:%M:%S %z", localtime_r(&sec, &tm)) <= 0) + return NULL; + + return buf; +} + +char *format_timestamp_pretty(char *buf, size_t l, usec_t t) { + usec_t n, d; + + n = now(CLOCK_REALTIME); + + if (t <= 0 || t > n || t + USEC_PER_DAY*7 <= t) + return NULL; + + d = n - t; + + if (d >= USEC_PER_YEAR) + snprintf(buf, l, "%llu years and %llu months ago", + (unsigned long long) (d / USEC_PER_YEAR), + (unsigned long long) ((d % USEC_PER_YEAR) / USEC_PER_MONTH)); + else if (d >= USEC_PER_MONTH) + snprintf(buf, l, "%llu months and %llu days ago", + (unsigned long long) (d / USEC_PER_MONTH), + (unsigned long long) ((d % USEC_PER_MONTH) / USEC_PER_DAY)); + else if (d >= USEC_PER_WEEK) + snprintf(buf, l, "%llu weeks and %llu days ago", + (unsigned long long) (d / USEC_PER_WEEK), + (unsigned long long) ((d % USEC_PER_WEEK) / USEC_PER_DAY)); + else if (d >= 2*USEC_PER_DAY) + snprintf(buf, l, "%llu days ago", (unsigned long long) (d / USEC_PER_DAY)); + else if (d >= 25*USEC_PER_HOUR) + snprintf(buf, l, "1 day and %lluh ago", + (unsigned long long) ((d - USEC_PER_DAY) / USEC_PER_HOUR)); + else if (d >= 6*USEC_PER_HOUR) + snprintf(buf, l, "%lluh ago", + (unsigned long long) (d / USEC_PER_HOUR)); + else if (d >= USEC_PER_HOUR) + snprintf(buf, l, "%lluh %llumin ago", + (unsigned long long) (d / USEC_PER_HOUR), + (unsigned long long) ((d % USEC_PER_HOUR) / USEC_PER_MINUTE)); + else if (d >= 5*USEC_PER_MINUTE) + snprintf(buf, l, "%llumin ago", + (unsigned long long) (d / USEC_PER_MINUTE)); + else if (d >= USEC_PER_MINUTE) + snprintf(buf, l, "%llumin %llus ago", + (unsigned long long) (d / USEC_PER_MINUTE), + (unsigned long long) ((d % USEC_PER_MINUTE) / USEC_PER_SEC)); + else if (d >= USEC_PER_SEC) + snprintf(buf, l, "%llus ago", + (unsigned long long) (d / USEC_PER_SEC)); + else if (d >= USEC_PER_MSEC) + snprintf(buf, l, "%llums ago", + (unsigned long long) (d / USEC_PER_MSEC)); + else if (d > 0) + snprintf(buf, l, "%lluus ago", + (unsigned long long) d); + else + snprintf(buf, l, "now"); + + buf[l-1] = 0; + return buf; +} + +char *format_timespan(char *buf, size_t l, usec_t t) { + static const struct { + const char *suffix; + usec_t usec; + } table[] = { + { "w", USEC_PER_WEEK }, + { "d", USEC_PER_DAY }, + { "h", USEC_PER_HOUR }, + { "min", USEC_PER_MINUTE }, + { "s", USEC_PER_SEC }, + { "ms", USEC_PER_MSEC }, + { "us", 1 }, + }; + + unsigned i; + char *p = buf; + + assert(buf); + assert(l > 0); + + if (t == (usec_t) -1) + return NULL; + + if (t == 0) { + snprintf(p, l, "0"); + p[l-1] = 0; + return p; + } + + /* The result of this function can be parsed with parse_usec */ + + for (i = 0; i < ELEMENTSOF(table); i++) { + int k; + size_t n; + + if (t < table[i].usec) + continue; + + if (l <= 1) + break; + + k = snprintf(p, l, "%s%llu%s", p > buf ? " " : "", (unsigned long long) (t / table[i].usec), table[i].suffix); + n = MIN((size_t) k, l); + + l -= n; + p += n; + + t %= table[i].usec; + } + + *p = 0; + + return buf; +} + +bool fstype_is_network(const char *fstype) { + static const char * const table[] = { + "cifs", + "smbfs", + "ncpfs", + "nfs", + "nfs4", + "gfs", + "gfs2" + }; + + unsigned i; + + for (i = 0; i < ELEMENTSOF(table); i++) + if (streq(table[i], fstype)) + return true; + + return false; +} + +int chvt(int vt) { + int fd, r = 0; + + if ((fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0) + return -errno; + + if (vt < 0) { + int tiocl[2] = { + TIOCL_GETKMSGREDIRECT, + 0 + }; + + if (ioctl(fd, TIOCLINUX, tiocl) < 0) { + r = -errno; + goto fail; + } + + vt = tiocl[0] <= 0 ? 1 : tiocl[0]; + } + + if (ioctl(fd, VT_ACTIVATE, vt) < 0) + r = -errno; + +fail: + close_nointr_nofail(fd); + return r; +} + +int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { + struct termios old_termios, new_termios; + char c; + char line[LINE_MAX]; + + assert(f); + assert(ret); + + if (tcgetattr(fileno(f), &old_termios) >= 0) { + new_termios = old_termios; + + new_termios.c_lflag &= ~ICANON; + new_termios.c_cc[VMIN] = 1; + new_termios.c_cc[VTIME] = 0; + + if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) { + size_t k; + + if (t != (usec_t) -1) { + if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) { + tcsetattr(fileno(f), TCSADRAIN, &old_termios); + return -ETIMEDOUT; + } + } + + k = fread(&c, 1, 1, f); + + tcsetattr(fileno(f), TCSADRAIN, &old_termios); + + if (k <= 0) + return -EIO; + + if (need_nl) + *need_nl = c != '\n'; + + *ret = c; + return 0; + } + } + + if (t != (usec_t) -1) + if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) + return -ETIMEDOUT; + + if (!fgets(line, sizeof(line), f)) + return -EIO; + + truncate_nl(line); + + if (strlen(line) != 1) + return -EBADMSG; + + if (need_nl) + *need_nl = false; + + *ret = line[0]; + return 0; +} + +int ask(char *ret, const char *replies, const char *text, ...) { + bool on_tty; + + assert(ret); + assert(replies); + assert(text); + + on_tty = isatty(STDOUT_FILENO); + + for (;;) { + va_list ap; + char c; + int r; + bool need_nl = true; + + if (on_tty) + fputs(ANSI_HIGHLIGHT_ON, stdout); + + va_start(ap, text); + vprintf(text, ap); + va_end(ap); + + if (on_tty) + fputs(ANSI_HIGHLIGHT_OFF, stdout); + + fflush(stdout); + + r = read_one_char(stdin, &c, (usec_t) -1, &need_nl); + if (r < 0) { + + if (r == -EBADMSG) { + puts("Bad input, please try again."); + continue; + } + + putchar('\n'); + return r; + } + + if (need_nl) + putchar('\n'); + + if (strchr(replies, c)) { + *ret = c; + return 0; + } + + puts("Read unexpected character, please try again."); + } +} + +int reset_terminal_fd(int fd, bool switch_to_text) { + struct termios termios; + int r = 0; + + /* Set terminal to some sane defaults */ + + assert(fd >= 0); + + /* We leave locked terminal attributes untouched, so that + * Plymouth may set whatever it wants to set, and we don't + * interfere with that. */ + + /* Disable exclusive mode, just in case */ + ioctl(fd, TIOCNXCL); + + /* Switch to text mode */ + if (switch_to_text) + ioctl(fd, KDSETMODE, KD_TEXT); + + /* Enable console unicode mode */ + ioctl(fd, KDSKBMODE, K_UNICODE); + + if (tcgetattr(fd, &termios) < 0) { + r = -errno; + goto finish; + } + + /* We only reset the stuff that matters to the software. How + * hardware is set up we don't touch assuming that somebody + * else will do that for us */ + + termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC); + termios.c_iflag |= ICRNL | IMAXBEL | IUTF8; + termios.c_oflag |= ONLCR; + termios.c_cflag |= CREAD; + termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE; + + termios.c_cc[VINTR] = 03; /* ^C */ + termios.c_cc[VQUIT] = 034; /* ^\ */ + termios.c_cc[VERASE] = 0177; + termios.c_cc[VKILL] = 025; /* ^X */ + termios.c_cc[VEOF] = 04; /* ^D */ + termios.c_cc[VSTART] = 021; /* ^Q */ + termios.c_cc[VSTOP] = 023; /* ^S */ + termios.c_cc[VSUSP] = 032; /* ^Z */ + termios.c_cc[VLNEXT] = 026; /* ^V */ + termios.c_cc[VWERASE] = 027; /* ^W */ + termios.c_cc[VREPRINT] = 022; /* ^R */ + termios.c_cc[VEOL] = 0; + termios.c_cc[VEOL2] = 0; + + termios.c_cc[VTIME] = 0; + termios.c_cc[VMIN] = 1; + + if (tcsetattr(fd, TCSANOW, &termios) < 0) + r = -errno; + +finish: + /* Just in case, flush all crap out */ + tcflush(fd, TCIOFLUSH); + + return r; +} + +int reset_terminal(const char *name) { + int fd, r; + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + r = reset_terminal_fd(fd, true); + close_nointr_nofail(fd); + + return r; +} + +int open_terminal(const char *name, int mode) { + int fd, r; + unsigned c = 0; + + /* + * If a TTY is in the process of being closed opening it might + * cause EIO. This is horribly awful, but unlikely to be + * changed in the kernel. Hence we work around this problem by + * retrying a couple of times. + * + * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 + */ + + for (;;) { + if ((fd = open(name, mode)) >= 0) + break; + + if (errno != EIO) + return -errno; + + if (c >= 20) + return -errno; + + usleep(50 * USEC_PER_MSEC); + c++; + } + + if (fd < 0) + return -errno; + + if ((r = isatty(fd)) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + if (!r) { + close_nointr_nofail(fd); + return -ENOTTY; + } + + return fd; +} + +int flush_fd(int fd) { + struct pollfd pollfd; + + zero(pollfd); + pollfd.fd = fd; + pollfd.events = POLLIN; + + for (;;) { + char buf[LINE_MAX]; + ssize_t l; + int r; + + if ((r = poll(&pollfd, 1, 0)) < 0) { + + if (errno == EINTR) + continue; + + return -errno; + } + + if (r == 0) + return 0; + + if ((l = read(fd, buf, sizeof(buf))) < 0) { + + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return 0; + + return -errno; + } + + if (l <= 0) + return 0; + } +} + +int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm) { + int fd = -1, notify = -1, r, wd = -1; + + assert(name); + + /* We use inotify to be notified when the tty is closed. We + * create the watch before checking if we can actually acquire + * it, so that we don't lose any event. + * + * Note: strictly speaking this actually watches for the + * device being closed, it does *not* really watch whether a + * tty loses its controlling process. However, unless some + * rogue process uses TIOCNOTTY on /dev/tty *after* closing + * its tty otherwise this will not become a problem. As long + * as the administrator makes sure not configure any service + * on the same tty as an untrusted user this should not be a + * problem. (Which he probably should not do anyway.) */ + + if (!fail && !force) { + if ((notify = inotify_init1(IN_CLOEXEC)) < 0) { + r = -errno; + goto fail; + } + + if ((wd = inotify_add_watch(notify, name, IN_CLOSE)) < 0) { + r = -errno; + goto fail; + } + } + + for (;;) { + if (notify >= 0) + if ((r = flush_fd(notify)) < 0) + goto fail; + + /* We pass here O_NOCTTY only so that we can check the return + * value TIOCSCTTY and have a reliable way to figure out if we + * successfully became the controlling process of the tty */ + if ((fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0) + return fd; + + /* First, try to get the tty */ + r = ioctl(fd, TIOCSCTTY, force); + + /* Sometimes it makes sense to ignore TIOCSCTTY + * returning EPERM, i.e. when very likely we already + * are have this controlling terminal. */ + if (r < 0 && errno == EPERM && ignore_tiocstty_eperm) + r = 0; + + if (r < 0 && (force || fail || errno != EPERM)) { + r = -errno; + goto fail; + } + + if (r >= 0) + break; + + assert(!fail); + assert(!force); + assert(notify >= 0); + + for (;;) { + uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX]; + ssize_t l; + struct inotify_event *e; + + if ((l = read(notify, inotify_buffer, sizeof(inotify_buffer))) < 0) { + + if (errno == EINTR) + continue; + + r = -errno; + goto fail; + } + + e = (struct inotify_event*) inotify_buffer; + + while (l > 0) { + size_t step; + + if (e->wd != wd || !(e->mask & IN_CLOSE)) { + r = -EIO; + goto fail; + } + + step = sizeof(struct inotify_event) + e->len; + assert(step <= (size_t) l); + + e = (struct inotify_event*) ((uint8_t*) e + step); + l -= step; + } + + break; + } + + /* We close the tty fd here since if the old session + * ended our handle will be dead. It's important that + * we do this after sleeping, so that we don't enter + * an endless loop. */ + close_nointr_nofail(fd); + } + + if (notify >= 0) + close_nointr_nofail(notify); + + r = reset_terminal_fd(fd, true); + if (r < 0) + log_warning("Failed to reset terminal: %s", strerror(-r)); + + return fd; + +fail: + if (fd >= 0) + close_nointr_nofail(fd); + + if (notify >= 0) + close_nointr_nofail(notify); + + return r; +} + +int release_terminal(void) { + int r = 0, fd; + struct sigaction sa_old, sa_new; + + if ((fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY|O_CLOEXEC)) < 0) + return -errno; + + /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed + * by our own TIOCNOTTY */ + + zero(sa_new); + sa_new.sa_handler = SIG_IGN; + sa_new.sa_flags = SA_RESTART; + assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); + + if (ioctl(fd, TIOCNOTTY) < 0) + r = -errno; + + assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); + + close_nointr_nofail(fd); + return r; +} + +int sigaction_many(const struct sigaction *sa, ...) { + va_list ap; + int r = 0, sig; + + va_start(ap, sa); + while ((sig = va_arg(ap, int)) > 0) + if (sigaction(sig, sa, NULL) < 0) + r = -errno; + va_end(ap); + + return r; +} + +int ignore_signals(int sig, ...) { + struct sigaction sa; + va_list ap; + int r = 0; + + zero(sa); + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_RESTART; + + if (sigaction(sig, &sa, NULL) < 0) + r = -errno; + + va_start(ap, sig); + while ((sig = va_arg(ap, int)) > 0) + if (sigaction(sig, &sa, NULL) < 0) + r = -errno; + va_end(ap); + + return r; +} + +int default_signals(int sig, ...) { + struct sigaction sa; + va_list ap; + int r = 0; + + zero(sa); + sa.sa_handler = SIG_DFL; + sa.sa_flags = SA_RESTART; + + if (sigaction(sig, &sa, NULL) < 0) + r = -errno; + + va_start(ap, sig); + while ((sig = va_arg(ap, int)) > 0) + if (sigaction(sig, &sa, NULL) < 0) + r = -errno; + va_end(ap); + + return r; +} + +int close_pipe(int p[]) { + int a = 0, b = 0; + + assert(p); + + if (p[0] >= 0) { + a = close_nointr(p[0]); + p[0] = -1; + } + + if (p[1] >= 0) { + b = close_nointr(p[1]); + p[1] = -1; + } + + return a < 0 ? a : b; +} + +ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { + uint8_t *p; + ssize_t n = 0; + + assert(fd >= 0); + assert(buf); + + p = buf; + + while (nbytes > 0) { + ssize_t k; + + if ((k = read(fd, p, nbytes)) <= 0) { + + if (k < 0 && errno == EINTR) + continue; + + if (k < 0 && errno == EAGAIN && do_poll) { + struct pollfd pollfd; + + zero(pollfd); + pollfd.fd = fd; + pollfd.events = POLLIN; + + if (poll(&pollfd, 1, -1) < 0) { + if (errno == EINTR) + continue; + + return n > 0 ? n : -errno; + } + + if (pollfd.revents != POLLIN) + return n > 0 ? n : -EIO; + + continue; + } + + return n > 0 ? n : (k < 0 ? -errno : 0); + } + + p += k; + nbytes -= k; + n += k; + } + + return n; +} + +ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { + const uint8_t *p; + ssize_t n = 0; + + assert(fd >= 0); + assert(buf); + + p = buf; + + while (nbytes > 0) { + ssize_t k; + + k = write(fd, p, nbytes); + if (k <= 0) { + + if (k < 0 && errno == EINTR) + continue; + + if (k < 0 && errno == EAGAIN && do_poll) { + struct pollfd pollfd; + + zero(pollfd); + pollfd.fd = fd; + pollfd.events = POLLOUT; + + if (poll(&pollfd, 1, -1) < 0) { + if (errno == EINTR) + continue; + + return n > 0 ? n : -errno; + } + + if (pollfd.revents != POLLOUT) + return n > 0 ? n : -EIO; + + continue; + } + + return n > 0 ? n : (k < 0 ? -errno : 0); + } + + p += k; + nbytes -= k; + n += k; + } + + return n; +} + +int path_is_mount_point(const char *t, bool allow_symlink) { + struct stat a, b; + char *parent; + int r; + + if (allow_symlink) + r = stat(t, &a); + else + r = lstat(t, &a); + + if (r < 0) { + if (errno == ENOENT) + return 0; + + return -errno; + } + + r = parent_of_path(t, &parent); + if (r < 0) + return r; + + r = lstat(parent, &b); + free(parent); + + if (r < 0) + return -errno; + + return a.st_dev != b.st_dev; +} + +int parse_usec(const char *t, usec_t *usec) { + static const struct { + const char *suffix; + usec_t usec; + } table[] = { + { "sec", USEC_PER_SEC }, + { "s", USEC_PER_SEC }, + { "min", USEC_PER_MINUTE }, + { "hr", USEC_PER_HOUR }, + { "h", USEC_PER_HOUR }, + { "d", USEC_PER_DAY }, + { "w", USEC_PER_WEEK }, + { "msec", USEC_PER_MSEC }, + { "ms", USEC_PER_MSEC }, + { "m", USEC_PER_MINUTE }, + { "usec", 1ULL }, + { "us", 1ULL }, + { "", USEC_PER_SEC }, + }; + + const char *p; + usec_t r = 0; + + assert(t); + assert(usec); + + p = t; + do { + long long l; + char *e; + unsigned i; + + errno = 0; + l = strtoll(p, &e, 10); + + if (errno != 0) + return -errno; + + if (l < 0) + return -ERANGE; + + if (e == p) + return -EINVAL; + + e += strspn(e, WHITESPACE); + + for (i = 0; i < ELEMENTSOF(table); i++) + if (startswith(e, table[i].suffix)) { + r += (usec_t) l * table[i].usec; + p = e + strlen(table[i].suffix); + break; + } + + if (i >= ELEMENTSOF(table)) + return -EINVAL; + + } while (*p != 0); + + *usec = r; + + return 0; +} + +int parse_bytes(const char *t, off_t *bytes) { + static const struct { + const char *suffix; + off_t factor; + } table[] = { + { "B", 1 }, + { "K", 1024ULL }, + { "M", 1024ULL*1024ULL }, + { "G", 1024ULL*1024ULL*1024ULL }, + { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, + { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "", 1 }, + }; + + const char *p; + off_t r = 0; + + assert(t); + assert(bytes); + + p = t; + do { + long long l; + char *e; + unsigned i; + + errno = 0; + l = strtoll(p, &e, 10); + + if (errno != 0) + return -errno; + + if (l < 0) + return -ERANGE; + + if (e == p) + return -EINVAL; + + e += strspn(e, WHITESPACE); + + for (i = 0; i < ELEMENTSOF(table); i++) + if (startswith(e, table[i].suffix)) { + r += (off_t) l * table[i].factor; + p = e + strlen(table[i].suffix); + break; + } + + if (i >= ELEMENTSOF(table)) + return -EINVAL; + + } while (*p != 0); + + *bytes = r; + + return 0; +} + +int make_stdio(int fd) { + int r, s, t; + + assert(fd >= 0); + + r = dup2(fd, STDIN_FILENO); + s = dup2(fd, STDOUT_FILENO); + t = dup2(fd, STDERR_FILENO); + + if (fd >= 3) + close_nointr_nofail(fd); + + if (r < 0 || s < 0 || t < 0) + return -errno; + + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_cloexec(STDERR_FILENO, false); + + return 0; +} + +int make_null_stdio(void) { + int null_fd; + + if ((null_fd = open("/dev/null", O_RDWR|O_NOCTTY)) < 0) + return -errno; + + return make_stdio(null_fd); +} + +bool is_device_path(const char *path) { + + /* Returns true on paths that refer to a device, either in + * sysfs or in /dev */ + + return + path_startswith(path, "/dev/") || + path_startswith(path, "/sys/"); +} + +int dir_is_empty(const char *path) { + DIR *d; + int r; + struct dirent buf, *de; + + if (!(d = opendir(path))) + return -errno; + + for (;;) { + if ((r = readdir_r(d, &buf, &de)) > 0) { + r = -r; + break; + } + + if (!de) { + r = 1; + break; + } + + if (!ignore_file(de->d_name)) { + r = 0; + break; + } + } + + closedir(d); + return r; +} + +unsigned long long random_ull(void) { + int fd; + uint64_t ull; + ssize_t r; + + if ((fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY)) < 0) + goto fallback; + + r = loop_read(fd, &ull, sizeof(ull), true); + close_nointr_nofail(fd); + + if (r != sizeof(ull)) + goto fallback; + + return ull; + +fallback: + return random() * RAND_MAX + random(); +} + +void rename_process(const char name[8]) { + assert(name); + + /* This is a like a poor man's setproctitle(). It changes the + * comm field, argv[0], and also the glibc's internally used + * name of the process. For the first one a limit of 16 chars + * applies, to the second one usually one of 10 (i.e. length + * of "/sbin/init"), to the third one one of 7 (i.e. length of + * "systemd"). If you pass a longer string it will be + * truncated */ + + prctl(PR_SET_NAME, name); + + if (program_invocation_name) + strncpy(program_invocation_name, name, strlen(program_invocation_name)); + + if (saved_argc > 0) { + int i; + + if (saved_argv[0]) + strncpy(saved_argv[0], name, strlen(saved_argv[0])); + + for (i = 1; i < saved_argc; i++) { + if (!saved_argv[i]) + break; + + memset(saved_argv[i], 0, strlen(saved_argv[i])); + } + } +} + +void sigset_add_many(sigset_t *ss, ...) { + va_list ap; + int sig; + + assert(ss); + + va_start(ap, ss); + while ((sig = va_arg(ap, int)) > 0) + assert_se(sigaddset(ss, sig) == 0); + va_end(ap); +} + +char* gethostname_malloc(void) { + struct utsname u; + + assert_se(uname(&u) >= 0); + + if (u.nodename[0]) + return strdup(u.nodename); + + return strdup(u.sysname); +} + +char* getlogname_malloc(void) { + uid_t uid; + long bufsize; + char *buf, *name; + struct passwd pwbuf, *pw = NULL; + struct stat st; + + if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) + uid = st.st_uid; + else + uid = getuid(); + + /* Shortcut things to avoid NSS lookups */ + if (uid == 0) + return strdup("root"); + + if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) <= 0) + bufsize = 4096; + + if (!(buf = malloc(bufsize))) + return NULL; + + if (getpwuid_r(uid, &pwbuf, buf, bufsize, &pw) == 0 && pw) { + name = strdup(pw->pw_name); + free(buf); + return name; + } + + free(buf); + + if (asprintf(&name, "%lu", (unsigned long) uid) < 0) + return NULL; + + return name; +} + +int getttyname_malloc(int fd, char **r) { + char path[PATH_MAX], *c; + int k; + + assert(r); + + if ((k = ttyname_r(fd, path, sizeof(path))) != 0) + return -k; + + char_array_0(path); + + if (!(c = strdup(startswith(path, "/dev/") ? path + 5 : path))) + return -ENOMEM; + + *r = c; + return 0; +} + +int getttyname_harder(int fd, char **r) { + int k; + char *s; + + if ((k = getttyname_malloc(fd, &s)) < 0) + return k; + + if (streq(s, "tty")) { + free(s); + return get_ctty(0, NULL, r); + } + + *r = s; + return 0; +} + +int get_ctty_devnr(pid_t pid, dev_t *d) { + int k; + char line[LINE_MAX], *p, *fn; + unsigned long ttynr; + FILE *f; + + if (asprintf(&fn, "/proc/%lu/stat", (unsigned long) (pid <= 0 ? getpid() : pid)) < 0) + return -ENOMEM; + + f = fopen(fn, "re"); + free(fn); + if (!f) + return -errno; + + if (!fgets(line, sizeof(line), f)) { + k = feof(f) ? -EIO : -errno; + fclose(f); + return k; + } + + fclose(f); + + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; + + if (sscanf(p, " " + "%*c " /* state */ + "%*d " /* ppid */ + "%*d " /* pgrp */ + "%*d " /* session */ + "%lu ", /* ttynr */ + &ttynr) != 1) + return -EIO; + + *d = (dev_t) ttynr; + return 0; +} + +int get_ctty(pid_t pid, dev_t *_devnr, char **r) { + int k; + char fn[PATH_MAX], *s, *b, *p; + dev_t devnr; + + assert(r); + + k = get_ctty_devnr(pid, &devnr); + if (k < 0) + return k; + + snprintf(fn, sizeof(fn), "/dev/char/%u:%u", major(devnr), minor(devnr)); + char_array_0(fn); + + if ((k = readlink_malloc(fn, &s)) < 0) { + + if (k != -ENOENT) + return k; + + /* This is an ugly hack */ + if (major(devnr) == 136) { + if (asprintf(&b, "pts/%lu", (unsigned long) minor(devnr)) < 0) + return -ENOMEM; + + *r = b; + if (_devnr) + *_devnr = devnr; + + return 0; + } + + /* Probably something like the ptys which have no + * symlink in /dev/char. Let's return something + * vaguely useful. */ + + if (!(b = strdup(fn + 5))) + return -ENOMEM; + + *r = b; + if (_devnr) + *_devnr = devnr; + + return 0; + } + + if (startswith(s, "/dev/")) + p = s + 5; + else if (startswith(s, "../")) + p = s + 3; + else + p = s; + + b = strdup(p); + free(s); + + if (!b) + return -ENOMEM; + + *r = b; + if (_devnr) + *_devnr = devnr; + + return 0; +} + +static int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { + DIR *d; + int ret = 0; + + assert(fd >= 0); + + /* This returns the first error we run into, but nevertheless + * tries to go on */ + + if (!(d = fdopendir(fd))) { + close_nointr_nofail(fd); + + return errno == ENOENT ? 0 : -errno; + } + + for (;;) { + struct dirent buf, *de; + bool is_dir, keep_around = false; + int r; + + if ((r = readdir_r(d, &buf, &de)) != 0) { + if (ret == 0) + ret = -r; + break; + } + + if (!de) + break; + + if (streq(de->d_name, ".") || streq(de->d_name, "..")) + continue; + + if (de->d_type == DT_UNKNOWN) { + struct stat st; + + if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + if (honour_sticky) + keep_around = + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); + + is_dir = S_ISDIR(st.st_mode); + + } else { + if (honour_sticky) { + struct stat st; + + if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + keep_around = + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); + } + + is_dir = de->d_type == DT_DIR; + } + + if (is_dir) { + int subdir_fd; + + subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (subdir_fd < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + if ((r = rm_rf_children(subdir_fd, only_dirs, honour_sticky)) < 0) { + if (ret == 0) + ret = r; + } + + if (!keep_around) + if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + + } else if (!only_dirs && !keep_around) { + + if (unlinkat(fd, de->d_name, 0) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + } + } + + closedir(d); + + return ret; +} + +int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { + int fd; + int r; + + assert(path); + + if ((fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)) < 0) { + + if (errno != ENOTDIR) + return -errno; + + if (delete_root && !only_dirs) + if (unlink(path) < 0) + return -errno; + + return 0; + } + + r = rm_rf_children(fd, only_dirs, honour_sticky); + + if (delete_root) { + + if (honour_sticky && file_is_priv_sticky(path) > 0) + return r; + + if (rmdir(path) < 0 && errno != ENOENT) { + if (r == 0) + r = -errno; + } + } + + return r; +} + +int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { + assert(path); + + /* Under the assumption that we are running privileged we + * first change the access mode and only then hand out + * ownership to avoid a window where access is too open. */ + + if (mode != (mode_t) -1) + if (chmod(path, mode) < 0) + return -errno; + + if (uid != (uid_t) -1 || gid != (gid_t) -1) + if (chown(path, uid, gid) < 0) + return -errno; + + return 0; +} + +int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid) { + assert(fd >= 0); + + /* Under the assumption that we are running privileged we + * first change the access mode and only then hand out + * ownership to avoid a window where access is too open. */ + + if (fchmod(fd, mode) < 0) + return -errno; + + if (fchown(fd, uid, gid) < 0) + return -errno; + + return 0; +} + +cpu_set_t* cpu_set_malloc(unsigned *ncpus) { + cpu_set_t *r; + unsigned n = 1024; + + /* Allocates the cpuset in the right size */ + + for (;;) { + if (!(r = CPU_ALLOC(n))) + return NULL; + + if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), r) >= 0) { + CPU_ZERO_S(CPU_ALLOC_SIZE(n), r); + + if (ncpus) + *ncpus = n; + + return r; + } + + CPU_FREE(r); + + if (errno != EINVAL) + return NULL; + + n *= 2; + } +} + +void status_vprintf(const char *status, bool ellipse, const char *format, va_list ap) { + char *s = NULL, *spaces = NULL, *e; + int fd = -1, c; + size_t emax, sl, left; + struct iovec iovec[5]; + int n = 0; + + assert(format); + + /* This independent of logging, as status messages are + * optional and go exclusively to the console. */ + + if (vasprintf(&s, format, ap) < 0) + goto finish; + + fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + goto finish; + + if (ellipse) { + c = fd_columns(fd); + if (c <= 0) + c = 80; + + if (status) { + sl = 2 + 6 + 1; /* " [" status "]" */ + emax = (size_t) c > sl ? c - sl - 1 : 0; + } else + emax = c - 1; + + e = ellipsize(s, emax, 75); + if (e) { + free(s); + s = e; + } + } + + zero(iovec); + IOVEC_SET_STRING(iovec[n++], s); + + if (ellipse) { + sl = strlen(s); + left = emax > sl ? emax - sl : 0; + if (left > 0) { + spaces = malloc(left); + if (spaces) { + memset(spaces, ' ', left); + iovec[n].iov_base = spaces; + iovec[n].iov_len = left; + n++; + } + } + } + + if (status) { + IOVEC_SET_STRING(iovec[n++], " ["); + IOVEC_SET_STRING(iovec[n++], status); + IOVEC_SET_STRING(iovec[n++], "]\n"); + } else + IOVEC_SET_STRING(iovec[n++], "\n"); + + writev(fd, iovec, n); + +finish: + free(s); + free(spaces); + + if (fd >= 0) + close_nointr_nofail(fd); +} + +void status_printf(const char *status, bool ellipse, const char *format, ...) { + va_list ap; + + assert(format); + + va_start(ap, format); + status_vprintf(status, ellipse, format, ap); + va_end(ap); +} + +void status_welcome(void) { + char *pretty_name = NULL, *ansi_color = NULL; + const char *const_pretty = NULL, *const_color = NULL; + int r; + + if ((r = parse_env_file("/etc/os-release", NEWLINE, + "PRETTY_NAME", &pretty_name, + "ANSI_COLOR", &ansi_color, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/os-release: %s", strerror(-r)); + } + + if (!pretty_name && !const_pretty) + const_pretty = "Linux"; + + if (!ansi_color && !const_color) + const_color = "1"; + + status_printf(NULL, + false, + "\nWelcome to \x1B[%sm%s\x1B[0m!\n", + const_color ? const_color : ansi_color, + const_pretty ? const_pretty : pretty_name); + + free(ansi_color); + free(pretty_name); +} + +char *replace_env(const char *format, char **env) { + enum { + WORD, + CURLY, + VARIABLE + } state = WORD; + + const char *e, *word = format; + char *r = NULL, *k; + + assert(format); + + for (e = format; *e; e ++) { + + switch (state) { + + case WORD: + if (*e == '$') + state = CURLY; + break; + + case CURLY: + if (*e == '{') { + if (!(k = strnappend(r, word, e-word-1))) + goto fail; + + free(r); + r = k; + + word = e-1; + state = VARIABLE; + + } else if (*e == '$') { + if (!(k = strnappend(r, word, e-word))) + goto fail; + + free(r); + r = k; + + word = e+1; + state = WORD; + } else + state = WORD; + break; + + case VARIABLE: + if (*e == '}') { + const char *t; + + if (!(t = strv_env_get_with_length(env, word+2, e-word-2))) + t = ""; + + if (!(k = strappend(r, t))) + goto fail; + + free(r); + r = k; + + word = e+1; + state = WORD; + } + break; + } + } + + if (!(k = strnappend(r, word, e-word))) + goto fail; + + free(r); + return k; + +fail: + free(r); + return NULL; +} + +char **replace_env_argv(char **argv, char **env) { + char **r, **i; + unsigned k = 0, l = 0; + + l = strv_length(argv); + + if (!(r = new(char*, l+1))) + return NULL; + + STRV_FOREACH(i, argv) { + + /* If $FOO appears as single word, replace it by the split up variable */ + if ((*i)[0] == '$' && (*i)[1] != '{') { + char *e; + char **w, **m; + unsigned q; + + if ((e = strv_env_get(env, *i+1))) { + + if (!(m = strv_split_quoted(e))) { + r[k] = NULL; + strv_free(r); + return NULL; + } + } else + m = NULL; + + q = strv_length(m); + l = l + q - 1; + + if (!(w = realloc(r, sizeof(char*) * (l+1)))) { + r[k] = NULL; + strv_free(r); + strv_free(m); + return NULL; + } + + r = w; + if (m) { + memcpy(r + k, m, q * sizeof(char*)); + free(m); + } + + k += q; + continue; + } + + /* If ${FOO} appears as part of a word, replace it by the variable as-is */ + if (!(r[k++] = replace_env(*i, env))) { + strv_free(r); + return NULL; + } + } + + r[k] = NULL; + return r; +} + +int fd_columns(int fd) { + struct winsize ws; + zero(ws); + + if (ioctl(fd, TIOCGWINSZ, &ws) < 0) + return -errno; + + if (ws.ws_col <= 0) + return -EIO; + + return ws.ws_col; +} + +unsigned columns(void) { + static __thread int parsed_columns = 0; + const char *e; + + if (_likely_(parsed_columns > 0)) + return parsed_columns; + + e = getenv("COLUMNS"); + if (e) + parsed_columns = atoi(e); + + if (parsed_columns <= 0) + parsed_columns = fd_columns(STDOUT_FILENO); + + if (parsed_columns <= 0) + parsed_columns = 80; + + return parsed_columns; +} + +int fd_lines(int fd) { + struct winsize ws; + zero(ws); + + if (ioctl(fd, TIOCGWINSZ, &ws) < 0) + return -errno; + + if (ws.ws_row <= 0) + return -EIO; + + return ws.ws_row; +} + +unsigned lines(void) { + static __thread int parsed_lines = 0; + const char *e; + + if (_likely_(parsed_lines > 0)) + return parsed_lines; + + e = getenv("LINES"); + if (e) + parsed_lines = atoi(e); + + if (parsed_lines <= 0) + parsed_lines = fd_lines(STDOUT_FILENO); + + if (parsed_lines <= 0) + parsed_lines = 25; + + return parsed_lines; +} + +int running_in_chroot(void) { + struct stat a, b; + + zero(a); + zero(b); + + /* Only works as root */ + + if (stat("/proc/1/root", &a) < 0) + return -errno; + + if (stat("/", &b) < 0) + return -errno; + + return + a.st_dev != b.st_dev || + a.st_ino != b.st_ino; +} + +char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { + size_t x; + char *r; + + assert(s); + assert(percent <= 100); + assert(new_length >= 3); + + if (old_length <= 3 || old_length <= new_length) + return strndup(s, old_length); + + r = new0(char, new_length+1); + if (!r) + return r; + + x = (new_length * percent) / 100; + + if (x > new_length - 3) + x = new_length - 3; + + memcpy(r, s, x); + r[x] = '.'; + r[x+1] = '.'; + r[x+2] = '.'; + memcpy(r + x + 3, + s + old_length - (new_length - x - 3), + new_length - x - 3); + + return r; +} + +char *ellipsize(const char *s, size_t length, unsigned percent) { + return ellipsize_mem(s, strlen(s), length, percent); +} + +int touch(const char *path) { + int fd; + + assert(path); + + if ((fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644)) < 0) + return -errno; + + close_nointr_nofail(fd); + return 0; +} + +char *unquote(const char *s, const char* quotes) { + size_t l; + assert(s); + + l = strlen(s); + if (l < 2) + return strdup(s); + + if (strchr(quotes, s[0]) && s[l-1] == s[0]) + return strndup(s+1, l-2); + + return strdup(s); +} + +char *normalize_env_assignment(const char *s) { + char *name, *value, *p, *r; + + p = strchr(s, '='); + + if (!p) { + if (!(r = strdup(s))) + return NULL; + + return strstrip(r); + } + + if (!(name = strndup(s, p - s))) + return NULL; + + if (!(p = strdup(p+1))) { + free(name); + return NULL; + } + + value = unquote(strstrip(p), QUOTES); + free(p); + + if (!value) { + free(name); + return NULL; + } + + if (asprintf(&r, "%s=%s", name, value) < 0) + r = NULL; + + free(value); + free(name); + + return r; +} + +int wait_for_terminate(pid_t pid, siginfo_t *status) { + siginfo_t dummy; + + assert(pid >= 1); + + if (!status) + status = &dummy; + + for (;;) { + zero(*status); + + if (waitid(P_PID, pid, status, WEXITED) < 0) { + + if (errno == EINTR) + continue; + + return -errno; + } + + return 0; + } +} + +int wait_for_terminate_and_warn(const char *name, pid_t pid) { + int r; + siginfo_t status; + + assert(name); + assert(pid > 1); + + if ((r = wait_for_terminate(pid, &status)) < 0) { + log_warning("Failed to wait for %s: %s", name, strerror(-r)); + return r; + } + + if (status.si_code == CLD_EXITED) { + if (status.si_status != 0) { + log_warning("%s failed with error code %i.", name, status.si_status); + return status.si_status; + } + + log_debug("%s succeeded.", name); + return 0; + + } else if (status.si_code == CLD_KILLED || + status.si_code == CLD_DUMPED) { + + log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status)); + return -EPROTO; + } + + log_warning("%s failed due to unknown reason.", name); + return -EPROTO; + +} + +_noreturn_ void freeze(void) { + + /* Make sure nobody waits for us on a socket anymore */ + close_all_fds(NULL, 0); + + sync(); + + for (;;) + pause(); +} + +bool null_or_empty(struct stat *st) { + assert(st); + + if (S_ISREG(st->st_mode) && st->st_size <= 0) + return true; + + if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) + return true; + + return false; +} + +int null_or_empty_path(const char *fn) { + struct stat st; + + assert(fn); + + if (stat(fn, &st) < 0) + return -errno; + + return null_or_empty(&st); +} + +DIR *xopendirat(int fd, const char *name, int flags) { + int nfd; + DIR *d; + + if ((nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags)) < 0) + return NULL; + + if (!(d = fdopendir(nfd))) { + close_nointr_nofail(nfd); + return NULL; + } + + return d; +} + +int signal_from_string_try_harder(const char *s) { + int signo; + assert(s); + + if ((signo = signal_from_string(s)) <= 0) + if (startswith(s, "SIG")) + return signal_from_string(s+3); + + return signo; +} + +void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) { + + assert(f); + assert(name); + assert(t); + + if (!dual_timestamp_is_set(t)) + return; + + fprintf(f, "%s=%llu %llu\n", + name, + (unsigned long long) t->realtime, + (unsigned long long) t->monotonic); +} + +void dual_timestamp_deserialize(const char *value, dual_timestamp *t) { + unsigned long long a, b; + + assert(value); + assert(t); + + if (sscanf(value, "%lli %llu", &a, &b) != 2) + log_debug("Failed to parse finish timestamp value %s", value); + else { + t->realtime = a; + t->monotonic = b; + } +} + +char *fstab_node_to_udev_node(const char *p) { + char *dn, *t, *u; + int r; + + /* FIXME: to follow udev's logic 100% we need to leave valid + * UTF8 chars unescaped */ + + if (startswith(p, "LABEL=")) { + + if (!(u = unquote(p+6, "\"\'"))) + return NULL; + + t = xescape(u, "/ "); + free(u); + + if (!t) + return NULL; + + r = asprintf(&dn, "/dev/disk/by-label/%s", t); + free(t); + + if (r < 0) + return NULL; + + return dn; + } + + if (startswith(p, "UUID=")) { + + if (!(u = unquote(p+5, "\"\'"))) + return NULL; + + t = xescape(u, "/ "); + free(u); + + if (!t) + return NULL; + + r = asprintf(&dn, "/dev/disk/by-uuid/%s", t); + free(t); + + if (r < 0) + return NULL; + + return dn; + } + + return strdup(p); +} + +void filter_environ(const char *prefix) { + int i, j; + assert(prefix); + + if (!environ) + return; + + for (i = 0, j = 0; environ[i]; i++) { + + if (startswith(environ[i], prefix)) + continue; + + environ[j++] = environ[i]; + } + + environ[j] = NULL; +} + +bool tty_is_vc(const char *tty) { + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + return vtnr_from_tty(tty) >= 0; +} + +int vtnr_from_tty(const char *tty) { + int i, r; + + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + if (!startswith(tty, "tty") ) + return -EINVAL; + + if (tty[3] < '0' || tty[3] > '9') + return -EINVAL; + + r = safe_atoi(tty+3, &i); + if (r < 0) + return r; + + if (i < 0 || i > 63) + return -EINVAL; + + return i; +} + +bool tty_is_vc_resolve(const char *tty) { + char *active = NULL; + bool b; + + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + /* Resolve where /dev/console is pointing to */ + if (streq(tty, "console")) + if (read_one_line_file("/sys/class/tty/console/active", &active) >= 0) { + /* If multiple log outputs are configured the + * last one is what /dev/console points to */ + tty = strrchr(active, ' '); + if (tty) + tty++; + else + tty = active; + } + + b = tty_is_vc(tty); + free(active); + + return b; +} + +const char *default_term_for_tty(const char *tty) { + assert(tty); + + return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt100"; +} + +bool dirent_is_file(const struct dirent *de) { + assert(de); + + if (ignore_file(de->d_name)) + return false; + + if (de->d_type != DT_REG && + de->d_type != DT_LNK && + de->d_type != DT_UNKNOWN) + return false; + + return true; +} + +bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { + assert(de); + + if (!dirent_is_file(de)) + return false; + + return endswith(de->d_name, suffix); +} + +void execute_directory(const char *directory, DIR *d, char *argv[]) { + DIR *_d = NULL; + struct dirent *de; + Hashmap *pids = NULL; + + assert(directory); + + /* Executes all binaries in a directory in parallel and waits + * until all they all finished. */ + + if (!d) { + if (!(_d = opendir(directory))) { + + if (errno == ENOENT) + return; + + log_error("Failed to enumerate directory %s: %m", directory); + return; + } + + d = _d; + } + + if (!(pids = hashmap_new(trivial_hash_func, trivial_compare_func))) { + log_error("Failed to allocate set."); + goto finish; + } + + while ((de = readdir(d))) { + char *path; + pid_t pid; + int k; + + if (!dirent_is_file(de)) + continue; + + if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) { + log_error("Out of memory"); + continue; + } + + if ((pid = fork()) < 0) { + log_error("Failed to fork: %m"); + free(path); + continue; + } + + if (pid == 0) { + char *_argv[2]; + /* Child */ + + if (!argv) { + _argv[0] = path; + _argv[1] = NULL; + argv = _argv; + } else + if (!argv[0]) + argv[0] = path; + + execv(path, argv); + + log_error("Failed to execute %s: %m", path); + _exit(EXIT_FAILURE); + } + + log_debug("Spawned %s as %lu", path, (unsigned long) pid); + + if ((k = hashmap_put(pids, UINT_TO_PTR(pid), path)) < 0) { + log_error("Failed to add PID to set: %s", strerror(-k)); + free(path); + } + } + + while (!hashmap_isempty(pids)) { + pid_t pid = PTR_TO_UINT(hashmap_first_key(pids)); + siginfo_t si; + char *path; + + zero(si); + if (waitid(P_PID, pid, &si, WEXITED) < 0) { + + if (errno == EINTR) + continue; + + log_error("waitid() failed: %m"); + goto finish; + } + + if ((path = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)))) { + if (!is_clean_exit(si.si_code, si.si_status)) { + if (si.si_code == CLD_EXITED) + log_error("%s exited with exit status %i.", path, si.si_status); + else + log_error("%s terminated by signal %s.", path, signal_to_string(si.si_status)); + } else + log_debug("%s exited successfully.", path); + + free(path); + } + } + +finish: + if (_d) + closedir(_d); + + if (pids) + hashmap_free_free(pids); +} + +int kill_and_sigcont(pid_t pid, int sig) { + int r; + + r = kill(pid, sig) < 0 ? -errno : 0; + + if (r >= 0) + kill(pid, SIGCONT); + + return r; +} + +bool nulstr_contains(const char*nulstr, const char *needle) { + const char *i; + + if (!nulstr) + return false; + + NULSTR_FOREACH(i, nulstr) + if (streq(i, needle)) + return true; + + return false; +} + +bool plymouth_running(void) { + return access("/run/plymouth/pid", F_OK) >= 0; +} + +void parse_syslog_priority(char **p, int *priority) { + int a = 0, b = 0, c = 0; + int k; + + assert(p); + assert(*p); + assert(priority); + + if ((*p)[0] != '<') + return; + + if (!strchr(*p, '>')) + return; + + if ((*p)[2] == '>') { + c = undecchar((*p)[1]); + k = 3; + } else if ((*p)[3] == '>') { + b = undecchar((*p)[1]); + c = undecchar((*p)[2]); + k = 4; + } else if ((*p)[4] == '>') { + a = undecchar((*p)[1]); + b = undecchar((*p)[2]); + c = undecchar((*p)[3]); + k = 5; + } else + return; + + if (a < 0 || b < 0 || c < 0) + return; + + *priority = a*100+b*10+c; + *p += k; +} + +void skip_syslog_pid(char **buf) { + char *p; + + assert(buf); + assert(*buf); + + p = *buf; + + if (*p != '[') + return; + + p++; + p += strspn(p, "0123456789"); + + if (*p != ']') + return; + + p++; + + *buf = p; +} + +void skip_syslog_date(char **buf) { + enum { + LETTER, + SPACE, + NUMBER, + SPACE_OR_NUMBER, + COLON + } sequence[] = { + LETTER, LETTER, LETTER, + SPACE, + SPACE_OR_NUMBER, NUMBER, + SPACE, + SPACE_OR_NUMBER, NUMBER, + COLON, + SPACE_OR_NUMBER, NUMBER, + COLON, + SPACE_OR_NUMBER, NUMBER, + SPACE + }; + + char *p; + unsigned i; + + assert(buf); + assert(*buf); + + p = *buf; + + for (i = 0; i < ELEMENTSOF(sequence); i++, p++) { + + if (!*p) + return; + + switch (sequence[i]) { + + case SPACE: + if (*p != ' ') + return; + break; + + case SPACE_OR_NUMBER: + if (*p == ' ') + break; + + /* fall through */ + + case NUMBER: + if (*p < '0' || *p > '9') + return; + + break; + + case LETTER: + if (!(*p >= 'A' && *p <= 'Z') && + !(*p >= 'a' && *p <= 'z')) + return; + + break; + + case COLON: + if (*p != ':') + return; + break; + + } + } + + *buf = p; +} + +int have_effective_cap(int value) { + cap_t cap; + cap_flag_value_t fv; + int r; + + if (!(cap = cap_get_proc())) + return -errno; + + if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0) + r = -errno; + else + r = fv == CAP_SET; + + cap_free(cap); + return r; +} + +char* strshorten(char *s, size_t l) { + assert(s); + + if (l < strlen(s)) + s[l] = 0; + + return s; +} + +static bool hostname_valid_char(char c) { + return + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '-' || + c == '_' || + c == '.'; +} + +bool hostname_is_valid(const char *s) { + const char *p; + + if (isempty(s)) + return false; + + for (p = s; *p; p++) + if (!hostname_valid_char(*p)) + return false; + + if (p-s > HOST_NAME_MAX) + return false; + + return true; +} + +char* hostname_cleanup(char *s) { + char *p, *d; + + for (p = s, d = s; *p; p++) + if ((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9') || + *p == '-' || + *p == '_' || + *p == '.') + *(d++) = *p; + + *d = 0; + + strshorten(s, HOST_NAME_MAX); + return s; +} + +int pipe_eof(int fd) { + struct pollfd pollfd; + int r; + + zero(pollfd); + pollfd.fd = fd; + pollfd.events = POLLIN|POLLHUP; + + r = poll(&pollfd, 1, 0); + if (r < 0) + return -errno; + + if (r == 0) + return 0; + + return pollfd.revents & POLLHUP; +} + +int fd_wait_for_event(int fd, int event, usec_t t) { + struct pollfd pollfd; + int r; + + zero(pollfd); + pollfd.fd = fd; + pollfd.events = event; + + r = poll(&pollfd, 1, t == (usec_t) -1 ? -1 : (int) (t / USEC_PER_MSEC)); + if (r < 0) + return -errno; + + if (r == 0) + return 0; + + return pollfd.revents; +} + +int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { + FILE *f; + char *t; + const char *fn; + size_t k; + int fd; + + assert(path); + assert(_f); + assert(_temp_path); + + t = new(char, strlen(path) + 1 + 6 + 1); + if (!t) + return -ENOMEM; + + fn = file_name_from_path(path); + k = fn-path; + memcpy(t, path, k); + t[k] = '.'; + stpcpy(stpcpy(t+k+1, fn), "XXXXXX"); + + fd = mkostemp(t, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + free(t); + return -errno; + } + + f = fdopen(fd, "we"); + if (!f) { + unlink(t); + free(t); + return -errno; + } + + *_f = f; + *_temp_path = t; + + return 0; +} + +int terminal_vhangup_fd(int fd) { + assert(fd >= 0); + + if (ioctl(fd, TIOCVHANGUP) < 0) + return -errno; + + return 0; +} + +int terminal_vhangup(const char *name) { + int fd, r; + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + r = terminal_vhangup_fd(fd); + close_nointr_nofail(fd); + + return r; +} + +int vt_disallocate(const char *name) { + int fd, r; + unsigned u; + + /* Deallocate the VT if possible. If not possible + * (i.e. because it is the active one), at least clear it + * entirely (including the scrollback buffer) */ + + if (!startswith(name, "/dev/")) + return -EINVAL; + + if (!tty_is_vc(name)) { + /* So this is not a VT. I guess we cannot deallocate + * it then. But let's at least clear the screen */ + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + loop_write(fd, + "\033[r" /* clear scrolling region */ + "\033[H" /* move home */ + "\033[2J", /* clear screen */ + 10, false); + close_nointr_nofail(fd); + + return 0; + } + + if (!startswith(name, "/dev/tty")) + return -EINVAL; + + r = safe_atou(name+8, &u); + if (r < 0) + return r; + + if (u <= 0) + return -EINVAL; + + /* Try to deallocate */ + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + r = ioctl(fd, VT_DISALLOCATE, u); + close_nointr_nofail(fd); + + if (r >= 0) + return 0; + + if (errno != EBUSY) + return -errno; + + /* Couldn't deallocate, so let's clear it fully with + * scrollback */ + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + loop_write(fd, + "\033[r" /* clear scrolling region */ + "\033[H" /* move home */ + "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */ + 10, false); + close_nointr_nofail(fd); + + return 0; +} + +static int files_add(Hashmap *h, const char *path, const char *suffix) { + DIR *dir; + struct dirent buffer, *de; + int r = 0; + + dir = opendir(path); + if (!dir) { + if (errno == ENOENT) + return 0; + return -errno; + } + + for (;;) { + int k; + char *p, *f; + + k = readdir_r(dir, &buffer, &de); + if (k != 0) { + r = -k; + goto finish; + } + + if (!de) + break; + + if (!dirent_is_file_with_suffix(de, suffix)) + continue; + + if (asprintf(&p, "%s/%s", path, de->d_name) < 0) { + r = -ENOMEM; + goto finish; + } + + f = canonicalize_file_name(p); + if (!f) { + log_error("Failed to canonicalize file name '%s': %m", p); + free(p); + continue; + } + free(p); + + log_debug("found: %s\n", f); + if (hashmap_put(h, file_name_from_path(f), f) <= 0) + free(f); + } + +finish: + closedir(dir); + return r; +} + +static int base_cmp(const void *a, const void *b) { + const char *s1, *s2; + + s1 = *(char * const *)a; + s2 = *(char * const *)b; + return strcmp(file_name_from_path(s1), file_name_from_path(s2)); +} + +int conf_files_list(char ***strv, const char *suffix, const char *dir, ...) { + Hashmap *fh = NULL; + char **dirs = NULL; + char **files = NULL; + char **p; + va_list ap; + int r = 0; + + va_start(ap, dir); + dirs = strv_new_ap(dir, ap); + va_end(ap); + if (!dirs) { + r = -ENOMEM; + goto finish; + } + if (!strv_path_canonicalize(dirs)) { + r = -ENOMEM; + goto finish; + } + if (!strv_uniq(dirs)) { + r = -ENOMEM; + goto finish; + } + + fh = hashmap_new(string_hash_func, string_compare_func); + if (!fh) { + r = -ENOMEM; + goto finish; + } + + STRV_FOREACH(p, dirs) { + if (files_add(fh, *p, suffix) < 0) { + log_error("Failed to search for files."); + r = -EINVAL; + goto finish; + } + } + + files = hashmap_get_strv(fh); + if (files == NULL) { + log_error("Failed to compose list of files."); + r = -ENOMEM; + goto finish; + } + + qsort(files, hashmap_size(fh), sizeof(char *), base_cmp); + +finish: + strv_free(dirs); + hashmap_free(fh); + *strv = files; + return r; +} + +int hwclock_is_localtime(void) { + FILE *f; + bool local = false; + + /* + * The third line of adjtime is "UTC" or "LOCAL" or nothing. + * # /etc/adjtime + * 0.0 0 0 + * 0 + * UTC + */ + f = fopen("/etc/adjtime", "re"); + if (f) { + char line[LINE_MAX]; + bool b; + + b = fgets(line, sizeof(line), f) && + fgets(line, sizeof(line), f) && + fgets(line, sizeof(line), f); + + fclose(f); + + if (!b) + return -EIO; + + + truncate_nl(line); + local = streq(line, "LOCAL"); + + } else if (errno != -ENOENT) + return -errno; + + return local; +} + +int hwclock_apply_localtime_delta(int *min) { + const struct timeval *tv_null = NULL; + struct timespec ts; + struct tm *tm; + int minuteswest; + struct timezone tz; + + assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + assert_se(tm = localtime(&ts.tv_sec)); + minuteswest = tm->tm_gmtoff / 60; + + tz.tz_minuteswest = -minuteswest; + tz.tz_dsttime = 0; /* DST_NONE*/ + + /* + * If the hardware clock does not run in UTC, but in local time: + * The very first time we set the kernel's timezone, it will warp + * the clock so that it runs in UTC instead of local time. + */ + if (settimeofday(tv_null, &tz) < 0) + return -errno; + if (min) + *min = minuteswest; + return 0; +} + +int hwclock_reset_localtime_delta(void) { + const struct timeval *tv_null = NULL; + struct timezone tz; + + tz.tz_minuteswest = 0; + tz.tz_dsttime = 0; /* DST_NONE*/ + + if (settimeofday(tv_null, &tz) < 0) + return -errno; + + return 0; +} + +int rtc_open(int flags) { + int fd; + DIR *d; + + /* First, we try to make use of the /dev/rtc symlink. If that + * doesn't exist, we open the first RTC which has hctosys=1 + * set. If we don't find any we just take the first RTC that + * exists at all. */ + + fd = open("/dev/rtc", flags); + if (fd >= 0) + return fd; + + d = opendir("/sys/class/rtc"); + if (!d) + goto fallback; + + for (;;) { + char *p, *v; + struct dirent buf, *de; + int r; + + r = readdir_r(d, &buf, &de); + if (r != 0) + goto fallback; + + if (!de) + goto fallback; + + if (ignore_file(de->d_name)) + continue; + + p = join("/sys/class/rtc/", de->d_name, "/hctosys", NULL); + if (!p) { + closedir(d); + return -ENOMEM; + } + + r = read_one_line_file(p, &v); + free(p); + + if (r < 0) + continue; + + r = parse_boolean(v); + free(v); + + if (r <= 0) + continue; + + p = strappend("/dev/", de->d_name); + fd = open(p, flags); + free(p); + + if (fd >= 0) { + closedir(d); + return fd; + } + } + +fallback: + if (d) + closedir(d); + + fd = open("/dev/rtc0", flags); + if (fd < 0) + return -errno; + + return fd; +} + +int hwclock_get_time(struct tm *tm) { + int fd; + int err = 0; + + assert(tm); + + fd = rtc_open(O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + /* This leaves the timezone fields of struct tm + * uninitialized! */ + if (ioctl(fd, RTC_RD_TIME, tm) < 0) + err = -errno; + + /* We don't now daylight saving, so we reset this in order not + * to confused mktime(). */ + tm->tm_isdst = -1; + + close_nointr_nofail(fd); + + return err; +} + +int hwclock_set_time(const struct tm *tm) { + int fd; + int err = 0; + + assert(tm); + + fd = rtc_open(O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + if (ioctl(fd, RTC_SET_TIME, tm) < 0) + err = -errno; + + close_nointr_nofail(fd); + + return err; +} + +int copy_file(const char *from, const char *to) { + int r, fdf, fdt; + + assert(from); + assert(to); + + fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fdf < 0) + return -errno; + + fdt = open(to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY, 0644); + if (fdt < 0) { + close_nointr_nofail(fdf); + return -errno; + } + + for (;;) { + char buf[PIPE_BUF]; + ssize_t n, k; + + n = read(fdf, buf, sizeof(buf)); + if (n < 0) { + r = -errno; + + close_nointr_nofail(fdf); + close_nointr(fdt); + unlink(to); + + return r; + } + + if (n == 0) + break; + + errno = 0; + k = loop_write(fdt, buf, n, false); + if (n != k) { + r = k < 0 ? k : (errno ? -errno : -EIO); + + close_nointr_nofail(fdf); + close_nointr(fdt); + + unlink(to); + return r; + } + } + + close_nointr_nofail(fdf); + r = close_nointr(fdt); + + if (r < 0) { + unlink(to); + return r; + } + + return 0; +} + +int symlink_or_copy(const char *from, const char *to) { + char *pf = NULL, *pt = NULL; + struct stat a, b; + int r; + + assert(from); + assert(to); + + if (parent_of_path(from, &pf) < 0 || + parent_of_path(to, &pt) < 0) { + r = -ENOMEM; + goto finish; + } + + if (stat(pf, &a) < 0 || + stat(pt, &b) < 0) { + r = -errno; + goto finish; + } + + if (a.st_dev != b.st_dev) { + free(pf); + free(pt); + + return copy_file(from, to); + } + + if (symlink(from, to) < 0) { + r = -errno; + goto finish; + } + + r = 0; + +finish: + free(pf); + free(pt); + + return r; +} + +int symlink_or_copy_atomic(const char *from, const char *to) { + char *t, *x; + const char *fn; + size_t k; + unsigned long long ull; + unsigned i; + int r; + + assert(from); + assert(to); + + t = new(char, strlen(to) + 1 + 16 + 1); + if (!t) + return -ENOMEM; + + fn = file_name_from_path(to); + k = fn-to; + memcpy(t, to, k); + t[k] = '.'; + x = stpcpy(t+k+1, fn); + + ull = random_ull(); + for (i = 0; i < 16; i++) { + *(x++) = hexchar(ull & 0xF); + ull >>= 4; + } + + *x = 0; + + r = symlink_or_copy(from, t); + if (r < 0) { + unlink(t); + free(t); + return r; + } + + if (rename(t, to) < 0) { + r = -errno; + unlink(t); + free(t); + return r; + } + + free(t); + return r; +} + +int audit_session_from_pid(pid_t pid, uint32_t *id) { + char *s; + uint32_t u; + int r; + + assert(id); + + if (have_effective_cap(CAP_AUDIT_CONTROL) <= 0) + return -ENOENT; + + if (pid == 0) + r = read_one_line_file("/proc/self/sessionid", &s); + else { + char *p; + + if (asprintf(&p, "/proc/%lu/sessionid", (unsigned long) pid) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &s); + free(p); + } + + if (r < 0) + return r; + + r = safe_atou32(s, &u); + free(s); + + if (r < 0) + return r; + + if (u == (uint32_t) -1 || u <= 0) + return -ENOENT; + + *id = u; + return 0; +} + +int audit_loginuid_from_pid(pid_t pid, uid_t *uid) { + char *s; + uid_t u; + int r; + + assert(uid); + + /* Only use audit login uid if we are executed with sufficient + * capabilities so that pam_loginuid could do its job. If we + * are lacking the CAP_AUDIT_CONTROL capabality we most likely + * are being run in a container and /proc/self/loginuid is + * useless since it probably contains a uid of the host + * system. */ + + if (have_effective_cap(CAP_AUDIT_CONTROL) <= 0) + return -ENOENT; + + if (pid == 0) + r = read_one_line_file("/proc/self/loginuid", &s); + else { + char *p; + + if (asprintf(&p, "/proc/%lu/loginuid", (unsigned long) pid) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &s); + free(p); + } + + if (r < 0) + return r; + + r = parse_uid(s, &u); + free(s); + + if (r < 0) + return r; + + if (u == (uid_t) -1) + return -ENOENT; + + *uid = (uid_t) u; + return 0; +} + +bool display_is_local(const char *display) { + assert(display); + + return + display[0] == ':' && + display[1] >= '0' && + display[1] <= '9'; +} + +int socket_from_display(const char *display, char **path) { + size_t k; + char *f, *c; + + assert(display); + assert(path); + + if (!display_is_local(display)) + return -EINVAL; + + k = strspn(display+1, "0123456789"); + + f = new(char, sizeof("/tmp/.X11-unix/X") + k); + if (!f) + return -ENOMEM; + + c = stpcpy(f, "/tmp/.X11-unix/X"); + memcpy(c, display+1, k); + c[k] = 0; + + *path = f; + + return 0; +} + +int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home) { + struct passwd *p; + uid_t u; + + assert(username); + assert(*username); + + /* We enforce some special rules for uid=0: in order to avoid + * NSS lookups for root we hardcode its data. */ + + if (streq(*username, "root") || streq(*username, "0")) { + *username = "root"; + + if (uid) + *uid = 0; + + if (gid) + *gid = 0; + + if (home) + *home = "/root"; + return 0; + } + + if (parse_uid(*username, &u) >= 0) { + errno = 0; + p = getpwuid(u); + + /* If there are multiple users with the same id, make + * sure to leave $USER to the configured value instead + * of the first occurrence in the database. However if + * the uid was configured by a numeric uid, then let's + * pick the real username from /etc/passwd. */ + if (p) + *username = p->pw_name; + } else { + errno = 0; + p = getpwnam(*username); + } + + if (!p) + return errno != 0 ? -errno : -ESRCH; + + if (uid) + *uid = p->pw_uid; + + if (gid) + *gid = p->pw_gid; + + if (home) + *home = p->pw_dir; + + return 0; +} + +int get_group_creds(const char **groupname, gid_t *gid) { + struct group *g; + gid_t id; + + assert(groupname); + + /* We enforce some special rules for gid=0: in order to avoid + * NSS lookups for root we hardcode its data. */ + + if (streq(*groupname, "root") || streq(*groupname, "0")) { + *groupname = "root"; + + if (gid) + *gid = 0; + + return 0; + } + + if (parse_gid(*groupname, &id) >= 0) { + errno = 0; + g = getgrgid(id); + + if (g) + *groupname = g->gr_name; + } else { + errno = 0; + g = getgrnam(*groupname); + } + + if (!g) + return errno != 0 ? -errno : -ESRCH; + + if (gid) + *gid = g->gr_gid; + + return 0; +} + +int in_group(const char *name) { + gid_t gid, *gids; + int ngroups_max, r, i; + + r = get_group_creds(&name, &gid); + if (r < 0) + return r; + + if (getgid() == gid) + return 1; + + if (getegid() == gid) + return 1; + + ngroups_max = sysconf(_SC_NGROUPS_MAX); + assert(ngroups_max > 0); + + gids = alloca(sizeof(gid_t) * ngroups_max); + + r = getgroups(ngroups_max, gids); + if (r < 0) + return -errno; + + for (i = 0; i < r; i++) + if (gids[i] == gid) + return 1; + + return 0; +} + +int glob_exists(const char *path) { + glob_t g; + int r, k; + + assert(path); + + zero(g); + errno = 0; + k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); + + if (k == GLOB_NOMATCH) + r = 0; + else if (k == GLOB_NOSPACE) + r = -ENOMEM; + else if (k == 0) + r = !strv_isempty(g.gl_pathv); + else + r = errno ? -errno : -EIO; + + globfree(&g); + + return r; +} + +int dirent_ensure_type(DIR *d, struct dirent *de) { + struct stat st; + + assert(d); + assert(de); + + if (de->d_type != DT_UNKNOWN) + return 0; + + if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) + return -errno; + + de->d_type = + S_ISREG(st.st_mode) ? DT_REG : + S_ISDIR(st.st_mode) ? DT_DIR : + S_ISLNK(st.st_mode) ? DT_LNK : + S_ISFIFO(st.st_mode) ? DT_FIFO : + S_ISSOCK(st.st_mode) ? DT_SOCK : + S_ISCHR(st.st_mode) ? DT_CHR : + S_ISBLK(st.st_mode) ? DT_BLK : + DT_UNKNOWN; + + return 0; +} + +int in_search_path(const char *path, char **search) { + char **i, *parent; + int r; + + r = parent_of_path(path, &parent); + if (r < 0) + return r; + + r = 0; + + STRV_FOREACH(i, search) { + if (path_equal(parent, *i)) { + r = 1; + break; + } + } + + free(parent); + + return r; +} + +int get_files_in_directory(const char *path, char ***list) { + DIR *d; + int r = 0; + unsigned n = 0; + char **l = NULL; + + assert(path); + + /* Returns all files in a directory in *list, and the number + * of files as return value. If list is NULL returns only the + * number */ + + d = opendir(path); + if (!d) + return -errno; + + for (;;) { + struct dirent buffer, *de; + int k; + + k = readdir_r(d, &buffer, &de); + if (k != 0) { + r = -k; + goto finish; + } + + if (!de) + break; + + dirent_ensure_type(d, de); + + if (!dirent_is_file(de)) + continue; + + if (list) { + if ((unsigned) r >= n) { + char **t; + + n = MAX(16, 2*r); + t = realloc(l, sizeof(char*) * n); + if (!t) { + r = -ENOMEM; + goto finish; + } + + l = t; + } + + assert((unsigned) r < n); + + l[r] = strdup(de->d_name); + if (!l[r]) { + r = -ENOMEM; + goto finish; + } + + l[++r] = NULL; + } else + r++; + } + +finish: + if (d) + closedir(d); + + if (r >= 0) { + if (list) + *list = l; + } else + strv_free(l); + + return r; +} + +char *join(const char *x, ...) { + va_list ap; + size_t l; + char *r, *p; + + va_start(ap, x); + + if (x) { + l = strlen(x); + + for (;;) { + const char *t; + + t = va_arg(ap, const char *); + if (!t) + break; + + l += strlen(t); + } + } else + l = 0; + + va_end(ap); + + r = new(char, l+1); + if (!r) + return NULL; + + if (x) { + p = stpcpy(r, x); + + va_start(ap, x); + + for (;;) { + const char *t; + + t = va_arg(ap, const char *); + if (!t) + break; + + p = stpcpy(p, t); + } + + va_end(ap); + } else + r[0] = 0; + + return r; +} + +bool is_main_thread(void) { + static __thread int cached = 0; + + if (_unlikely_(cached == 0)) + cached = getpid() == gettid() ? 1 : -1; + + return cached > 0; +} + +int block_get_whole_disk(dev_t d, dev_t *ret) { + char *p, *s; + int r; + unsigned n, m; + + assert(ret); + + /* If it has a queue this is good enough for us */ + if (asprintf(&p, "/sys/dev/block/%u:%u/queue", major(d), minor(d)) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + if (r >= 0) { + *ret = d; + return 0; + } + + /* If it is a partition find the originating device */ + if (asprintf(&p, "/sys/dev/block/%u:%u/partition", major(d), minor(d)) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + if (r < 0) + return -ENOENT; + + /* Get parent dev_t */ + if (asprintf(&p, "/sys/dev/block/%u:%u/../dev", major(d), minor(d)) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &s); + free(p); + + if (r < 0) + return r; + + r = sscanf(s, "%u:%u", &m, &n); + free(s); + + if (r != 2) + return -EINVAL; + + /* Only return this if it is really good enough for us. */ + if (asprintf(&p, "/sys/dev/block/%u:%u/queue", m, n) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + if (r >= 0) { + *ret = makedev(m, n); + return 0; + } + + return -ENOENT; +} + +int file_is_priv_sticky(const char *p) { + struct stat st; + + assert(p); + + if (lstat(p, &st) < 0) + return -errno; + + return + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); +} + +static const char *const ioprio_class_table[] = { + [IOPRIO_CLASS_NONE] = "none", + [IOPRIO_CLASS_RT] = "realtime", + [IOPRIO_CLASS_BE] = "best-effort", + [IOPRIO_CLASS_IDLE] = "idle" +}; + +DEFINE_STRING_TABLE_LOOKUP(ioprio_class, int); + +static const char *const sigchld_code_table[] = { + [CLD_EXITED] = "exited", + [CLD_KILLED] = "killed", + [CLD_DUMPED] = "dumped", + [CLD_TRAPPED] = "trapped", + [CLD_STOPPED] = "stopped", + [CLD_CONTINUED] = "continued", +}; + +DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int); + +static const char *const log_facility_unshifted_table[LOG_NFACILITIES] = { + [LOG_FAC(LOG_KERN)] = "kern", + [LOG_FAC(LOG_USER)] = "user", + [LOG_FAC(LOG_MAIL)] = "mail", + [LOG_FAC(LOG_DAEMON)] = "daemon", + [LOG_FAC(LOG_AUTH)] = "auth", + [LOG_FAC(LOG_SYSLOG)] = "syslog", + [LOG_FAC(LOG_LPR)] = "lpr", + [LOG_FAC(LOG_NEWS)] = "news", + [LOG_FAC(LOG_UUCP)] = "uucp", + [LOG_FAC(LOG_CRON)] = "cron", + [LOG_FAC(LOG_AUTHPRIV)] = "authpriv", + [LOG_FAC(LOG_FTP)] = "ftp", + [LOG_FAC(LOG_LOCAL0)] = "local0", + [LOG_FAC(LOG_LOCAL1)] = "local1", + [LOG_FAC(LOG_LOCAL2)] = "local2", + [LOG_FAC(LOG_LOCAL3)] = "local3", + [LOG_FAC(LOG_LOCAL4)] = "local4", + [LOG_FAC(LOG_LOCAL5)] = "local5", + [LOG_FAC(LOG_LOCAL6)] = "local6", + [LOG_FAC(LOG_LOCAL7)] = "local7" +}; + +DEFINE_STRING_TABLE_LOOKUP(log_facility_unshifted, int); + +static const char *const log_level_table[] = { + [LOG_EMERG] = "emerg", + [LOG_ALERT] = "alert", + [LOG_CRIT] = "crit", + [LOG_ERR] = "err", + [LOG_WARNING] = "warning", + [LOG_NOTICE] = "notice", + [LOG_INFO] = "info", + [LOG_DEBUG] = "debug" +}; + +DEFINE_STRING_TABLE_LOOKUP(log_level, int); + +static const char* const sched_policy_table[] = { + [SCHED_OTHER] = "other", + [SCHED_BATCH] = "batch", + [SCHED_IDLE] = "idle", + [SCHED_FIFO] = "fifo", + [SCHED_RR] = "rr" +}; + +DEFINE_STRING_TABLE_LOOKUP(sched_policy, int); + +static const char* const rlimit_table[] = { + [RLIMIT_CPU] = "LimitCPU", + [RLIMIT_FSIZE] = "LimitFSIZE", + [RLIMIT_DATA] = "LimitDATA", + [RLIMIT_STACK] = "LimitSTACK", + [RLIMIT_CORE] = "LimitCORE", + [RLIMIT_RSS] = "LimitRSS", + [RLIMIT_NOFILE] = "LimitNOFILE", + [RLIMIT_AS] = "LimitAS", + [RLIMIT_NPROC] = "LimitNPROC", + [RLIMIT_MEMLOCK] = "LimitMEMLOCK", + [RLIMIT_LOCKS] = "LimitLOCKS", + [RLIMIT_SIGPENDING] = "LimitSIGPENDING", + [RLIMIT_MSGQUEUE] = "LimitMSGQUEUE", + [RLIMIT_NICE] = "LimitNICE", + [RLIMIT_RTPRIO] = "LimitRTPRIO", + [RLIMIT_RTTIME] = "LimitRTTIME" +}; + +DEFINE_STRING_TABLE_LOOKUP(rlimit, int); + +static const char* const ip_tos_table[] = { + [IPTOS_LOWDELAY] = "low-delay", + [IPTOS_THROUGHPUT] = "throughput", + [IPTOS_RELIABILITY] = "reliability", + [IPTOS_LOWCOST] = "low-cost", +}; + +DEFINE_STRING_TABLE_LOOKUP(ip_tos, int); + +static const char *const __signal_table[] = { + [SIGHUP] = "HUP", + [SIGINT] = "INT", + [SIGQUIT] = "QUIT", + [SIGILL] = "ILL", + [SIGTRAP] = "TRAP", + [SIGABRT] = "ABRT", + [SIGBUS] = "BUS", + [SIGFPE] = "FPE", + [SIGKILL] = "KILL", + [SIGUSR1] = "USR1", + [SIGSEGV] = "SEGV", + [SIGUSR2] = "USR2", + [SIGPIPE] = "PIPE", + [SIGALRM] = "ALRM", + [SIGTERM] = "TERM", +#ifdef SIGSTKFLT + [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ +#endif + [SIGCHLD] = "CHLD", + [SIGCONT] = "CONT", + [SIGSTOP] = "STOP", + [SIGTSTP] = "TSTP", + [SIGTTIN] = "TTIN", + [SIGTTOU] = "TTOU", + [SIGURG] = "URG", + [SIGXCPU] = "XCPU", + [SIGXFSZ] = "XFSZ", + [SIGVTALRM] = "VTALRM", + [SIGPROF] = "PROF", + [SIGWINCH] = "WINCH", + [SIGIO] = "IO", + [SIGPWR] = "PWR", + [SIGSYS] = "SYS" +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int); + +const char *signal_to_string(int signo) { + static __thread char buf[12]; + const char *name; + + name = __signal_to_string(signo); + if (name) + return name; + + if (signo >= SIGRTMIN && signo <= SIGRTMAX) + snprintf(buf, sizeof(buf) - 1, "RTMIN+%d", signo - SIGRTMIN); + else + snprintf(buf, sizeof(buf) - 1, "%d", signo); + char_array_0(buf); + return buf; +} + +int signal_from_string(const char *s) { + int signo; + int offset = 0; + unsigned u; + + signo =__signal_from_string(s); + if (signo > 0) + return signo; + + if (startswith(s, "RTMIN+")) { + s += 6; + offset = SIGRTMIN; + } + if (safe_atou(s, &u) >= 0) { + signo = (int) u + offset; + if (signo > 0 && signo < _NSIG) + return signo; + } + return -1; +} + +bool kexec_loaded(void) { + bool loaded = false; + char *s; + + if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) { + if (s[0] == '1') + loaded = true; + free(s); + } + return loaded; +} + +int strdup_or_null(const char *a, char **b) { + char *c; + + assert(b); + + if (!a) { + *b = NULL; + return 0; + } + + c = strdup(a); + if (!c) + return -ENOMEM; + + *b = c; + return 0; +} + +int prot_from_flags(int flags) { + + switch (flags & O_ACCMODE) { + + case O_RDONLY: + return PROT_READ; + + case O_WRONLY: + return PROT_WRITE; + + case O_RDWR: + return PROT_READ|PROT_WRITE; + + default: + return -EINVAL; + } +} + +unsigned long cap_last_cap(void) { + static __thread unsigned long saved; + static __thread bool valid = false; + unsigned long p; + + if (valid) + return saved; + + p = (unsigned long) CAP_LAST_CAP; + + if (prctl(PR_CAPBSET_READ, p) < 0) { + + /* Hmm, look downwards, until we find one that + * works */ + for (p--; p > 0; p --) + if (prctl(PR_CAPBSET_READ, p) >= 0) + break; + + } else { + + /* Hmm, look upwards, until we find one that doesn't + * work */ + for (;; p++) + if (prctl(PR_CAPBSET_READ, p+1) < 0) + break; + } + + saved = p; + valid = true; + + return p; +} + +char *format_bytes(char *buf, size_t l, off_t t) { + unsigned i; + + static const struct { + const char *suffix; + off_t factor; + } table[] = { + { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, + { "G", 1024ULL*1024ULL*1024ULL }, + { "M", 1024ULL*1024ULL }, + { "K", 1024ULL }, + }; + + for (i = 0; i < ELEMENTSOF(table); i++) { + + if (t >= table[i].factor) { + snprintf(buf, l, + "%llu.%llu%s", + (unsigned long long) (t / table[i].factor), + (unsigned long long) (((t*10ULL) / table[i].factor) % 10ULL), + table[i].suffix); + + goto finish; + } + } + + snprintf(buf, l, "%lluB", (unsigned long long) t); + +finish: + buf[l-1] = 0; + return buf; + +} + +void* memdup(const void *p, size_t l) { + void *r; + + assert(p); + + r = malloc(l); + if (!r) + return NULL; + + memcpy(r, p, l); + return r; +} + +int fd_inc_sndbuf(int fd, size_t n) { + int r, value; + socklen_t l = sizeof(value); + + r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l); + if (r >= 0 && + l == sizeof(value) && + (size_t) value >= n*2) + return 0; + + value = (int) n; + r = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)); + if (r < 0) + return -errno; + + return 1; +} + +int fd_inc_rcvbuf(int fd, size_t n) { + int r, value; + socklen_t l = sizeof(value); + + r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l); + if (r >= 0 && + l == sizeof(value) && + (size_t) value >= n*2) + return 0; + + value = (int) n; + r = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)); + if (r < 0) + return -errno; + + return 1; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 000000000..b1af6dbe7 --- /dev/null +++ b/src/util.h @@ -0,0 +1,544 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooutilhfoo +#define fooutilhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "macro.h" + +typedef uint64_t usec_t; + +typedef struct dual_timestamp { + usec_t realtime; + usec_t monotonic; +} dual_timestamp; + +#define MSEC_PER_SEC 1000ULL +#define USEC_PER_SEC 1000000ULL +#define USEC_PER_MSEC 1000ULL +#define NSEC_PER_SEC 1000000000ULL +#define NSEC_PER_MSEC 1000000ULL +#define NSEC_PER_USEC 1000ULL + +#define USEC_PER_MINUTE (60ULL*USEC_PER_SEC) +#define USEC_PER_HOUR (60ULL*USEC_PER_MINUTE) +#define USEC_PER_DAY (24ULL*USEC_PER_HOUR) +#define USEC_PER_WEEK (7ULL*USEC_PER_DAY) +#define USEC_PER_MONTH (2629800ULL*USEC_PER_SEC) +#define USEC_PER_YEAR (31557600ULL*USEC_PER_SEC) + +/* What is interpreted as whitespace? */ +#define WHITESPACE " \t\n\r" +#define NEWLINE "\n\r" +#define QUOTES "\"\'" +#define COMMENTS "#;\n" + +#define FORMAT_TIMESTAMP_MAX 64 +#define FORMAT_TIMESTAMP_PRETTY_MAX 256 +#define FORMAT_TIMESPAN_MAX 64 +#define FORMAT_BYTES_MAX 8 + +#define ANSI_HIGHLIGHT_ON "\x1B[1;39m" +#define ANSI_HIGHLIGHT_RED_ON "\x1B[1;31m" +#define ANSI_HIGHLIGHT_GREEN_ON "\x1B[1;32m" +#define ANSI_HIGHLIGHT_OFF "\x1B[0m" + +usec_t now(clockid_t clock); + +dual_timestamp* dual_timestamp_get(dual_timestamp *ts); +dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u); + +#define dual_timestamp_is_set(ts) ((ts)->realtime > 0) + +usec_t timespec_load(const struct timespec *ts); +struct timespec *timespec_store(struct timespec *ts, usec_t u); + +usec_t timeval_load(const struct timeval *tv); +struct timeval *timeval_store(struct timeval *tv, usec_t u); + +size_t page_size(void); +#define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) + +#define streq(a,b) (strcmp((a),(b)) == 0) +#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0) + +bool streq_ptr(const char *a, const char *b); + +#define new(t, n) ((t*) malloc(sizeof(t)*(n))) + +#define new0(t, n) ((t*) calloc((n), sizeof(t))) + +#define malloc0(n) (calloc((n), 1)) + +static inline const char* yes_no(bool b) { + return b ? "yes" : "no"; +} + +static inline const char* strempty(const char *s) { + return s ? s : ""; +} + +static inline const char* strnull(const char *s) { + return s ? s : "(null)"; +} + +static inline const char *strna(const char *s) { + return s ? s : "n/a"; +} + +static inline bool is_path_absolute(const char *p) { + return *p == '/'; +} + +static inline bool isempty(const char *p) { + return !p || !p[0]; +} + +bool endswith(const char *s, const char *postfix); +bool startswith(const char *s, const char *prefix); +bool startswith_no_case(const char *s, const char *prefix); + +bool first_word(const char *s, const char *word); + +int close_nointr(int fd); +void close_nointr_nofail(int fd); +void close_many(const int fds[], unsigned n_fd); + +int parse_boolean(const char *v); +int parse_usec(const char *t, usec_t *usec); +int parse_bytes(const char *t, off_t *bytes); +int parse_pid(const char *s, pid_t* ret_pid); +int parse_uid(const char *s, uid_t* ret_uid); +#define parse_gid(s, ret_uid) parse_uid(s, ret_uid) + +int safe_atou(const char *s, unsigned *ret_u); +int safe_atoi(const char *s, int *ret_i); + +int safe_atollu(const char *s, unsigned long long *ret_u); +int safe_atolli(const char *s, long long int *ret_i); + +#if __WORDSIZE == 32 +static inline int safe_atolu(const char *s, unsigned long *ret_u) { + assert_cc(sizeof(unsigned long) == sizeof(unsigned)); + return safe_atou(s, (unsigned*) ret_u); +} +static inline int safe_atoli(const char *s, long int *ret_u) { + assert_cc(sizeof(long int) == sizeof(int)); + return safe_atoi(s, (int*) ret_u); +} +#else +static inline int safe_atolu(const char *s, unsigned long *ret_u) { + assert_cc(sizeof(unsigned long) == sizeof(unsigned long long)); + return safe_atollu(s, (unsigned long long*) ret_u); +} +static inline int safe_atoli(const char *s, long int *ret_u) { + assert_cc(sizeof(long int) == sizeof(long long int)); + return safe_atolli(s, (long long int*) ret_u); +} +#endif + +static inline int safe_atou32(const char *s, uint32_t *ret_u) { + assert_cc(sizeof(uint32_t) == sizeof(unsigned)); + return safe_atou(s, (unsigned*) ret_u); +} + +static inline int safe_atoi32(const char *s, int32_t *ret_i) { + assert_cc(sizeof(int32_t) == sizeof(int)); + return safe_atoi(s, (int*) ret_i); +} + +static inline int safe_atou64(const char *s, uint64_t *ret_u) { + assert_cc(sizeof(uint64_t) == sizeof(unsigned long long)); + return safe_atollu(s, (unsigned long long*) ret_u); +} + +static inline int safe_atoi64(const char *s, int64_t *ret_i) { + assert_cc(sizeof(int64_t) == sizeof(long long int)); + return safe_atolli(s, (long long int*) ret_i); +} + +char *split(const char *c, size_t *l, const char *separator, char **state); +char *split_quoted(const char *c, size_t *l, char **state); + +#define FOREACH_WORD(word, length, s, state) \ + for ((state) = NULL, (word) = split((s), &(length), WHITESPACE, &(state)); (word); (word) = split((s), &(length), WHITESPACE, &(state))) + +#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \ + for ((state) = NULL, (word) = split((s), &(length), (separator), &(state)); (word); (word) = split((s), &(length), (separator), &(state))) + +#define FOREACH_WORD_QUOTED(word, length, s, state) \ + for ((state) = NULL, (word) = split_quoted((s), &(length), &(state)); (word); (word) = split_quoted((s), &(length), &(state))) + +char **split_path_and_make_absolute(const char *p); + +pid_t get_parent_of_pid(pid_t pid, pid_t *ppid); +int get_starttime_of_pid(pid_t pid, unsigned long long *st); + +int write_one_line_file(const char *fn, const char *line); +int write_one_line_file_atomic(const char *fn, const char *line); +int read_one_line_file(const char *fn, char **line); +int read_full_file(const char *fn, char **contents, size_t *size); + +int parse_env_file(const char *fname, const char *separator, ...) _sentinel_; +int load_env_file(const char *fname, char ***l); +int write_env_file(const char *fname, char **l); + +char *strappend(const char *s, const char *suffix); +char *strnappend(const char *s, const char *suffix, size_t length); + +char *replace_env(const char *format, char **env); +char **replace_env_argv(char **argv, char **env); + +int readlink_malloc(const char *p, char **r); +int readlink_and_make_absolute(const char *p, char **r); +int readlink_and_canonicalize(const char *p, char **r); + +char *file_name_from_path(const char *p); +bool is_path(const char *p); + +bool path_is_absolute(const char *p); +char *path_make_absolute(const char *p, const char *prefix); +char *path_make_absolute_cwd(const char *p); + +char **strv_path_make_absolute_cwd(char **l); +char **strv_path_canonicalize(char **l); +char **strv_path_remove_empty(char **l); + +int reset_all_signal_handlers(void); + +char *strstrip(char *s); +char *delete_chars(char *s, const char *bad); +char *truncate_nl(char *s); + +char *file_in_same_dir(const char *path, const char *filename); +int safe_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid); +int mkdir_parents(const char *path, mode_t mode); +int mkdir_p(const char *path, mode_t mode); + +int parent_of_path(const char *path, char **parent); + +int rmdir_parents(const char *path, const char *stop); + +int get_process_comm(pid_t pid, char **name); +int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line); +int get_process_exe(pid_t pid, char **name); +int get_process_uid(pid_t pid, uid_t *uid); + +char hexchar(int x); +int unhexchar(char c); +char octchar(int x); +int unoctchar(char c); +char decchar(int x); +int undecchar(char c); + +char *cescape(const char *s); +char *cunescape(const char *s); +char *cunescape_length(const char *s, size_t length); + +char *xescape(const char *s, const char *bad); + +char *bus_path_escape(const char *s); +char *bus_path_unescape(const char *s); + +char *path_kill_slashes(char *path); + +bool path_startswith(const char *path, const char *prefix); +bool path_equal(const char *a, const char *b); + +char *ascii_strlower(char *path); + +bool dirent_is_file(const struct dirent *de); +bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix); + +bool ignore_file(const char *filename); + +bool chars_intersect(const char *a, const char *b); + +char *format_timestamp(char *buf, size_t l, usec_t t); +char *format_timestamp_pretty(char *buf, size_t l, usec_t t); +char *format_timespan(char *buf, size_t l, usec_t t); + +int make_stdio(int fd); +int make_null_stdio(void); + +unsigned long long random_ull(void); + +#define __DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \ + scope const char *name##_to_string(type i) { \ + if (i < 0 || i >= (type) ELEMENTSOF(name##_table)) \ + return NULL; \ + return name##_table[i]; \ + } \ + scope type name##_from_string(const char *s) { \ + type i; \ + unsigned u = 0; \ + assert(s); \ + for (i = 0; i < (type)ELEMENTSOF(name##_table); i++) \ + if (name##_table[i] && \ + streq(name##_table[i], s)) \ + return i; \ + if (safe_atou(s, &u) >= 0 && \ + u < ELEMENTSOF(name##_table)) \ + return (type) u; \ + return (type) -1; \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +#define DEFINE_STRING_TABLE_LOOKUP(name,type) __DEFINE_STRING_TABLE_LOOKUP(name,type,) +#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) __DEFINE_STRING_TABLE_LOOKUP(name,type,static) + +int fd_nonblock(int fd, bool nonblock); +int fd_cloexec(int fd, bool cloexec); + +int close_all_fds(const int except[], unsigned n_except); + +bool fstype_is_network(const char *fstype); + +int chvt(int vt); + +int read_one_char(FILE *f, char *ret, usec_t timeout, bool *need_nl); +int ask(char *ret, const char *replies, const char *text, ...); + +int reset_terminal_fd(int fd, bool switch_to_text); +int reset_terminal(const char *name); + +int open_terminal(const char *name, int mode); +int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm); +int release_terminal(void); + +int flush_fd(int fd); + +int ignore_signals(int sig, ...); +int default_signals(int sig, ...); +int sigaction_many(const struct sigaction *sa, ...); + +int close_pipe(int p[]); +int fopen_temporary(const char *path, FILE **_f, char **_temp_path); + +ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll); +ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll); + +int path_is_mount_point(const char *path, bool allow_symlink); + +bool is_device_path(const char *path); + +int dir_is_empty(const char *path); + +void rename_process(const char name[8]); + +void sigset_add_many(sigset_t *ss, ...); + +char* gethostname_malloc(void); +char* getlogname_malloc(void); + +int getttyname_malloc(int fd, char **r); +int getttyname_harder(int fd, char **r); + +int get_ctty_devnr(pid_t pid, dev_t *d); +int get_ctty(pid_t, dev_t *_devnr, char **r); + +int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); +int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid); + +int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky); + +int pipe_eof(int fd); + +cpu_set_t* cpu_set_malloc(unsigned *ncpus); + +void status_vprintf(const char *status, bool ellipse, const char *format, va_list ap); +void status_printf(const char *status, bool ellipse, const char *format, ...); +void status_welcome(void); + +int fd_columns(int fd); +unsigned columns(void); + +int fd_lines(int fd); +unsigned lines(void); + +int running_in_chroot(void); + +char *ellipsize(const char *s, size_t length, unsigned percent); +char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent); + +int touch(const char *path); + +char *unquote(const char *s, const char *quotes); +char *normalize_env_assignment(const char *s); + +int wait_for_terminate(pid_t pid, siginfo_t *status); +int wait_for_terminate_and_warn(const char *name, pid_t pid); + +_noreturn_ void freeze(void); + +bool null_or_empty(struct stat *st); +int null_or_empty_path(const char *fn); + +DIR *xopendirat(int dirfd, const char *name, int flags); + +void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t); +void dual_timestamp_deserialize(const char *value, dual_timestamp *t); + +char *fstab_node_to_udev_node(const char *p); + +void filter_environ(const char *prefix); + +bool tty_is_vc(const char *tty); +bool tty_is_vc_resolve(const char *tty); +int vtnr_from_tty(const char *tty); +const char *default_term_for_tty(const char *tty); + +void execute_directory(const char *directory, DIR *_d, char *argv[]); + +int kill_and_sigcont(pid_t pid, int sig); + +bool nulstr_contains(const char*nulstr, const char *needle); + +bool plymouth_running(void); + +void parse_syslog_priority(char **p, int *priority); +void skip_syslog_pid(char **buf); +void skip_syslog_date(char **buf); + +int have_effective_cap(int value); + +bool hostname_is_valid(const char *s); +char* hostname_cleanup(char *s); + +char* strshorten(char *s, size_t l); + +int terminal_vhangup_fd(int fd); +int terminal_vhangup(const char *name); + +int vt_disallocate(const char *name); + +int copy_file(const char *from, const char *to); +int symlink_or_copy(const char *from, const char *to); +int symlink_or_copy_atomic(const char *from, const char *to); + +int fchmod_umask(int fd, mode_t mode); + +int conf_files_list(char ***strv, const char *suffix, const char *dir, ...); + +int hwclock_is_localtime(void); +int hwclock_apply_localtime_delta(int *min); +int hwclock_reset_localtime_delta(void); +int hwclock_get_time(struct tm *tm); +int hwclock_set_time(const struct tm *tm); + +int audit_session_from_pid(pid_t pid, uint32_t *id); +int audit_loginuid_from_pid(pid_t pid, uid_t *uid); + +bool display_is_local(const char *display); +int socket_from_display(const char *display, char **path); + +int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home); +int get_group_creds(const char **groupname, gid_t *gid); + +int in_group(const char *name); + +int glob_exists(const char *path); + +int dirent_ensure_type(DIR *d, struct dirent *de); + +int in_search_path(const char *path, char **search); +int get_files_in_directory(const char *path, char ***list); + +char *join(const char *x, ...) _sentinel_; + +bool is_main_thread(void); + +bool in_charset(const char *s, const char* charset); + +int block_get_whole_disk(dev_t d, dev_t *ret); + +int file_is_priv_sticky(const char *p); + +int strdup_or_null(const char *a, char **b); + +#define NULSTR_FOREACH(i, l) \ + for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1) + +#define NULSTR_FOREACH_PAIR(i, j, l) \ + for ((i) = (l), (j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i)) + +const char *ioprio_class_to_string(int i); +int ioprio_class_from_string(const char *s); + +const char *sigchld_code_to_string(int i); +int sigchld_code_from_string(const char *s); + +const char *log_facility_unshifted_to_string(int i); +int log_facility_unshifted_from_string(const char *s); + +const char *log_level_to_string(int i); +int log_level_from_string(const char *s); + +const char *sched_policy_to_string(int i); +int sched_policy_from_string(const char *s); + +const char *rlimit_to_string(int i); +int rlimit_from_string(const char *s); + +const char *ip_tos_to_string(int i); +int ip_tos_from_string(const char *s); + +const char *signal_to_string(int i); +int signal_from_string(const char *s); + +int signal_from_string_try_harder(const char *s); + +extern int saved_argc; +extern char **saved_argv; + +bool kexec_loaded(void); + +int prot_from_flags(int flags); + +unsigned long cap_last_cap(void); + +char *format_bytes(char *buf, size_t l, off_t t); + +int fd_wait_for_event(int fd, int event, usec_t timeout); + +void* memdup(const void *p, size_t l); + +int rtc_open(int flags); + +int is_kernel_thread(pid_t pid); + +int fd_inc_sndbuf(int fd, size_t n); +int fd_inc_rcvbuf(int fd, size_t n); + +#endif diff --git a/src/utmp-wtmp.c b/src/utmp-wtmp.c new file mode 100644 index 000000000..217ae1e2c --- /dev/null +++ b/src/utmp-wtmp.c @@ -0,0 +1,430 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "macro.h" +#include "utmp-wtmp.h" + +int utmp_get_runlevel(int *runlevel, int *previous) { + struct utmpx lookup, *found; + int r; + const char *e; + + assert(runlevel); + + /* If these values are set in the environment this takes + * precedence. Presumably, sysvinit does this to work around a + * race condition that would otherwise exist where we'd always + * go to disk and hence might read runlevel data that might be + * very new and does not apply to the current script being + * executed. */ + + if ((e = getenv("RUNLEVEL")) && e[0] > 0) { + *runlevel = e[0]; + + if (previous) { + /* $PREVLEVEL seems to be an Upstart thing */ + + if ((e = getenv("PREVLEVEL")) && e[0] > 0) + *previous = e[0]; + else + *previous = 0; + } + + return 0; + } + + if (utmpxname(_PATH_UTMPX) < 0) + return -errno; + + setutxent(); + + zero(lookup); + lookup.ut_type = RUN_LVL; + + if (!(found = getutxid(&lookup))) + r = -errno; + else { + int a, b; + + a = found->ut_pid & 0xFF; + b = (found->ut_pid >> 8) & 0xFF; + + if (a < 0 || b < 0) + r = -EIO; + else { + *runlevel = a; + + if (previous) + *previous = b; + r = 0; + } + } + + endutxent(); + + return r; +} + +static void init_timestamp(struct utmpx *store, usec_t t) { + assert(store); + + zero(*store); + + if (t <= 0) + t = now(CLOCK_REALTIME); + + store->ut_tv.tv_sec = t / USEC_PER_SEC; + store->ut_tv.tv_usec = t % USEC_PER_SEC; +} + +static void init_entry(struct utmpx *store, usec_t t) { + struct utsname uts; + + assert(store); + + init_timestamp(store, t); + + zero(uts); + + if (uname(&uts) >= 0) + strncpy(store->ut_host, uts.release, sizeof(store->ut_host)); + + strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */ + strncpy(store->ut_id, "~~", sizeof(store->ut_id)); +} + +static int write_entry_utmp(const struct utmpx *store) { + int r; + + assert(store); + + /* utmp is similar to wtmp, but there is only one entry for + * each entry type resp. user; i.e. basically a key/value + * table. */ + + if (utmpxname(_PATH_UTMPX) < 0) + return -errno; + + setutxent(); + + if (!pututxline(store)) + r = -errno; + else + r = 0; + + endutxent(); + + return r; +} + +static int write_entry_wtmp(const struct utmpx *store) { + assert(store); + + /* wtmp is a simple append-only file where each entry is + simply appended to * the end; i.e. basically a log. */ + + errno = 0; + updwtmpx(_PATH_WTMPX, store); + return -errno; +} + +static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) { + int r, s; + + r = write_entry_utmp(store_utmp); + s = write_entry_wtmp(store_wtmp); + + if (r >= 0) + r = s; + + /* If utmp/wtmp have been disabled, that's a good thing, hence + * ignore the errors */ + if (r == -ENOENT) + r = 0; + + return r; +} + +static int write_entry_both(const struct utmpx *store) { + return write_utmp_wtmp(store, store); +} + +int utmp_put_shutdown(void) { + struct utmpx store; + + init_entry(&store, 0); + + store.ut_type = RUN_LVL; + strncpy(store.ut_user, "shutdown", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +int utmp_put_reboot(usec_t t) { + struct utmpx store; + + init_entry(&store, t); + + store.ut_type = BOOT_TIME; + strncpy(store.ut_user, "reboot", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +static const char *sanitize_id(const char *id) { + size_t l; + + assert(id); + l = strlen(id); + + if (l <= sizeof(((struct utmpx*) NULL)->ut_id)) + return id; + + return id + l - sizeof(((struct utmpx*) NULL)->ut_id); +} + +int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line) { + struct utmpx store; + + assert(id); + + init_timestamp(&store, 0); + + store.ut_type = INIT_PROCESS; + store.ut_pid = pid; + store.ut_session = sid; + + strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id)); + + if (line) + strncpy(store.ut_line, file_name_from_path(line), sizeof(store.ut_line)); + + return write_entry_both(&store); +} + +int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) { + struct utmpx lookup, store, store_wtmp, *found; + + assert(id); + + setutxent(); + + zero(lookup); + lookup.ut_type = INIT_PROCESS; /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */ + strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id)); + + if (!(found = getutxid(&lookup))) + return 0; + + if (found->ut_pid != pid) + return 0; + + memcpy(&store, found, sizeof(store)); + store.ut_type = DEAD_PROCESS; + store.ut_exit.e_termination = code; + store.ut_exit.e_exit = status; + + zero(store.ut_user); + zero(store.ut_host); + zero(store.ut_tv); + + memcpy(&store_wtmp, &store, sizeof(store_wtmp)); + /* wtmp wants the current time */ + init_timestamp(&store_wtmp, 0); + + return write_utmp_wtmp(&store, &store_wtmp); +} + + +int utmp_put_runlevel(int runlevel, int previous) { + struct utmpx store; + int r; + + assert(runlevel > 0); + + if (previous <= 0) { + /* Find the old runlevel automatically */ + + if ((r = utmp_get_runlevel(&previous, NULL)) < 0) { + if (r != -ESRCH) + return r; + + previous = 0; + } + } + + if (previous == runlevel) + return 0; + + init_entry(&store, 0); + + store.ut_type = RUN_LVL; + store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8); + strncpy(store.ut_user, "runlevel", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +#define TIMEOUT_MSEC 50 + +static int write_to_terminal(const char *tty, const char *message) { + int fd, r; + const char *p; + size_t left; + usec_t end; + + assert(tty); + assert(message); + + if ((fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC)) < 0) + return -errno; + + if (!isatty(fd)) { + r = -errno; + goto finish; + } + + p = message; + left = strlen(message); + + end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC; + + while (left > 0) { + ssize_t n; + struct pollfd pollfd; + usec_t t; + int k; + + t = now(CLOCK_MONOTONIC); + + if (t >= end) { + r = -ETIME; + goto finish; + } + + zero(pollfd); + pollfd.fd = fd; + pollfd.events = POLLOUT; + + if ((k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC)) < 0) + return -errno; + + if (k <= 0) { + r = -ETIME; + goto finish; + } + + if ((n = write(fd, p, left)) < 0) { + + if (errno == EAGAIN) + continue; + + r = -errno; + goto finish; + } + + assert((size_t) n <= left); + + p += n; + left -= n; + } + + r = 0; + +finish: + close_nointr_nofail(fd); + + return r; +} + +int utmp_wall(const char *message, bool (*match_tty)(const char *tty)) { + struct utmpx *u; + char date[FORMAT_TIMESTAMP_MAX]; + char *text = NULL, *hn = NULL, *un = NULL, *tty = NULL; + int r; + + if (!(hn = gethostname_malloc()) || + !(un = getlogname_malloc())) { + r = -ENOMEM; + goto finish; + } + + getttyname_harder(STDIN_FILENO, &tty); + + if (asprintf(&text, + "\a\r\n" + "Broadcast message from %s@%s%s%s (%s):\r\n\r\n" + "%s\r\n\r\n", + un, hn, + tty ? " on " : "", strempty(tty), + format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)), + message) < 0) { + r = -ENOMEM; + goto finish; + } + + setutxent(); + + r = 0; + + while ((u = getutxent())) { + int q; + const char *path; + char *buf = NULL; + + if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0) + continue; + + if (path_startswith(u->ut_line, "/dev/")) + path = u->ut_line; + else { + if (asprintf(&buf, "/dev/%s", u->ut_line) < 0) { + r = -ENOMEM; + goto finish; + } + + path = buf; + } + + if (!match_tty || match_tty(path)) + if ((q = write_to_terminal(path, text)) < 0) + r = q; + + free(buf); + } + +finish: + free(hn); + free(un); + free(tty); + free(text); + + return r; +} diff --git a/src/utmp-wtmp.h b/src/utmp-wtmp.h new file mode 100644 index 000000000..a5998ebb2 --- /dev/null +++ b/src/utmp-wtmp.h @@ -0,0 +1,38 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooutmpwtmphfoo +#define fooutmpwtmphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include "util.h" + +int utmp_get_runlevel(int *runlevel, int *previous); + +int utmp_put_shutdown(void); +int utmp_put_reboot(usec_t timestamp); +int utmp_put_runlevel(int runlevel, int previous); + +int utmp_put_dead_process(const char *id, pid_t pid, int code, int status); +int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line); + +int utmp_wall(const char *message, bool (*match_tty)(const char *tty)); + +#endif diff --git a/src/vconsole/Makefile b/src/vconsole/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/vconsole/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c new file mode 100644 index 000000000..91967891f --- /dev/null +++ b/src/vconsole/vconsole-setup.c @@ -0,0 +1,459 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "log.h" +#include "macro.h" +#include "virt.h" + +static bool is_vconsole(int fd) { + unsigned char data[1]; + + data[0] = TIOCL_GETFGCONSOLE; + return ioctl(fd, TIOCLINUX, data) >= 0; +} + +static bool is_locale_utf8(void) { + const char *set; + + if (!setlocale(LC_ALL, "")) + return true; + + set = nl_langinfo(CODESET); + if (!set) + return true; + + return streq(set, "UTF-8"); +} + +static int disable_utf8(int fd) { + int r = 0, k; + + if (ioctl(fd, KDSKBMODE, K_XLATE) < 0) + r = -errno; + + if (loop_write(fd, "\033%@", 3, false) < 0) + r = -errno; + + if ((k = write_one_line_file("/sys/module/vt/parameters/default_utf8", "0")) < 0) + r = k; + + if (r < 0) + log_warning("Failed to disable UTF-8: %s", strerror(errno)); + + return r; +} + +static int load_keymap(const char *vc, const char *map, const char *map_toggle, bool utf8, pid_t *_pid) { + const char *args[8]; + int i = 0; + pid_t pid; + + if (isempty(map)) { + /* An empty map means kernel map */ + *_pid = 0; + return 0; + } + + args[i++] = KBD_LOADKEYS; + args[i++] = "-q"; + args[i++] = "-C"; + args[i++] = vc; + if (utf8) + args[i++] = "-u"; + args[i++] = map; + if (map_toggle) + args[i++] = map_toggle; + args[i++] = NULL; + + if ((pid = fork()) < 0) { + log_error("Failed to fork: %m"); + return -errno; + } else if (pid == 0) { + execv(args[0], (char **) args); + _exit(EXIT_FAILURE); + } + + *_pid = pid; + return 0; +} + +static int load_font(const char *vc, const char *font, const char *map, const char *unimap, pid_t *_pid) { + const char *args[9]; + int i = 0; + pid_t pid; + + if (isempty(font)) { + /* An empty font means kernel font */ + *_pid = 0; + return 0; + } + + args[i++] = KBD_SETFONT; + args[i++] = "-C"; + args[i++] = vc; + args[i++] = font; + if (map) { + args[i++] = "-m"; + args[i++] = map; + } + if (unimap) { + args[i++] = "-u"; + args[i++] = unimap; + } + args[i++] = NULL; + + if ((pid = fork()) < 0) { + log_error("Failed to fork: %m"); + return -errno; + } else if (pid == 0) { + execv(args[0], (char **) args); + _exit(EXIT_FAILURE); + } + + *_pid = pid; + return 0; +} + +int main(int argc, char **argv) { + const char *vc; + char *vc_keymap = NULL; + char *vc_keymap_toggle = NULL; + char *vc_font = NULL; + char *vc_font_map = NULL; + char *vc_font_unimap = NULL; +#ifdef TARGET_GENTOO + char *vc_unicode = NULL; +#endif +#if defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA) + char *vc_keytable = NULL; +#endif + int fd = -1; + bool utf8; + int r = EXIT_FAILURE; + pid_t font_pid = 0, keymap_pid = 0; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argv[1]) + vc = argv[1]; + else + vc = "/dev/tty0"; + + if ((fd = open_terminal(vc, O_RDWR|O_CLOEXEC)) < 0) { + log_error("Failed to open %s: %m", vc); + goto finish; + } + + if (!is_vconsole(fd)) { + log_error("Device %s is not a virtual console.", vc); + goto finish; + } + + utf8 = is_locale_utf8(); + + vc_keymap = strdup("us"); + vc_font = strdup(DEFAULT_FONT); + + if (!vc_keymap || !vc_font) { + log_error("Failed to allocate strings."); + goto finish; + } + + r = 0; + + if (detect_container(NULL) <= 0) + if ((r = parse_env_file("/proc/cmdline", WHITESPACE, + "vconsole.keymap", &vc_keymap, + "vconsole.keymap.toggle", &vc_keymap_toggle, + "vconsole.font", &vc_font, + "vconsole.font.map", &vc_font_map, + "vconsole.font.unimap", &vc_font_unimap, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /proc/cmdline: %s", strerror(-r)); + } + + /* Hmm, nothing set on the kernel cmd line? Then let's + * try /etc/vconsole.conf */ + if (r <= 0 && + (r = parse_env_file("/etc/vconsole.conf", NEWLINE, + "KEYMAP", &vc_keymap, + "KEYMAP_TOGGLE", &vc_keymap_toggle, + "FONT", &vc_font, + "FONT_MAP", &vc_font_map, + "FONT_UNIMAP", &vc_font_unimap, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/vconsole.conf: %s", strerror(-r)); + } + + if (r <= 0) { +#if defined(TARGET_FEDORA) || defined(TARGET_MEEGO) + if ((r = parse_env_file("/etc/sysconfig/i18n", NEWLINE, + "SYSFONT", &vc_font, + "SYSFONTACM", &vc_font_map, + "UNIMAP", &vc_font_unimap, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r)); + } + + if ((r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE, + "KEYTABLE", &vc_keymap, + "KEYMAP", &vc_keymap, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r)); + } + + if (access("/etc/sysconfig/console/default.kmap", F_OK) >= 0) { + char *t; + + if (!(t = strdup("/etc/sysconfig/console/default.kmap"))) { + log_error("Out of memory."); + goto finish; + } + + free(vc_keymap); + vc_keymap = t; + } + +#elif defined(TARGET_SUSE) + if ((r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE, + "KEYTABLE", &vc_keymap, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r)); + } + + if ((r = parse_env_file("/etc/sysconfig/console", NEWLINE, + "CONSOLE_FONT", &vc_font, + "CONSOLE_SCREENMAP", &vc_font_map, + "CONSOLE_UNICODEMAP", &vc_font_unimap, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/console: %s", strerror(-r)); + } + +#elif defined(TARGET_ARCH) + if ((r = parse_env_file("/etc/rc.conf", NEWLINE, + "KEYMAP", &vc_keymap, + "CONSOLEFONT", &vc_font, + "CONSOLEMAP", &vc_font_map, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/rc.conf: %s", strerror(-r)); + } + +#elif defined(TARGET_FRUGALWARE) + if ((r = parse_env_file("/etc/sysconfig/keymap", NEWLINE, + "keymap", &vc_keymap, + NULL)) < 0) { + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/keymap: %s", strerror(-r)); + } + if ((r = parse_env_file("/etc/sysconfig/font", NEWLINE, + "font", &vc_font, + NULL)) < 0) { + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/font: %s", strerror(-r)); + } + +#elif defined(TARGET_ALTLINUX) + if ((r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE, + "KEYTABLE", &vc_keymap, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r)); + } + + if ((r = parse_env_file("/etc/sysconfig/consolefont", NEWLINE, + "SYSFONT", &vc_font, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/console: %s", strerror(-r)); + } + +#elif defined(TARGET_GENTOO) + if ((r = parse_env_file("/etc/rc.conf", NEWLINE, + "unicode", &vc_unicode, + NULL)) < 0) { + if (r != -ENOENT) + log_warning("Failed to read /etc/rc.conf: %s", strerror(-r)); + } + + if (vc_unicode) { + int rc_unicode; + + if ((rc_unicode = parse_boolean(vc_unicode)) < 0) + log_error("Unknown value for /etc/rc.conf unicode=%s", vc_unicode); + else { + if (rc_unicode && !utf8) + log_warning("/etc/rc.conf wants unicode, but current locale is not UTF-8 capable!"); + else if (!rc_unicode && utf8) { + log_debug("/etc/rc.conf does not want unicode, leave it on in kernel but does not apply to vconsole."); + utf8 = false; + } + } + } + + /* /etc/conf.d/consolefont comments and gentoo + * documentation mention uppercase, but the actual + * contents are lowercase. the existing + * /etc/init.d/consolefont tries both + */ + if ((r = parse_env_file("/etc/conf.d/consolefont", NEWLINE, + "CONSOLEFONT", &vc_font, + "consolefont", &vc_font, + "consoletranslation", &vc_font_map, + "CONSOLETRANSLATION", &vc_font_map, + "unicodemap", &vc_font_unimap, + "UNICODEMAP", &vc_font_unimap, + NULL)) < 0) { + if (r != -ENOENT) + log_warning("Failed to read /etc/conf.d/consolefont: %s", strerror(-r)); + } + + if ((r = parse_env_file("/etc/conf.d/keymaps", NEWLINE, + "keymap", &vc_keymap, + "KEYMAP", &vc_keymap, + NULL)) < 0) { + if (r != -ENOENT) + log_warning("Failed to read /etc/conf.d/keymaps: %s", strerror(-r)); + } + +#elif defined(TARGET_MANDRIVA) || defined (TARGET_MAGEIA) + + if ((r = parse_env_file("/etc/sysconfig/i18n", NEWLINE, + "SYSFONT", &vc_font, + "SYSFONTACM", &vc_font_map, + "UNIMAP", &vc_font_unimap, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r)); + } + + if ((r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE, + "KEYTABLE", &vc_keytable, + "KEYMAP", &vc_keymap, + "UNIKEYTABLE", &vc_keymap, + "GRP_TOGGLE", &vc_keymap_toggle, + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r)); + } + + if (vc_keytable) { + if (vc_keymap) + free(vc_keymap); + if (utf8) { + if (endswith(vc_keytable, ".uni") || strstr(vc_keytable, ".uni.")) + vc_keymap = strdup(vc_keytable); + else { + char *s; + if ((s = strstr(vc_keytable, ".map"))) + vc_keytable[s-vc_keytable+1] = '\0'; + vc_keymap = strappend(vc_keytable, ".uni"); + } + } else + vc_keymap = strdup(vc_keytable); + + free(vc_keytable); + + if (!vc_keymap) { + log_error("Out of memory."); + goto finish; + } + } + + if (access("/etc/sysconfig/console/default.kmap", F_OK) >= 0) { + char *t; + + if (!(t = strdup("/etc/sysconfig/console/default.kmap"))) { + log_error("Out of memory."); + goto finish; + } + + free(vc_keymap); + vc_keymap = t; + } +#endif + } + + r = EXIT_FAILURE; + + if (!utf8) + disable_utf8(fd); + + if (load_keymap(vc, vc_keymap, vc_keymap_toggle, utf8, &keymap_pid) >= 0 && + load_font(vc, vc_font, vc_font_map, vc_font_unimap, &font_pid) >= 0) + r = EXIT_SUCCESS; + +finish: + if (keymap_pid > 0) + wait_for_terminate_and_warn(KBD_LOADKEYS, keymap_pid); + + if (font_pid > 0) + wait_for_terminate_and_warn(KBD_SETFONT, font_pid); + + free(vc_keymap); + free(vc_font); + free(vc_font_map); + free(vc_font_unimap); + + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} diff --git a/src/virt.c b/src/virt.c new file mode 100644 index 000000000..4c526ff45 --- /dev/null +++ b/src/virt.c @@ -0,0 +1,292 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "util.h" +#include "virt.h" + +/* Returns a short identifier for the various VM implementations */ +int detect_vm(const char **id) { + +#if defined(__i386__) || defined(__x86_64__) + + /* Both CPUID and DMI are x86 specific interfaces... */ + + static const char *const dmi_vendors[] = { + "/sys/class/dmi/id/sys_vendor", + "/sys/class/dmi/id/board_vendor", + "/sys/class/dmi/id/bios_vendor" + }; + + static const char dmi_vendor_table[] = + "QEMU\0" "qemu\0" + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + "VMware\0" "vmware\0" + "VMW\0" "vmware\0" + "Microsoft Corporation\0" "microsoft\0" + "innotek GmbH\0" "oracle\0" + "Xen\0" "xen\0" + "Bochs\0" "bochs\0"; + + static const char cpuid_vendor_table[] = + "XenVMMXenVMM\0" "xen\0" + "KVMKVMKVM\0" "kvm\0" + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + "VMwareVMware\0" "vmware\0" + /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */ + "Microsoft Hv\0" "microsoft\0"; + + uint32_t eax, ecx; + union { + uint32_t sig32[3]; + char text[13]; + } sig; + unsigned i; + const char *j, *k; + bool hypervisor; + + /* http://lwn.net/Articles/301888/ */ + zero(sig); + +#if defined (__i386__) +#define REG_a "eax" +#define REG_b "ebx" +#elif defined (__amd64__) +#define REG_a "rax" +#define REG_b "rbx" +#endif + + /* First detect whether there is a hypervisor */ + eax = 1; + __asm__ __volatile__ ( + /* ebx/rbx is being used for PIC! */ + " push %%"REG_b" \n\t" + " cpuid \n\t" + " pop %%"REG_b" \n\t" + + : "=a" (eax), "=c" (ecx) + : "0" (eax) + ); + + hypervisor = !!(ecx & 0x80000000U); + + if (hypervisor) { + + /* There is a hypervisor, see what it is */ + eax = 0x40000000U; + __asm__ __volatile__ ( + /* ebx/rbx is being used for PIC! */ + " push %%"REG_b" \n\t" + " cpuid \n\t" + " mov %%ebx, %1 \n\t" + " pop %%"REG_b" \n\t" + + : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2]) + : "0" (eax) + ); + + NULSTR_FOREACH_PAIR(j, k, cpuid_vendor_table) + if (streq(sig.text, j)) { + + if (id) + *id = k; + + return 1; + } + } + + for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) { + char *s; + int r; + const char *found = NULL; + + if ((r = read_one_line_file(dmi_vendors[i], &s)) < 0) { + if (r != -ENOENT) + return r; + + continue; + } + + NULSTR_FOREACH_PAIR(j, k, dmi_vendor_table) + if (startswith(s, j)) + found = k; + free(s); + + if (found) { + if (id) + *id = found; + + return 1; + } + } + + if (hypervisor) { + if (id) + *id = "other"; + + return 1; + } + +#endif + return 0; +} + +int detect_container(const char **id) { + FILE *f; + + /* Unfortunately many of these operations require root access + * in one way or another */ + + if (geteuid() != 0) + return -EPERM; + + if (running_in_chroot() > 0) { + + if (id) + *id = "chroot"; + + return 1; + } + + /* /proc/vz exists in container and outside of the container, + * /proc/bc only outside of the container. */ + if (access("/proc/vz", F_OK) >= 0 && + access("/proc/bc", F_OK) < 0) { + + if (id) + *id = "openvz"; + + return 1; + } + + f = fopen("/proc/1/environ", "re"); + if (f) { + bool done = false; + + do { + char line[LINE_MAX]; + unsigned i; + + for (i = 0; i < sizeof(line)-1; i++) { + int c; + + c = getc(f); + if (_unlikely_(c == EOF)) { + done = true; + break; + } else if (c == 0) + break; + + line[i] = c; + } + line[i] = 0; + + if (streq(line, "container=lxc")) { + fclose(f); + + if (id) + *id = "lxc"; + return 1; + + } else if (streq(line, "container=lxc-libvirt")) { + fclose(f); + + if (id) + *id = "lxc-libvirt"; + return 1; + + } else if (streq(line, "container=systemd-nspawn")) { + fclose(f); + + if (id) + *id = "systemd-nspawn"; + return 1; + + } else if (startswith(line, "container=")) { + fclose(f); + + if (id) + *id = "other"; + return 1; + } + + } while (!done); + + fclose(f); + } + + return 0; +} + +/* Returns a short identifier for the various VM/container implementations */ +Virtualization detect_virtualization(const char **id) { + + static __thread Virtualization cached_virt = _VIRTUALIZATION_INVALID; + static __thread const char *cached_id = NULL; + + const char *_id; + int r; + Virtualization v; + + if (_likely_(cached_virt >= 0)) { + + if (id && cached_virt > 0) + *id = cached_id; + + return cached_virt; + } + + r = detect_container(&_id); + if (r < 0) { + v = r; + goto finish; + } else if (r > 0) { + v = VIRTUALIZATION_CONTAINER; + goto finish; + } + + r = detect_vm(&_id); + if (r < 0) { + v = r; + goto finish; + } else if (r > 0) { + v = VIRTUALIZATION_VM; + goto finish; + } + + v = VIRTUALIZATION_NONE; + +finish: + if (v > 0) { + cached_id = _id; + + if (id) + *id = _id; + } + + if (v >= 0) + cached_virt = v; + + return v; +} diff --git a/src/virt.h b/src/virt.h new file mode 100644 index 000000000..f55c9a68f --- /dev/null +++ b/src/virt.h @@ -0,0 +1,38 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foovirthfoo +#define foovirthfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +int detect_vm(const char **id); +int detect_container(const char **id); + +typedef enum Virtualization { + VIRTUALIZATION_NONE = 0, + VIRTUALIZATION_VM, + VIRTUALIZATION_CONTAINER, + _VIRTUALIZATION_MAX, + _VIRTUALIZATION_INVALID = -1 +} Virtualization; + +Virtualization detect_virtualization(const char **id); + +#endif diff --git a/sysctl.d/.gitignore b/sysctl.d/.gitignore new file mode 100644 index 000000000..7563539ab --- /dev/null +++ b/sysctl.d/.gitignore @@ -0,0 +1 @@ +/coredump.conf diff --git a/sysctl.d/Makefile b/sysctl.d/Makefile new file mode 120000 index 000000000..bd1047548 --- /dev/null +++ b/sysctl.d/Makefile @@ -0,0 +1 @@ +../src/Makefile \ No newline at end of file diff --git a/sysctl.d/coredump.conf.in b/sysctl.d/coredump.conf.in new file mode 100644 index 000000000..ab19b1e98 --- /dev/null +++ b/sysctl.d/coredump.conf.in @@ -0,0 +1,10 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See sysctl.d(5) for details + +kernel.core_pattern=|@rootlibexecdir@/systemd-coredump %p %u %g %s %t %e diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 000000000..9aa46b4eb --- /dev/null +++ b/test/Makefile @@ -0,0 +1,7 @@ +# Just a little hook script to easy building when in this directory + +all: + $(MAKE) -C .. + +clean: + $(MAKE) -C .. clean diff --git a/test/a.service b/test/a.service new file mode 100644 index 000000000..4168d2d05 --- /dev/null +++ b/test/a.service @@ -0,0 +1,7 @@ +[Unit] +Description=A +Requires=b.service +Before=b.service + +[Service] +ExecStart=/bin/true diff --git a/test/b.service b/test/b.service new file mode 100644 index 000000000..e03bae36b --- /dev/null +++ b/test/b.service @@ -0,0 +1,6 @@ +[Unit] +Description=B +Wants=f.service + +[Service] +ExecStart=/bin/true diff --git a/test/c.service b/test/c.service new file mode 100644 index 000000000..e2f60a8fb --- /dev/null +++ b/test/c.service @@ -0,0 +1,6 @@ +[Unit] +Description=C +Requires=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/d.service b/test/d.service new file mode 100644 index 000000000..921fd2ee1 --- /dev/null +++ b/test/d.service @@ -0,0 +1,8 @@ +[Unit] +Description=D:Cyclic +After=b.service +Before=a.service +Requires=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/e.service b/test/e.service new file mode 100644 index 000000000..5ba98c7c4 --- /dev/null +++ b/test/e.service @@ -0,0 +1,8 @@ +[Unit] +Description=E:Cyclic +After=b.service +Before=a.service +Wants=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/f.service b/test/f.service new file mode 100644 index 000000000..7dde681c1 --- /dev/null +++ b/test/f.service @@ -0,0 +1,5 @@ +[Unit] +Description=F + +[Service] +ExecStart=/bin/true diff --git a/test/g.service b/test/g.service new file mode 100644 index 000000000..cbfa82a45 --- /dev/null +++ b/test/g.service @@ -0,0 +1,6 @@ +[Unit] +Description=G +Conflicts=e.service + +[Service] +ExecStart=/bin/true diff --git a/test/h.service b/test/h.service new file mode 100644 index 000000000..74a7751ca --- /dev/null +++ b/test/h.service @@ -0,0 +1,6 @@ +[Unit] +Description=H +Wants=g.service + +[Service] +ExecStart=/bin/true diff --git a/tmpfiles.d/Makefile b/tmpfiles.d/Makefile new file mode 120000 index 000000000..bd1047548 --- /dev/null +++ b/tmpfiles.d/Makefile @@ -0,0 +1 @@ +../src/Makefile \ No newline at end of file diff --git a/tmpfiles.d/legacy.conf b/tmpfiles.d/legacy.conf new file mode 100644 index 000000000..9198e89dd --- /dev/null +++ b/tmpfiles.d/legacy.conf @@ -0,0 +1,22 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See tmpfiles.d(5) for details + +# These files are considered legacy and are unnecessary on legacy-free +# systems. /run/lock/subsys is used for serializing SysV service +# execution, and hence without use on SysV-less systems. +# +# /run/lock/lockdev is used to serialize access to tty devices via +# LCK..xxx style lock files, For more information see: +# http://lists.freedesktop.org/archives/systemd-devel/2011-March/001823.html +# On modern systems a BSD file lock is a better choice if +# serialization is needed on those devices. + +d /run/lock 0755 root root - +d /run/lock/subsys 0755 root root - +d /run/lock/lockdev 0775 root lock - diff --git a/tmpfiles.d/systemd.conf b/tmpfiles.d/systemd.conf new file mode 100644 index 000000000..be29c068a --- /dev/null +++ b/tmpfiles.d/systemd.conf @@ -0,0 +1,25 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See tmpfiles.d(5) for details + +d /run/user 0755 root root 10d +F /run/utmp 0664 root utmp - + +f /var/log/wtmp 0664 root utmp - +f /var/log/btmp 0600 root utmp - + +d /var/cache/man - - - 30d + +r /forcefsck +r /forcequotacheck +r /fastboot + +d /run/systemd/ask-password 0755 root root - +d /run/systemd/seats 0755 root root - +d /run/systemd/sessions 0755 root root - +d /run/systemd/users 0755 root root - diff --git a/tmpfiles.d/tmp.conf b/tmpfiles.d/tmp.conf new file mode 100644 index 000000000..8915b82ab --- /dev/null +++ b/tmpfiles.d/tmp.conf @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See tmpfiles.d(5) for details + +# Clear tmp directories separately, to make them easier to override +d /tmp 1777 root root 10d +d /var/tmp 1777 root root 30d diff --git a/tmpfiles.d/x11.conf b/tmpfiles.d/x11.conf new file mode 100644 index 000000000..7f81af62d --- /dev/null +++ b/tmpfiles.d/x11.conf @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See tmpfiles.d(5) for details + +# Make sure these are created by default so that nobody else can +d /tmp/.X11-unix 1777 root root 10d +d /tmp/.ICE-unix 1777 root root 10d +d /tmp/.XIM-unix 1777 root root 10d +d /tmp/.font-unix 1777 root root 10d +d /tmp/.Test-unix 1777 root root 10d + +# Unlink the X11 lock files +r /tmp/.X[0-9]*-lock diff --git a/units/.gitignore b/units/.gitignore new file mode 100644 index 000000000..94412d52e --- /dev/null +++ b/units/.gitignore @@ -0,0 +1,42 @@ +/systemd-journald.service +user@.service +systemd-logind.service +systemd-localed.service +systemd-timedated.service +systemd-hostnamed.service +console-shell.service +systemd-sysctl.service +systemd-ask-password-console.service +rescue.service +systemd-ask-password-plymouth.service +systemd-ask-password-wall.service +quotacheck.service +fsck@.service +fsck-root.service +systemd-tmpfiles-clean.service +systemd-tmpfiles-setup.service +halt.service +poweroff.service +reboot.service +kexec.service +systemd-user-sessions.service +systemd-readahead-done.service +systemd-tmpfiles.service +systemd-readahead-collect.service +systemd-readahead-replay.service +serial-getty@.service +systemd-kmsg-syslogd.service +systemd-modules-load.service +systemd-remount-api-vfs.service +systemd-vconsole-setup.service +systemd-auto-serial-getty.service +systemd-shutdownd.service +systemd-random-seed-load.service +systemd-random-seed-save.service +systemd-initctl.service +systemd-stdout-syslog-bridge.service +getty@.service +systemd-update-utmp-runlevel.service +systemd-update-utmp-shutdown.service +test-env-replace +systemd-binfmt.service diff --git a/units/Makefile b/units/Makefile new file mode 120000 index 000000000..bd1047548 --- /dev/null +++ b/units/Makefile @@ -0,0 +1 @@ +../src/Makefile \ No newline at end of file diff --git a/units/basic.target b/units/basic.target new file mode 100644 index 000000000..0258ca0c0 --- /dev/null +++ b/units/basic.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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Basic System +Requires=sysinit.target sockets.target +After=sysinit.target sockets.target +RefuseManualStart=yes diff --git a/units/bluetooth.target b/units/bluetooth.target new file mode 100644 index 000000000..c66718e11 --- /dev/null +++ b/units/bluetooth.target @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Bluetooth +StopWhenUnneeded=yes diff --git a/units/console-shell.service.m4 b/units/console-shell.service.m4 new file mode 100644 index 000000000..fef9e1b17 --- /dev/null +++ b/units/console-shell.service.m4 @@ -0,0 +1,47 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Console Shell +After=systemd-user-sessions.service plymouth-quit-wait.service +m4_ifdef(`TARGET_FEDORA', +After=rc-local.service +)m4_dnl +m4_ifdef(`TARGET_ARCH', +After=rc-local.service +)m4_dnl +m4_ifdef(`TARGET_FRUGALWARE', +After=local.service +)m4_dnl +m4_ifdef(`TARGET_ALTLINUX', +After=rc-local.service +)m4_dnl +m4_ifdef(`TARGET_MANDRIVA', +After=rc-local.service +)m4_dnl +m4_ifdef(`TARGET_MAGEIA', +After=rc-local.service +)m4_dnl +Before=getty.target + +[Service] +Environment=HOME=/root +WorkingDirectory=/root +ExecStart=-/sbin/sulogin +ExecStopPost=-/bin/systemctl poweroff +StandardInput=tty-force +StandardOutput=inherit +StandardError=inherit +KillMode=process +IgnoreSIGPIPE=no + +# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash +# terminates cleanly. +KillSignal=SIGHUP + +[Install] +WantedBy=getty.target diff --git a/units/cryptsetup.target b/units/cryptsetup.target new file mode 100644 index 000000000..64ee8c614 --- /dev/null +++ b/units/cryptsetup.target @@ -0,0 +1,11 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Encrypted Volumes diff --git a/units/dev-hugepages.mount b/units/dev-hugepages.mount new file mode 100644 index 000000000..72a522e69 --- /dev/null +++ b/units/dev-hugepages.mount @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Huge Pages File System +DefaultDependencies=no +Before=sysinit.target +ConditionPathExists=/sys/kernel/mm/hugepages + +[Mount] +What=hugetlbfs +Where=/dev/hugepages +Type=hugetlbfs diff --git a/units/dev-mqueue.mount b/units/dev-mqueue.mount new file mode 100644 index 000000000..cffdaf773 --- /dev/null +++ b/units/dev-mqueue.mount @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=POSIX Message Queue File System +DefaultDependencies=no +Before=sysinit.target +ConditionPathExists=/proc/sys/fs/mqueue + +[Mount] +What=mqueue +Where=/dev/mqueue +Type=mqueue diff --git a/units/emergency.service b/units/emergency.service new file mode 100644 index 000000000..43a74d7a3 --- /dev/null +++ b/units/emergency.service @@ -0,0 +1,31 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Emergency Shell +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target + +[Service] +Environment=HOME=/root +WorkingDirectory=/root +ExecStartPre=-/bin/plymouth quit +ExecStartPre=-/bin/echo 'Welcome to emergency mode. Use "systemctl default" or ^D to enter default mode.' +ExecStart=-/sbin/sulogin +ExecStopPost=/bin/systemctl --fail --no-block default +StandardInput=tty-force +StandardOutput=inherit +StandardError=inherit +KillMode=process +IgnoreSIGPIPE=no + +# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash +# terminates cleanly. +KillSignal=SIGHUP diff --git a/units/emergency.target b/units/emergency.target new file mode 100644 index 000000000..6a99e05f0 --- /dev/null +++ b/units/emergency.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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Emergency Mode +Requires=emergency.service +After=emergency.service +AllowIsolate=yes diff --git a/units/fedora/Makefile b/units/fedora/Makefile new file mode 120000 index 000000000..50be21181 --- /dev/null +++ b/units/fedora/Makefile @@ -0,0 +1 @@ +../../src/Makefile \ No newline at end of file diff --git a/units/fedora/halt-local.service b/units/fedora/halt-local.service new file mode 100644 index 000000000..a9f6feb32 --- /dev/null +++ b/units/fedora/halt-local.service @@ -0,0 +1,20 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=/sbin/halt.local Compatibility +ConditionFileIsExecutable=/sbin/halt.local +DefaultDependencies=no +After=shutdown.target +Before=final.target + +[Service] +Type=oneshot +ExecStart=/sbin/halt.local +TimeoutSec=0 +StandardOutput=tty +RemainAfterExit=yes diff --git a/units/fedora/prefdm.service b/units/fedora/prefdm.service new file mode 100644 index 000000000..77a0e9ad7 --- /dev/null +++ b/units/fedora/prefdm.service @@ -0,0 +1,21 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Display Manager +After=livesys-late.service rc-local.service systemd-user-sessions.service + +# On Fedora gdm/X11 is on tty1. We explicitly cancel the getty here to +# avoid any races around that. +Conflicts=getty@tty1.service plymouth-quit.service +After=getty@tty1.service plymouth-quit.service + +[Service] +ExecStart=/etc/X11/prefdm -nodaemon +Restart=always +RestartSec=0 +IgnoreSIGPIPE=no diff --git a/units/fedora/rc-local.service b/units/fedora/rc-local.service new file mode 100644 index 000000000..0bef5c72a --- /dev/null +++ b/units/fedora/rc-local.service @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# This unit gets pulled automatically into multi-user.target by +# systemd-rc-local-generator if /etc/rc.d/rc.local is executable. +[Unit] +Description=/etc/rc.d/rc.local Compatibility +After=network.target + +[Service] +Type=forking +ExecStart=/etc/rc.d/rc.local start +TimeoutSec=0 +RemainAfterExit=yes +SysVStartPriority=99 diff --git a/units/final.target b/units/final.target new file mode 100644 index 000000000..9cfda197b --- /dev/null +++ b/units/final.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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Final Step +DefaultDependencies=no +RefuseManualStart=yes +After=shutdown.target umount.target diff --git a/units/frugalware/display-manager.service b/units/frugalware/display-manager.service new file mode 100644 index 000000000..2376e1977 --- /dev/null +++ b/units/frugalware/display-manager.service @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Display Manager +After=local.service systemd-user-sessions.service + +[Service] +EnvironmentFile=/etc/sysconfig/desktop +ExecStart=/bin/bash -c "exec ${desktop}" +Restart=always +RestartSec=0 diff --git a/units/fsck-root.service.in b/units/fsck-root.service.in new file mode 100644 index 000000000..408614912 --- /dev/null +++ b/units/fsck-root.service.in @@ -0,0 +1,23 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=File System Check on Root Device +DefaultDependencies=no +After=systemd-readahead-collect.service systemd-readahead-replay.service +Before=local-fs.target shutdown.target + +# Dracut informs us with this flag file if the root fsck was already run +ConditionPathExists=!/run/initramfs/root-fsck + +[Service] +Type=oneshot +RemainAfterExit=no +ExecStart=@rootlibexecdir@/systemd-fsck +StandardOutput=journal+console +FsckPassNo=1 +TimeoutSec=0 diff --git a/units/fsck@.service.in b/units/fsck@.service.in new file mode 100644 index 000000000..c06684b63 --- /dev/null +++ b/units/fsck@.service.in @@ -0,0 +1,20 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=File System Check on %f +DefaultDependencies=no +BindTo=%i.device +After=systemd-readahead-collect.service systemd-readahead-replay.service %i.device +Before=shutdown.target + +[Service] +Type=oneshot +RemainAfterExit=no +ExecStart=@rootlibexecdir@/systemd-fsck %f +StandardOutput=journal+console +TimeoutSec=0 diff --git a/units/getty.target b/units/getty.target new file mode 100644 index 000000000..e4435dc8e --- /dev/null +++ b/units/getty.target @@ -0,0 +1,9 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Login Prompts diff --git a/units/getty@.service.m4 b/units/getty@.service.m4 new file mode 100644 index 000000000..a02838d78 --- /dev/null +++ b/units/getty@.service.m4 @@ -0,0 +1,58 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Getty on %I +BindTo=dev-%i.device +After=dev-%i.device systemd-user-sessions.service plymouth-quit-wait.service +m4_ifdef(`TARGET_FEDORA', +After=rc-local.service +)m4_dnl +m4_ifdef(`TARGET_ARCH', +After=rc-local.service +)m4_dnl +m4_ifdef(`TARGET_FRUGALWARE', +After=local.service +)m4_dnl +m4_ifdef(`TARGET_ALTLINUX', +After=rc-local.service +)m4_dnl +m4_ifdef(`TARGET_MANDRIVA', +After=rc-local.service +)m4_dnl +m4_ifdef(`TARGET_MAGEIA', +After=rc-local.service +)m4_dnl + +# If additional gettys are spawned during boot then we should make +# sure that this is synchronized before getty.target, even though +# getty.target didn't actually pull it in. +Before=getty.target + +[Service] +Environment=TERM=linux +ExecStart=-/sbin/agetty %I 38400 +Restart=always +RestartSec=0 +UtmpIdentifier=%I +TTYPath=/dev/%I +TTYReset=yes +TTYVHangup=yes +TTYVTDisallocate=yes +KillMode=process +IgnoreSIGPIPE=no + +# Unset locale for the console getty since the console has problems +# displaying some internationalized messages. +Environment=LANG= LANGUAGE= LC_CTYPE= LC_NUMERIC= LC_TIME= LC_COLLATE= LC_MONETARY= LC_MESSAGES= LC_PAPER= LC_NAME= LC_ADDRESS= LC_TELEPHONE= LC_MEASUREMENT= LC_IDENTIFICATION= + +# Some login implementations ignore SIGTERM, so we send SIGHUP +# instead, to ensure that login terminates cleanly. +KillSignal=SIGHUP + +[Install] +Alias=getty.target.wants/getty@tty1.service diff --git a/units/graphical.target b/units/graphical.target new file mode 100644 index 000000000..f2e30341d --- /dev/null +++ b/units/graphical.target @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Graphical Interface +Requires=multi-user.target +After=multi-user.target +Conflicts=rescue.target +AllowIsolate=yes + +[Install] +Alias=default.target diff --git a/units/halt.service.in b/units/halt.service.in new file mode 100644 index 000000000..42e347043 --- /dev/null +++ b/units/halt.service.in @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Halt +DefaultDependencies=no +Requires=shutdown.target umount.target final.target +After=shutdown.target umount.target final.target + +[Service] +Type=oneshot +ExecStart=@SYSTEMCTL@ --force halt diff --git a/units/halt.target b/units/halt.target new file mode 100644 index 000000000..04b42cd3d --- /dev/null +++ b/units/halt.target @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Halt +DefaultDependencies=no +Requires=halt.service +After=halt.service +AllowIsolate=yes + +[Install] +Alias=ctrl-alt-del.target diff --git a/units/http-daemon.target b/units/http-daemon.target new file mode 100644 index 000000000..45f10182e --- /dev/null +++ b/units/http-daemon.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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +# This exists mostly for compatibility with SysV/LSB units, and +# implementations lacking socket/bus activation. + +[Unit] +Description=Web Server diff --git a/units/kexec.service.in b/units/kexec.service.in new file mode 100644 index 000000000..cf6bd6561 --- /dev/null +++ b/units/kexec.service.in @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Reboot via kexec +DefaultDependencies=no +Requires=shutdown.target umount.target final.target +After=shutdown.target umount.target final.target + +[Service] +Type=oneshot +ExecStart=@SYSTEMCTL@ --force kexec diff --git a/units/kexec.target b/units/kexec.target new file mode 100644 index 000000000..b77e6a43f --- /dev/null +++ b/units/kexec.target @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Reboot via kexec +DefaultDependencies=no +Requires=kexec.service +After=kexec.service +AllowIsolate=yes + +[Install] +Alias=ctrl-alt-del.target diff --git a/units/local-fs-pre.target b/units/local-fs-pre.target new file mode 100644 index 000000000..11e67bac1 --- /dev/null +++ b/units/local-fs-pre.target @@ -0,0 +1,11 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Local File Systems (Pre) diff --git a/units/local-fs.target b/units/local-fs.target new file mode 100644 index 000000000..1886f7499 --- /dev/null +++ b/units/local-fs.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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Local File Systems +OnFailure=emergency.target +OnFailureIsolate=yes diff --git a/units/mageia/prefdm.service b/units/mageia/prefdm.service new file mode 100644 index 000000000..4a896bf58 --- /dev/null +++ b/units/mageia/prefdm.service @@ -0,0 +1,21 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Display Manager +After=livesys-late.service rc-local.service systemd-user-sessions.service +After=network.target acpid.service fs.service haldaemon.service + +# Do not stop plymouth, it is done in prefdm if required +Conflicts=plymouth-quit.service +After=plymouth-quit.service + +[Service] +ExecStart=/etc/X11/prefdm +Type=forking +Restart=always +RestartSec=0 diff --git a/units/mail-transfer-agent.target b/units/mail-transfer-agent.target new file mode 100644 index 000000000..ebb1ea125 --- /dev/null +++ b/units/mail-transfer-agent.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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +# This exists mostly for compatibility with SysV/LSB units, and +# implementations lacking socket/bus activation. + +[Unit] +Description=Mail Transfer Agent diff --git a/units/mandriva/prefdm.service b/units/mandriva/prefdm.service new file mode 100644 index 000000000..4a896bf58 --- /dev/null +++ b/units/mandriva/prefdm.service @@ -0,0 +1,21 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Display Manager +After=livesys-late.service rc-local.service systemd-user-sessions.service +After=network.target acpid.service fs.service haldaemon.service + +# Do not stop plymouth, it is done in prefdm if required +Conflicts=plymouth-quit.service +After=plymouth-quit.service + +[Service] +ExecStart=/etc/X11/prefdm +Type=forking +Restart=always +RestartSec=0 diff --git a/units/multi-user.target b/units/multi-user.target new file mode 100644 index 000000000..66f1a950f --- /dev/null +++ b/units/multi-user.target @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Multi-User +Requires=basic.target +Conflicts=rescue.service rescue.target +After=basic.target rescue.service rescue.target +AllowIsolate=yes + +[Install] +Alias=default.target diff --git a/units/network.target b/units/network.target new file mode 100644 index 000000000..d97f64f67 --- /dev/null +++ b/units/network.target @@ -0,0 +1,11 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Network diff --git a/units/nss-lookup.target b/units/nss-lookup.target new file mode 100644 index 000000000..bdca03cd8 --- /dev/null +++ b/units/nss-lookup.target @@ -0,0 +1,15 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +# This exists mostly for compatibility with SysV/LSB units, and +# implementations lacking socket/bus activation. + +[Unit] +Description=Name Lookups +After=network.target diff --git a/units/plymouth-halt.service b/units/plymouth-halt.service new file mode 100644 index 000000000..2e194b360 --- /dev/null +++ b/units/plymouth-halt.service @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Show Plymouth Halt Screen +After=getty@tty1.service prefdm.service plymouth-start.service +Before=halt.service +DefaultDependencies=no +ConditionKernelCommandLine=!plymouth.enable=0 + +[Service] +ExecStart=/sbin/plymouthd --mode=shutdown +ExecStartPost=-/bin/plymouth --show-splash +Type=forking diff --git a/units/plymouth-kexec.service b/units/plymouth-kexec.service new file mode 100644 index 000000000..919c3f129 --- /dev/null +++ b/units/plymouth-kexec.service @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Show Plymouth Reboot with kexec Screen +After=getty@tty1.service prefdm.service plymouth-start.service +Before=kexec.service +DefaultDependencies=no +ConditionKernelCommandLine=!plymouth.enable=0 + +[Service] +ExecStart=/sbin/plymouthd --mode=shutdown +ExecStartPost=-/bin/plymouth --show-splash +Type=forking diff --git a/units/plymouth-poweroff.service b/units/plymouth-poweroff.service new file mode 100644 index 000000000..8fcff3bab --- /dev/null +++ b/units/plymouth-poweroff.service @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Show Plymouth Power Off Screen +After=getty@tty1.service prefdm.service plymouth-start.service +Before=poweroff.service +DefaultDependencies=no +ConditionKernelCommandLine=!plymouth.enable=0 + +[Service] +ExecStart=/sbin/plymouthd --mode=shutdown +ExecStartPost=-/bin/plymouth --show-splash +Type=forking diff --git a/units/plymouth-quit-wait.service b/units/plymouth-quit-wait.service new file mode 100644 index 000000000..45c67bdab --- /dev/null +++ b/units/plymouth-quit-wait.service @@ -0,0 +1,15 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Wait for Plymouth Boot Screen to Quit +After=rc-local.service plymouth-start.service + +[Service] +ExecStart=-/bin/plymouth --wait +Type=oneshot +TimeoutSec=20 diff --git a/units/plymouth-quit.service b/units/plymouth-quit.service new file mode 100644 index 000000000..164499a2a --- /dev/null +++ b/units/plymouth-quit.service @@ -0,0 +1,15 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Terminate Plymouth Boot Screen +After=rc-local.service plymouth-start.service + +[Service] +ExecStart=-/bin/plymouth quit +Type=oneshot +TimeoutSec=20 diff --git a/units/plymouth-read-write.service b/units/plymouth-read-write.service new file mode 100644 index 000000000..09fbf7d4c --- /dev/null +++ b/units/plymouth-read-write.service @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Tell Plymouth To Write Out Runtime Data +DefaultDependencies=no +After=local-fs.target +Before=sysinit.target + +[Service] +ExecStart=-/bin/plymouth update-root-fs --read-write +Type=oneshot diff --git a/units/plymouth-reboot.service b/units/plymouth-reboot.service new file mode 100644 index 000000000..fb65bcc83 --- /dev/null +++ b/units/plymouth-reboot.service @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Show Plymouth Reboot Screen +After=getty@tty1.service prefdm.service plymouth-start.service +Before=reboot.service +DefaultDependencies=no +ConditionKernelCommandLine=!plymouth.enable=0 + +[Service] +ExecStart=/sbin/plymouthd --mode=shutdown +ExecStartPost=-/bin/plymouth --show-splash +Type=forking diff --git a/units/plymouth-start.service b/units/plymouth-start.service new file mode 100644 index 000000000..f618257a9 --- /dev/null +++ b/units/plymouth-start.service @@ -0,0 +1,22 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Show Plymouth Boot Screen +DefaultDependencies=no +Wants=systemd-ask-password-plymouth.path +After=systemd-vconsole-setup.service udev-settle.service +Before=systemd-ask-password-plymouth.service + +# Dracut informs us with this flag file if plymouth is already running +ConditionPathExists=!/run/plymouth/pid +ConditionKernelCommandLine=!plymouth.enable=0 + +[Service] +ExecStart=/sbin/plymouthd --mode=boot --pid-file=/run/plymouth/pid +ExecStartPost=-/bin/plymouth --show-splash +Type=forking diff --git a/units/poweroff.service.in b/units/poweroff.service.in new file mode 100644 index 000000000..124a4c0f3 --- /dev/null +++ b/units/poweroff.service.in @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Power-Off +DefaultDependencies=no +Requires=shutdown.target umount.target final.target +After=shutdown.target umount.target final.target + +[Service] +Type=oneshot +ExecStart=@SYSTEMCTL@ --force poweroff diff --git a/units/poweroff.target b/units/poweroff.target new file mode 100644 index 000000000..d2ccf4b2c --- /dev/null +++ b/units/poweroff.target @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Power-Off +DefaultDependencies=no +Requires=poweroff.service +After=poweroff.service +AllowIsolate=yes + +[Install] +Alias=ctrl-alt-del.target diff --git a/units/printer.target b/units/printer.target new file mode 100644 index 000000000..14c90ff84 --- /dev/null +++ b/units/printer.target @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Printer +StopWhenUnneeded=yes diff --git a/units/proc-sys-fs-binfmt_misc.automount b/units/proc-sys-fs-binfmt_misc.automount new file mode 100644 index 000000000..acbbcbb0c --- /dev/null +++ b/units/proc-sys-fs-binfmt_misc.automount @@ -0,0 +1,15 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Arbitrary Executable File Formats File System Automount Point +DefaultDependencies=no +Before=sysinit.target +ConditionPathExists=/proc/sys/fs/binfmt_misc + +[Automount] +Where=/proc/sys/fs/binfmt_misc diff --git a/units/proc-sys-fs-binfmt_misc.mount b/units/proc-sys-fs-binfmt_misc.mount new file mode 100644 index 000000000..1829c2162 --- /dev/null +++ b/units/proc-sys-fs-binfmt_misc.mount @@ -0,0 +1,15 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Arbitrary Executable File Formats File System +DefaultDependencies=no + +[Mount] +What=binfmt_misc +Where=/proc/sys/fs/binfmt_misc +Type=binfmt_misc diff --git a/units/quotacheck.service.in b/units/quotacheck.service.in new file mode 100644 index 000000000..c97b7a468 --- /dev/null +++ b/units/quotacheck.service.in @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=File System Quota Check +DefaultDependencies=no +After=systemd-readahead-collect.service systemd-readahead-replay.service remount-rootfs.service +Before=local-fs.target shutdown.target +ConditionPathExists=/sbin/quotacheck + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@rootlibexecdir@/systemd-quotacheck +TimeoutSec=0 diff --git a/units/quotaon.service b/units/quotaon.service new file mode 100644 index 000000000..ef2fc8c97 --- /dev/null +++ b/units/quotaon.service @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Enable File System Quotas +DefaultDependencies=no +After=systemd-readahead-collect.service systemd-readahead-replay.service quotacheck.service +Before=local-fs.target shutdown.target +ConditionPathExists=/sbin/quotaon + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/sbin/quotaon -aug diff --git a/units/reboot.service.in b/units/reboot.service.in new file mode 100644 index 000000000..f320fd886 --- /dev/null +++ b/units/reboot.service.in @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Reboot +DefaultDependencies=no +Requires=shutdown.target umount.target final.target +After=shutdown.target umount.target final.target + +[Service] +Type=oneshot +ExecStart=@SYSTEMCTL@ --force reboot diff --git a/units/reboot.target b/units/reboot.target new file mode 100644 index 000000000..41e133cb7 --- /dev/null +++ b/units/reboot.target @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Reboot +DefaultDependencies=no +Requires=reboot.service +After=reboot.service +AllowIsolate=yes + +[Install] +Alias=ctrl-alt-del.target diff --git a/units/remote-fs-pre.target b/units/remote-fs-pre.target new file mode 100644 index 000000000..8aceb08b9 --- /dev/null +++ b/units/remote-fs-pre.target @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Remote File Systems (Pre) +After=network.target diff --git a/units/remote-fs.target b/units/remote-fs.target new file mode 100644 index 000000000..a48f87e5d --- /dev/null +++ b/units/remote-fs.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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Remote File Systems + +[Install] +WantedBy=multi-user.target diff --git a/units/remount-rootfs.service b/units/remount-rootfs.service new file mode 100644 index 000000000..7b63752c7 --- /dev/null +++ b/units/remount-rootfs.service @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Remount Root FS +DefaultDependencies=no +Conflicts=shutdown.target +After=systemd-readahead-collect.service systemd-readahead-replay.service fsck-root.service +Before=local-fs-pre.target local-fs.target shutdown.target +Wants=local-fs-pre.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/mount / -o remount diff --git a/units/rescue.service.m4 b/units/rescue.service.m4 new file mode 100644 index 000000000..310bbce1e --- /dev/null +++ b/units/rescue.service.m4 @@ -0,0 +1,43 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Rescue Shell +DefaultDependencies=no +Conflicts=shutdown.target +After=basic.target plymouth-start.service +Before=shutdown.target + +[Service] +Environment=HOME=/root +WorkingDirectory=/root +ExecStartPre=-/bin/plymouth quit +ExecStartPre=-/bin/echo 'Welcome to rescue mode. Use "systemctl default" or ^D to enter default mode.' +m4_ifdef(`TARGET_FEDORA', +`EnvironmentFile=/etc/sysconfig/init +ExecStart=-/bin/bash -c "exec ${SINGLE}"', +m4_ifdef(`TARGET_MANDRIVA', +`EnvironmentFile=/etc/sysconfig/init +ExecStart=-/bin/bash -c "exec ${SINGLE}"', +m4_ifdef(`TARGET_MAGEIA', +`EnvironmentFile=/etc/sysconfig/init +ExecStart=-/bin/bash -c "exec ${SINGLE}"', +m4_ifdef(`TARGET_MEEGO', +`EnvironmentFile=/etc/sysconfig/init +ExecStart=-/bin/bash -c "exec ${SINGLE}"', +`ExecStart=-/sbin/sulogin')))) +ExecStopPost=-/bin/systemctl --fail --no-block default +StandardInput=tty-force +StandardOutput=inherit +StandardError=inherit +KillMode=process + +# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash +# terminates cleanly. +KillSignal=SIGHUP diff --git a/units/rescue.target b/units/rescue.target new file mode 100644 index 000000000..5bf3f8e8e --- /dev/null +++ b/units/rescue.target @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Rescue Mode +Requires=basic.target rescue.service +After=basic.target rescue.service +AllowIsolate=yes + +[Install] +Alias=kbrequest.target diff --git a/units/rpcbind.target b/units/rpcbind.target new file mode 100644 index 000000000..a5cea8c57 --- /dev/null +++ b/units/rpcbind.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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +# This exists mostly for compatibility with SysV/LSB units, and +# implementations lacking socket/bus activation. + +[Unit] +Description=RPC Port Mapper diff --git a/units/serial-getty@.service.m4 b/units/serial-getty@.service.m4 new file mode 100644 index 000000000..fc8b57b93 --- /dev/null +++ b/units/serial-getty@.service.m4 @@ -0,0 +1,50 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Serial Getty on %I +BindTo=dev-%i.device +After=dev-%i.device systemd-user-sessions.service plymouth-quit-wait.service +m4_ifdef(`TARGET_FEDORA', +After=rc-local.service +)m4_dnl +m4_ifdef(`TARGET_ARCH', +After=rc-local.service +)m4_dnl +m4_ifdef(`TARGET_FRUGALWARE', +After=local.service +)m4_dnl +m4_ifdef(`TARGET_ALTLINUX', +After=rc-local.service +)m4_dnl +m4_ifdef(`TARGET_MANDRIVA', +After=rc-local.service +)m4_dnl +m4_ifdef(`TARGET_MAGEIA', +After=rc-local.service +)m4_dnl + +# If additional gettys are spawned during boot then we should make +# sure that this is synchronized before getty.target, even though +# getty.target didn't actually pull it in. +Before=getty.target + +[Service] +Environment=TERM=vt100 +ExecStart=-/sbin/agetty -s %I 115200,38400,9600 +Restart=always +RestartSec=0 +UtmpIdentifier=%I +TTYPath=/dev/%I +TTYReset=yes +TTYVHangup=yes +KillMode=process +IgnoreSIGPIPE=no + +# Some login implementations ignore SIGTERM, so we send SIGHUP +# instead, to ensure that login terminates cleanly. +KillSignal=SIGHUP diff --git a/units/shutdown.target b/units/shutdown.target new file mode 100644 index 000000000..99a659e92 --- /dev/null +++ b/units/shutdown.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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Shutdown +DefaultDependencies=no +RefuseManualStart=yes diff --git a/units/sigpwr.target b/units/sigpwr.target new file mode 100644 index 000000000..0ca502dbb --- /dev/null +++ b/units/sigpwr.target @@ -0,0 +1,11 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Power Failure diff --git a/units/smartcard.target b/units/smartcard.target new file mode 100644 index 000000000..28dd2bba1 --- /dev/null +++ b/units/smartcard.target @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Smart Card +StopWhenUnneeded=yes diff --git a/units/sockets.target b/units/sockets.target new file mode 100644 index 000000000..22963128d --- /dev/null +++ b/units/sockets.target @@ -0,0 +1,11 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Sockets diff --git a/units/sound.target b/units/sound.target new file mode 100644 index 000000000..e53221c7a --- /dev/null +++ b/units/sound.target @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Sound Card +StopWhenUnneeded=yes diff --git a/units/suse/halt-local.service b/units/suse/halt-local.service new file mode 100644 index 000000000..796012c0c --- /dev/null +++ b/units/suse/halt-local.service @@ -0,0 +1,20 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=/etc/init.d/halt.local Compatibility +ConditionFileIsExecutable=/etc/init.d/halt.local +DefaultDependencies=no +After=shutdown.target +Before=final.target + +[Service] +Type=oneshot +ExecStart=/etc/init.d/halt.local +TimeoutSec=0 +StandardOutput=tty +RemainAfterExit=yes diff --git a/units/suse/rc-local.service b/units/suse/rc-local.service new file mode 100644 index 000000000..2384a18f9 --- /dev/null +++ b/units/suse/rc-local.service @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# This unit gets pulled automatically into multi-user.target by +# systemd-rc-local-generator if /etc/init.d/boot.local is executable. +[Unit] +Description=/etc/init.d/boot.local Compatibility +After=network.target + +[Service] +Type=oneshot +ExecStart=/etc/init.d/boot.local +TimeoutSec=0 +RemainAfterExit=yes +SysVStartPriority=99 diff --git a/units/swap.target b/units/swap.target new file mode 100644 index 000000000..26dd261d1 --- /dev/null +++ b/units/swap.target @@ -0,0 +1,11 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Swap diff --git a/units/sys-fs-fuse-connections.mount b/units/sys-fs-fuse-connections.mount new file mode 100644 index 000000000..037471537 --- /dev/null +++ b/units/sys-fs-fuse-connections.mount @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=FUSE Control File System +DefaultDependencies=no +ConditionPathExists=/sys/fs/fuse/connections +After=systemd-modules-load.service +Before=sysinit.target + +[Mount] +What=fusectl +Where=/sys/fs/fuse/connections +Type=fusectl diff --git a/units/sys-kernel-config.mount b/units/sys-kernel-config.mount new file mode 100644 index 000000000..d6862bf6b --- /dev/null +++ b/units/sys-kernel-config.mount @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Configuration File System +DefaultDependencies=no +ConditionPathExists=/sys/kernel/config +After=systemd-modules-load.service +Before=sysinit.target + +[Mount] +What=configfs +Where=/sys/kernel/config +Type=configfs diff --git a/units/sys-kernel-debug.mount b/units/sys-kernel-debug.mount new file mode 100644 index 000000000..d9fca1ff3 --- /dev/null +++ b/units/sys-kernel-debug.mount @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Debug File System +DefaultDependencies=no +ConditionPathExists=/sys/kernel/debug +Before=sysinit.target + +[Mount] +What=debugfs +Where=/sys/kernel/debug +Type=debugfs diff --git a/units/sysinit.target b/units/sysinit.target new file mode 100644 index 000000000..eb9a1c7cc --- /dev/null +++ b/units/sysinit.target @@ -0,0 +1,15 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=System Initialization +Conflicts=emergency.service emergency.target +Wants=local-fs.target swap.target +After=local-fs.target swap.target emergency.service emergency.target +RefuseManualStart=yes diff --git a/units/syslog.socket b/units/syslog.socket new file mode 100644 index 000000000..0e211e16e --- /dev/null +++ b/units/syslog.socket @@ -0,0 +1,40 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Syslog Socket +DefaultDependencies=no +Before=sockets.target syslog.target +Conflicts=shutdown.target +Before=shutdown.target + +# Pull in syslog.target to tell people that /dev/log is now accessible +Wants=syslog.target + +[Socket] +ListenDatagram=/run/systemd/journal/syslog +SocketMode=0666 +PassCredentials=yes +PassSecurity=yes +ReceiveBuffer=8M + +# The default syslog implementation should make syslog.service a +# symlink to itself, so that this socket activates the right actual +# syslog service. +# +# Examples: +# +# /etc/systemd/system/syslog.service -> /lib/systemd/system/rsyslog.service +# /etc/systemd/system/syslog.service -> /lib/systemd/system/syslog-ng.service +# +# Best way to achieve that is by adding this to your unit file +# (i.e. to rsyslog.service or syslog-ng.service): +# +# [Install] +# Alias=syslog.service diff --git a/units/syslog.target b/units/syslog.target new file mode 100644 index 000000000..825b26e7b --- /dev/null +++ b/units/syslog.target @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +# This exists mostly for compatibility with SysV/LSB units, and +# implementations lacking socket/bus activation. + +[Unit] +Description=Syslog + +# Avoid that we conflict with shutdown.target, so that we can stay +# until the very end and do not cancel shutdown.target if we should +# hapen to be activated very late. +DefaultDependencies=no diff --git a/units/systemd-ask-password-console.path b/units/systemd-ask-password-console.path new file mode 100644 index 000000000..c3143d1da --- /dev/null +++ b/units/systemd-ask-password-console.path @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Dispatch Password Requests to Console Directory Watch +DefaultDependencies=no +Conflicts=shutdown.target +After=plymouth-start.service +Before=basic.target shutdown.target +ConditionPathExists=!/run/plymouth/pid + +[Path] +DirectoryNotEmpty=/run/systemd/ask-password +MakeDirectory=yes diff --git a/units/systemd-ask-password-console.service.in b/units/systemd-ask-password-console.service.in new file mode 100644 index 000000000..5ff3ed55d --- /dev/null +++ b/units/systemd-ask-password-console.service.in @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Dispatch Password Requests to Console +DefaultDependencies=no +Conflicts=shutdown.target +After=plymouth-start.service +Before=shutdown.target +ConditionPathExists=!/run/plymouth/pid + +[Service] +ExecStart=@rootbindir@/systemd-tty-ask-password-agent --watch --console diff --git a/units/systemd-ask-password-plymouth.path b/units/systemd-ask-password-plymouth.path new file mode 100644 index 000000000..06a587620 --- /dev/null +++ b/units/systemd-ask-password-plymouth.path @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Forward Password Requests to Plymouth Directory Watch +DefaultDependencies=no +Conflicts=shutdown.target +After=plymouth-start.service +Before=basic.target shutdown.target +ConditionKernelCommandLine=!plymouth.enable=0 +ConditionPathExists=/run/plymouth/pid + +[Path] +DirectoryNotEmpty=/run/systemd/ask-password +MakeDirectory=yes diff --git a/units/systemd-ask-password-plymouth.service.in b/units/systemd-ask-password-plymouth.service.in new file mode 100644 index 000000000..92cbfdbf0 --- /dev/null +++ b/units/systemd-ask-password-plymouth.service.in @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Forward Password Requests to Plymouth +DefaultDependencies=no +Conflicts=shutdown.target +After=plymouth-start.service +Before=shutdown.target +ConditionKernelCommandLine=!plymouth.enable=0 +ConditionPathExists=/run/plymouth/pid + +[Service] +ExecStart=@rootbindir@/systemd-tty-ask-password-agent --watch --plymouth diff --git a/units/systemd-ask-password-wall.path b/units/systemd-ask-password-wall.path new file mode 100644 index 000000000..050b73b2e --- /dev/null +++ b/units/systemd-ask-password-wall.path @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Forward Password Requests to Wall Directory Watch +DefaultDependencies=no +Conflicts=shutdown.target +Before=basic.target shutdown.target + +[Path] +DirectoryNotEmpty=/run/systemd/ask-password +MakeDirectory=yes diff --git a/units/systemd-ask-password-wall.service.in b/units/systemd-ask-password-wall.service.in new file mode 100644 index 000000000..71ec1d68f --- /dev/null +++ b/units/systemd-ask-password-wall.service.in @@ -0,0 +1,15 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Forward Password Requests to Wall +After=systemd-user-sessions.service + +[Service] +ExecStartPre=-@rootbindir@/systemctl stop systemd-ask-password-console.path systemd-ask-password-console.service +ExecStartPre=-@rootbindir@/systemctl stop systemd-ask-password-plymouth.path systemd-ask-password-plymouth.service +ExecStart=@rootbindir@/systemd-tty-ask-password-agent --wall diff --git a/units/systemd-binfmt.service.in b/units/systemd-binfmt.service.in new file mode 100644 index 000000000..d43497c15 --- /dev/null +++ b/units/systemd-binfmt.service.in @@ -0,0 +1,22 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Set Up Additional Binary Formats +DefaultDependencies=no +Conflicts=shutdown.target +After=systemd-readahead-collect.service systemd-readahead-replay.service proc-sys-fs-binfmt_misc.automount +Before=sysinit.target shutdown.target +ConditionDirectoryNotEmpty=|/usr/lib/binfmt.d +ConditionDirectoryNotEmpty=|/usr/local/lib/binfmt.d +ConditionDirectoryNotEmpty=|/etc/binfmt.d +ConditionDirectoryNotEmpty=|/run/binfmt.d + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@rootlibexecdir@/systemd-binfmt diff --git a/units/systemd-hostnamed.service.in b/units/systemd-hostnamed.service.in new file mode 100644 index 000000000..6efab1e25 --- /dev/null +++ b/units/systemd-hostnamed.service.in @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Hostname Service + +[Service] +ExecStart=@rootlibexecdir@/systemd-hostnamed +Type=dbus +BusName=org.freedesktop.hostname1 +CapabilityBoundingSet=CAP_SYS_ADMIN diff --git a/units/systemd-initctl.service.in b/units/systemd-initctl.service.in new file mode 100644 index 000000000..7df3aa6db --- /dev/null +++ b/units/systemd-initctl.service.in @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=/dev/initctl Compatibility Daemon +DefaultDependencies=no + +[Service] +ExecStart=@rootlibexecdir@/systemd-initctl +NotifyAccess=all diff --git a/units/systemd-initctl.socket b/units/systemd-initctl.socket new file mode 100644 index 000000000..7a3a0236e --- /dev/null +++ b/units/systemd-initctl.socket @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=/dev/initctl Compatibility Named Pipe +DefaultDependencies=no +Before=sockets.target + +[Socket] +ListenFIFO=/dev/initctl +SocketMode=0600 diff --git a/units/systemd-journald.service.in b/units/systemd-journald.service.in new file mode 100644 index 000000000..92606b0d8 --- /dev/null +++ b/units/systemd-journald.service.in @@ -0,0 +1,25 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Journal Service +DefaultDependencies=no +Requires=systemd-journald.socket +After=systemd-journald.socket +After=syslog.socket + +[Service] +ExecStart=@rootlibexecdir@/systemd-journald +NotifyAccess=all +StandardOutput=null +CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID + +# Increase the default a bit in order to allow many simultaneous +# services being run since we keep one fd open per service. +LimitNOFILE=16384 diff --git a/units/systemd-journald.socket b/units/systemd-journald.socket new file mode 100644 index 000000000..15fc49ef2 --- /dev/null +++ b/units/systemd-journald.socket @@ -0,0 +1,27 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Journal Socket +DefaultDependencies=no +Before=sockets.target syslog.target + +# Mount and swap units need this. If this socket unit is removed by an +# isolate request the mount and and swap units would be removed too, +# hence let's exclude this from isolate requests. +IgnoreOnIsolate=yes + +[Socket] +ListenStream=/run/systemd/journal/stdout +ListenDatagram=/run/systemd/journal/socket +ListenDatagram=/dev/log +SocketMode=0666 +PassCredentials=yes +PassSecurity=yes +ReceiveBuffer=8M diff --git a/units/systemd-localed.service.in b/units/systemd-localed.service.in new file mode 100644 index 000000000..4be65df75 --- /dev/null +++ b/units/systemd-localed.service.in @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Locale Service + +[Service] +ExecStart=@rootlibexecdir@/systemd-localed +Type=dbus +BusName=org.freedesktop.locale1 +CapabilityBoundingSet= diff --git a/units/systemd-logind.service.in b/units/systemd-logind.service.in new file mode 100644 index 000000000..531b8f7e9 --- /dev/null +++ b/units/systemd-logind.service.in @@ -0,0 +1,21 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Login Service + +[Service] +ExecStart=@rootlibexecdir@/systemd-logind +Type=dbus +BusName=org.freedesktop.login1 +CapabilityBoundingSet=CAP_AUDIT_CONTROL CAP_CHOWN CAP_KILL CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_FOWNER CAP_SYS_TTY_CONFIG + +# Increase the default a bit in order to allow many simultaneous +# logins since we keep one fd open per session. +LimitNOFILE=16384 diff --git a/units/systemd-modules-load.service.in b/units/systemd-modules-load.service.in new file mode 100644 index 000000000..5dc373d20 --- /dev/null +++ b/units/systemd-modules-load.service.in @@ -0,0 +1,23 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Load Kernel Modules +DefaultDependencies=no +Conflicts=shutdown.target +After=systemd-readahead-collect.service systemd-readahead-replay.service +Before=sysinit.target shutdown.target +ConditionDirectoryNotEmpty=|/lib/modules-load.d +ConditionDirectoryNotEmpty=|/usr/lib/modules-load.d +ConditionDirectoryNotEmpty=|/usr/local/lib/modules-load.d +ConditionDirectoryNotEmpty=|/etc/modules-load.d +ConditionDirectoryNotEmpty=|/run/modules-load.d + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@rootlibexecdir@/systemd-modules-load diff --git a/units/systemd-random-seed-load.service.in b/units/systemd-random-seed-load.service.in new file mode 100644 index 000000000..a2b6a557d --- /dev/null +++ b/units/systemd-random-seed-load.service.in @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Load Random Seed +DefaultDependencies=no +Wants=local-fs.target +Conflicts=shutdown.target +After=systemd-readahead-collect.service systemd-readahead-replay.service local-fs.target +Before=sysinit.target shutdown.target + +[Service] +Type=oneshot +ExecStart=@rootlibexecdir@/systemd-random-seed load diff --git a/units/systemd-random-seed-save.service.in b/units/systemd-random-seed-save.service.in new file mode 100644 index 000000000..9a074cf3f --- /dev/null +++ b/units/systemd-random-seed-save.service.in @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Save Random Seed +DefaultDependencies=no +After=systemd-random-seed-load.service +Before=shutdown.target +Conflicts=systemd-random-seed-load.service + +[Service] +Type=oneshot +ExecStart=@rootlibexecdir@/systemd-random-seed save diff --git a/units/systemd-readahead-collect.service.in b/units/systemd-readahead-collect.service.in new file mode 100644 index 000000000..56ba54f0b --- /dev/null +++ b/units/systemd-readahead-collect.service.in @@ -0,0 +1,22 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Collect Read-Ahead Data +DefaultDependencies=no +Wants=systemd-readahead-done.timer +Conflicts=shutdown.target +Before=sysinit.target shutdown.target + +[Service] +Type=notify +ExecStart=@rootlibexecdir@/systemd-readahead-collect +RemainAfterExit=yes +StandardOutput=null + +[Install] +WantedBy=default.target diff --git a/units/systemd-readahead-done.service.in b/units/systemd-readahead-done.service.in new file mode 100644 index 000000000..d665e45f2 --- /dev/null +++ b/units/systemd-readahead-done.service.in @@ -0,0 +1,20 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Stop Read-Ahead Data Collection +DefaultDependencies=no +Conflicts=shutdown.target +After=default.target +Before=shutdown.target + +[Service] +Type=oneshot +ExecStart=@SYSTEMD_NOTIFY@ --readahead=done + +[Install] +Also=systemd-readahead-collect.service diff --git a/units/systemd-readahead-done.timer b/units/systemd-readahead-done.timer new file mode 100644 index 000000000..d144bfaec --- /dev/null +++ b/units/systemd-readahead-done.timer @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Stop Read-Ahead Data Collection 10s After Completed Startup +DefaultDependencies=no +Conflicts=shutdown.target +After=default.target +Before=shutdown.target + +[Timer] +OnActiveSec=10s + +[Install] +Also=systemd-readahead-collect.service diff --git a/units/systemd-readahead-replay.service.in b/units/systemd-readahead-replay.service.in new file mode 100644 index 000000000..7c82e408e --- /dev/null +++ b/units/systemd-readahead-replay.service.in @@ -0,0 +1,22 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Replay Read-Ahead Data +DefaultDependencies=no +Conflicts=shutdown.target +Before=sysinit.target shutdown.target +ConditionPathExists=/.readahead + +[Service] +Type=notify +ExecStart=@rootlibexecdir@/systemd-readahead-replay +RemainAfterExit=yes +StandardOutput=null + +[Install] +WantedBy=default.target diff --git a/units/systemd-remount-api-vfs.service.in b/units/systemd-remount-api-vfs.service.in new file mode 100644 index 000000000..f4df0ca26 --- /dev/null +++ b/units/systemd-remount-api-vfs.service.in @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Remount API VFS +DefaultDependencies=no +Conflicts=shutdown.target +After=systemd-readahead-collect.service systemd-readahead-replay.service +Before=local-fs-pre.target local-fs.target shutdown.target +Wants=local-fs-pre.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@rootlibexecdir@/systemd-remount-api-vfs diff --git a/units/systemd-shutdownd.service.in b/units/systemd-shutdownd.service.in new file mode 100644 index 000000000..657365a45 --- /dev/null +++ b/units/systemd-shutdownd.service.in @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Delayed Shutdown Service +DefaultDependencies=no + +[Service] +ExecStart=@rootlibexecdir@/systemd-shutdownd +NotifyAccess=all diff --git a/units/systemd-shutdownd.socket b/units/systemd-shutdownd.socket new file mode 100644 index 000000000..7f13c9386 --- /dev/null +++ b/units/systemd-shutdownd.socket @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Delayed Shutdown Socket +DefaultDependencies=no +Before=sockets.target + +[Socket] +ListenDatagram=/run/systemd/shutdownd +SocketMode=0600 +PassCredentials=yes +PassSecurity=yes diff --git a/units/systemd-sysctl.service.in b/units/systemd-sysctl.service.in new file mode 100644 index 000000000..6d5342263 --- /dev/null +++ b/units/systemd-sysctl.service.in @@ -0,0 +1,24 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Apply Kernel Variables +DefaultDependencies=no +Conflicts=shutdown.target +After=systemd-readahead-collect.service systemd-readahead-replay.service +Before=sysinit.target shutdown.target +ConditionPathExists=|/etc/sysctl.conf +ConditionDirectoryNotEmpty=|/lib/sysctl.d +ConditionDirectoryNotEmpty=|/usr/lib/sysctl.d +ConditionDirectoryNotEmpty=|/usr/local/lib/sysctl.d +ConditionDirectoryNotEmpty=|/etc/sysctl.d +ConditionDirectoryNotEmpty=|/run/sysctl.d + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@rootlibexecdir@/systemd-sysctl diff --git a/units/systemd-timedated.service.in b/units/systemd-timedated.service.in new file mode 100644 index 000000000..90ff4432b --- /dev/null +++ b/units/systemd-timedated.service.in @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Time & Date Service + +[Service] +ExecStart=@rootlibexecdir@/systemd-timedated +Type=dbus +BusName=org.freedesktop.timedate1 +CapabilityBoundingSet=CAP_SYS_TIME diff --git a/units/systemd-tmpfiles-clean.service.in b/units/systemd-tmpfiles-clean.service.in new file mode 100644 index 000000000..3c8e72ebf --- /dev/null +++ b/units/systemd-tmpfiles-clean.service.in @@ -0,0 +1,22 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Cleanup of Temporary Directories +DefaultDependencies=no +Wants=local-fs.target +After=systemd-readahead-collect.service systemd-readahead-replay.service local-fs.target +Before=sysinit.target shutdown.target +ConditionDirectoryNotEmpty=|/usr/lib/tmpfiles.d +ConditionDirectoryNotEmpty=|/usr/local/lib/tmpfiles.d +ConditionDirectoryNotEmpty=|/etc/tmpfiles.d +ConditionDirectoryNotEmpty=|/run/tmpfiles.d + +[Service] +Type=oneshot +ExecStart=@rootbindir@/systemd-tmpfiles --clean +IOSchedulingClass=idle diff --git a/units/systemd-tmpfiles-clean.timer b/units/systemd-tmpfiles-clean.timer new file mode 100644 index 000000000..d8529a8d7 --- /dev/null +++ b/units/systemd-tmpfiles-clean.timer @@ -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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Daily Cleanup of Temporary Directories + +[Timer] +OnBootSec=15min +OnUnitActiveSec=1d diff --git a/units/systemd-tmpfiles-setup.service.in b/units/systemd-tmpfiles-setup.service.in new file mode 100644 index 000000000..f90121e12 --- /dev/null +++ b/units/systemd-tmpfiles-setup.service.in @@ -0,0 +1,22 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Recreate Volatile Files and Directories +DefaultDependencies=no +Wants=local-fs.target +After=systemd-readahead-collect.service systemd-readahead-replay.service local-fs.target +Before=sysinit.target shutdown.target +ConditionDirectoryNotEmpty=|/usr/lib/tmpfiles.d +ConditionDirectoryNotEmpty=|/usr/local/lib/tmpfiles.d +ConditionDirectoryNotEmpty=|/etc/tmpfiles.d +ConditionDirectoryNotEmpty=|/run/tmpfiles.d + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@rootbindir@/systemd-tmpfiles --create --remove diff --git a/units/systemd-update-utmp-runlevel.service.in b/units/systemd-update-utmp-runlevel.service.in new file mode 100644 index 000000000..614c759a6 --- /dev/null +++ b/units/systemd-update-utmp-runlevel.service.in @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Notify Audit System and Update UTMP about System Runlevel Changes +DefaultDependencies=no +After=local-fs.target sysinit.target auditd.service runlevel1.target runlevel2.target runlevel3.target runlevel4.target runlevel5.target systemd-tmpfiles-setup.service +Before=poweroff.service reboot.service halt.service + +[Service] +Type=oneshot +ExecStart=@rootlibexecdir@/systemd-update-utmp runlevel diff --git a/units/systemd-update-utmp-shutdown.service.in b/units/systemd-update-utmp-shutdown.service.in new file mode 100644 index 000000000..e7c3c04a0 --- /dev/null +++ b/units/systemd-update-utmp-shutdown.service.in @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Notify Audit System and Update UTMP about System Shutdown +DefaultDependencies=no +After=local-fs.target sysinit.target auditd.service systemd-update-utmp-runlevel.service +Before=poweroff.service reboot.service halt.service + +[Service] +Type=oneshot +ExecStart=@rootlibexecdir@/systemd-update-utmp shutdown diff --git a/units/systemd-user-sessions.service.in b/units/systemd-user-sessions.service.in new file mode 100644 index 000000000..a93d586a4 --- /dev/null +++ b/units/systemd-user-sessions.service.in @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Permit User Sessions +After=local-fs.target remote-fs.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@rootlibexecdir@/systemd-user-sessions start +ExecStop=@rootlibexecdir@/systemd-user-sessions stop diff --git a/units/systemd-vconsole-setup.service.in b/units/systemd-vconsole-setup.service.in new file mode 100644 index 000000000..673fb6ccf --- /dev/null +++ b/units/systemd-vconsole-setup.service.in @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Setup Virtual Console +DefaultDependencies=no +Conflicts=shutdown.target +After=systemd-readahead-collect.service systemd-readahead-replay.service +Before=sysinit.target shutdown.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@rootlibexecdir@/systemd-vconsole-setup diff --git a/units/time-sync.target b/units/time-sync.target new file mode 100644 index 000000000..aa34ecb5f --- /dev/null +++ b/units/time-sync.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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +# This exists mostly for compatibility with SysV/LSB units, and +# implementations lacking socket/bus activation. + +[Unit] +Description=System Time Synchronized diff --git a/units/tmp.mount b/units/tmp.mount new file mode 100644 index 000000000..8d0b8afb1 --- /dev/null +++ b/units/tmp.mount @@ -0,0 +1,16 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=Temporary Directory +Before=local-fs.target + +[Mount] +What=tmpfs +Where=/tmp +Type=tmpfs +Options=mode=1777 diff --git a/units/umount.target b/units/umount.target new file mode 100644 index 000000000..b9ecca6fb --- /dev/null +++ b/units/umount.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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Unmount All Filesystems +DefaultDependencies=no +RefuseManualStart=yes diff --git a/units/user/.gitignore b/units/user/.gitignore new file mode 100644 index 000000000..eeb62b32b --- /dev/null +++ b/units/user/.gitignore @@ -0,0 +1 @@ +exit.service diff --git a/units/user/Makefile b/units/user/Makefile new file mode 120000 index 000000000..50be21181 --- /dev/null +++ b/units/user/Makefile @@ -0,0 +1 @@ +../../src/Makefile \ No newline at end of file diff --git a/units/user/default.target b/units/user/default.target new file mode 100644 index 000000000..deb310c2f --- /dev/null +++ b/units/user/default.target @@ -0,0 +1,11 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Default diff --git a/units/user/exit.service.in b/units/user/exit.service.in new file mode 100644 index 000000000..a20b089c9 --- /dev/null +++ b/units/user/exit.service.in @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Exit the Session +DefaultDependencies=no +Requires=shutdown.target +After=shutdown.target + +[Service] +Type=oneshot +ExecStart=@SYSTEMCTL@ --user --force exit diff --git a/units/user/exit.target b/units/user/exit.target new file mode 100644 index 000000000..f34844c0d --- /dev/null +++ b/units/user/exit.target @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# See systemd.special(7) for details + +[Unit] +Description=Exit the Session +DefaultDependencies=no +Requires=exit.service +After=exit.service +AllowIsolate=yes + +[Install] +Alias=ctrl-alt-del.target diff --git a/units/user@.service.in b/units/user@.service.in new file mode 100644 index 000000000..91e3b2515 --- /dev/null +++ b/units/user@.service.in @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +[Unit] +Description=User Manager for %I +After=systemd-user-sessions.service + +[Service] +User=%I +PAMName=systemd-shared +ControlGroup=%R/user/%I/shared cpu:/ +ControlGroupModify=yes +Type=notify +ExecStart=-@rootlibexecdir@/systemd --user +Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%I/dbus/user_bus_socket