From 97875af3bd283984a4c5281c9b0a64fb731c0e42 Mon Sep 17 00:00:00 2001 From: Zhang Qiang Date: Fri, 18 May 2012 19:40:17 +0800 Subject: [PATCH] Initial code release --- COPYING | 339 +++++++++++++++ Changelog | 393 +++++++++++++++++ Makefile | 108 +++++ README | 52 +++ TESTPLAN | 89 ++++ TODO | 18 + acpi_genetlink.h | 33 ++ acpi_ids.c | 259 ++++++++++++ acpi_ids.h | 30 ++ acpi_listen.8 | 47 +++ acpi_listen.c | 247 +++++++++++ acpid-design.odg | Bin 0 -> 14671 bytes acpid.8 | 170 ++++++++ acpid.c | 474 +++++++++++++++++++++ acpid.h | 53 +++ connection_list.c | 182 ++++++++ connection_list.h | 78 ++++ event.c | 796 +++++++++++++++++++++++++++++++++++ event.h | 32 ++ genetlink.h | 81 ++++ inotify_handler.c | 148 +++++++ inotify_handler.h | 31 ++ input_layer.c | 389 +++++++++++++++++ input_layer.h | 35 ++ kacpimon/README | 3 + kacpimon/acpi_genetlink.h | 33 ++ kacpimon/acpi_ids.c | 252 +++++++++++ kacpimon/acpi_ids.h | 30 ++ kacpimon/connection_list.c | 121 ++++++ kacpimon/connection_list.h | 59 +++ kacpimon/genetlink.h | 81 ++++ kacpimon/input_layer.c | 154 +++++++ kacpimon/input_layer.h | 32 ++ kacpimon/kacpimon.8 | 68 +++ kacpimon/kacpimon.c | 205 +++++++++ kacpimon/kacpimon.h | 35 ++ kacpimon/libnetlink.c | 592 ++++++++++++++++++++++++++ kacpimon/libnetlink.h | 91 ++++ kacpimon/makefile | 56 +++ kacpimon/netlink.c | 205 +++++++++ kacpimon/netlink.h | 31 ++ kacpimon/sample.out | 16 + libnetlink.c | 591 ++++++++++++++++++++++++++ libnetlink.h | 91 ++++ log.c | 56 +++ log.h | 37 ++ netlink.c | 240 +++++++++++ netlink.h | 31 ++ packaging/acpid | 1 + packaging/acpid-2.0.9-makefile.patch | 16 + packaging/acpid-start-script | 4 + packaging/acpid.ac.conf | 0 packaging/acpid.battery.conf | 5 + packaging/acpid.battery.sh | 39 ++ packaging/acpid.lid.conf | 5 + packaging/acpid.lid.sh | 37 ++ packaging/acpid.power.conf | 5 + packaging/acpid.power.sh | 29 ++ packaging/acpid.service | 10 + packaging/acpid.spec | 124 ++++++ packaging/acpid.start.sh | 7 + packaging/acpid.video.conf | 6 + proc.c | 217 ++++++++++ proc.h | 30 ++ samples/acpi_handler-conf | 6 + samples/acpi_handler.sh | 24 ++ samples/battery/battery-conf | 10 + samples/battery/battery.sh | 36 ++ samples/panasonic/ac_adapt.pl | 95 +++++ samples/panasonic/ac_adapter | 5 + samples/panasonic/hotkey | 5 + samples/panasonic/hotkey.pl | 301 +++++++++++++ samples/power | 7 + samples/power.sh | 10 + samples/powerbtn/powerbtn-conf | 13 + samples/powerbtn/powerbtn.sh | 68 +++ samples/powerbtn/powerbtn.sh.old | 38 ++ sock.c | 181 ++++++++ sock.h | 35 ++ ud_socket.c | 132 ++++++ ud_socket.h | 16 + 81 files changed, 8711 insertions(+) create mode 100644 COPYING create mode 100644 Changelog create mode 100644 Makefile create mode 100644 README create mode 100644 TESTPLAN create mode 100644 TODO create mode 100644 acpi_genetlink.h create mode 100644 acpi_ids.c create mode 100644 acpi_ids.h create mode 100644 acpi_listen.8 create mode 100644 acpi_listen.c create mode 100644 acpid-design.odg create mode 100644 acpid.8 create mode 100644 acpid.c create mode 100644 acpid.h create mode 100644 connection_list.c create mode 100644 connection_list.h create mode 100644 event.c create mode 100644 event.h create mode 100644 genetlink.h create mode 100644 inotify_handler.c create mode 100644 inotify_handler.h create mode 100644 input_layer.c create mode 100644 input_layer.h create mode 100644 kacpimon/README create mode 100644 kacpimon/acpi_genetlink.h create mode 100644 kacpimon/acpi_ids.c create mode 100644 kacpimon/acpi_ids.h create mode 100644 kacpimon/connection_list.c create mode 100644 kacpimon/connection_list.h create mode 100644 kacpimon/genetlink.h create mode 100644 kacpimon/input_layer.c create mode 100644 kacpimon/input_layer.h create mode 100644 kacpimon/kacpimon.8 create mode 100644 kacpimon/kacpimon.c create mode 100644 kacpimon/kacpimon.h create mode 100644 kacpimon/libnetlink.c create mode 100644 kacpimon/libnetlink.h create mode 100644 kacpimon/makefile create mode 100644 kacpimon/netlink.c create mode 100644 kacpimon/netlink.h create mode 100644 kacpimon/sample.out create mode 100644 libnetlink.c create mode 100644 libnetlink.h create mode 100644 log.c create mode 100644 log.h create mode 100644 netlink.c create mode 100644 netlink.h create mode 100644 packaging/acpid create mode 100644 packaging/acpid-2.0.9-makefile.patch create mode 100644 packaging/acpid-start-script create mode 100644 packaging/acpid.ac.conf create mode 100644 packaging/acpid.battery.conf create mode 100644 packaging/acpid.battery.sh create mode 100644 packaging/acpid.lid.conf create mode 100644 packaging/acpid.lid.sh create mode 100644 packaging/acpid.power.conf create mode 100644 packaging/acpid.power.sh create mode 100644 packaging/acpid.service create mode 100644 packaging/acpid.spec create mode 100644 packaging/acpid.start.sh create mode 100644 packaging/acpid.video.conf create mode 100644 proc.c create mode 100644 proc.h create mode 100644 samples/acpi_handler-conf create mode 100755 samples/acpi_handler.sh create mode 100644 samples/battery/battery-conf create mode 100644 samples/battery/battery.sh create mode 100644 samples/panasonic/ac_adapt.pl create mode 100644 samples/panasonic/ac_adapter create mode 100644 samples/panasonic/hotkey create mode 100644 samples/panasonic/hotkey.pl create mode 100644 samples/power create mode 100755 samples/power.sh create mode 100644 samples/powerbtn/powerbtn-conf create mode 100755 samples/powerbtn/powerbtn.sh create mode 100755 samples/powerbtn/powerbtn.sh.old create mode 100644 sock.c create mode 100644 sock.h create mode 100644 ud_socket.c create mode 100644 ud_socket.h diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..a43ea21 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, 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 Library 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 + + Appendix: 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) 19yy + + 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., 675 Mass Ave, Cambridge, MA 02139, 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) 19yy 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 Library General +Public License instead of this License. diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..b6d09d7 --- /dev/null +++ b/Changelog @@ -0,0 +1,393 @@ +* Thu Dec 15 2011 Ted Felix + - 2.0.14 release + - Fixed brace style. (inotify_handler.c acpid.c) (Ted Felix) + - Added support for a "K" suffix on event strings to indicate that they + may have originated from a keyboard input layer device. This can be + used to differentiate between a power switch on the keyboard, and a power + switch on the computer's case. + + (connection_list.h inotify_handler.c input_layer.c netlink.c proc.c + sock.c) (Ted Felix) + + - Added a pathname to connection along with a find_connection_name(). + Modifications to process_inotify() to log IN_DELETE events. Additional + debugging output. + + These changes were experimentation related to dealing with a ThinkPad + suspend problem. They should have no effect on acpid's behavior. They are + mainly noticeable as a change in the logging. + + The Problem: When resuming from suspend, the lid switch and power button's + /dev/input event files do not trigger an IN_CREATE, so acpid doesn't + reconnect to them. Restarting acpid fixes this. + + Tried using IN_DELETE instead of ENODEV to detect the drivers going away. + Worked fine for disconnecting/reconnecting a USB keyboard, but not for the + ThinkPad suspend problem. I've given up on fixing this as it appears to be + a kernel or driver issue. + + (connection_list.c connection_list.h inotify_handler.c input_layer.c + netlink.c proc.c sock.c) (Ted Felix) + +* Tue Nov 15 2011 Ted Felix + - 2.0.13 release + - Fix for socket name buffer overflow. (ud_socket.c) (Ted Felix) + - Moved acpid_log() out of acpid.h/.c and into log.h/.c to make it easier + for all parts of acpid to use. (Makefile acpid.h acpid.c connection_list.c + event.c inotify_handler.c input_layer.c log.h log.c netlink.c proc.c + sock.c ud_socket.c) (Ted Felix) + - Cleaned up #includes and comment style. (libnetlink.c libnetlink.h) + (Ted Felix) + +* Mon Aug 15 2011 Ted Felix + - 2.0.12 release + - Changed exit() to _exit() if execl() fails. (event.c) (Ted Felix) + +* Sat Jul 30 2011 Ted Felix + - 2.0.11 release + - Set umask to 0077 for scripts run by acpid. (event.c) (Ted Felix) + +* Tue May 17 2011 Ted Felix + - 2.0.10 release + - Fixed compiler warnings in kacpimon. (kacpimon/libnetlink.c) + (Michael Meskes) + - systemd support. The socket acpid creates to connect with clients can now + be passed as stdin (fd 0) at startup. (acpid.c sock.h sock.c acpid.8) + (Reiner Klussmeier) + - man page improvements (acpid.8) (Ted Felix) + +* Fri Apr 15 2011 Ted Felix + - 2.0.9 release + - Removed newlines from acpid_log() calls and modified acpid_log() to + no longer need newlines. This change to acpid prevents blank lines in + the log for versions of syslogd that preserve newlines. (acpid.c + connection_list.c event.c inotify_handler.c input_layer.c netlink.c + proc.c sock.c) (Michael Meskes, Ted Felix) + - Added fcntl() for O_NONBLOCK on the client sockets. This prevents acpid + from hanging if a client behaves badly. (sock.c) (Vasiliy Kulikov) + From: http://www.openwall.com/lists/oss-security/2011/01/19/4 + - Improvements to error handling. (sock.c) (Ted Felix) + +* Tue Feb 15 2011 Ted Felix + - 2.0.8 release + - Fixed "comparison between signed and unsigned integer expressions" + error with gcc 4.6. (libnetlink.c) (Eugeni Dodonov) + - Fixed unused variable "type" with gcc 4.6. (libnetlink.c) (Jiri Skala) + +* Mon Nov 15 2010 Ted Felix + - 2.0.7 release + - Reduced the startup logging and skipped processing of "." and ".." + in the config files directory. Debian #598198. (event.c proc.c) + (Ted Felix) + - Added CD-related buttons. Debian #600564. (input_layer.c) + (Stanislav Maslovski) + - Removed the "getppid() == 1" hack from daemonize(). + Red Hat #629740. "While forking is ugly in theory, this has the advantage + that it is clear that the acpid unix socket is properly installed before + systemd continues starting other units that want to be started after + acpid." - Lennart Poettering + For upstart, specify "expect fork" in your upstart .conf file for acpid. + For systemd, specify "Type=forking" in your systemd service file for + acpid. + (acpid.c) (Lennart Poettering) + - Added FD_CLOEXEC to the input layer fd's. Red Hat #648221. + (input_layer.c) (Daniel Walsh) + +* Tue Jun 15 2010 Ted Felix + - 2.0.6 release + - Added getXconsole() to samples/powerbtn/powerbtn.sh to eliminate + dependency on other parts of Debian. (samples/powerbtn/powerbtn.sh) + (Debian and Timo Gurr) + +* Sat May 15 2010 Ted Felix + - 2.0.5 release + - Moved powerbtn.sh to powerbtn.sh.old and brought in the latest + powerbtn.sh from Debian which handles KDE4. + (samples/powerbtn/powerbtn.sh samples/powerbtn/powerbtn.sh.old) + (Ted Felix) + +* Thu Apr 15 2010 Ted Felix + - 2.0.4 release + - Replaced all the mandb code in the makefile with a comment. (Makefile) + (Ted Felix) + - Revamped logging. (acpid.c event.c inotify_handler.c input_layer.c + netlink.c proc.c sock.c) (Ted Felix) + - Removed CR's (\r) from files. (Changelog connection_list.c sock.c) + (Ted Felix) + - Cleaned up the samples directory a bit. Also added powerbtn samples + taken from Debian. (samples/*) (Ted Felix) + - Removed the %changelog token from the top of the Changelog. This + appears to be a Red Hat-ism. (Changelog) (Ted Felix) + +* Mon Mar 15 2010 Ted Felix + - 2.0.3 release + - Fixed problem in makefile with mandb line when DESTDIR is not empty. + (Makefile) (Jiri Skala) + - Added missing headers needed by Red Hat/Fedora. (acpid.c sock.c) + (Jiri Skala) + - Updated daemonize() to be in keeping with Doug Potter's "How to Daemonize + in Linux". (acpid.c) (Ted Felix) + - Removed the test for bad kernels. (acpid.c) (Ted Felix) + +* Mon Feb 15 2010 Ted Felix + - 2.0.2 release + - Increased MAX_CONNECTIONS to 20. (connection_list.c) + - Recalculate highestfd in delete_connection(). (connection_list.c) + (Willi Mann) + - Removed old naming convention code for configuration files. (event.c) + - Fixed unescaped dashes in manpage. (acpid.8) (Michael Meskes) + - Added fix for mandb issue. (Makefile) (Robby Workman) + - Moved README-NETLINK into README. (README-NETLINK README) + +* Fri Jan 15 2010 Ted Felix + - 2.0.1 release + - Makefile improvements for packagers. (Makefile) (Robby Workman) + * Use DESTDIR instead of INSTPREFIX + * Allow custom compiler optimizations + * Allow alternative prefix + * Allow custom manpage directory + * Add DOCDIR and install docs + * Remove reference to "mandb -q" - this doesn't exist everywhere + + After this patch, packagers can do e.g.: + + make install \ + OPT="-O3" \ + PREFIX=/opt \ + MANDIR=/opt/man \ + DOCDIR=/opt/doc/apcid \ + DESTDIR=/tmp/package + + - run-parts(8) naming convention for configuration files. (event.c + acpid.8) (Debian) + +* Tue Dec 15 2009 Ted Felix + - 2.0.0 release + - Fixed gcc 4.4 warnings for strict aliasing. (kacpimon/acpi_ids.c) + (Debian) + - Fixed a printf() warning. (kacpimon/input_layer.c) (Debian) + - Fixed kacpimon makefile for a release build. (kacpimon/makefile) + (Ted Felix) + - Added dist and install targets to kacpimon makefile. Added + a set of CFLAGS for Debian-style (-g -O2) builds. Fixed error + messages in clean target with a "-f" to rm. (kacpimon/makefile) + (Ted Felix) + - Increased MAX_CONNECTIONS to 20 for kacpimon. + (kacpimon/connection_list.c) (Ted Felix) + - Improved "dist" target with DISTNAME in makefile. (Makefile) + (Ted Felix) + - Added mandb to the install target so that the man database will get + updated after the man pages are installed. (Makefile) + (Ted Felix) + - Updated the "event=" line in sample.conf to be more modern. + (samples/sample.conf) (Ted Felix) + - Improved the Example in the man page. (acpid.8) (Ted Felix) + +* Thu Nov 13 2009 Ted Felix + - 1.0.10-netlink6 release + - Implemented discovery of new input layer devices using inotify(7). + (inotify_handler.h inotify_handler.c acpid.c input_layer.h input_layer.c) + (Ted Felix) + - Updated input layer event table to incorporate more events and + to support a format compatible with older event configuration + files. (input_layer.c) (Harald Braumann and Ted Felix) + - Cleanup and move of input layer constants. (acpid.h input_layer.c + inotify_handler.c) (Ted Felix) + - kacpimon now opens all event sources and reports where each event + comes from. This makes it more useful for discovering events. + (kacpimon/kacpimon.c kacpimon/input_layer.c kacpimon/netlink.c) + (Ted Felix) + - Turned off strict aliasing optimizations as the netlink code is + filled with strict aliasing problems. (Makefile) (Ted Felix) + +* Fri Nov 6 2009 Ted Felix + - 1.0.10-netlink5 release + - Fixed exit on device removal. (input_layer.c connection_list.h + connection_list.c) (Mikhail Krivtsov) + +* Sun Jul 19 2009 Ted Felix + - 1.0.10-netlink4 release + - Added events to input_layer.c to cover more buttons on the Thinkpad X40 + (input_layer.c) (Peter Stuge) + - Fixed Makefile "dist" target to work properly. (Makefile) (Ted Felix) + - Added kacpimon to the codebase as a debugging tool. (kacpimon directory, + Makefile) (Ted Felix) + - Removed erroneous comment in sock.c about the 256 connection limit. + (sock.c) (Ted Felix) + - Removed unnecessary #include from connection_list.c. (connection_list.c) + (Ted Felix) + +* Mon May 04 2009 Ted Felix + - Fixed strict aliasing issue with gcc 4.4. (acpi_ids.c) (Michael Meskes + and Peter Alfredsen) + - 1.0.10-netlink3 release. + +* Sat May 02 2009 Ted Felix + - Merge of the following three 1.0.10 changes into 1.0.10-netlink2 + (Michael Meskes and Ted Felix) + +* Wed Apr 22 2009 Tim Hockin + - Bump version to 1.0.10 for release. + +* Wed Apr 22 2009 Tim Hockin + - Add a -C (--clientmax) command line flag to set max number of non-root + socket connections. (acpi.c acpid.h acpid.8 event.c) + - Set the maximum number of socket clients to 256 by default. (acpid.h) + - Close clients that have disconnected. (acpid.c event.c) (Aaron Plattner + ) + - Give up and exit() if 5 accept() calls fail in a row. (acpid.c) + +* Mon Feb 09 2009 Tim Hockin + - Open /dev/null O_RDWR, rather than O_RDONLY. (acpid.c) + +* Thu Dec 11 2008 Ted Felix + - Version 1.0.8ted1 + - Netlink and Input Layer support. Many files have been changed and + several have been added. (Ted Felix ) + +* Tue Oct 28 2008 Tim Hockin + - Bump version to 1.0.8 for release. + +* Sun Oct 26 2008 Tim Hockin + - Define _GNU_SOURCE. (Makefile) (Ted Felix ) + - Rename a variable to avoid shadowing a global. (event.c) (Ted Felix + ) + - Fix typos in man pages. (acpid.8, acpi_listen.8) (Ted Felix + ) + - GCC 4.3.2 gives chdir() the _wur attribute (warn unused result). Check + for errors. (acpid.c) (Ted Felix ) + - Check for ferror() in parse_file(). (event.c) (Ted Felix + ) + - Only read regular files in acpid_read_conf(). (event.c) (Ted Felix + ) + - Stop processing ACPI events when a lockfile exists (from Debian). + (acpid.8, acpid.c, acpid.h) (Ted Felix ) + +* Sat Nov 24 2007 Tim Hockin + - Build with -O2 flag (Makefile). + - Add -l (--logevents) option to enable logging of all events. Due to a + number of reports of log flooding (bad ACPI BIOS?) The new default is to + NOT log events. (acpid.c acpid.h event.h acpid.8) + - Add pidfile support and a -p (--pidfile) option to change the pidfile. + Default is /var/run/acpid.pid. (acpid.c acpid.8) (Javier Pello + ) + - Rename ACPI_* constants to ACPID_*. (acpid.c acpi_listen.c) + - Remove a bad cast of malloc()'s return value. (event.c) + - Add proper make dependencies. (Makefile) + - Close client file descriptors on exec(). (acpid.c) (Zdenek Prikryl + ) + +* Mon Sep 24 2007 Tim Hockin + - Don't use a mode argument on open("/dev/null") (acpid.c) (Steve Grubb + ) + - Use GCC "__attribute__((format(printf)))" for acpid_log (acpid.c) (Steve + Grubb ) + - Fix a shadowed variable name (event.c) (Steve Grubb ) + - Fix a leaked fd on error (event.c) (Steve Grubb ) + - Fix a signed/unsigned comparison (event.c) (Steve Grubb + ) + - Compile with more warnings (Makefile) (Steve Grubb ) + +* Sat June 30 2007 Tim Hockin + - Bump version to 1.0.6 for release. + +* Thu May 24 2007 Tim Hockin + - Print event handler output to stdout only in debug mode (acpid.c, event.c). + - Update man page for new logging. + +* Wed May 23 2007 Tim Hockin + - Correctly check for malloc() failures (event.c) + - Skip editor backup files when scanning for conf files (event.c) (Stefan + Seyfried ) + - Use syslog() for logging (acpid.c, event.c, acpid.h) (Stefan Seyfried + ) + +* Fri Dec 30 2005 Tim Hockin + - Add a do_detach argument to acpid_cleanup_rules() to avoid closing + clients on a HUP (acpid.c, event.c) (Frederic Lepied + ) + +* Sat Sep 24 2005 Tim Hockin + - Document -f option in the acpid man page (acpid.8) + +* Fri Sep 23 2005 Tim Hockin + - Fix rule and fd leak when clients disconnect on socket (event.c) (Timo + Hoenig ) + +* Fri Oct 19 2005 Tim Hockin + - Use socklen_t for sockets calls (ud_socket.c) + +* Sun Oct 24 2004 Tim Hockin + - Update my own email to not say @sun.com anymore + +* Sun Oct 17 2004 Tim Hockin + - mkdir BINDIR in Makefile + +* Sun Oct 17 2004 Tim Hockin + - set acpi_listen stdout to be line-buffered (Gilles Chanteperdrix + ) + - detect a closed socket and exit (acpi_listen.c) + - detect a closed events file and exit (acpid.c) + - print read_line() errors (acpi_listen.c, acpid.c) + - added sample battery config and handler (Frank Dietrich + ) + - added sample AC adapter config/handler and hotkey config/handler for + Panasonic notebooks (David Bronaugh ) + - prep for 1.0.4 release + +* Fri Feb 13 2004 Tim Hockin + - dump debian/ and redhat/ dirs -- too much hassle + - change 'make rpm' to 'make dist' + - minor Makefile cleanup + - README cleanup + - prep for 1.0.3 release + +* Thu Dec 18 2003 Tim Hockin + - unblock signals before exec()ing a handler + - remove odd 'been_here' from signals_handled() (debug artifact?) + +* Mon Nov 24 2003 Tim Hockin + - add -c, --count option to acpi_listen (Luca Capello ) + - add -t, --time option to acpi_listen (Luca Capello ) + - return a meaningful value if we break out of the main loop (acpi_listen.c) + - break out usage() from handle_cmdline() (acpi_listen.c) + +* Mon Nov 17 2003 Tim Hockin + - Decouple logging to stdout from foregrounding + - Add acpi_listen (source and man) + - Add ud_connect() + - Remove (euid == 0) check + - ifdef the bad-kernel checking - it consumes a byte of data! + +* Fri Nov 14 2003 Tim Hockin + - Add -f option (run in foreground) + +* Tue May 13 2003 Tim Hockin + - Fixed a dumb bug with %e expansion for commands + - Add COPYING file + - Add TODO file + +* Fri Mar 15 2002 Tim Hockin + - Updated RPM spec with patch from sun for chkconfig on/off + - Add Changelog, make 'make rpm' use it. + - 1.0.1 + +* Wed Mar 13 2002 Tim Hockin + - Fixed logging bug - not appending to log (O_APPEND needed) + - Fix 'make install' to not need root access + - Fix RPM spec to not need root + +* Thu Sep 6 2001 Tim Hockin + - 1.0.0 + +* Thu Aug 16 2001 Tim Hockin + - Added commandline options to actions + +* Wed Aug 15 2001 Tim Hockin + - Added UNIX domain socket support + - Changed /etc/acpid.d to /etc/acpid/events + +* Mon Aug 13 2001 Tim Hockin + - added changelog + - 0.99.1-1 + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a849fff --- /dev/null +++ b/Makefile @@ -0,0 +1,108 @@ +# Makefile for ACPI daemon + +# update these numbers for new releases +VERSION = 2.0.14 + +OPT = -O2 + +DESTDIR = +PREFIX = /usr + +BINDIR = $(PREFIX)/bin +SBINDIR = $(PREFIX)/sbin +MANDIR = $(PREFIX)/share/man +DOCDIR = $(PREFIX)/share/doc/acpid + +SBIN_PROGS = acpid +BIN_PROGS = acpi_listen +PROGS = $(SBIN_PROGS) $(BIN_PROGS) + +acpid_SRCS = acpid.c acpi_ids.c connection_list.c event.c input_layer.c \ + inotify_handler.c libnetlink.c log.c netlink.c proc.c sock.c ud_socket.c +acpid_OBJS = $(acpid_SRCS:.c=.o) + +acpi_listen_SRCS = acpi_listen.c log.c ud_socket.c +acpi_listen_OBJS = $(acpi_listen_SRCS:.c=.o) + +all_SRCS = $(acpid_SRCS) $(acpi_listen_SRCS) + +MAN8 = acpid.8 acpi_listen.8 +MAN8GZ = $(MAN8:.8=.8.gz) + +DOCS = COPYING Changelog README TESTPLAN TODO + +CFLAGS = -W -Wall -Werror -Wundef -Wshadow -D_GNU_SOURCE $(OPT) \ + -fno-strict-aliasing -g $(DEFS) +DEFS = -DVERSION="\"$(VERSION)\"" + +all: $(PROGS) + +acpid: $(acpid_OBJS) + +acpi_listen: $(acpi_listen_OBJS) + +man: $(MAN8) + for a in $^; do gzip -f -9 -c $$a > $$a.gz; done + +install_docs: + mkdir -p $(DESTDIR)/$(DOCDIR) + for a in $(DOCS); do install -m 0644 $$a $(DESTDIR)/$(DOCDIR) ; done + cp -a samples $(DESTDIR)/$(DOCDIR) + +install: $(PROGS) man install_docs + mkdir -p $(DESTDIR)/$(SBINDIR) + mkdir -p $(DESTDIR)/$(BINDIR) + install -m 0750 acpid $(DESTDIR)/$(SBINDIR) + install -m 0755 acpi_listen $(DESTDIR)/$(BINDIR) + mkdir -p $(DESTDIR)/$(MANDIR)/man8 + install -m 0644 $(MAN8GZ) $(DESTDIR)/$(MANDIR)/man8 +# You might want to run mandb(8) after install in case your system uses it. + +DISTTMP=/tmp +DISTNAME=acpid-$(VERSION) +FULLTMP = $(DISTTMP)/$(DISTNAME) +dist: + rm -rf $(FULLTMP) + mkdir -p $(FULLTMP) + cp -a * $(FULLTMP) + find $(FULLTMP) -type d -name CVS | xargs rm -rf + make -C $(FULLTMP) clean + make -C $(FULLTMP)/kacpimon clean + rm -f $(FULLTMP)/cscope.out + rm -f $(FULLTMP)/*anjuta* + find $(FULLTMP) -name '*~' | xargs rm -f + # Get rid of previous dist + rm -f $(FULLTMP)/$(DISTNAME).tar.gz + tar -C $(DISTTMP) -zcvf $(DISTNAME).tar.gz $(DISTNAME) + rm -rf $(FULLTMP) + +clean: + $(RM) $(PROGS) $(MAN8GZ) *.o .depend + +dep depend: + @$(RM) .depend + @$(MAKE) .depend + +.depend: $(all_SRCS) + @for f in $^; do \ + OBJ=$$(echo $$f | sed 's/\.cp*$$/.o/'); \ + $(CPP) $(PP_INCLUDES) -MM $$f -MT $$OBJ; \ + done > $@ + +# NOTE: 'sinclude' is "silent-include". This suppresses a warning if +# .depend does not exist. Since Makefile includes this file, and this +# file includes .depend, .depend is itself "a makefile" and Makefile is +# dependent on it. Any makefile for which there is a rule (as above for +# .depend) will be evaluated before anything else. If the rule executes +# and the makefile is updated, make will reload the original Makefile and +# start over. +# +# This means that the .depend rule will always be checked first. If +# .depend gets rebuilt, then the dependencies we have already sincluded +# must have been stale. Make starts over, the old dependencies are +# tossed, and the new dependencies are sincluded. +# +# So why use 'sinclude' instead of 'include'? We want to ALWAYS make +# Makefile depend on .depend, even if .depend doesn't exist yet. But we +# don't want that pesky warning. +sinclude .depend diff --git a/README b/README new file mode 100644 index 0000000..7552670 --- /dev/null +++ b/README @@ -0,0 +1,52 @@ +acpid for netlink + +This is Ted Felix's branch of the acpid project +which includes support for netlink and the input layer. + +/proc/acpi/event is a deprecated kernel interface for ACPI events. Newer +kernels rely on netlink and the input layer to send ACPI-related events. +This branch of acpid uses these new interfaces. + +Any comments or patches for this branch should be sent to Ted Felix: +http://www.tedfelix.com + +*********************************************************** +* README for acpid * +* * +* Daemon for Advanced Configuration and Power Interface * +* Tim Hockin * +* * +* * +*********************************************************** + +Feb 13, 2004 + +Overview +-------- + + ACPID used to try to handle events internally. Rather than try to climb + an ever-growing mountain, ACPID now lets YOU define what events to handle. + Any event that publishes itself to /proc/acpi/event can be handled. + + ACPID reads a set of configuration files which define event->action pairs. + This is how you make it do stuff. See the man page for details. + +Implementation status +--------------------- + + This version should have complete support for generic handling of events. + +Requirements +------------ + + For 2.6 and newer 2.4 kernels, ACPI seems to be fully integrated. That + should be all you need. + + The ACPI kernel driver should be working on your system before trying + ACPID. Verify ACPI is loaded by verifying the existence of + /proc/acpi/event. + +Compiling ACPID +--------------- + + type "make" diff --git a/TESTPLAN b/TESTPLAN new file mode 100644 index 0000000..7d91413 --- /dev/null +++ b/TESTPLAN @@ -0,0 +1,89 @@ +acpid Test Plan + +Suggestions + +- Run all these tests with valgrind to detect memory leaks. +- It's best to test without a window manager running (such as GNOME or KDE) as they tend to handle acpi events on their own and override acpid. To bring down X on a system that is configured with a graphical login, there's usually an "init" script you can run. As an example, with Debian/GNOME, log off of your X/GNOME session, switch to another tty (e.g. Alt-Ctrl-F1), login, and do this: + sudo /etc/init.d/gdm stop + It's different if you are using upstart: + sudo initctl stop gdm + And systemd requires a different incantation: + [anyone care to enlighten me?] + Now X is out of the way and you can test from the console. +- You can kill acpid with "sudo killall acpid". Or if you are using upstart: + sudo initctl stop acpid + For systemd: + [anyone?] +- To make testing more convenient, you can run acpid from a shell as "acpid -ld" to get maximum logging. Use Ctrl-C to stop acpid. + +Normal Paths + +* proc fs, all events +Start acpid against /proc/acpi/event (if it exists). +Test each of the following events: +1. Power Button +2. Laptop Lid Switch +3. Sleep Button +4. Laptop AC Adapter +5. Laptop Battery + +* input layer/netlink, all events +Start acpid against the input layer and netlink. +Test each of the following events: +1. Power Button (tests ACPI -> input layer) +2. Laptop Lid Switch (tests ACPI -> input layer) +3. Sleep Button (tests ACPI -> input layer) +4. Laptop AC Adapter (tests ACPI -> netlink) +5. Laptop Battery (tests ACPI -> netlink) +6. Special Keyboard Key(s) + +* input layer/netlink fallback +Start acpid with a bogus events file specified via the options. + acpid -e /proc/acpi/bogus +Make sure a connection is made via the input layer and netlink. + +* lockfile procfs +Start acpid against the proc fs +Try some events and make sure they are coming through. +Create the lockfile. +Try some events and make sure they do not come through. +Remove the lockfile. +Try some events and make sure they are coming through. + +* lockfile netlink +Start acpid against input layer and netlink. +Try some events and make sure they are coming through. +Create the lockfile. +Try some events and make sure they do not come through. +Remove the lockfile. +Try some events and make sure they are coming through. + +* USB disconnect +Start acpid (input layer and netlink) with a second USB keyboard attached. +Try some events and make sure they are coming through. +Disconnect the second USB keyboard. +Make sure acpid is still running. +Try some events and make sure they are coming through. + +* USB connect +Start acpid against input layer and netlink. +Try some events and make sure they are coming through. +Connect a second USB keyboard. +Check logging to see if acpid has found the new keyboard. +Try some events from the second USB keyboard and make sure they are coming through. + + +Debugging Paths + +* event logging +Run acpid without the -l option and make sure no events are logged to syslog. +Run acpid with the -l option and make sure events are logged to syslog. + +* debug mode +Run acpid with the -d option and note that it runs in the foreground and provides debugging info to the console. +acpid also supports up to 4 debug levels in the event handler. Might want to try "-dddd" and see what happens. + +* foreground mode +Run acpid with the -f option and note that it runs in the foreground. +Run acpid without the -f option and note that it runs in the background. + diff --git a/TODO b/TODO new file mode 100644 index 0000000..347442d --- /dev/null +++ b/TODO @@ -0,0 +1,18 @@ +Future Enhancements +------------------- + +systemd support + - Support receiving unix sockets from systemd. + - Make daemonize() do what's right for systemd. + - Can we auto-detect systemd? Maybe the presence of the environment vars + for the unix sockets? + +Look into using libnl for netlink + - Can this be done? + +DBUS support +Re-implement autoconf/automake support +Re-add ancillary files needed for wider distribution +Add a main config file + - support multiple event sources (such as named pipes) +Allow socket-connected clients to filter incoming events diff --git a/acpi_genetlink.h b/acpi_genetlink.h new file mode 100644 index 0000000..ce24e57 --- /dev/null +++ b/acpi_genetlink.h @@ -0,0 +1,33 @@ +#ifndef __ACPI_GENETLINK_H__ +#define __ACPI_GENETLINK_H__ 1 + +#include + +struct acpi_genl_event { + char device_class[20]; + char bus_id[15]; + __u32 type; + __u32 data; +}; + +/* attributes of acpi_genl_family */ +enum { + ACPI_GENL_ATTR_UNSPEC, + ACPI_GENL_ATTR_EVENT, /* ACPI event info needed by user space */ + __ACPI_GENL_ATTR_MAX, +}; +#define ACPI_GENL_ATTR_MAX (__ACPI_GENL_ATTR_MAX - 1) + +/* commands supported by the acpi_genl_family */ +enum { + ACPI_GENL_CMD_UNSPEC, + ACPI_GENL_CMD_EVENT, /* kernel->user notifications for ACPI events */ __ACPI_GENL_CMD_MAX, +}; +#define ACPI_GENL_CMD_MAX (__ACPI_GENL_CMD_MAX - 1) +#define GENL_MAX_FAM_OPS 256 +#define GENL_MAX_FAM_GRPS 256 + +#define ACPI_EVENT_FAMILY_NAME "acpi_event" +#define ACPI_EVENT_MCAST_GROUP_NAME "acpi_mc_group" + +#endif diff --git a/acpi_ids.c b/acpi_ids.c new file mode 100644 index 0000000..f275cfc --- /dev/null +++ b/acpi_ids.c @@ -0,0 +1,259 @@ +/* + * acpi_ids.c - ACPI Netlink Group and Family IDs + * + * Copyright (C) 2008 Ted Felix (www.tedfelix.com) + * Portions from acpi_genl Copyright (C) Zhang Rui + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +/* needed by netlink.h, should be in there */ +#include +#include +#include + +#include "genetlink.h" +#include "libnetlink.h" + +#include "acpid.h" + +#define GENL_MAX_FAM_GRPS 256 +#define ACPI_EVENT_FAMILY_NAME "acpi_event" +#define ACPI_EVENT_MCAST_GROUP_NAME "acpi_mc_group" + +static int initialized = 0; +static __u16 acpi_event_family_id = 0; +static __u32 acpi_event_mcast_group_id = 0; + +/* + * A CTRL_CMD_GETFAMILY message returns an attribute table that looks + * like this: + * + * CTRL_ATTR_FAMILY_ID Use this to make sure we get the proper msgs + * CTRL_ATTR_MCAST_GROUPS + * CTRL_ATTR_MCAST_GRP_NAME + * CTRL_ATTR_MCAST_GRP_ID Need this for the group mask + * ... + */ + +static int +get_ctrl_grp_id(struct rtattr *arg) +{ + struct rtattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1]; + char *name; + + if (arg == NULL) + return -1; + + /* nested within the CTRL_ATTR_MCAST_GROUPS attribute are the */ + /* group name and ID */ + parse_rtattr_nested(tb, CTRL_ATTR_MCAST_GRP_MAX, arg); + + /* if either of the entries needed cannot be found, bail */ + if (!tb[CTRL_ATTR_MCAST_GRP_NAME] || !tb[CTRL_ATTR_MCAST_GRP_ID]) + return -1; + + /* get the name of this multicast group we've found */ + name = RTA_DATA(tb[CTRL_ATTR_MCAST_GRP_NAME]); + + /* if it does not match the ACPI event multicast group name, bail */ + if (strcmp(name, ACPI_EVENT_MCAST_GROUP_NAME)) + return -1; + + /* At this point, we've found what we were looking for. We now */ + /* have the multicast group ID for ACPI events over generic netlink. */ + acpi_event_mcast_group_id = + *((__u32 *)RTA_DATA(tb[CTRL_ATTR_MCAST_GRP_ID])); + + return 0; +} + +/* n = the response to a CTRL_CMD_GETFAMILY message */ +static int +genl_get_mcast_group_id(struct nlmsghdr *n) +{ + /* + * Attribute table. Note the type name "rtattr" which means "route + * attribute". This is a vestige of one of netlink's main uses: + * routing. + */ + struct rtattr *tb[CTRL_ATTR_MAX + 1]; + /* place for the generic netlink header in the incoming message */ + struct genlmsghdr ghdr; + /* length of the attribute and payload */ + int len = n->nlmsg_len - NLMSG_LENGTH(GENL_HDRLEN); + /* Pointer to the attribute portion of the message */ + struct rtattr *attrs; + + if (len < 0) { + fprintf(stderr, "%s: netlink CTRL_CMD_GETFAMILY response, " + "wrong controller message len: %d\n", progname, len); + return -1; + } + + if (n->nlmsg_type != GENL_ID_CTRL) { + fprintf(stderr, "%s: not a netlink controller message, " + "nlmsg_len=%d nlmsg_type=0x%x\n", + progname, n->nlmsg_len, n->nlmsg_type); + return 0; + } + + /* copy generic netlink header into structure */ + memcpy(&ghdr, NLMSG_DATA(n), GENL_HDRLEN); + + if (ghdr.cmd != CTRL_CMD_GETFAMILY && + ghdr.cmd != CTRL_CMD_DELFAMILY && + ghdr.cmd != CTRL_CMD_NEWFAMILY && + ghdr.cmd != CTRL_CMD_NEWMCAST_GRP && + ghdr.cmd != CTRL_CMD_DELMCAST_GRP) { + fprintf(stderr, "%s: unknown netlink controller command %d\n", + progname, ghdr.cmd); + return 0; + } + + /* set attrs to point to the attribute */ + attrs = (struct rtattr *)(NLMSG_DATA(n) + GENL_HDRLEN); + /* Read the table from the message into "tb". This actually just */ + /* places pointers into the message into tb[]. */ + parse_rtattr(tb, CTRL_ATTR_MAX, attrs, len); + + /* if a family ID attribute is present, get it */ + if (tb[CTRL_ATTR_FAMILY_ID]) + { + acpi_event_family_id = + *((__u32 *)RTA_DATA(tb[CTRL_ATTR_FAMILY_ID])); + } + + /* if a "multicast groups" attribute is present... */ + if (tb[CTRL_ATTR_MCAST_GROUPS]) { + struct rtattr *tb2[GENL_MAX_FAM_GRPS + 1]; + int i; + + /* get the group table within this attribute */ + parse_rtattr_nested(tb2, GENL_MAX_FAM_GRPS, + tb[CTRL_ATTR_MCAST_GROUPS]); + + /* for each group */ + for (i = 0; i < GENL_MAX_FAM_GRPS; i++) + /* if this group is valid */ + if (tb2[i]) + /* Parse the ID. If successful, we're done. */ + if (!get_ctrl_grp_id(tb2[i])) + return 0; + } + + return -1; +} + +static int +genl_get_ids(char *family_name) +{ + /* handle to the netlink connection */ + struct rtnl_handle rth; + /* holds the request we are going to send and the reply */ + struct { + struct nlmsghdr n; + char buf[4096]; /* ??? Is this big enough for all cases? */ + } req; + /* pointer to the nlmsghdr in req */ + struct nlmsghdr *nlh; + /* place for the generic netlink header before copied into req */ + struct genlmsghdr ghdr; + /* return value */ + int ret = -1; + + /* clear out the request */ + memset(&req, 0, sizeof(req)); + + /* set up nlh to point to the netlink header in req */ + nlh = &req.n; + /* set up the netlink header */ + nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_type = GENL_ID_CTRL; + + /* clear out the generic netlink message header */ + memset(&ghdr, 0, sizeof(struct genlmsghdr)); + /* set the command we want to run: "GETFAMILY" */ + ghdr.cmd = CTRL_CMD_GETFAMILY; + /* copy it into req */ + memcpy(NLMSG_DATA(&req.n), &ghdr, GENL_HDRLEN); + + /* the message payload is the family name */ + addattr_l(nlh, 128, CTRL_ATTR_FAMILY_NAME, + family_name, strlen(family_name) + 1); + + /* open a generic netlink connection */ + if (rtnl_open_byproto(&rth, 0, NETLINK_GENERIC) < 0) { + fprintf(stderr, "%s: cannot open generic netlink socket\n", + progname); + return -1; + } + + /* + * Send CTRL_CMD_GETFAMILY message (in nlh) to the generic + * netlink controller. Reply will be in nlh upon return. + */ + if (rtnl_talk(&rth, nlh, 0, 0, nlh, NULL, NULL) < 0) { + fprintf(stderr, "%s: error talking to the kernel via netlink\n", + progname); + goto ctrl_done; + } + + /* process the response */ + if (genl_get_mcast_group_id(nlh) < 0) { + fprintf(stderr, "%s: failed to get acpi_event netlink " + "multicast group\n", progname); + goto ctrl_done; + } + + ret = 0; + +ctrl_done: + rtnl_close(&rth); + return ret; +} + +/* initialize the ACPI IDs */ +static void +acpi_ids_init() +{ + genl_get_ids(ACPI_EVENT_FAMILY_NAME); + + initialized = 1; +} + +/* returns the netlink family ID for ACPI event messages */ +__u16 +acpi_ids_getfamily() +{ + /* if the IDs haven't been initialized, initialize them */ + if (initialized == 0) + acpi_ids_init(); + + return acpi_event_family_id; +} + +/* returns the netlink multicast group ID for ACPI event messages */ +__u32 +acpi_ids_getgroup() +{ + /* if the IDs haven't been initialized, initialize them */ + if (initialized == 0) + acpi_ids_init(); + + return acpi_event_mcast_group_id; +} diff --git a/acpi_ids.h b/acpi_ids.h new file mode 100644 index 0000000..52a9ff7 --- /dev/null +++ b/acpi_ids.h @@ -0,0 +1,30 @@ +/* + * acpi_ids.h - ACPI Netlink Group and Family IDs + * + * Copyright (C) 2008 Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef ACPI_IDS_H__ +#define ACPI_IDS_H__ + +/* returns the netlink family ID for ACPI event messages */ +extern __u16 acpi_ids_getfamily(); + +/* returns the netlink multicast group ID for ACPI event messages */ +extern __u32 acpi_ids_getgroup(); + +#endif /* ACPI_IDS_H__ */ diff --git a/acpi_listen.8 b/acpi_listen.8 new file mode 100644 index 0000000..43e95a7 --- /dev/null +++ b/acpi_listen.8 @@ -0,0 +1,47 @@ +.TH acpi_listen 8 "Nov 2003" +.\" Portions Copyright (c) 2003 Sun Microsystems +.\" Copyright (c) 2004 Tim Hockin (thockin@hockin.org) +.\" Some parts (C) 2003 - Gismo / Luca Capello http://luca.pca.it +.SH NAME +acpi_listen \- ACPI event listener +.SH SYNOPSIS +\fBacpi_listen\fP [\fIoptions\fP] + +.SH DESCRIPTION +\fBacpid\fP is the system-wide ACPI event catcher. \fBacpi_listen\fP is a +simple shell-friendly tool which connects to acpid and listens for events. +When an event occurs, acpi_listen will print it on stdout. + +.SH OPTIONS +.TP +.BI \-c "\fR, \fP" \--count " events" +Receive up to a maximum number of ACPI events, then exit. +.TP +.BI \-s "\fR, \fP" \--socketfile " filename" +This option changes the name of the UNIX domain socket which \fBacpid\fP opens. +Default is \fI/var/run/acpid.socket\fP. +.TP +.BI \-t "\fR, \fP" \--time " seconds" +Listen for the specified time in seconds, before exiting. +.TP +.BI \-v "\fR, \fP" \--version +Print version information and exit. +.TP +.BI \-h "\fR, \fP" \--help +Show help and exit. + +.SH FILES +.PD 0 +.B /var/run/acpid.socket +.PD + +.SH BUGS +There are no known bugs. To file bug reports, see \fBAUTHORS\fP below. +.SH SEE ALSO +regcomp(3), sh(1), socket(2), connect(2) +.SH AUTHORS +Tim Hockin +.br +Luca Capello +.br + diff --git a/acpi_listen.c b/acpi_listen.c new file mode 100644 index 0000000..de1da25 --- /dev/null +++ b/acpi_listen.c @@ -0,0 +1,247 @@ +/* + * acpi_listen.c - ACPI client for acpid's UNIX socket + * + * Portions Copyright (C) 2003 Sun Microsystems (thockin@sun.com) + * Some parts (C) 2003 - Gismo / Luca Capello http://luca.pca.it + * Copyright (C) 2004 Tim Hockin (thockin@hockin.org) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acpid.h" +#include "ud_socket.h" + +static int handle_cmdline(int *argc, char ***argv); +static char *read_line(int fd); + +const char *progname; +const char *socketfile = ACPID_SOCKETFILE; +static int max_events; + +static void +time_expired(int signum __attribute__((unused))) +{ + exit(EXIT_SUCCESS); +} + +int +main(int argc, char **argv) +{ + int sock_fd; + int ret; + + /* handle an alarm */ + signal(SIGALRM, time_expired); + + /* learn who we really are */ + progname = (const char *)strrchr(argv[0], '/'); + progname = progname ? (progname + 1) : argv[0]; + + /* handle the commandline */ + handle_cmdline(&argc, &argv); + + /* open the socket */ + sock_fd = ud_connect(socketfile); + if (sock_fd < 0) { + fprintf(stderr, "%s: can't open socket %s: %s\n", + progname, socketfile, strerror(errno)); + exit(EXIT_FAILURE); + } + fcntl(sock_fd, F_SETFD, FD_CLOEXEC); + + /* set stdout to be line buffered */ + setvbuf(stdout, NULL, _IOLBF, 0); + + /* main loop */ + ret = 0; + while (1) { + char *event; + + /* read and handle an event */ + event = read_line(sock_fd); + if (event) { + fprintf(stdout, "%s\n", event); + } else if (errno == EPIPE) { + fprintf(stderr, "connection closed\n"); + break; + } else { + static int nerrs; + if (++nerrs >= ACPID_MAX_ERRS) { + fprintf(stderr, "too many errors - aborting\n"); + ret = 1; + break; + } + } + + if (max_events > 0 && --max_events == 0) { + break; + } + } + + return ret; +} + +static struct option opts[] = { + {"count", 0, 0, 'c'}, + {"socketfile", 1, 0, 's'}, + {"time", 0, 0, 't'}, + {"version", 0, 0, 'v'}, + {"help", 0, 0, 'h'}, + {NULL, 0, 0, 0}, +}; +static const char *opts_help[] = { + "Set the maximum number of events.", /* count */ + "Use the specified socket file.", /* socketfile */ + "Listen for the specified time (in seconds).",/* time */ + "Print version information.", /* version */ + "Print this message.", /* help */ +}; + +static void +usage(FILE *fp) +{ + struct option *opt; + const char **hlp; + int max, size; + + fprintf(fp, "Usage: %s [OPTIONS]\n", progname); + max = 0; + for (opt = opts; opt->name; opt++) { + size = strlen(opt->name); + if (size > max) + max = size; + } + for (opt = opts, hlp = opts_help; opt->name; opt++, hlp++) { + fprintf(fp, " -%c, --%s", opt->val, opt->name); + size = strlen(opt->name); + for (; size < max; size++) + fprintf(fp, " "); + fprintf(fp, " %s\n", *hlp); + } +} + +/* + * Parse command line arguments + */ +static int +handle_cmdline(int *argc, char ***argv) +{ + for (;;) { + int i; + i = getopt_long(*argc, *argv, "c:s:t:vh", opts, NULL); + if (i == -1) { + break; + } + switch (i) { + case 'c': + if (!isdigit(optarg[0])) { + usage(stderr); + exit(EXIT_FAILURE); + } + max_events = atoi(optarg); + break; + case 's': + socketfile = optarg; + break; + case 't': + if (!isdigit(optarg[0])) { + usage(stderr); + exit(EXIT_FAILURE); + } + alarm(atoi(optarg)); + break; + case 'v': + printf(PACKAGE "-" VERSION "\n"); + exit(EXIT_SUCCESS); + case 'h': + usage(stdout); + exit(EXIT_SUCCESS); + default: + usage(stderr); + exit(EXIT_FAILURE); + break; + } + } + + *argc -= optind; + *argv += optind; + + return 0; +} + +#define MAX_BUFLEN 1024 +static char * +read_line(int fd) +{ + static char *buf; + int buflen = 64; + int i = 0; + int r; + int searching = 1; + + while (searching) { + buf = realloc(buf, buflen); + if (!buf) { + fprintf(stderr, "ERR: malloc(%d): %s\n", + buflen, strerror(errno)); + return NULL; + } + memset(buf+i, 0, buflen-i); + + while (i < buflen) { + r = read(fd, buf+i, 1); + if (r < 0 && errno != EINTR) { + /* we should do something with the data */ + fprintf(stderr, "ERR: read(): %s\n", + strerror(errno)); + return NULL; + } else if (r == 0) { + /* signal this in an almost standard way */ + errno = EPIPE; + return NULL; + } else if (r == 1) { + /* scan for a newline */ + if (buf[i] == '\n') { + searching = 0; + buf[i] = '\0'; + break; + } + i++; + } + } + if (buflen >= MAX_BUFLEN) { + break; + } + buflen *= 2; + } + + return buf; +} diff --git a/acpid-design.odg b/acpid-design.odg new file mode 100644 index 0000000000000000000000000000000000000000..d56496297b4a70490d51676d0674c2cae8cb4f95 GIT binary patch literal 14671 zcma)j1ymhL(>89wgS!TI_XKx$ceu#K9Rk7K-6eSNAVC8J5AJTk-Gl#--E6Y^?|$bk zm^nSuRZmw}_w@9wsgjohhd={)CfHum)J0IJ()I7}(lc0gMbB z0X9GeXP_~?je!Hef!@Z}1ZZqyTH79Lw!`)de;SCZdP&QFwM=G52+URg1uA z&x2mud!bzmn!25dCK=!F?U8{j-_ko*qwzM@Cb{#w@i%=d_s7nyrde$7!CMR7mX(hz{3z@u`*i_SJ+1pBl$Q zH|_~zBcI)pa!^3Kp0HCbO=y9$>fVavNRh^yoa4k}bH9#N%10MNEKo;$g*SYeuF25_ zId&np>#WnCxC8HCZ6W{8y(ZOx_BG^6|L2LlBFh?dJr{?A?Z&jD94<|wa(pvMHy&kQ zbpd7H2FtWu!z#14JwphrB^7CV?B%W{Jc$#FxzuZ9(}5ZmE(h2~5v60<+WY-F%TCa; z*9)V%_{w^Aiuy|mQHUjC!J-K3v@KDrBWHLcmB$(I*QO{EBwq-?m#NZlO;C;wZm!by z=FQ(JO;E?J_ha-4zpGBfjzKNqdy(BlVXh$hQRvO?WumMgV;Oioaq})zg~Qj7eNcsD zTnXy1mp))h-KT&;zv`a;hMIZTO_i3~_#CoyQJ+M}xCi0HeuTB%+^;CR^OTQ?Z?#kH z4pS0{-hA$Y5;m!;wP)=qs9AO#@+feKJcSIgu#0S1IG*rR!Rbp0MTr`0T<-tU56Zp? z+1?S2ShNWBHKtZS-P0hJ;)Mb;^u9N}$eY-RPkF8sgL$fSp$JjgH52F8n$Fas%YsbAN&LPAr~#+016LYW5w zQh`ur#^!gl*l@$$(Kbk#!wpXDQ*b zRC5nm)u2tyY=|2zlB-2SGi3&iaZ)E}dm%2SlY6B%9zAuKAswaqp3+@dT^qT;p-_2fj2 z=!{DW)JBtI*b>@pPBMNZCad6U;F~9{AV-2&`i$r+T7Bs* z-1;cSHXR1pZpK{>ERR*vj@pH{anRD#=?59z>z}J1<~s5xz#7uoa1{etX;FKF-`f9zeuIBm&8d+OIm9 z8(W~(^x^~lJV1h5RJ|do@|e=#N@q&jW2}$;YGEp{PJlC{Vx;*7`_P)^%Sqhp1s*AW zh!c;mEYfoAO*S$}R~;8atx?#SlTzufq*d_3jcD=nLg@S zaJM^1ZxE+NV&6ZWzb?mTCO8%?#;U`Rf#lsHG(D6yEzq)NCoNPR?5a*imOz>0A$kgn zRe0S2lO#(#2HC?kh)E?Y-Wt8?YerTUj!}0hxZEGU9frU*-CtKr#=|wCzEfv)V(~qr zw%QsWwi(~zgCnTYJnyDZgbSAtlNhlaGwn`>#y*ye07FJZ{A`C){rrKW2h)b(!OhwA z!sM8Hhe25qx-X!N(h-()*Ywk#oLBvrOz;<>twP5Gs(g`Z5<^B-^))}0L5RLLkP9ky zT>FA`GBmBgX}`QZ=S6UbhIT{Sbijslk+yKB&<07h^-Ed~zCDkFR_L6Et$RWl?GWSb zmU!Tc68RCU$~4jWx~=qSB^4;sn!O#UOb&-ZGbs-bWiOf{vJOS0sFI-M0Jb&W9WaD* zPfS+iPb$QEw;A&6GnOLiW(4WZ#-Wa<~~*c zD;?jy^tH)=g*aMEj6FE=taoe<;ec)K3oz>@zCk7Ij;`ote|OboH?X&%csvXt!nr(y z=_RIg%5==%8eoUYC5kr6YvdY*lOQ3bTVVf2P(#Bu<#r|V9)&5kTt(hvT3R zOD8IlLs=&AI*VpOgw+lO;|=+gq7bmgz`1^1;Vs{LGI!F4fWpe2M~4{>su_odroRW5 zx$-eTMghBHZOQPBij(zW!Z?GEB;&w)0wYcxOjiUUvPp1a21P1;W

i5Vi@aa8i0X zR_^Qr9_4Mz9b(%bCUT$>*f10FHMoc7Jy8o!YywHiT&Yh|AraQ$FhB> z>D6%2SD1;gHd3S+>dJ~$fdeCtv*X_Vo<{ccU(t8ntvOqY&J?5wIucc)1WdE)(DVfe znbgMU*OQr5J>;V%STcq?86z+1dTrkmETVh^cY+pZP`;{!prOw%3D;p3H7q8B275D& zD`jZAxFW7*gCk-gr|Ab%qAE)^SDRt=;wqz~Ki^XBKA>w-ZXdpyj~f-I4#%%!D4ban zJsgFLNsz5PEbPHtSe&njF5~gxzGr73$|Y_Yf%asOU&C%QJ(fp?KTW4~glA>OQO@>N z!!Y(9Nut!8XcF5;l4FRX((MFII64XQP%PbdGWf-6&hPGHkQ+I11s_;l6JZ92f=4cD zgtt$vU?9h5p9;HHFgm}YitzvOt-nKA*j)fsDvUqyIGk} zUNWN!o8X5C8NHfZR+{gsktm@{*|Yg9G$St67fs?jQ`=^cjgCB)!cethOMox2TW8kT z<0HBgEFLi!=k9qb;~E%4lOU3l?B>KX4Siua*{jD*(y-9{mvx>Ps6vHZqIq zpafYm8>LE1@*siv8_;7|Ap$428alQ2jk=$tg8dQE-Uy}cl2@U4YT|`~_F(KzWq zsU>!bp=IJNj;-g4l$>+T=H;&YlFdS;f$-?3Lm0$=fp#3^V@1&OBFR}$gq4_%9?h_l z=}I+J@D2+`Oue=tcPx0=C{C9h%po-J&?PYQ_{z>DdFct6YHBG!mTq#`kk2)l^PNa| zNX-?|O|wcHX}d9JQZ~$?)~i*>HcjVRrbhe3{4tq@@4-=7wVtLINa1l&+)KSVEf zf_veS%`ln{>17M`U>h#G$3hcMkbmaQ8$Gu*S^{odX^=ObRLb_0siw zna?YTt$^ED{;D3 zXaW78JC3M8Ui4Y}C8d*xZts-h-Sv_Kw_~KgnjBRFi&bd&vW9Vqf>#@A!^(yue@##6 zc>Ozpmh_;DsxQ*=Ab~ID?q*GbWffb1EMEUpab_G zReU5)_CW3*t{85hfwjq#F^#x?_sMYo&dvR!p+D}pS^KF7rEN09?+)oIxZF_MqY-0ApX$;00?jd z7(8{+-sIV}#;@PtG&`ClhngF)=;;D>JciGjeh> zv-~OjcRpTYBkq4`=eN7h9RC8z%EZma#>?=t@(&$MjGr_In$a1#89nXN`Ty$SKkomX z+4xUKB)_~e3yTa3hti*l{uKJF{tq!H2NQcbV-r&V@X0Jc?DzcG@sUUXO>KynNErSc zrvHXJUqMiz$pFpqX(&;&_xp6RGPbIdgd#|%k4SZjVk<(Zw=cB z8f#e?V&!Q*W?>dLg{u96Hn5_)0;4$$Ks*p0N=(b~yw(-e_iuaf*rR$CudWU7v}0R% zW+C}$BsZDMX*26=7-+P=`LhU9f%}{%h{o}Ngj3mrfCy0Wg9wv=fRKyAg0TAQgFK+a zfw;pXgAjFtgUatcVh2M49s89}u8g`)w}aT*F44D&HuY^Tye|B&1zzX`HE@20Kg#_| zbnr0G`wk)YY)O)Hc3bYTq1u*lS$?yAo1HYlX?;5R5!W#S#5Bm%<|WqcHERedp+rEF z7S`bbwe%&T6ZdgYu=#Wue7!z!I2mP!XbtJxdQz_wN`0Zj>k8B+vJ;f5j^OcC$Xi&J zz>93r7Uvvx;5_$QtI19bq613{UMeq8nYGovO&vK!-}Xr4#p$GUPnJbZ(d_7udHn|a z%a9lf%t($-7>j6OwmeiP*qa)4RoQ}gBxzP{0~jG@MEO>ll2pYdXvO2s<=_g>m>B#T%Wgy|5B7*g6pLEfzgXSv4!UQXIBoO)Yxe%dRxLP`8v` zemSZp?k;irY@%Zqbara`=S3l?;*?cWu_m*2WGxwl(7cWHn2+y^W$&0Vhdj20x} z+0lO@7X1=~07lrYU0&G~JuUE?%)YrtBh{T#BEOK>a5wwHj!`$n@^fK_~%bWqPBd?tlxwgGf<{0e}%>1cT zjs2k@Wf@9mIOdre7wNmECI`vl!krc*AuDERs27Y66w?CVi?(BoAF>(J5F1mmP!dD} z;-q|y-{Fug@q@aC%U|5~Tfj19+E|x-A!)BjIl?i`VP9h11Vz?N{^YWi6&Ui`Ixm4D z$O8BxVt+L$^Tm9HDNfSf68|U0rG%7_V?GZovU(SaW=_Y)%A10BF62wwFQ(8zqEI|h zhw=#8(>hvw%>-_9F-fbzZm$rxLVrjZ)NmmcSwT&7MC62mk$huga!yWBam z@~f3B((U`GFYcF2eC(TBncP?PR&rIbv3GLit#?k(M*A=VSU4weGGTU%&CM|J!lmtw z(f!b=MNLeI2fr`o42LtW(7y)VhI>I*9&-29Ha4@Xmino`vnYe;%Whh4$wgTv-k5_Z z#i>w=RTSvCzGDTiwR4sW?Px@;5kAcR%{`GxOOhS|nJy1u%)vWzwgZrUiB!ajMwP8N zdE5;&8_pH*Pop~Sw3^hfh4AJTrYR1N_&Y}EnX)<5P7&I z__k}T*tq~BZTl+_Q9hrIxUal<5&MJ;FbE)8Ln`1!@>8lKbF0m-79E5COTu+D192K|~q^Vzr^bcV&62jy-SR)ys8rpt6NH5Wz0}&gnMCZ&&QIm={)o z;^$rWDE zY38bqd#Dct`MO;SD=RF?`xI{@hY?Q*9KPAGh03NVPBW6~%Zuy94b8%rm?t6P_eZ8I zjW>p8=B&3hi_3ye8#;PnF#v>10K5`>y*qmwoC9Nwf^&z_G({RRYo}p7(w7f&{dV>3 zt`n+D#28FdmNuqMkzH^P^jljy;je-n^19Tvlkla@wM5@=YV=VcoI{!kLBFti>kz2y ze6UHRS`-YO4I20BB!of@ENK)|tV2L7UyiqDAhJ9n<(Iwzf?nr|EmdV4A=Y|weTL@K zSfOw4yG0qc_^v4U*m`YY99G`M7VPGn*5%<5yJpXJZTsE0B43zQ@$iWGGh25z2}tF4 zFWyVPTDQHukHnDPOJb_7T?;SpQb9PBAda&RT8V4#B{U1BWwYnd1Hqv~c)gusbtz=i z$4exuF9i)Atq+EBgW=nQ8}|-Hkd`7DBg>`;{Jx;l@ytN-#2RU1YABs&KoeoWA9|do z**sQZ8wIW%UA_0cK+QTYUq~Zx)hOP=%nQHJc`)f3&H4KFXq}NhQ!g~i7u$29JY1;` zSdXK57ai3=6wBtn^_ZweGeIbaH(lcl=@7A6elxDdAi3YBk>hRIP1hBkHc#dj;_k*o z@xkBDhsN$haiQgvg5Sq);%T2E(mT(H%?SKoNn#!ae|RXW7qGp*2hhxWW`8z~8gN2_bg8L@7b_Dk#kLJ;u~2*5 z96lAtB2Rbir==lE5Ik?bwwE1beV>_i-y!?zjtihIB*OftvXaqhOHw4z9|!KrG*7ge zsu*?|x=_9Rc1@b%HaHe+?>d$g_8S%ivvMG?F<6nref2Y1|HxrT>`=yb8k;#mIlq-% zeb?RP>cu&0`gg9vj#_tPOom<4|NM3davgoP7|n zRi5WN{xZpR(I2=gDIO)Rf!9siI~vt##&eT~RE_2nr=yYPIw;tmKHB8YC%;OV(eG;( zqJsIBocoqR=TMH&NEifuZcuLLczEtm?SeWe(t=dcs0XAw@Je(phz8bV`N2)l?wE$8eI`>*YeRZ2X>ykl@j>~EO2fBMX~y~DuP@#>qim|b)tT9TebUZL_nxMn zD2#qAt#^R%;>_hT<}9o%3sJTpr?{DhVoJ{6ZGZovGIraBi_el2tpQa@4xMh}(LpJy zk7+dW69)WR6lz+g1?I8gGA19^oeuE@g27$cH8bbHN=&04K%aVx(L-{X>ZErz!pb;n z-UsD+cq!pvr1-%1&QVqgfA!s-(iIiidiu~?5Y6-4NN+|kIrmBiG?P9b?7EaNEdWoF z?KY1&iQnt-!b6{q?GNSA&eGxGvSog+nymELv9ZOK=#`D&9q=dC=3PHQQU^)Cg}61w zDM9Q&rEcA(t#HT_aYKd2O?uG^?8}({hCM{;FsIi`T2tUq(qx^YX`Cd4pA!Lk>yf2| zUzMI2qS!PYs+-sk`h8$x0W~X`Lhpce4k=#72D+@=F^B_R7%8^LGx+5o}=$l z%MrNQX#{N(3Y3?=5MuTgiL97Ry^4M5TXrl-)N^LLm10p6q)pre&WjRP@sbWJpP>d> zVt~p7dvPh?1#BMOq~*MMUi)6dK@>k$eF|UsAk25ASh0h&;ZFYI6^N^}tciHwkv$^?+pfRxd0rL%lrq_|^e2XaXt4Q+(TyRCS2t|VNIKQ=UqR^=Hm zn-XJr&-Q3)YPJ|rq<;cZ$K3j0IqxjD^e&#O_+WXPIa_30;T4yzd!I$8a|N1? zcEFuMPrJQAr#OnUX-TI`DWWnU3gZ!)_MB`S9+?6x3BR3=z!Wr`X=@Oc|M;+Vv%cM2 zYMlwhaQ+?;f|Kq8&NVZ;cn1SHM}rxXIqa&;kH!a0mYj-dDZ z9M_3drAm<TG^<(S)|ckQCZ67-qrD^2+wEj2&^jR^k!0Y9B=4T8ZHs_&+R zc>E)RNrU7OtOtv~jnGb$>Og_Ug>%}sH^`Kb)QF1CU+>-_6XrABrjU^Bczil`^Px(IDo;IS0pbKj^JAoW}v zqafaLhRxx^$q?@|?c&fN*|2&gmr8W@PJ>Dk9!a69?6(xB6jO;c{>5btDXkc0>^aFd z<@NQ_FEthi2yqB5CRKTP5=Ty~zQrgV>werT@@Uzlhu@={LV zbyW7jUcTx~y{`>(YPyy&Gmb)Mej(L}UAC~@*nPWDc^~1|q`c@!$EAUz<8_nrS@!O` z&8?DpVQo3O$8Hx#u#WD@06?jaSHzg2Eb`XYZDU-cU5}H_>vMx*7&oaG-b=rP5l-dy zTl$ISK-l%>E9mA!Iheel^%_ZG~rg^xy@s$Tr`3}cJ?riY}@?}_nwzUj&#UXM19_0gQh*fRHH^j|X47J#k zs?ac9O`407ssyly%9|QC)s@zbk=zPIV?K@%2~wsIrsK+yq1Giap5cXI0f6gbHq1PU zzVRydS9EN?d9hg>qgEgCKbDee#oXx`>qF&H<|TTz54kqpSK~vQSzStZ_Jv63A6n&} z0Gn(Wbu&Bij)=Dq3MlZQ#Zb;$9_qP2swT%&DC;vtC8ezFvFC*;wviJP{)vlA1uOgMGlw8}(4^eBDY;FSdGehhe>!9d6B zF^GtZelBx6rApUJHYRZVenD=_eS5a1yGDi(S4lGKJvHh!;*Cu2rsLAV;+0BQ6L6Ms z1emTRMc(LzXk`fejH|EnxB;>rWaaO*7oOGpUhha}!7Sv}>+*4Hi5&GCV-7_~clM$B zt*PBJ9JKD^m+RY9Lo`*hF0=uc8}qlj$5#fK4x&d1y5(qc(sqY1>#`}%F2v#u*Z1dd zI#0jeeQcz?m`OMk=N^jpJPH>{^eK-wCch9o3@d`w=Vi%Cz;(^FAmOJ1$RIZG$dIkt zeC_`JmO9b8uy70R?b5qMa?{pj7VNt^f5VV%n*hYUy^TX!+lT`5GmoGcq~g5KOaq+? z;+tf6g|v|Fxv_mvklY-OY@@Ovp@Mb7ND$NCE(2b^jJTDKU}^J&!>i)W&z3RO`Pz3+ zhH_)16Yg^6#@fG}N5I)M-ig_gVpk6vCN3r|u3?SVogOoWACgN!`idSr#UeT4cA`C> zudCK^YZxr}zOqfu%UKN0evCS(V3>2A4YyyV`{|8oP>Vq(OEd4^bv(F{UNPMfwec-h zb)7c0d1ecr&8j<*C{vU$4ma*)gp_Q!zy$&IU;ykVem?h@vU7=kOjKd8cXgpCIxVYT zG#nMWkyHE=>Wrs%2T9+k)-1hWBsl%Xy@E5HUE>@6@Bq4Xxo<@%@wiNiq3YfsB7GzX z*)Q^Gqz9xZhZe4jj$DE4ad{>;u2Wq(Vm7zv4$Q7VyGz^3P>cD@l{V1Q^OvyeVoQGn{WD}zJ+ zij4I;(kF`E3`{8gh|<&SRA-r;Rbx|RSC<0`cl$<-4`1ABtdx7!ids2_5+$yi_LSK) z3=gUskh~%84!XNcrc_(4ZweLOGc24?dyG})SqSvt7Mzg=F=qM>J(hEBPd2b_W1b*w zRrL+!o68>99)arVYeFFQ&rhfS7J3E$9C~Hq`22C_NAT66rk2ARJKEc$ieA^75q@O) z*{8X-4}C!|R%F>;`$jc56V!D>r1iIT9R>q;3nP*wYLMQ$f$-V2VD#L@Uyvh*|KU4^HkS zCfUvzi|dcRrvw{~e;e2JdU`%Jtu!sr>f&2~{>{YcregP?g;SL}9)oNoHd5(DSXfwz zTazx112P<(1gR}k#~@$YNB%q(JPZ+yvZaM#EDv-|8+9sE2%8Cutdi2U=~5AY2?hF= zy0scgRc5)qv>HJ)F%g6FIfSxtzhy?3Mav57CU(Frvx?(Io-euZ+>+Ri(ctuH{O+I` ze3+WtarI@(;u*q=GU*_2oSGS}w)g?{mM_Zg2mKNU<0ej+TS7D7$$iqtH?w_PW82;< zuZL<@YTb$$NGz{CblZ(!?zmaC%VCrH6isImlVrm`AecGch9kXO!Xyn{V;ZM*JC3Cw z9}S}P=lLQe9l(Qn@8v`FO%ZP;nV`VikHMzFeLLNxcY@qL?`t&K8pf!P&vrDVCby&B zLE~-`O=L$*qXpSA?wmo3bVxyQkcm;N2 z66Vc@A=L&^`i}s3hEwj(pSW7XF#@wW#8~~s>UOE{idaZEL6{&OdNF3%>uBRIs?5Qo z-ZMk4&1V7x8}n~JN%zvxcM zZ@UA}WboiR$5p|8dRbUljpxkoMN3;PV)P7ZibmiumJ;->3~ULB%xaIX%z?TJ zc4rJVxm%QoyLd^DuaSt`NXgj5;Z*9i^LsV%uVq}|QnJZ8{^BA$v9pt3#HwWi8aMsSPV+55IB#%jPUEN_L+wz6eK6DMKUunwe;6(_)2EHDM7FB}JOR#Txo zu96}gj5PYqRNi8FaJgbZ5deU?bb0 zI>tPH%N1JuHp8<^FOzGZlg3o|rmfvxFF_%5WlC6KaMd^#$jN%sQVLsdfMo;*90r7E z^j44-!M&ea{gRlNvw|Z3nQ~TEI%s4UFnHlt`Y9u#{Dw|K zR+Pc|De}P7#NkJ-Ol^XcMJFTj>npy1(IwS+6(&0{x^svf&{wB3DBD4%XtDEZh~LK9 z;|tVz5X6rnL~#IBb6QFVjIo#HFXaaFibklGEjgUmjP7;Yf+tT64BB@#~uqD(a|z*$(?A=1>Bub&|o&_Mn>nd&((=okG= zt@@|bFAflp=VYT_r0`VyXTs9&`BQ(i{`))hXA;zN2GuVLc`E)B=RfkP{x9X8;{blq z%2V;5pue)I{%)9GQvd8C#*-=kiSm1P)qmqW=U4qA*?+_NTej7IBmKQ!!T*BvEBoqy zMQQyPlwa9c|BdpTkM)br{{`j$%g*`(?B^`_J-q!nKkFAUJxvayU)fqeNBXDW?{RI< z(eJ+q9`^TG_@8pm_0PF-|BQP3B`1yYlu!5+%=8=s_fzU;Yrk&=|89RQjQ@@{`6tTn z3*z&(@fQWr|9unrPs!gO|EWj literal 0 HcmV?d00001 diff --git a/acpid.8 b/acpid.8 new file mode 100644 index 0000000..6d93e7d --- /dev/null +++ b/acpid.8 @@ -0,0 +1,170 @@ +.TH acpid 8 "" +.\" Portions Copyright (c) 2001 Sun Microsystems +.\" Portions Copyright (c) Tim Hockin (thockin@hockin.org) +.SH NAME +acpid \- Advanced Configuration and Power Interface event daemon +.SH SYNOPSIS +\fBacpid\fP [\fIoptions\fP] + +.SH DESCRIPTION +\fBacpid\fP is designed to notify user-space programs of ACPI events. +\fBacpid\fP should be started during the system boot, and will run as a +background process, by default. It will open an events file +(\fI/proc/acpi/event\fP by default) and attempt to read whole lines which +represent ACPI events. If the events file does not exist, \fBacpid\fP will +attempt to connect to the Linux kernel via the input layer and netlink. When an +ACPI event is received from one of these sources, \fBacpid\fP will examine a +list of rules, and execute the rules that match the event. \fBacpid\fP will +ignore all incoming ACPI events if a lock file exists (\fI/var/lock/acpid\fP by +default). +.PP +\fIRules\fP are defined by simple configuration files. \fBacpid\fP +will look in a configuration directory (\fI/etc/acpi/events\fP by default), +and parse all regular files with names that consist entirely of upper and +lower case letters, digits, underscores, and hyphens (similar to +run-parts(8)). +.\" that do not begin with a period ('.') or end with a tilde (~). +Each file must define two things: an \fIevent\fP and an +\fIaction\fP. Any blank lines, or lines where the first character is a +hash ('#') are ignored. Extraneous lines are flagged as warnings, but +are not fatal. Each line has three tokens: the key, a literal equal sign, +and the value. The key can be up to 63 characters, and is case-insensitive +(but whitespace matters). The value can be up to 511 characters, and is +case and whitespace sensitive. +.PP +The event value is a regular expression (see regcomp(3)), against which events are matched. +.PP +The action value is a commandline, which will be invoked via \fI/bin/sh\fP +whenever an event matching the rule in question occurs. The commandline may +include shell-special characters, and they will be preserved. The only special +characters in an action value are "%" escaped. The string "%e" will be +replaced by the literal text of the event for which the action was invoked. +This string may contain spaces, so the commandline must take care to quote the "%e" if it wants a single token. The string "%%" will be replaced by a +literal "%". All other "%" escapes are reserved, and will cause a rule to +not load. +.PP +This feature allows multiple rules to be defined for the same event (though no +ordering is guaranteed), as well as one rule to be defined for multiple events. +To force \fBacpid\fP to reload the rule configuration, send it a SIGHUP. +.PP +In addition to rule files, \fBacpid\fP also accepts connections on a UNIX +domain socket (\fI/var/run/acpid.socket\fP by default). Any application may +connect to this socket. Once connected, \fBacpid\fP will send the text of +all ACPI events to the client. The client has the responsibility of filtering +for messages about which it cares. \fBacpid\fP will not close the client +socket except in the case of a SIGHUP or \fBacpid\fP exiting. +.PP +For faster startup, this socket can be passed in as stdin so that \fBacpid\fP +need not create the socket. In addition, if a socket is passed in as stdin, +\fBacpid\fP will not daemonize. It will be run in foreground. This behavior +is provided to support systemd(1). +.PP +.B acpid +will log all of its activities, as well as the stdout and stderr of any +actions, to syslog. +.PP +All the default files and directories can be changed with commandline options. +.SH OPTIONS +.TP 12 +.BI \-c "\fR, \fP" \-\-confdir " directory" +This option changes the directory in which \fBacpid\fP looks for rule +configuration files. Default is \fI/etc/acpi/events\fP. +.TP 12 +.BI \-C "\fR, \fP" \-\-clientmax " number" +This option changes the maximum number of non-root socket connections which +can be made to the \fBacpid\fP socket. Default is \fI256\fP. +.TP 12 +.BI \-d "\fR, \fP" \-\-debug +This option increases the \fBacpid\fP debug level by one. If the debug level +is non-zero, \fBacpid\fP will run in the foreground, and will log to +stderr, in addition to the regular syslog. +.TP +.BI \-e "\fR, \fP" \-\-eventfile " filename" +This option changes the event file from which \fBacpid\fP reads events. +Default is \fI/proc/acpi/event\fP. +.TP +.BI \-n "\fR, \fP" \-\-netlink +This option forces \fBacpid\fP to use the Linux kernel input layer and netlink interface for ACPI events. +.TP +.BI \-f "\fR, \fP" \-\-foreground +This option keeps \fBacpid\fP in the foreground by not forking at startup. +.TP +.BI \-l "\fR, \fP" \-\-logevents +This option tells \fBacpid\fP to log information about all events and actions. +.TP +.BI \-L "\fR, \fP" \-\-lockfile " filename" +This option changes the lock file used to stop event processing. +Default is \fI/var/lock/acpid\fP. +.TP +.BI \-g "\fR, \fP" \-\-socketgroup " groupname" +This option changes the group ownership of the UNIX domain socket to which +\fBacpid\fP publishes events. +.TP +.BI \-m "\fR, \fP" \-\-socketmode " mode" +This option changes the permissions of the UNIX domain socket to which +\fBacpid\fP publishes events. Default is \fI0666\fP. +.TP +.BI \-s "\fR, \fP" \-\-socketfile " filename" +This option changes the name of the UNIX domain socket which \fBacpid\fP opens. +Default is \fI/var/run/acpid.socket\fP. +.TP +.BI \-S "\fR, \fP" \-\-nosocket " filename" +This option tells \fBacpid\fP not to open a UNIX domain socket. This +overrides the \fI-s\fP option, and negates all other socket options. +.TP +.BI \-p "\fR, \fP" \-\-pidfile " filename" +This option tells \fBacpid\fP to use the specified file as its pidfile. If +the file exists, it will be removed and over-written. +Default is \fI/var/run/acpid.pid\fP. +.TP +.BI \-v "\fR, \fP" \-\-version +Print version information and exit. +.TP +.BI \-h "\fR, \fP" \-\-help +Show help and exit. +.SH EXAMPLE +This example will shut down your system if you press the power button. +.PP +Create a file named /etc/acpi/events/power that contains the following: +.IP +.br +event=button/power +.br +action=/etc/acpi/power.sh "%e" +.PP +Then create a file named /etc/acpi/power.sh that contains the following: +.IP +/sbin/shutdown \-h now "Power button pressed" +.PP +Now, when \fBacpid\fP is running, a press of the power button will cause the +rule in /etc/acpi/events/power to trigger the script in /etc/acpi/power.sh. +The script will then shut down the system. +.SH DEPENDENCIES +\fBacpid\fP should work on any linux kernel released since 2003. +.SH FILES +.PD 0 +.B /proc/acpi/event +.br +.B /dev/input/event* +.br +.B /etc/acpi/ +.br +.B /var/run/acpid.socket +.br +.B /var/run/acpid.pid +.br +.B /var/lock/acpid +.br +.PD +.SH BUGS +There are no known bugs. To file bug reports, see \fBAUTHORS\fP below. +.SH SEE ALSO +regcomp(3), sh(1), socket(2), connect(2), systemd(1), acpi_listen(8), +kacpimon(8) +.SH AUTHORS +Ted Felix (www.tedfelix.com) +.br +Tim Hockin +.br +Andrew Henroid + diff --git a/acpid.c b/acpid.c new file mode 100644 index 0000000..962e577 --- /dev/null +++ b/acpid.c @@ -0,0 +1,474 @@ +/* + * acpid.c - ACPI daemon + * + * Portions Copyright (C) 2000 Andrew Henroid + * Portions Copyright (C) 2001 Sun Microsystems + * Portions Copyright (C) 2004 Tim Hockin (thockin@hockin.org) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acpid.h" +#include "log.h" +#include "event.h" +#include "connection_list.h" +#include "proc.h" +#include "sock.h" +#include "input_layer.h" +#include "inotify_handler.h" +#include "netlink.h" + +static int handle_cmdline(int *argc, char ***argv); +static void close_fds(void); +static int daemonize(void); +static void open_log(void); +static int std2null(void); +static int create_pidfile(void); +static void clean_exit(int sig); +static void reload_conf(int sig); + +/* global debug level */ +int acpid_debug; + +/* do we log event info? */ +int logevents; + +const char *progname; +static const char *confdir = ACPID_CONFDIR; +static const char *lockfile = ACPID_LOCKFILE; +static int nosocket; +static int foreground; +static const char *pidfile = ACPID_PIDFILE; +static int netlink; + +int +main(int argc, char **argv) +{ + /* learn who we really are */ + progname = (const char *)strrchr(argv[0], '/'); + progname = progname ? (progname + 1) : argv[0]; + + /* handle the commandline */ + handle_cmdline(&argc, &argv); + + /* close any extra file descriptors */ + close_fds(); + + /* open the log */ + open_log(); + + if (!netlink) { + /* open the acpi event file in the proc fs */ + /* if the open fails, try netlink */ + if (open_proc()) + netlink = 1; + } + + if (netlink) { + /* open the input layer */ + open_input(); + + /* watch for new input layer devices */ + open_inotify(); + + /* open netlink */ + open_netlink(); + } + + /* open our socket */ + if (!nosocket) { + open_sock(); + } + + /* if we're running in the background, and we're not being started */ + /* by systemd */ + if (!foreground && !is_socket(STDIN_FILENO)) { + if (daemonize() < 0) + exit(EXIT_FAILURE); + } + + /* redirect standard files to /dev/null */ + if (std2null() < 0) { + exit(EXIT_FAILURE); + } + + acpid_log(LOG_INFO, "starting up with %s", + netlink ? "netlink and the input layer" : "proc fs"); + + /* trap key signals */ + signal(SIGHUP, reload_conf); + signal(SIGINT, clean_exit); + signal(SIGQUIT, clean_exit); + signal(SIGTERM, clean_exit); + signal(SIGPIPE, SIG_IGN); + + /* read in our configuration */ + if (acpid_read_conf(confdir)) { + exit(EXIT_FAILURE); + } + + /* create our pidfile */ + if (create_pidfile() < 0) { + exit(EXIT_FAILURE); + } + + acpid_log(LOG_INFO, "waiting for events: event logging is %s", + logevents ? "on" : "off"); + + /* main loop */ + while (1) { + fd_set readfds; + int nready; + int i; + struct connection *p; + + /* it's going to get clobbered, so use a copy */ + readfds = *get_fdset(); + + /* wait on data */ + nready = select(get_highestfd() + 1, &readfds, NULL, NULL, NULL); + + if (nready < 0 && errno == EINTR) { + continue; + } else if (nready < 0) { + acpid_log(LOG_ERR, "select(): %s", strerror(errno)); + continue; + } + + /* house keeping */ + acpid_close_dead_clients(); + + /* for each connection */ + for (i = 0; i <= get_number_of_connections(); ++i) { + int fd; + + p = get_connection(i); + + /* if this connection is invalid, bail */ + if (!p) + break; + + /* get the file descriptor */ + fd = p->fd; + + /* if this file descriptor has data waiting */ + if (FD_ISSET(fd, &readfds)) { + /* delegate to this connection's process function */ + p->process(fd); + } + } + } + + clean_exit_with_status(EXIT_SUCCESS); + + return 0; +} + +/* + * Parse command line arguments + */ +static int +handle_cmdline(int *argc, char ***argv) +{ + struct option opts[] = { + {"confdir", 1, 0, 'c'}, + {"clientmax", 1, 0, 'C'}, + {"debug", 0, 0, 'd'}, + {"eventfile", 1, 0, 'e'}, + {"foreground", 0, 0, 'f'}, + {"logevents", 0, 0, 'l'}, + {"socketgroup", 1, 0, 'g'}, + {"socketmode", 1, 0, 'm'}, + {"socketfile", 1, 0, 's'}, + {"nosocket", 1, 0, 'S'}, + {"pidfile", 1, 0, 'p'}, + {"lockfile", 1, 0, 'L'}, + {"netlink", 0, 0, 'n'}, + {"version", 0, 0, 'v'}, + {"help", 0, 0, 'h'}, + {NULL, 0, 0, 0}, + }; + const char *opts_help[] = { + "Set the configuration directory.", /* confdir */ + "Set the limit on non-root socket connections.",/* clientmax */ + "Increase debugging level (implies -f).",/* debug */ + "Use the specified file for events.", /* eventfile */ + "Run in the foreground.", /* foreground */ + "Log all event activity.", /* logevents */ + "Set the group on the socket file.", /* socketgroup */ + "Set the permissions on the socket file.",/* socketmode */ + "Use the specified socket file.", /* socketfile */ + "Do not listen on a UNIX socket (overrides -s).",/* nosocket */ + "Use the specified PID file.", /* pidfile */ + "Use the specified lockfile to stop processing.", /* lockfile */ + "Force netlink/input layer mode. (overrides -e)", /* netlink */ + "Print version information.", /* version */ + "Print this message.", /* help */ + }; + struct option *opt; + const char **hlp; + int max, size; + + for (;;) { + int i; + i = getopt_long(*argc, *argv, + "c:C:de:flg:m:s:Sp:L:nvh", opts, NULL); + if (i == -1) { + break; + } + switch (i) { + case 'c': + confdir = optarg; + break; + case 'C': + clientmax = strtol(optarg, NULL, 0); + break; + case 'd': + foreground = 1; + acpid_debug++; + log_debug_to_stderr = 1; + break; + case 'e': + eventfile = optarg; + break; + case 'f': + foreground = 1; + break; + case 'l': + logevents = 1; + break; + case 'g': + socketgroup = optarg; + break; + case 'm': + socketmode = strtol(optarg, NULL, 8); + break; + case 's': + socketfile = optarg; + break; + case 'S': + nosocket = 1; + break; + case 'p': + pidfile = optarg; + break; + case 'L': + lockfile = optarg; + break; + case 'n': + netlink = 1; + break; + case 'v': + printf(PACKAGE "-" VERSION "\n"); + exit(EXIT_SUCCESS); + case 'h': + default: + fprintf(stderr, "Usage: %s [OPTIONS]\n", progname); + max = 0; + for (opt = opts; opt->name; opt++) { + size = strlen(opt->name); + if (size > max) + max = size; + } + for (opt = opts, hlp = opts_help; + opt->name; + opt++, hlp++) { + fprintf(stderr, " -%c, --%s", + opt->val, opt->name); + size = strlen(opt->name); + for (; size < max; size++) + fprintf(stderr, " "); + fprintf(stderr, " %s\n", *hlp); + } + exit(EXIT_FAILURE); + break; + } + } + + *argc -= optind; + *argv += optind; + + return 0; +} + +static void +close_fds(void) +{ + int fd, max; + max = sysconf(_SC_OPEN_MAX); + for (fd = 3; fd < max; fd++) + close(fd); +} + +static int +daemonize(void) +{ + pid_t pid, sid; + + /* fork off the parent process */ + pid = fork(); + if (pid < 0) { + acpid_log(LOG_ERR, "fork: %s", strerror(errno)); + return -1; + } + /* if we got a good PID, then we can exit the parent process */ + if (pid > 0) { + exit(EXIT_SUCCESS); + } + + /* at this point we are executing as the child process */ + + /* change the umask to something predictable instead of inheriting */ + /* whatever from the parent */ + umask(0); + + /* create a new SID for the child process and */ + /* detach the process from the parent (normally a shell) */ + sid = setsid(); + if (sid < 0) { + acpid_log(LOG_ERR, "setsid: %s", strerror(errno)); + return -1; + } + + /* Change the current working directory. This prevents the current + directory from being locked; hence not being able to remove it. */ + if (chdir("/") < 0) { + acpid_log(LOG_ERR, "chdir(\"/\"): %s", strerror(errno)); + return -1; + } + + return 0; +} + +static void +open_log(void) +{ + int log_opts; + + /* open the syslog */ + log_opts = LOG_CONS|LOG_NDELAY; + if (acpid_debug) { + log_opts |= LOG_PERROR; + } + openlog(PACKAGE, log_opts, LOG_DAEMON); +} + +static int +std2null(void) +{ + int nullfd; + + /* open /dev/null */ + nullfd = open("/dev/null", O_RDWR); + if (nullfd < 0) { + acpid_log(LOG_ERR, "can't open /dev/null: %s", strerror(errno)); + return -1; + } + + /* set up stdin, stdout, stderr to /dev/null */ + + /* don't redirect stdin if we're being sent a socket by systemd */ + if (!is_socket(STDIN_FILENO) && + dup2(nullfd, STDIN_FILENO) != STDIN_FILENO) { + acpid_log(LOG_ERR, "dup2() stdin: %s", strerror(errno)); + return -1; + } + if (!acpid_debug && dup2(nullfd, STDOUT_FILENO) != STDOUT_FILENO) { + acpid_log(LOG_ERR, "dup2() stdout: %s", strerror(errno)); + return -1; + } + if (!acpid_debug && dup2(nullfd, STDERR_FILENO) != STDERR_FILENO) { + acpid_log(LOG_ERR, "dup2() stderr: %s", strerror(errno)); + return -1; + } + + close(nullfd); + + return 0; +} + +static int +create_pidfile(void) +{ + int fd; + + /* JIC */ + unlink(pidfile); + + /* open the pidfile */ + fd = open(pidfile, O_WRONLY|O_CREAT|O_EXCL, 0644); + if (fd >= 0) { + FILE *f; + + /* write our pid to it */ + f = fdopen(fd, "w"); + if (f != NULL) { + fprintf(f, "%d\n", getpid()); + fclose(f); + /* leave the fd open */ + return 0; + } + close(fd); + } + + /* something went wrong */ + acpid_log(LOG_ERR, "can't create pidfile %s: %s", + pidfile, strerror(errno)); + return -1; +} + +void +clean_exit_with_status(int status) +{ + acpid_cleanup_rules(1); + acpid_log(LOG_NOTICE, "exiting"); + unlink(pidfile); + exit(status); +} + +static void +clean_exit(int sig __attribute__((unused))) +{ + clean_exit_with_status(EXIT_SUCCESS); +} + +static void +reload_conf(int sig __attribute__((unused))) +{ + acpid_log(LOG_NOTICE, "reloading configuration"); + acpid_cleanup_rules(0); + acpid_read_conf(confdir); +} + +int +locked() +{ + struct stat trash; + + /* check for existence of a lockfile */ + return (stat(lockfile, &trash) == 0); +} + diff --git a/acpid.h b/acpid.h new file mode 100644 index 0000000..350edf1 --- /dev/null +++ b/acpid.h @@ -0,0 +1,53 @@ +/* + * acpid.h - ACPI daemon + * + * Copyright (C) 1999-2000 Andrew Henroid + * Copyright (C) 2001 Sun Microsystems + * Portions Copyright (C) 2004 Tim Hockin (thockin@hockin.org) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef ACPID_H__ +#define ACPID_H__ + +#define ACPI_PROCDIR "/proc/acpi" +#define ACPID_EVENTFILE ACPI_PROCDIR "/event" +#define ACPID_CONFDIR "/etc/acpi/events" +#define ACPID_SOCKETFILE "/var/run/acpid.socket" +#define ACPID_SOCKETMODE 0666 +#define ACPID_CLIENTMAX 256 +#define ACPID_PIDFILE "/var/run/acpid.pid" +#define ACPID_LOCKFILE "/var/lock/acpid" +#define ACPID_MAX_ERRS 5 + +/* ??? make these changeable by commandline option? */ +#define ACPID_INPUTLAYERDIR "/dev/input" +#define ACPID_INPUTLAYERFILES ACPID_INPUTLAYERDIR "/event*" + +#define PACKAGE "acpid" + +/* + * acpid.c + */ +extern int acpid_debug; +extern int logevents; +extern const char *progname; + +extern int locked(); + +extern void clean_exit_with_status(int status); + +#endif /* ACPID_H__ */ diff --git a/connection_list.c b/connection_list.c new file mode 100644 index 0000000..0fc7c53 --- /dev/null +++ b/connection_list.c @@ -0,0 +1,182 @@ +/* + * connection_list.c - ACPI daemon connection list + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Tabs at 4 + */ + +#include +#include +#include +#include + +#include "acpid.h" +#include "log.h" + +#include "connection_list.h" + +#define max(a, b) (((a)>(b))?(a):(b)) + +/*---------------------------------------------------------------*/ +/* private objects */ + +#define MAX_CONNECTIONS 20 + +static struct connection connection_list[MAX_CONNECTIONS]; + +static int nconnections = 0; + +/* fd_set containing all the fd's that come in */ +static fd_set allfds; + +/* highest fd that is opened */ +/* (-2 + 1) causes select() to return immediately */ +static int highestfd = -2; + +/*---------------------------------------------------------------*/ +/* public functions */ + +void +add_connection(struct connection *p) +{ + if (nconnections < 0) + return; + if (nconnections >= MAX_CONNECTIONS) { + acpid_log(LOG_ERR, "Too many connections."); + /* ??? This routine should return -1 in this situation so that */ + /* callers can clean up any open fds and whatnot. */ + return; + } + + if (nconnections == 0) + FD_ZERO(&allfds); + + /* add the connection to the connection list */ + connection_list[nconnections] = *p; + ++nconnections; + + /* add to the fd set */ + FD_SET(p->fd, &allfds); + highestfd = max(highestfd, p->fd); +} + +/*---------------------------------------------------------------*/ + +void +delete_connection(int fd) +{ + int i; + + close(fd); + + /* remove from the fd set */ + FD_CLR(fd, &allfds); + + for (i = 0; i < nconnections; ++i) { + /* if the file descriptors match, delete the connection */ + if (connection_list[i].fd == fd) { + free(connection_list[i].pathname); + + --nconnections; + connection_list[i] = connection_list[nconnections]; + + break; + } + } + + /* prepare for recalculation of highestfd */ + highestfd = -2; + + /* recalculate highestfd */ + for (i = 0; i < nconnections; ++i) { + highestfd = max(highestfd, connection_list[i].fd); + } +} + +/*---------------------------------------------------------------*/ + +struct connection * +find_connection(int fd) +{ + int i; + + /* for each connection */ + for (i = 0; i < nconnections; ++i) { + /* if the file descriptors match, return the connection */ + if (connection_list[i].fd == fd) + return &connection_list[i]; + } + + return NULL; +} + +/*---------------------------------------------------------------*/ + +struct connection * +find_connection_name(char *pathname) +{ + int i; + + /* for each connection */ + for (i = 0; i < nconnections; ++i) { + /* skip null pathnames */ + if (connection_list[i].pathname == NULL) + continue; + + /* if the pathname matches, return the connection */ + if (strcmp(connection_list[i].pathname, pathname) == 0) + return &connection_list[i]; + } + + return NULL; +} + +/*---------------------------------------------------------------*/ + +int +get_number_of_connections() +{ + return nconnections; +} + +/*---------------------------------------------------------------*/ + +struct connection * +get_connection(int i) +{ + if (i < 0 || i >= nconnections) + return NULL; + + return &connection_list[i]; +} + +/*---------------------------------------------------------------*/ + +const fd_set * +get_fdset() +{ + return &allfds; +} + +/*---------------------------------------------------------------*/ + +int +get_highestfd() +{ + return highestfd; +} diff --git a/connection_list.h b/connection_list.h new file mode 100644 index 0000000..16271b1 --- /dev/null +++ b/connection_list.h @@ -0,0 +1,78 @@ +/* + * connection_list.h - ACPI daemon connection list + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Tabs at 4 + */ + +#ifndef CONNECTION_LIST_H__ +#define CONNECTION_LIST_H__ + +#include + +/***************************************************************** + * Connection List Public Members + *****************************************************************/ + +struct connection +{ + /* file descriptor */ + int fd; + + /* process incoming data on the connection */ + /* ??? suggest passing a pointer to this connection struct */ + void (* process)(int fd); + + /* Optional. Used by find_connection_name() to find the connection for a + specific file. Set to NULL if not specified. Memory will be freed + with free() when connection is deleted. */ + char *pathname; + + /* 0 indicates this is probably not a keyboard device */ + int kybd; +}; + +/* add a connection to the list */ +extern void add_connection(struct connection *p); + +/* delete a connection from the list */ +extern void delete_connection(int fd); + +/* find a connection in the list by file descriptor */ +/* ??? This routine is unnecessary. When we call the connection's process + * routine, we should pass a pointer to the connection. That will have + * the usual fd along with everything else. */ +extern struct connection *find_connection(int fd); + +/* find a connection in the list by pathname */ +/* ??? unused last I checked */ +extern struct connection *find_connection_name(char *pathname); + +/* get the number of connections in the list */ +extern int get_number_of_connections(); + +/* get a specific connection by index from the list */ +extern struct connection *get_connection(int i); + +/* get an fd_set with all the fd's that have been added to the list */ +extern const fd_set *get_fdset(); + +/* get the highest fd that was added to the list */ +extern int get_highestfd(); + +#endif /* CONNECTION_LIST_H__ */ diff --git a/event.c b/event.c new file mode 100644 index 0000000..1c4cbc2 --- /dev/null +++ b/event.c @@ -0,0 +1,796 @@ +/* + * event.c - ACPI daemon event handler + * + * Copyright (C) 2000 Andrew Henroid + * Copyright (C) 2001 Sun Microsystems (thockin@sun.com) + * Copyright (C) 2004 Tim Hockin (thockin@hockin.org) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acpid.h" +#include "log.h" +#include "sock.h" +#include "ud_socket.h" + +/* + * What is a rule? It's polymorphic, pretty much. + */ +#define RULE_REGEX_FLAGS (REG_EXTENDED | REG_ICASE | REG_NOSUB | REG_NEWLINE) +struct rule { + enum { + RULE_NONE = 0, + RULE_CMD, + RULE_CLIENT, + } type; + char *origin; + regex_t *event; + union { + char *cmd; + int fd; + } action; + struct rule *next; + struct rule *prev; +}; +struct rule_list { + struct rule *head; + struct rule *tail; +}; +static struct rule_list cmd_list; +static struct rule_list client_list; + +/* rule routines */ +static void enlist_rule(struct rule_list *list, struct rule *r); +static void delist_rule(struct rule_list *list, struct rule *r); +static struct rule *new_rule(void); +static void free_rule(struct rule *r); + +/* other helper routines */ +static void lock_rules(void); +static void unlock_rules(void); +static sigset_t *signals_handled(void); +static struct rule *parse_file(const char *file); +static struct rule *parse_client(int client); +static int do_cmd_rule(struct rule *r, const char *event); +static int do_client_rule(struct rule *r, const char *event); +static int safe_write(int fd, const char *buf, int len); +static char *parse_cmd(const char *cmd, const char *event); +static int check_escapes(const char *str); + +/* + * read in all the configuration files + */ +int +acpid_read_conf(const char *confdir) +{ + DIR *dir; + struct dirent *dirent; + char *file = NULL; + int nrules = 0; + regex_t preg; + int rc = 0; + + lock_rules(); + + dir = opendir(confdir); + if (!dir) { + acpid_log(LOG_ERR, "opendir(%s): %s", + confdir, strerror(errno)); + unlock_rules(); + return -1; + } + + /* Compile the regular expression. This is based on run-parts(8). */ + rc = regcomp(&preg, "^[a-zA-Z0-9_-]+$", RULE_REGEX_FLAGS); + if (rc) { + acpid_log(LOG_ERR, "regcomp(): %d", rc); + unlock_rules(); + return -1; + } + + /* scan all the files */ + while ((dirent = readdir(dir))) { + int len; + struct rule *r; + struct stat stat_buf; + + len = strlen(dirent->d_name); + + /* skip "." and ".." */ + if (strncmp(dirent->d_name, ".", sizeof(dirent->d_name)) == 0) + continue; + if (strncmp(dirent->d_name, "..", sizeof(dirent->d_name)) == 0) + continue; + + /* skip any files that don't match the run-parts convention */ + if (regexec(&preg, dirent->d_name, 0, NULL, 0) != 0) { + acpid_log(LOG_INFO, "skipping conf file %s/%s", + confdir, dirent->d_name); + continue; + } + + /* Compute the length of the full path name adding one for */ + /* the slash and one more for the NULL. */ + len += strlen(confdir) + 2; + + file = malloc(len); + if (!file) { + acpid_log(LOG_ERR, "malloc(): %s", strerror(errno)); + unlock_rules(); + return -1; + } + snprintf(file, len, "%s/%s", confdir, dirent->d_name); + + /* allow only regular files and symlinks to files */ + if (stat(file, &stat_buf) != 0) { + acpid_log(LOG_ERR, "stat(%s): %s", file, + strerror(errno)); + free(file); + continue; /* keep trying the rest of the files */ + } + if (!S_ISREG(stat_buf.st_mode)) { + acpid_log(LOG_INFO, "skipping non-file %s", file); + free(file); + continue; /* skip non-regular files */ + } + + r = parse_file(file); + if (r) { + enlist_rule(&cmd_list, r); + nrules++; + } + free(file); + } + closedir(dir); + unlock_rules(); + + acpid_log(LOG_INFO, "%d rule%s loaded", + nrules, (nrules == 1)?"":"s"); + + return 0; +} + +/* + * cleanup all rules + */ +int +acpid_cleanup_rules(int do_detach) +{ + struct rule *p; + struct rule *next; + + lock_rules(); + + if (acpid_debug >= 3) { + acpid_log(LOG_DEBUG, "cleaning up rules"); + } + + if (do_detach) { + /* tell our clients to buzz off */ + p = client_list.head; + while (p) { + next = p->next; + delist_rule(&client_list, p); + close(p->action.fd); + free_rule(p); + p = next; + } + } + + /* clear out our conf rules */ + p = cmd_list.head; + while (p) { + next = p->next; + delist_rule(&cmd_list, p); + free_rule(p); + p = next; + } + + unlock_rules(); + + return 0; +} + +static struct rule * +parse_file(const char *file) +{ + FILE *fp; + char buf[512]; + int line = 0; + struct rule *r; + + acpid_log(LOG_DEBUG, "parsing conf file %s", file); + + fp = fopen(file, "r"); + if (!fp) { + acpid_log(LOG_ERR, "fopen(%s): %s", file, strerror(errno)); + return NULL; + } + + /* make a new rule */ + r = new_rule(); + if (!r) { + fclose(fp); + return NULL; + } + r->type = RULE_CMD; + r->origin = strdup(file); + if (!r->origin) { + acpid_log(LOG_ERR, "strdup(): %s", strerror(errno)); + free_rule(r); + fclose(fp); + return NULL; + } + + /* read each line */ + while (!feof(fp) && !ferror(fp)) { + char *p = buf; + char key[64]; + char val[512]; + int n; + + line++; + memset(key, 0, sizeof(key)); + memset(val, 0, sizeof(val)); + + if (fgets(buf, sizeof(buf)-1, fp) == NULL) { + continue; + } + + /* skip leading whitespace */ + while (*p && isspace((int)*p)) { + p++; + } + /* blank lines and comments get ignored */ + if (!*p || *p == '#') { + continue; + } + + /* quick parse */ + n = sscanf(p, "%63[^=\n]=%255[^\n]", key, val); + if (n != 2) { + acpid_log(LOG_WARNING, "can't parse %s at line %d", + file, line); + continue; + } + if (acpid_debug >= 3) { + acpid_log(LOG_DEBUG, " key=\"%s\" val=\"%s\"", + key, val); + } + /* handle the parsed line */ + if (!strcasecmp(key, "event")) { + int rv; + r->event = malloc(sizeof(regex_t)); + if (!r->event) { + acpid_log(LOG_ERR, "malloc(): %s", + strerror(errno)); + free_rule(r); + fclose(fp); + return NULL; + } + rv = regcomp(r->event, val, RULE_REGEX_FLAGS); + if (rv) { + char rbuf[128]; + regerror(rv, r->event, rbuf, sizeof(rbuf)); + acpid_log(LOG_ERR, "regcomp(): %s", rbuf); + free_rule(r); + fclose(fp); + return NULL; + } + } else if (!strcasecmp(key, "action")) { + if (check_escapes(val) < 0) { + acpid_log(LOG_ERR, "can't load file %s", + file); + free_rule(r); + fclose(fp); + return NULL; + } + r->action.cmd = strdup(val); + if (!r->action.cmd) { + acpid_log(LOG_ERR, "strdup(): %s", + strerror(errno)); + free_rule(r); + fclose(fp); + return NULL; + } + } else { + acpid_log(LOG_WARNING, + "unknown option '%s' in %s at line %d", + key, file, line); + continue; + } + } + if (!r->event || !r->action.cmd) { + acpid_log(LOG_INFO, "skipping incomplete file %s", file); + free_rule(r); + fclose(fp); + return NULL; + } + fclose(fp); + + return r; +} + +int +acpid_add_client(int clifd, const char *origin) +{ + struct rule *r; + int nrules = 0; + + acpid_log(LOG_NOTICE, "client connected from %s", origin); + + r = parse_client(clifd); + if (r) { + r->origin = strdup(origin); + enlist_rule(&client_list, r); + nrules++; + } + + acpid_log(LOG_INFO, "%d client rule%s loaded", + nrules, (nrules == 1)?"":"s"); + + return 0; +} + +static struct rule * +parse_client(int client) +{ + struct rule *r; + int rv; + + /* make a new rule */ + r = new_rule(); + if (!r) { + return NULL; + } + r->type = RULE_CLIENT; + r->action.fd = client; + r->event = malloc(sizeof(regex_t)); + if (!r->event) { + acpid_log(LOG_ERR, "malloc(): %s", strerror(errno)); + free_rule(r); + return NULL; + } + rv = regcomp(r->event, ".*", RULE_REGEX_FLAGS); + if (rv) { + char buf[128]; + regerror(rv, r->event, buf, sizeof(buf)); + acpid_log(LOG_ERR, "regcomp(): %s", buf); + free_rule(r); + return NULL; + } + + return r; +} + +/* + * a few rule methods + */ + +static void +enlist_rule(struct rule_list *list, struct rule *r) +{ + r->next = r->prev = NULL; + if (!list->head) { + list->head = list->tail = r; + } else { + list->tail->next = r; + r->prev = list->tail; + list->tail = r; + } +} + +static void +delist_rule(struct rule_list *list, struct rule *r) +{ + if (r->next) { + r->next->prev = r->prev; + } else { + list->tail = r->prev; + } + + if (r->prev) { + r->prev->next = r->next; + } else { + list->head = r->next;; + } + + r->next = r->prev = NULL; +} + +static struct rule * +new_rule(void) +{ + struct rule *r; + + r = malloc(sizeof(*r)); + if (!r) { + acpid_log(LOG_ERR, "malloc(): %s", strerror(errno)); + return NULL; + } + + r->type = RULE_NONE; + r->origin = NULL; + r->event = NULL; + r->action.cmd = NULL; + r->prev = r->next = NULL; + + return r; +} + +/* I hope you delisted the rule before you free() it */ +static void +free_rule(struct rule *r) +{ + if (r->type == RULE_CMD) { + if (r->action.cmd) { + free(r->action.cmd); + } + } + + if (r->origin) { + free(r->origin); + } + if (r->event) { + regfree(r->event); + free(r->event); + } + + free(r); +} + +static int +client_is_dead(int fd) +{ + struct pollfd pfd; + int r; + + /* check the fd to see if it is dead */ + pfd.fd = fd; + pfd.events = POLLERR | POLLHUP; + r = poll(&pfd, 1, 0); + + if (r < 0) { + acpid_log(LOG_ERR, "poll(): %s", strerror(errno)); + return 0; + } + + return pfd.revents; +} + +void +acpid_close_dead_clients(void) +{ + struct rule *p; + + lock_rules(); + + /* scan our client list */ + p = client_list.head; + while (p) { + struct rule *next = p->next; + if (client_is_dead(p->action.fd)) { + struct ucred cred; + /* closed */ + acpid_log(LOG_NOTICE, + "client %s has disconnected", p->origin); + delist_rule(&client_list, p); + ud_get_peercred(p->action.fd, &cred); + if (cred.uid != 0) { + non_root_clients--; + } + close(p->action.fd); + free_rule(p); + } + p = next; + } + + unlock_rules(); +} + +/* + * the main hook for propogating events + */ +int +acpid_handle_event(const char *event) +{ + struct rule *p; + int nrules = 0; + struct rule_list *ar[] = { &client_list, &cmd_list, NULL }; + struct rule_list **lp; + + /* make an event be atomic wrt known signals */ + lock_rules(); + + /* scan each rule list for any rules that care about this event */ + for (lp = ar; *lp; lp++) { + struct rule_list *l = *lp; + p = l->head; + while (p) { + /* the list can change underneath us */ + struct rule *pnext = p->next; + if (!regexec(p->event, event, 0, NULL, 0)) { + /* a match! */ + if (logevents) { + acpid_log(LOG_INFO, + "rule from %s matched", + p->origin); + } + nrules++; + if (p->type == RULE_CMD) { + do_cmd_rule(p, event); + } else if (p->type == RULE_CLIENT) { + do_client_rule(p, event); + } else { + acpid_log(LOG_WARNING, + "unknown rule type: %d", + p->type); + } + } else { + if (acpid_debug >= 3 && logevents) { + acpid_log(LOG_INFO, + "rule from %s did not match", + p->origin); + } + } + p = pnext; + } + } + + unlock_rules(); + + if (logevents) { + acpid_log(LOG_INFO, "%d total rule%s matched", + nrules, (nrules == 1)?"":"s"); + } + + return 0; +} + +/* helper functions to block signals while iterating */ +static sigset_t * +signals_handled(void) +{ + static sigset_t set; + + sigemptyset(&set); + sigaddset(&set, SIGHUP); + sigaddset(&set, SIGTERM); + sigaddset(&set, SIGQUIT); + sigaddset(&set, SIGINT); + + return &set; +} + +static void +lock_rules(void) +{ + if (acpid_debug >= 4) { + acpid_log(LOG_DEBUG, "blocking signals for rule lock"); + } + sigprocmask(SIG_BLOCK, signals_handled(), NULL); +} + +static void +unlock_rules(void) +{ + if (acpid_debug >= 4) { + acpid_log(LOG_DEBUG, "unblocking signals for rule lock"); + } + sigprocmask(SIG_UNBLOCK, signals_handled(), NULL); +} + +/* + * the meat of the rules + */ + +static int +do_cmd_rule(struct rule *rule, const char *event) +{ + pid_t pid; + int status; + const char *action; + + pid = fork(); + switch (pid) { + case -1: + acpid_log(LOG_ERR, "fork(): %s", strerror(errno)); + return -1; + case 0: /* child */ + /* parse the commandline, doing any substitutions needed */ + action = parse_cmd(rule->action.cmd, event); + if (logevents) { + acpid_log(LOG_INFO, + "executing action \"%s\"", action); + } + + /* reset signals */ + signal(SIGHUP, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + sigprocmask(SIG_UNBLOCK, signals_handled(), NULL); + + if (acpid_debug && logevents) { + fprintf(stdout, "BEGIN HANDLER MESSAGES\n"); + } + umask(0077); + execl("/bin/sh", "/bin/sh", "-c", action, NULL); + /* should not get here */ + acpid_log(LOG_ERR, "execl(): %s", strerror(errno)); + _exit(EXIT_FAILURE); + } + + /* parent */ + waitpid(pid, &status, 0); + if (acpid_debug && logevents) { + fprintf(stdout, "END HANDLER MESSAGES\n"); + } + + if (logevents) { + if (WIFEXITED(status)) { + acpid_log(LOG_INFO, "action exited with status %d", + WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + acpid_log(LOG_INFO, "action exited on signal %d", + WTERMSIG(status)); + } else { + acpid_log(LOG_INFO, "action exited with status %d", + status); + } + } + + return 0; +} + +static int +do_client_rule(struct rule *rule, const char *event) +{ + int r; + int client = rule->action.fd; + + if (logevents) { + acpid_log(LOG_INFO, "notifying client %s", rule->origin); + } + + r = safe_write(client, event, strlen(event)); + if (r < 0 && errno == EPIPE) { + struct ucred cred; + /* closed */ + acpid_log(LOG_NOTICE, + "client %s has disconnected", rule->origin); + delist_rule(&client_list, rule); + ud_get_peercred(rule->action.fd, &cred); + if (cred.uid != 0) { + non_root_clients--; + } + close(rule->action.fd); + free_rule(rule); + return -1; + } + safe_write(client, "\n", 1); + + return 0; +} + +#define NTRIES 100 +static int +safe_write(int fd, const char *buf, int len) +{ + int r; + int ttl = 0; + int ntries = NTRIES; + + do { + r = write(fd, buf+ttl, len-ttl); + if (r < 0) { + if (errno != EAGAIN && errno != EINTR) { + /* a legit error */ + return r; + } + ntries--; + } else if (r > 0) { + /* as long as we make forward progress, reset ntries */ + ntries = NTRIES; + ttl += r; + } + } while (ttl < len && ntries); + + if (!ntries) { + if (acpid_debug >= 2) { + acpid_log(LOG_ERR, "safe_write() timed out"); + } + return r; + } + + return ttl; +} + +static char * +parse_cmd(const char *cmd, const char *event) +{ + static char buf[4096]; + size_t i; + const char *p; + + p = cmd; + i = 0; + + memset(buf, 0, sizeof(buf)); + while (i < (sizeof(buf)-1)) { + if (*p == '%') { + p++; + if (*p == 'e') { + /* handle an event expansion */ + size_t size = sizeof(buf) - i; + size = snprintf(buf+i, size, "%s", event); + i += size; + p++; + continue; + } + } + if (!*p) { + break; + } + buf[i++] = *p++; + } + if (acpid_debug >= 2) { + acpid_log(LOG_DEBUG, "expanded \"%s\" -> \"%s\"", cmd, buf); + } + + return buf; +} + +static int +check_escapes(const char *str) +{ + const char *p; + int r = 0; + + p = str; + while (*p) { + /* found an escape */ + if (*p == '%') { + p++; + if (!*p) { + acpid_log(LOG_WARNING, + "invalid escape at EOL"); + return -1; + } else if (*p != '%' && *p != 'e') { + acpid_log(LOG_WARNING, + "invalid escape \"%%%c\"", *p); + r = -1; + } + } + p++; + } + return r; +} diff --git a/event.h b/event.h new file mode 100644 index 0000000..1dc0a91 --- /dev/null +++ b/event.h @@ -0,0 +1,32 @@ +/* + * event.h - ACPI daemon event handler + * + * Copyright (C) 1999-2000 Andrew Henroid + * Copyright (C) 2001 Sun Microsystems + * Portions Copyright (C) 2004 Tim Hockin (thockin@hockin.org) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef EVENT_H__ +#define EVENT_H__ + +extern int acpid_read_conf(const char *confdir); +extern int acpid_add_client(int client, const char *origin); +extern int acpid_cleanup_rules(int do_detach); +extern int acpid_handle_event(const char *event); +extern void acpid_close_dead_clients(void); + +#endif /* EVENT_H__ */ diff --git a/genetlink.h b/genetlink.h new file mode 100644 index 0000000..7b1ae9a --- /dev/null +++ b/genetlink.h @@ -0,0 +1,81 @@ +#ifndef __LINUX_GENERIC_NETLINK_H +#define __LINUX_GENERIC_NETLINK_H + +#include + +#define GENL_NAMSIZ 16 /* length of family name */ + +#define GENL_MIN_ID NLMSG_MIN_TYPE +#define GENL_MAX_ID 1023 + +struct genlmsghdr { + __u8 cmd; + __u8 version; + __u16 reserved; +}; + +#define GENL_HDRLEN NLMSG_ALIGN(sizeof(struct genlmsghdr)) + +#define GENL_ADMIN_PERM 0x01 +#define GENL_CMD_CAP_DO 0x02 +#define GENL_CMD_CAP_DUMP 0x04 +#define GENL_CMD_CAP_HASPOL 0x08 + +/* + * List of reserved static generic netlink identifiers: + */ +#define GENL_ID_GENERATE 0 +#define GENL_ID_CTRL NLMSG_MIN_TYPE + +/************************************************************************** + * Controller + **************************************************************************/ + +enum { + CTRL_CMD_UNSPEC, + CTRL_CMD_NEWFAMILY, + CTRL_CMD_DELFAMILY, + CTRL_CMD_GETFAMILY, + CTRL_CMD_NEWOPS, + CTRL_CMD_DELOPS, + CTRL_CMD_GETOPS, + CTRL_CMD_NEWMCAST_GRP, + CTRL_CMD_DELMCAST_GRP, + CTRL_CMD_GETMCAST_GRP, /* unused */ + __CTRL_CMD_MAX, +}; + +#define CTRL_CMD_MAX (__CTRL_CMD_MAX - 1) + +enum { + CTRL_ATTR_UNSPEC, + CTRL_ATTR_FAMILY_ID, + CTRL_ATTR_FAMILY_NAME, + CTRL_ATTR_VERSION, + CTRL_ATTR_HDRSIZE, + CTRL_ATTR_MAXATTR, + CTRL_ATTR_OPS, + CTRL_ATTR_MCAST_GROUPS, + __CTRL_ATTR_MAX, +}; + +#define CTRL_ATTR_MAX (__CTRL_ATTR_MAX - 1) + +enum { + CTRL_ATTR_OP_UNSPEC, + CTRL_ATTR_OP_ID, + CTRL_ATTR_OP_FLAGS, + __CTRL_ATTR_OP_MAX, +}; + +#define CTRL_ATTR_OP_MAX (__CTRL_ATTR_OP_MAX - 1) + +enum { + CTRL_ATTR_MCAST_GRP_UNSPEC, + CTRL_ATTR_MCAST_GRP_NAME, + CTRL_ATTR_MCAST_GRP_ID, + __CTRL_ATTR_MCAST_GRP_MAX, +}; +#define CTRL_ATTR_MCAST_GRP_MAX (__CTRL_ATTR_MCAST_GRP_MAX - 1) + +#endif /* __LINUX_GENERIC_NETLINK_H */ diff --git a/inotify_handler.c b/inotify_handler.c new file mode 100644 index 0000000..f2c19d2 --- /dev/null +++ b/inotify_handler.c @@ -0,0 +1,148 @@ +/* + * inotify_handler.c - inotify Handler for New Devices + * + * Watches /dev/input for new input layer device files. + * + * Copyright (C) 2009, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * (tabs at 4) + */ + +/* system */ +#include +#include +#include +#include +#include +#include +#include + +/* local */ +#include "acpid.h" +#include "log.h" +#include "connection_list.h" +#include "input_layer.h" + +/*-----------------------------------------------------------------*/ +/* called when an inotify event is received */ +void process_inotify(int fd) +{ + int bytes; + /* union to avoid strict-aliasing problems */ + union { + char buffer[256]; /* a tad large */ + struct inotify_event event; + } eventbuf; + + bytes = read(fd, &eventbuf.buffer, sizeof(eventbuf.buffer)); + + acpid_log(LOG_DEBUG, "inotify read bytes: %d", bytes); + + /* eof is not expected */ + if (bytes == 0) { + acpid_log(LOG_WARNING, "inotify fd eof encountered"); + return; + } + else if (bytes < 0) { + /* EINVAL means buffer wasn't big enough. See inotify(7). */ + acpid_log(LOG_ERR, "inotify read error: %s (%d)", + strerror(errno), errno); + acpid_log(LOG_ERR, "disconnecting from inotify"); + delete_connection(fd); + return; + } + + acpid_log(LOG_DEBUG, "inotify name len: %d", eventbuf.event.len); + + const int dnsize = 256; + char devname[dnsize]; + + /* if a name is included */ + if (eventbuf.event.len > 0) { + /* devname = ACPID_INPUTLAYERDIR + "/" + pevent -> name */ + strcpy(devname, ACPID_INPUTLAYERDIR); + strcat(devname, "/"); + strncat(devname, eventbuf.event.name, dnsize - strlen(devname) - 1); + } + + /* if this is a create */ + if (eventbuf.event.mask & IN_CREATE) { + acpid_log(LOG_DEBUG, "inotify about to open: %s", devname); + + open_inputfile(devname); + } + + /* if this is a delete */ + if (eventbuf.event.mask & IN_DELETE) { + /* struct connection *c; */ + + acpid_log(LOG_DEBUG, "inotify received a delete for: %s", devname); + +#if 0 +/* Switching back to the original ENODEV detection scheme. See + process_input() in input_layer.c. */ +/* keeping this for future reference */ + /* search for the event file in the connection list */ + /* ??? Or should we just have a delete_connection_name()? */ + c = find_connection_name(devname); + + /* close that connection if found */ + if (c) + delete_connection(c->fd); +#endif + } +} + +/*-----------------------------------------------------------------*/ +/* Set up an inotify watch on /dev/input. */ +void open_inotify(void) +{ + int fd = -1; + int wd = -1; + struct connection c; + + /* set up inotify */ + fd = inotify_init(); + + if (fd < 0) { + acpid_log(LOG_ERR, "inotify_init() failed: %s (%d)", + strerror(errno), errno); + return; + } + + acpid_log(LOG_DEBUG, "inotify fd: %d", fd); + + /* watch for files being created or deleted in /dev/input */ + wd = inotify_add_watch(fd, ACPID_INPUTLAYERDIR, IN_CREATE | IN_DELETE); + + if (wd < 0) { + acpid_log(LOG_ERR, "inotify_add_watch() failed: %s (%d)", + strerror(errno), errno); + close(fd); + return; + } + + acpid_log(LOG_DEBUG, "inotify wd: %d", wd); + + /* add a connection to the list */ + c.fd = fd; + c.process = process_inotify; + c.pathname = NULL; + c.kybd = 0; + add_connection(&c); +} + diff --git a/inotify_handler.h b/inotify_handler.h new file mode 100644 index 0000000..b808727 --- /dev/null +++ b/inotify_handler.h @@ -0,0 +1,31 @@ +/* + * inotify_handler.h - inotify Handler for New Devices + * + * Watches /dev/input for new input layer device files. + * + * Copyright (C) 2009, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * (tabs at 4) + */ + +#ifndef INOTIFY_HANDLER_H__ +#define INOTIFY_HANDLER_H__ + +/* Set up an inotify watch on /dev/input. */ +extern void open_inotify(void); + +#endif /* INOTIFY_HANDLER_H__ */ diff --git a/input_layer.c b/input_layer.c new file mode 100644 index 0000000..e9b40f4 --- /dev/null +++ b/input_layer.c @@ -0,0 +1,389 @@ +/* + * input_layer - Kernel ACPI Event Input Layer Interface + * + * Handles the details of getting kernel ACPI events from the input + * layer (/dev/input/event*). + * + * Inspired by (and in some cases blatantly lifted from) Vojtech Pavlik's + * evtest.c. + * + * Copyright (C) 2008-2009, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * (tabs at 4) + */ + +/* system */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* local */ +#include "acpid.h" +#include "log.h" +#include "connection_list.h" +#include "event.h" + +#define DIM(a) (sizeof(a) / sizeof(a[0])) + +struct evtab_entry { + struct input_event event; + const char *str; +}; + +/* Event Table: Events we are interested in and their strings. Use + evtest.c, acpi_genl, or kacpimon to find new events to add to this + table. */ +static struct evtab_entry evtab[] = { + {{{0,0}, EV_KEY, KEY_POWER, 1}, "button/power PBTN 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_SUSPEND, 1}, + "button/suspend SUSP 00000080 00000000"}, + {{{0,0}, EV_SW, SW_LID, 1}, "button/lid LID close"}, + {{{0,0}, EV_SW, SW_LID, 0}, "button/lid LID open"}, + /* blue access IBM button on Thinkpad T42p*/ + {{{0,0}, EV_KEY, KEY_PROG1, 1}, "button/prog1 PROG1 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_VENDOR, 1}, "button/vendor VNDR 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_FN_F1, 1}, "button/fnf1 FNF1 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_FN_F2, 1}, "button/fnf2 FNF2 00000080 00000000"}, + /* Fn-F2 produces KEY_BATTERY on Thinkpad T42p */ + {{{0,0}, EV_KEY, KEY_BATTERY, 1}, + "button/battery BAT 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_SCREENLOCK, 1}, + "button/screenlock SCRNLCK 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_COFFEE, 1}, "button/coffee CFEE 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_SLEEP, 1}, "button/sleep SBTN 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_WLAN, 1}, "button/wlan WLAN 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_FN_F6, 1}, "button/fnf6 FNF6 00000080 00000000"}, + /* procfs on Thinkpad 600X reports "video VID0 00000080 00000000" */ + /* typical events file has "video.* 00000080" */ + {{{0,0}, EV_KEY, KEY_SWITCHVIDEOMODE, 1}, + "video/switchmode VMOD 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_FN_F9, 1}, "button/fnf9 FNF9 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_FN_F10, 1}, "button/fnf10 FF10 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_FN_F11, 1}, "button/fnf11 FF11 00000080 00000000"}, + /* Fn-F9 produces KEY_F24 on Thinkpad T42p */ + {{{0,0}, EV_KEY, KEY_F24, 1}, "button/f24 F24 00000080 00000000"}, + +#if 0 + /* These "EV_MSC, 4, x" events cause trouble. They are triggered */ + /* by unexpected keys on the keyboard. */ + /* The 4 is MSC_SCAN, so these are actually scan code events. */ + + /* EV_MSC, MSC_SCAN, KEY_MINUS This is triggered by the minus key. */ + {{{0,0}, EV_MSC, 4, 12}, "button/fnbs FNBS 00000080 00000000"}, + + /* EV_MSC, MSC_SCAN, KEY_EQUAL Triggered by the equals key. */ + {{{0,0}, EV_MSC, 4, 13}, "button/fnins FNINS 00000080 00000000"}, + + /* EV_MSC, MSC_SCAN, KEY_BACKSPACE Triggered by the backspace key. */ + {{{0,0}, EV_MSC, 4, 14}, "button/fndel FNDEL 00000080 00000000"}, + + /* EV_MSC, MSC_SCAN, KEY_E Triggered by the 'E' key. */ + {{{0,0}, EV_MSC, 4, 18}, "button/fnpgdown FNPGDOWN 00000080 00000000"}, +#endif + + {{{0,0}, EV_KEY, KEY_ZOOM, 1}, "button/zoom ZOOM 00000080 00000000"}, + /* typical events file has "video.* 00000087" */ + {{{0,0}, EV_KEY, KEY_BRIGHTNESSDOWN, 1}, + "video/brightnessdown BRTDN 00000087 00000000"}, + /* typical events file has "video.* 00000086" */ + {{{0,0}, EV_KEY, KEY_BRIGHTNESSUP, 1}, + "video/brightnessup BRTUP 00000086 00000000"}, + {{{0,0}, EV_KEY, KEY_KBDILLUMTOGGLE, 1}, + "button/kbdillumtoggle KBILLUM 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_VOLUMEDOWN, 1}, + "button/volumedown VOLDN 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_VOLUMEUP, 1}, + "button/volumeup VOLUP 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_MUTE, 1}, + "button/mute MUTE 00000080 00000000"}, + /* cd play/pause buttons */ + {{{0,0}, EV_KEY, KEY_NEXTSONG, 1}, + "cd/next CDNEXT 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_PREVIOUSSONG, 1}, + "cd/prev CDPREV 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_PLAYPAUSE, 1}, + "cd/play CDPLAY 00000080 00000000"}, + {{{0,0}, EV_KEY, KEY_STOPCD, 1}, + "cd/stop CDSTOP 00000080 00000000"}, + /* additional events divined from the kernel's video.c */ + {{{0,0}, EV_KEY, KEY_VIDEO_NEXT, 1}, + "video/next NEXT 00000083 00000000"}, + {{{0,0}, EV_KEY, KEY_VIDEO_PREV, 1}, + "video/prev PREV 00000084 00000000"}, + {{{0,0}, EV_KEY, KEY_BRIGHTNESS_CYCLE, 1}, + "video/brightnesscycle BCYC 00000085 00000000"}, + {{{0,0}, EV_KEY, KEY_BRIGHTNESS_ZERO, 1}, + "video/brightnesszero BZRO 00000088 00000000"}, + {{{0,0}, EV_KEY, KEY_DISPLAY_OFF, 1}, + "video/displayoff DOFF 00000089 00000000"} +}; + +/*----------------------------------------------------------------------*/ +/* Given an input event, returns the string corresponding to that event. + If there is no corresponding string, NULL is returned. */ +static const char * +event_string(struct input_event event) +{ + unsigned i; + + /* for each entry in the event table */ + /* ??? is there a faster way? */ + for (i = 0; i < DIM(evtab); ++i) + { + /* if this is a matching event, return its string */ + if (event.type == evtab[i].event.type && + event.code == evtab[i].event.code && + event.value == evtab[i].event.value) { + return evtab[i].str; + } + } + + return NULL; +} + +/*-----------------------------------------------------------------*/ +/* returns non-zero if the event type/code is one we need */ +static int +need_event(int type, int code) +{ + unsigned i; + + /* for each entry in the event table */ + for (i = 0; i < DIM(evtab); ++i) { + /* if we found a matching event */ + if (type == evtab[i].event.type && + code == evtab[i].event.code) { + return 1; + } + } + + return 0; +} + +/*-----------------------------------------------------------------*/ +/* called when an input layer event is received */ +void process_input(int fd) +{ + struct input_event event; + ssize_t nbytes; + const char *str; + static int nerrs; + struct connection *c; + char str2[100]; + + nbytes = read(fd, &event, sizeof(event)); + + if (nbytes == 0) { + acpid_log(LOG_WARNING, "input layer connection closed"); + exit(EXIT_FAILURE); + } + + if (nbytes < 0) { + /* if it's a signal, bail */ + if (errno == EINTR) + return; + if (errno == ENODEV) { + acpid_log(LOG_WARNING, "input device has been disconnected, fd %d", + fd); + delete_connection(fd); + return; + } + acpid_log(LOG_ERR, "input layer read error: %s (%d)", + strerror(errno), errno); + if (++nerrs >= ACPID_MAX_ERRS) { + acpid_log(LOG_ERR, + "too many errors reading " + "input layer - aborting"); + exit(EXIT_FAILURE); + } + return; + } + + /* ??? Is it possible for a partial message to come across? */ + /* If so, we've got more code to write... */ + + if (nbytes != sizeof(event)) { + acpid_log(LOG_WARNING, "input layer unexpected length: " + "%d expected: %d", nbytes, sizeof(event)); + return; + } + + c = find_connection(fd); + + /* if we're getting scancodes, we probably have a keyboard */ + if (event.type == EV_MSC && event.code == MSC_SCAN) { + if (c) + c->kybd = 1; /* appears to be a keyboard device */ + } + + /* convert the event into a string */ + str = event_string(event); + /* if this is not an event we care about, bail */ + if (str == NULL) + return; + + /* If we suspect this is a keyboard, and we have enough space, tack a + * "K" on to the end of the event string. */ + if (c && c->kybd && strnlen(str, sizeof(str2)) <= sizeof(str2) - 3) { + strcpy(str2, str); + strcat(str2, " K"); + str = str2; + } + + /* if we're locked, don't process the event */ + if (locked()) { + if (logevents) { + acpid_log(LOG_INFO, + "lockfile present, not processing " + "input layer event \"%s\"", str); + } + return; + } + + if (logevents) + acpid_log(LOG_INFO, + "received input layer event \"%s\"", str); + + /* send the event off to the handler */ + acpid_handle_event(str); + + if (logevents) + acpid_log(LOG_INFO, + "completed input layer event \"%s\"", str); +} + +#define BITS_PER_LONG (sizeof(long) * 8) +#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) +#define OFF(x) ((x)%BITS_PER_LONG) +#define LONG(x) ((x)/BITS_PER_LONG) +#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1) + +/*--------------------------------------------------------------------*/ +/* returns non-zero if the file descriptor supports one of the events */ +/* supported by event_string(). */ +static int +has_event(int fd) +{ + int type, code; + unsigned long bit[EV_MAX][NBITS(KEY_MAX)]; + + memset(bit, 0, sizeof(bit)); + /* get the event type bitmap */ + ioctl(fd, EVIOCGBIT(0, sizeof(bit[0])), bit[0]); + + /* for each event type */ + for (type = 0; type < EV_MAX; type++) { + /* if this event type is supported */ + if (test_bit(type, bit[0])) { + /* skip sync */ + if (type == EV_SYN) continue; + /* get the event code mask */ + ioctl(fd, EVIOCGBIT(type, sizeof(bit[type])), bit[type]); + /* for each event code */ + for (code = 0; code < KEY_MAX; code++) { + /* if this event code is supported */ + if (test_bit(code, bit[type])) { + /* if we need this event */ + if (need_event(type, code) != 0) + return 1; + } + } + } + } + return 0; +} + +/*-----------------------------------------------------------------* + * open a single input layer file for input */ +int open_inputfile(const char *filename) +{ + int fd; + struct connection c; + + fd = open(filename, O_RDONLY | O_NONBLOCK); + + /* Make sure scripts we exec() (in event.c) don't get our file + descriptors. */ + fcntl(fd, F_SETFD, FD_CLOEXEC); + + if (fd >= 0) { + char evname[256]; + + /* if this file doesn't have events we need, indicate failure */ + if (!has_event(fd)) { + close(fd); + return -1; + } + + /* get this event file's name for debugging */ + strcpy(evname, "Unknown"); + ioctl(fd, EVIOCGNAME(sizeof(evname)), evname); + + acpid_log(LOG_DEBUG, "input layer %s (%s) " + "opened successfully, fd %d", filename, evname, fd); + + /* add a connection to the list */ + c.fd = fd; + c.process = process_input; + /* delete_connection() will free */ + c.pathname = malloc(strlen(filename) + 1); + if (c.pathname) + strcpy(c.pathname, filename); + /* assume not a keyboard until we see a scancode */ + c.kybd = 0; + add_connection(&c); + + return 0; /* success */ + } + + /* open unsuccessful */ + return -1; +} + +/*-----------------------------------------------------------------* + * open each of the appropriate /dev/input/event* files for input */ +void open_input(void) +{ + char *filename = NULL; + glob_t globbuf; + unsigned i; + int success = 0; + + /* get all the matching event filenames */ + glob(ACPID_INPUTLAYERFILES, 0, NULL, &globbuf); + + /* for each event file */ + for (i = 0; i < globbuf.gl_pathc; ++i) { + filename = globbuf.gl_pathv[i]; + + /* open this input layer device file */ + if (open_inputfile(filename) == 0) + success = 1; + } + + if (!success) + acpid_log(LOG_ERR, "cannot open input layer"); + + globfree(&globbuf); +} + diff --git a/input_layer.h b/input_layer.h new file mode 100644 index 0000000..d6dccd0 --- /dev/null +++ b/input_layer.h @@ -0,0 +1,35 @@ +/* + * input_layer.h - Kernel ACPI Event Input Layer Interface + * + * Handles the details of getting kernel ACPI events from the input + * layer (/dev/input/event*). + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * (tabs at 4) + */ + +#ifndef INPUT_LAYER_H__ +#define INPUT_LAYER_H__ + +/* Open each of the appropriate /dev/input/event* files for input. */ +extern void open_input(void); + +/* Open a single input layer device file for input. */ +extern int open_inputfile(const char *filename); + +#endif /* INPUT_LAYER_H__ */ diff --git a/kacpimon/README b/kacpimon/README new file mode 100644 index 0000000..f4674c2 --- /dev/null +++ b/kacpimon/README @@ -0,0 +1,3 @@ +Moved to the man page. Try this: + + man -l kacpimon.8 diff --git a/kacpimon/acpi_genetlink.h b/kacpimon/acpi_genetlink.h new file mode 100644 index 0000000..ce24e57 --- /dev/null +++ b/kacpimon/acpi_genetlink.h @@ -0,0 +1,33 @@ +#ifndef __ACPI_GENETLINK_H__ +#define __ACPI_GENETLINK_H__ 1 + +#include + +struct acpi_genl_event { + char device_class[20]; + char bus_id[15]; + __u32 type; + __u32 data; +}; + +/* attributes of acpi_genl_family */ +enum { + ACPI_GENL_ATTR_UNSPEC, + ACPI_GENL_ATTR_EVENT, /* ACPI event info needed by user space */ + __ACPI_GENL_ATTR_MAX, +}; +#define ACPI_GENL_ATTR_MAX (__ACPI_GENL_ATTR_MAX - 1) + +/* commands supported by the acpi_genl_family */ +enum { + ACPI_GENL_CMD_UNSPEC, + ACPI_GENL_CMD_EVENT, /* kernel->user notifications for ACPI events */ __ACPI_GENL_CMD_MAX, +}; +#define ACPI_GENL_CMD_MAX (__ACPI_GENL_CMD_MAX - 1) +#define GENL_MAX_FAM_OPS 256 +#define GENL_MAX_FAM_GRPS 256 + +#define ACPI_EVENT_FAMILY_NAME "acpi_event" +#define ACPI_EVENT_MCAST_GROUP_NAME "acpi_mc_group" + +#endif diff --git a/kacpimon/acpi_ids.c b/kacpimon/acpi_ids.c new file mode 100644 index 0000000..389eacc --- /dev/null +++ b/kacpimon/acpi_ids.c @@ -0,0 +1,252 @@ +/* + * acpi_ids.c - ACPI Netlink Group and Family IDs + * + * Copyright (C) 2008 Ted Felix (www.tedfelix.com) + * Portions from acpi_genl Copyright (C) Zhang Rui + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +/* needed by netlink.h, should be in there */ +#include +#include +#include + +#include "genetlink.h" +#include "libnetlink.h" + +#define GENL_MAX_FAM_GRPS 256 +#define ACPI_EVENT_FAMILY_NAME "acpi_event" +#define ACPI_EVENT_MCAST_GROUP_NAME "acpi_mc_group" + +static int initialized = 0; +static __u16 acpi_event_family_id = 0; +static __u32 acpi_event_mcast_group_id = 0; + +/* + * A CTRL_CMD_GETFAMILY message returns an attribute table that looks + * like this: + * + * CTRL_ATTR_FAMILY_ID Use this to make sure we get the proper msgs + * CTRL_ATTR_MCAST_GROUPS + * CTRL_ATTR_MCAST_GRP_NAME + * CTRL_ATTR_MCAST_GRP_ID Need this for the group mask + * ... + */ + +static int +get_ctrl_grp_id(struct rtattr *arg) +{ + struct rtattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1]; + char *name; + + if (arg == NULL) + return -1; + + /* nested within the CTRL_ATTR_MCAST_GROUPS attribute are the */ + /* group name and ID */ + parse_rtattr_nested(tb, CTRL_ATTR_MCAST_GRP_MAX, arg); + + /* if either of the entries needed cannot be found, bail */ + if (!tb[CTRL_ATTR_MCAST_GRP_NAME] || !tb[CTRL_ATTR_MCAST_GRP_ID]) + return -1; + + /* get the name of this multicast group we've found */ + name = RTA_DATA(tb[CTRL_ATTR_MCAST_GRP_NAME]); + + /* if it does not match the ACPI event multicast group name, bail */ + if (strcmp(name, ACPI_EVENT_MCAST_GROUP_NAME)) + return -1; + + /* At this point, we've found what we were looking for. We now */ + /* have the multicast group ID for ACPI events over generic netlink. */ + acpi_event_mcast_group_id = + *((__u32 *)RTA_DATA(tb[CTRL_ATTR_MCAST_GRP_ID])); + + return 0; +} + +/* n = the response to a CTRL_CMD_GETFAMILY message */ +static int +genl_get_mcast_group_id(struct nlmsghdr *n) +{ + /* + * Attribute table. Note the type name "rtattr" which means "route + * attribute". This is a vestige of one of netlink's main uses: + * routing. + */ + struct rtattr *tb[CTRL_ATTR_MAX + 1]; + /* place for the generic netlink header in the incoming message */ + struct genlmsghdr ghdr; + /* length of the attribute and payload */ + int len = n->nlmsg_len - NLMSG_LENGTH(GENL_HDRLEN); + /* Pointer to the attribute portion of the message */ + struct rtattr *attrs; + + if (len < 0) { + printf("Netlink: CTRL_CMD_GETFAMILY response, " + "wrong controller message len: %d\n", len); + return -1; + } + + if (n->nlmsg_type != GENL_ID_CTRL) { + printf("Netlink: Not a controller message, nlmsg_len=%d " + "nlmsg_type=0x%x\n", n->nlmsg_len, n->nlmsg_type); + return 0; + } + + /* copy generic netlink header into structure */ + memcpy(&ghdr, NLMSG_DATA(n), GENL_HDRLEN); + + if (ghdr.cmd != CTRL_CMD_GETFAMILY && + ghdr.cmd != CTRL_CMD_DELFAMILY && + ghdr.cmd != CTRL_CMD_NEWFAMILY && + ghdr.cmd != CTRL_CMD_NEWMCAST_GRP && + ghdr.cmd != CTRL_CMD_DELMCAST_GRP) { + printf("Netlink: Unknown controller command %d\n", ghdr.cmd); + return 0; + } + + /* set attrs to point to the attribute */ + attrs = (struct rtattr *)(NLMSG_DATA(n) + GENL_HDRLEN); + /* Read the table from the message into "tb". This actually just */ + /* places pointers into the message into tb[]. */ + parse_rtattr(tb, CTRL_ATTR_MAX, attrs, len); + + /* if a family ID attribute is present, get it */ + if (tb[CTRL_ATTR_FAMILY_ID]) + { + acpi_event_family_id = + *((__u32 *)RTA_DATA(tb[CTRL_ATTR_FAMILY_ID])); + } + + /* if a "multicast groups" attribute is present... */ + if (tb[CTRL_ATTR_MCAST_GROUPS]) { + struct rtattr *tb2[GENL_MAX_FAM_GRPS + 1]; + int i; + + /* get the group table within this attribute */ + parse_rtattr_nested(tb2, GENL_MAX_FAM_GRPS, + tb[CTRL_ATTR_MCAST_GROUPS]); + + /* for each group */ + for (i = 0; i < GENL_MAX_FAM_GRPS; i++) + /* if this group is valid */ + if (tb2[i]) + /* Parse the ID. If successful, we're done. */ + if (!get_ctrl_grp_id(tb2[i])) + return 0; + } + + return -1; +} + +static int +genl_get_ids(char *family_name) +{ + /* handle to the netlink connection */ + struct rtnl_handle rth; + /* holds the request we are going to send and the reply */ + struct { + struct nlmsghdr n; + char buf[4096]; /* ??? Is this big enough for all cases? */ + } req; + /* pointer to the nlmsghdr in req */ + struct nlmsghdr *nlh; + /* place for the generic netlink header before copied into req */ + struct genlmsghdr ghdr; + /* return value */ + int ret = -1; + + /* clear out the request */ + memset(&req, 0, sizeof(req)); + + /* set up nlh to point to the netlink header in req */ + nlh = &req.n; + /* set up the netlink header */ + nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_type = GENL_ID_CTRL; + + /* clear out the generic netlink message header */ + memset(&ghdr, 0, sizeof(struct genlmsghdr)); + /* set the command we want to run: "GETFAMILY" */ + ghdr.cmd = CTRL_CMD_GETFAMILY; + /* copy it into req */ + memcpy(NLMSG_DATA(&req.n), &ghdr, GENL_HDRLEN); + + /* the message payload is the family name */ + addattr_l(nlh, 128, CTRL_ATTR_FAMILY_NAME, + family_name, strlen(family_name) + 1); + + /* open a generic netlink connection */ + if (rtnl_open_byproto(&rth, 0, NETLINK_GENERIC) < 0) { + printf("Netlink: Cannot open generic netlink socket\n"); + return -1; + } + + /* + * Send CTRL_CMD_GETFAMILY message (in nlh) to the generic + * netlink controller. Reply will be in nlh upon return. + */ + if (rtnl_talk(&rth, nlh, 0, 0, nlh, NULL, NULL) < 0) { + printf("Netlink: Error talking to the kernel\n"); + goto ctrl_done; + } + + /* process the response */ + if (genl_get_mcast_group_id(nlh) < 0) { + printf("Netlink: Failed to get acpi_event multicast group\n"); + goto ctrl_done; + } + + ret = 0; + +ctrl_done: + rtnl_close(&rth); + return ret; +} + +/* initialize the ACPI IDs */ +static void +acpi_ids_init() +{ + genl_get_ids(ACPI_EVENT_FAMILY_NAME); + + initialized = 1; +} + +/* returns the netlink family ID for ACPI event messages */ +__u16 +acpi_ids_getfamily() +{ + /* if the IDs haven't been initialized, initialize them */ + if (initialized == 0) + acpi_ids_init(); + + return acpi_event_family_id; +} + +/* returns the netlink multicast group ID for ACPI event messages */ +__u32 +acpi_ids_getgroup() +{ + /* if the IDs haven't been initialized, initialize them */ + if (initialized == 0) + acpi_ids_init(); + + return acpi_event_mcast_group_id; +} diff --git a/kacpimon/acpi_ids.h b/kacpimon/acpi_ids.h new file mode 100644 index 0000000..52a9ff7 --- /dev/null +++ b/kacpimon/acpi_ids.h @@ -0,0 +1,30 @@ +/* + * acpi_ids.h - ACPI Netlink Group and Family IDs + * + * Copyright (C) 2008 Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef ACPI_IDS_H__ +#define ACPI_IDS_H__ + +/* returns the netlink family ID for ACPI event messages */ +extern __u16 acpi_ids_getfamily(); + +/* returns the netlink multicast group ID for ACPI event messages */ +extern __u32 acpi_ids_getgroup(); + +#endif /* ACPI_IDS_H__ */ diff --git a/kacpimon/connection_list.c b/kacpimon/connection_list.c new file mode 100644 index 0000000..bd3c200 --- /dev/null +++ b/kacpimon/connection_list.c @@ -0,0 +1,121 @@ +/* + * connection_list.c - ACPI daemon connection list + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Tabs at 4 + */ + +#include +#include + +#include "connection_list.h" + +#define max(a, b) (((a)>(b))?(a):(b)) + +/*---------------------------------------------------------------*/ +/* private objects */ + +#define MAX_CONNECTIONS 20 + +static struct connection connection_list[MAX_CONNECTIONS]; + +static int nconnections = 0; + +/* fd_set containing all the fd's that come in */ +static fd_set allfds; + +/* highest fd that is opened */ +/* (-2 + 1) causes select() to return immediately */ +static int highestfd = -2; + +/*---------------------------------------------------------------*/ +/* public functions */ + +void +add_connection(struct connection *p) +{ + if (nconnections < 0) + return; + if (nconnections >= MAX_CONNECTIONS) { + printf("add_connection(): Too many connections.\n"); + return; + } + + if (nconnections == 0) + FD_ZERO(&allfds); + + /* add the connection to the connection list */ + connection_list[nconnections] = *p; + ++nconnections; + + /* add to the fd set */ + FD_SET(p->fd, &allfds); + highestfd = max(highestfd, p->fd); +} + +/*---------------------------------------------------------------*/ + +struct connection * +find_connection(int fd) +{ + int i; + + /* for each connection */ + for (i = 0; i < nconnections; ++i) { + /* if the file descriptors match, return the connection */ + if (connection_list[i].fd == fd) + return &connection_list[i]; + } + + return NULL; +} + +/*---------------------------------------------------------------*/ + +int +get_number_of_connections() +{ + return nconnections; +} + +/*---------------------------------------------------------------*/ + +struct connection * +get_connection(int i) +{ + if (i < 0 || i >= nconnections) + return NULL; + + return &connection_list[i]; +} + +/*---------------------------------------------------------------*/ + +const fd_set * +get_fdset() +{ + return &allfds; +} + +/*---------------------------------------------------------------*/ + +int +get_highestfd() +{ + return highestfd; +} diff --git a/kacpimon/connection_list.h b/kacpimon/connection_list.h new file mode 100644 index 0000000..eeb836e --- /dev/null +++ b/kacpimon/connection_list.h @@ -0,0 +1,59 @@ +/* + * connection_list.h - connection list + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Tabs at 4 + */ + +#ifndef CONNECTION_LIST_H__ +#define CONNECTION_LIST_H__ + +#include + +/***************************************************************** + * Connection List Public Members + *****************************************************************/ + +struct connection +{ + /* file descriptor */ + int fd; + + /* process incoming data on the connection */ + void (* process)(int fd); +}; + +/* add a connection to the list */ +extern void add_connection(struct connection *p); + +/* find a connection in the list by file descriptor */ +extern struct connection *find_connection(int fd); + +/* get the number of connections in the list */ +extern int get_number_of_connections(); + +/* get a specific connection by index from the list */ +extern struct connection *get_connection(int i); + +/* get an fd_set with all the fd's that have been added to the list */ +extern const fd_set *get_fdset(); + +/* get the highest fd that was added to the list */ +extern int get_highestfd(); + +#endif /* CONNECTION_LIST_H__ */ diff --git a/kacpimon/genetlink.h b/kacpimon/genetlink.h new file mode 100644 index 0000000..7b1ae9a --- /dev/null +++ b/kacpimon/genetlink.h @@ -0,0 +1,81 @@ +#ifndef __LINUX_GENERIC_NETLINK_H +#define __LINUX_GENERIC_NETLINK_H + +#include + +#define GENL_NAMSIZ 16 /* length of family name */ + +#define GENL_MIN_ID NLMSG_MIN_TYPE +#define GENL_MAX_ID 1023 + +struct genlmsghdr { + __u8 cmd; + __u8 version; + __u16 reserved; +}; + +#define GENL_HDRLEN NLMSG_ALIGN(sizeof(struct genlmsghdr)) + +#define GENL_ADMIN_PERM 0x01 +#define GENL_CMD_CAP_DO 0x02 +#define GENL_CMD_CAP_DUMP 0x04 +#define GENL_CMD_CAP_HASPOL 0x08 + +/* + * List of reserved static generic netlink identifiers: + */ +#define GENL_ID_GENERATE 0 +#define GENL_ID_CTRL NLMSG_MIN_TYPE + +/************************************************************************** + * Controller + **************************************************************************/ + +enum { + CTRL_CMD_UNSPEC, + CTRL_CMD_NEWFAMILY, + CTRL_CMD_DELFAMILY, + CTRL_CMD_GETFAMILY, + CTRL_CMD_NEWOPS, + CTRL_CMD_DELOPS, + CTRL_CMD_GETOPS, + CTRL_CMD_NEWMCAST_GRP, + CTRL_CMD_DELMCAST_GRP, + CTRL_CMD_GETMCAST_GRP, /* unused */ + __CTRL_CMD_MAX, +}; + +#define CTRL_CMD_MAX (__CTRL_CMD_MAX - 1) + +enum { + CTRL_ATTR_UNSPEC, + CTRL_ATTR_FAMILY_ID, + CTRL_ATTR_FAMILY_NAME, + CTRL_ATTR_VERSION, + CTRL_ATTR_HDRSIZE, + CTRL_ATTR_MAXATTR, + CTRL_ATTR_OPS, + CTRL_ATTR_MCAST_GROUPS, + __CTRL_ATTR_MAX, +}; + +#define CTRL_ATTR_MAX (__CTRL_ATTR_MAX - 1) + +enum { + CTRL_ATTR_OP_UNSPEC, + CTRL_ATTR_OP_ID, + CTRL_ATTR_OP_FLAGS, + __CTRL_ATTR_OP_MAX, +}; + +#define CTRL_ATTR_OP_MAX (__CTRL_ATTR_OP_MAX - 1) + +enum { + CTRL_ATTR_MCAST_GRP_UNSPEC, + CTRL_ATTR_MCAST_GRP_NAME, + CTRL_ATTR_MCAST_GRP_ID, + __CTRL_ATTR_MCAST_GRP_MAX, +}; +#define CTRL_ATTR_MCAST_GRP_MAX (__CTRL_ATTR_MCAST_GRP_MAX - 1) + +#endif /* __LINUX_GENERIC_NETLINK_H */ diff --git a/kacpimon/input_layer.c b/kacpimon/input_layer.c new file mode 100644 index 0000000..509d3e0 --- /dev/null +++ b/kacpimon/input_layer.c @@ -0,0 +1,154 @@ +/* + * input_layer - Kernel ACPI Event Input Layer Interface + * + * Handles the details of getting kernel ACPI events from the input + * layer (/dev/input/event*). + * + * Inspired by (and in some cases blatantly lifted from) Vojtech Pavlik's + * evtest.c. + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * (tabs at 4) + */ + +/* system */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* local */ +#include "connection_list.h" +#include "kacpimon.h" + +#define DIM(a) (sizeof(a) / sizeof(a[0])) + +#define INPUT_LAYER_FS "/dev/input/event*" + +/*-----------------------------------------------------------------*/ +/* called when an input layer event is received */ +void process_input(int fd) +{ + struct input_event event; + ssize_t nbytes; + + nbytes = read(fd, &event, sizeof(event)); + + if (nbytes == 0) { + printf("Input layer connection closed.\n"); + return; + } + + if (nbytes < 0) { + /* if it's a signal, bail */ + if (errno == EINTR) + return; + + printf("Input layer read error: %s (%d)\n", + strerror(errno), errno); + return; + } + + /* ??? Is it possible for a partial message to come across? */ + /* If so, we've got more code to write... */ + + if (nbytes != sizeof(event)) { + printf("Input Layer unexpected Length\n"); + printf(" Expected: %lu Got: %zd\n", + (unsigned long) sizeof(event), nbytes); + return; + } + + /* If the Escape key was pressed, set the exitflag to exit. */ + if (event.type == EV_KEY && + event.code == KEY_ESC && + event.value == 1) { + printf("Escape key pressed\n"); + exitflag = 1; + } + + if (event.type == EV_SYN) + printf("Input Layer: Sync\n"); + else + /* format and display the event struct in decimal */ + printf("Input Layer: " + "Type: %hu Code: %hu Value: %d\n", + event.type, event.code, event.value); +} + +#define BITS_PER_LONG (sizeof(long) * 8) +#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) +#define OFF(x) ((x)%BITS_PER_LONG) +#define LONG(x) ((x)/BITS_PER_LONG) +#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1) + +/*-----------------------------------------------------------------* + * open each of the appropriate /dev/input/event* files for input */ +void open_input(void) +{ + char *filename = NULL; + glob_t globbuf; + unsigned i; + int fd; + struct connection c; + int had_some_success = 0; + char evname[256]; + + /* get all the matching event filenames */ + glob(INPUT_LAYER_FS, 0, NULL, &globbuf); + + /* for each event file */ + for (i = 0; i < globbuf.gl_pathc; ++i) + { + filename = globbuf.gl_pathv[i]; + + fd = open(filename, O_RDONLY | O_NONBLOCK); + if (fd >= 0) { + /* get this event file's name */ + strcpy(evname, "Unknown"); + ioctl(fd, EVIOCGNAME(sizeof(evname)), evname); + + printf("%s (%s) opened successfully\n", filename, evname); + had_some_success = 1; + + /* add a connection to the list */ + c.fd = fd; + c.process = process_input; + add_connection(&c); + } + else + { + if (had_some_success == 1) + continue; + int errno2 = errno; + printf("open for %s failed: %s (%d)\n", + filename, strerror(errno2), errno2); + if (errno2 == EACCES) + printf(" (try running as root)\n"); + if (errno2 == ENOENT) + printf(" (input layer driver may not be present)\n"); + } + } + + globfree(&globbuf); +} + diff --git a/kacpimon/input_layer.h b/kacpimon/input_layer.h new file mode 100644 index 0000000..399ef23 --- /dev/null +++ b/kacpimon/input_layer.h @@ -0,0 +1,32 @@ +/* + * input_layer.h - Kernel ACPI Event Input Layer Interface + * + * Handles the details of getting kernel ACPI events from the input + * layer (/dev/input/event*). + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * (tabs at 4) + */ + +#ifndef INPUT_LAYER_H__ +#define INPUT_LAYER_H__ + +/* open each of the appropriate /dev/input/event* files for input */ +extern void open_input(void); + +#endif /* INPUT_LAYER_H__ */ diff --git a/kacpimon/kacpimon.8 b/kacpimon/kacpimon.8 new file mode 100644 index 0000000..bb3448f --- /dev/null +++ b/kacpimon/kacpimon.8 @@ -0,0 +1,68 @@ +.TH kacpimon 8 "Nov 2009" +.\" Copyright (c) 2009 Ted Felix (www.tedfelix.com) +.SH NAME +kacpimon \- Kernel ACPI Event Monitor +.SH SYNOPSIS +\fBkacpimon\fP +.SH DESCRIPTION +\fBkacpimon\fP is a monitor program that connects to three sources of ACPI +events: +.IP +1. /proc/acpi/event +.br +2. netlink +.br +3. the input layer (/dev/input/event*) +.PP +\fBkacpimon\fP then reports on what it sees while it is connected. +.PP +Use \fBkacpimon\fP to make sure your kernel is sending acpi-related events +via the three sources mentioned above. +.PP +Since both \fBacpid\fP and \fBkacpimon\fP connect to the same sources, be sure +to kill \fBacpid\fP before running kacpimon: +.IP +sudo killall acpid +.PP +Also be sure to run \fBkacpimon\fP as root or else it won't be able to open +/proc/acpi/event or the input layer: +.IP +sudo ./kacpimon +.PP +If you want to pipe the output of \fBkacpimon\fP to a file, be sure to use +the "Esc" key to exit rather than Ctrl-C. Using Ctrl-C will cause +output to be lost. +.SH FILES +.TP +.I /proc/acpi/event +kernel ACPI event file +.TP +.I /dev/input/event* +input layer event files +.SH SEE ALSO +.BR acpi_listen (8), +.BR acpid (8) +.SH AUTHOR +Ted Felix +.SH TODO +\- Make kacpimon something that actually gets installed? +.SH LICENSE +kacpimon \- Kernel ACPI Event Monitor +.br +Copyright (C) 2009 Ted Felix (www.tedfelix.com) +.PP +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. +.PP +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. +.PP +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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Or visit www.gnu.org. + diff --git a/kacpimon/kacpimon.c b/kacpimon/kacpimon.c new file mode 100644 index 0000000..af8176c --- /dev/null +++ b/kacpimon/kacpimon.c @@ -0,0 +1,205 @@ +/* + * kacpimon - Kernel ACPI Event Monitor + * + * Monitors kernel ACPI events from multiple interfaces and reports them + * to the console. + * + * Inspired by (and in some cases blatantly lifted from) Vojtech Pavlik's + * evtest.c, Zhang Rui's acpi_genl, and Alexey Kuznetsov's libnetlink. + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * (tabs at 4) + */ + +/* system */ +#include +#include +#include +#include +#include +#include +#include + +/* local */ +#include "libnetlink.h" +#include "genetlink.h" +#include "acpi_genetlink.h" + +#include "acpi_ids.h" +#include "connection_list.h" +#include "input_layer.h" +#include "netlink.h" + +/* ??? Isn't this in a system header someplace? */ +#define max(a, b) (((a)>(b))?(a):(b)) + +/*********************************************************************/ + +/* Exit flag that can be set by any of the functions to cause the */ +/* program to exit. */ +int exitflag = 0; + +/**************************************************************** + * Old /proc/acpi/event interface + ****************************************************************/ + +void process_proc(int fd) +{ + const int buffsize = 1024; + char buffer[buffsize]; + ssize_t nbytes; + + for (;;) + { + nbytes = read(fd, buffer, buffsize - 1); + + /* ??? Do we need to worry about partial messages? */ + + /* If there are no data to read, bail. */ + if (nbytes <= 0) + break; + + /* Ensure we have a zero terminator */ + buffer[nbytes] = 0; + + printf("/proc/acpi/event: %s", buffer); + } +} + +// --------------------------------------------------------------- + +void open_proc(void) +{ + char *filename = "/proc/acpi/event"; + int fd; + struct connection c; + + fd = open(filename, O_RDONLY | O_NONBLOCK); + if (fd >= 0) + { + printf("%s opened successfully\n", filename); + + /* Add a connection to the list. */ + c.fd = fd; + c.process = process_proc; + add_connection(&c); + } + else + { + int errno2 = errno; + printf("open for %s: %s (%d)\n", + filename, strerror(errno2), errno2); + if (errno2 == EACCES) + printf(" (try running as root)\n"); + if (errno2 == ENOENT) + printf(" (ACPI proc filesystem may not be present)\n"); + if (errno2 == EBUSY) + printf(" (ACPI proc filesystem is in use. " + "You might need to kill acpid.)\n"); + } +} + +/**************************************************************** + * Main Program Functions + ****************************************************************/ + +void monitor(void) +{ + while (exitflag == 0) + { + fd_set readfds; + int nready; + int i; + + /* It's going to get clobbered, so use a copy. */ + readfds = *get_fdset(); + + /* Wait on data. */ + nready = select(get_highestfd() + 1, &readfds, NULL, NULL, NULL); + + /* If something goes wrong, bail. */ + if (nready <= 0) + break; + + /* For each connection */ + for (i = 0; i <= get_number_of_connections(); ++i) + { + int fd; + struct connection *p; + + p = get_connection(i); + + /* If this connection is invalid, bail. */ + if (!p) + break; + + /* Get the file descriptor */ + fd = p -> fd; + + /* If this file descriptor has data waiting, */ + if (FD_ISSET(fd, &readfds)) + { + p->process(fd); + } + } + } +} + +// --------------------------------------------------------------- + +void close_all() +{ + int i = 0; + + /* For each connection */ + for (i = 0; i <= get_number_of_connections(); ++i) + { + struct connection *p; + + p = get_connection(i); + + /* If this connection is invalid, try the next. */ + if (p == 0) + continue; + + close(p -> fd); + } +} + +// --------------------------------------------------------------- + +int main(void) +{ + printf("Kernel ACPI Event Monitor...\n"); + + open_proc(); + open_input(); + open_netlink(); + + printf("Press Escape to exit, or Ctrl-C if that doesn't work.\n"); + + monitor(); + + printf("Closing files...\n"); + + close_all(); + + printf("Goodbye\n"); + + return 0; +} diff --git a/kacpimon/kacpimon.h b/kacpimon/kacpimon.h new file mode 100644 index 0000000..fc64420 --- /dev/null +++ b/kacpimon/kacpimon.h @@ -0,0 +1,35 @@ +/* + * kacpimon - Kernel ACPI Event Monitor + * + * Monitors kernel ACPI events from multiple interfaces and reports them + * to the console. + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * (tabs at 4) + */ + +#ifndef KACPIMON_H__ +#define KACPIMON_H__ + +/* ??? Encapsulate with accessor functions? */ + +/* Exit flag that can be set by any of the functions to cause the */ +/* program to exit. */ +extern int exitflag; + +#endif /* KACPIMON_H__ */ diff --git a/kacpimon/libnetlink.c b/kacpimon/libnetlink.c new file mode 100644 index 0000000..dc1f853 --- /dev/null +++ b/kacpimon/libnetlink.c @@ -0,0 +1,592 @@ +/* + * libnetlink.c RTnetlink service routines. + * + * 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. + * + * Authors: Alexey Kuznetsov, + * + * Modified by Ted Felix (www.tedfelix.com) to fix warnings. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libnetlink.h" + +void rtnl_close(struct rtnl_handle *rth) +{ + if (rth->fd >= 0) { + close(rth->fd); + rth->fd = -1; + } +} + +int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, + int protocol) +{ + socklen_t addr_len; + int sndbuf = 32768; + int rcvbuf = 32768; + + memset(rth, 0, sizeof(rth)); + + rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol); + if (rth->fd < 0) { + perror("Cannot open netlink socket"); + return -1; + } + + if (setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf)) < 0) { + perror("SO_SNDBUF"); + return -1; + } + + if (setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf)) < 0) { + perror("SO_RCVBUF"); + return -1; + } + + memset(&rth->local, 0, sizeof(rth->local)); + rth->local.nl_family = AF_NETLINK; + rth->local.nl_groups = subscriptions; + + if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0) { + perror("Cannot bind netlink socket"); + return -1; + } + addr_len = sizeof(rth->local); + if (getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len) < 0) { + perror("Cannot getsockname"); + return -1; + } + if (addr_len != sizeof(rth->local)) { + fprintf(stderr, "Wrong address length %d\n", addr_len); + return -1; + } + if (rth->local.nl_family != AF_NETLINK) { + fprintf(stderr, "Wrong address family %d\n", rth->local.nl_family); + return -1; + } + rth->seq = time(NULL); + return 0; +} + +int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions) +{ + return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE); +} + +int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type) +{ + struct { + struct nlmsghdr nlh; + struct rtgenmsg g; + } req; + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = type; + req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = rth->dump = ++rth->seq; + req.g.rtgen_family = family; + + return sendto(rth->fd, (void*)&req, sizeof(req), 0, + (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +int rtnl_send(struct rtnl_handle *rth, const char *buf, int len) +{ + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + return sendto(rth->fd, buf, len, 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) +{ + struct nlmsghdr nlh; + struct sockaddr_nl nladdr; + struct iovec iov[2] = { + { .iov_base = &nlh, .iov_len = sizeof(nlh) }, + { .iov_base = req, .iov_len = len } + }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = iov, + .msg_iovlen = 2, + }; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + nlh.nlmsg_len = NLMSG_LENGTH(len); + nlh.nlmsg_type = type; + nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + nlh.nlmsg_pid = 0; + nlh.nlmsg_seq = rth->dump = ++rth->seq; + + return sendmsg(rth->fd, &msg, 0); +} + +int rtnl_dump_filter(struct rtnl_handle *rth, + rtnl_filter_t filter, + void *arg1, + rtnl_filter_t junk, + void *arg2) +{ + struct sockaddr_nl nladdr; + struct iovec iov; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[16384]; + + iov.iov_base = buf; + while (1) { + int status; + struct nlmsghdr *h; + + iov.iov_len = sizeof(buf); + status = recvmsg(rth->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + perror("OVERRUN"); + continue; + } + + if (status == 0) { + fprintf(stderr, "EOF on netlink\n"); + return -1; + } + + h = (struct nlmsghdr*)buf; + while (NLMSG_OK(h, (unsigned)status)) { + int err; + + if (nladdr.nl_pid != 0 || + h->nlmsg_pid != rth->local.nl_pid || + h->nlmsg_seq != rth->dump) { + if (junk) { + err = junk(&nladdr, h, arg2); + if (err < 0) + return err; + } + goto skip_it; + } + + if (h->nlmsg_type == NLMSG_DONE) + return 0; + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *msgerr = (struct nlmsgerr*)NLMSG_DATA(h); + if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) { + fprintf(stderr, "ERROR truncated\n"); + } else { + errno = -msgerr->error; + perror("RTNETLINK answers"); + } + return -1; + } + err = filter(&nladdr, h, arg1); + if (err < 0) + return err; + +skip_it: + h = NLMSG_NEXT(h, status); + } + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Message truncated\n"); + continue; + } + if (status) { + fprintf(stderr, "!!!Remnant of size %d\n", status); + exit(1); + } + } +} + +int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + rtnl_filter_t junk, + void *jarg) +{ + int status; + unsigned seq; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov = { + .iov_base = (void*) n, + .iov_len = n->nlmsg_len + }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[16384]; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = peer; + nladdr.nl_groups = groups; + + n->nlmsg_seq = seq = ++rtnl->seq; + + if (answer == NULL) + n->nlmsg_flags |= NLM_F_ACK; + + status = sendmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + perror("Cannot talk to rtnetlink"); + return -1; + } + + memset(buf,0,sizeof(buf)); + + iov.iov_base = buf; + + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + perror("OVERRUN"); + continue; + } + if (status == 0) { + fprintf(stderr, "EOF on netlink\n"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + fprintf(stderr, "sender address length == %d\n", msg.msg_namelen); + exit(1); + } + for (h = (struct nlmsghdr*)buf; (unsigned)status >= sizeof(*h); ) { + int err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l<0 || len>status) { + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Truncated message\n"); + return -1; + } + fprintf(stderr, "!!!malformed message: len=%d\n", len); + exit(1); + } + + if (nladdr.nl_pid != (unsigned)peer || + h->nlmsg_pid != rtnl->local.nl_pid || + h->nlmsg_seq != seq) { + if (junk) { + err = junk(&nladdr, h, jarg); + if (err < 0) + return err; + } + /* Don't forget to skip that message. */ + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + continue; + } + + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *msgerr = (struct nlmsgerr*)NLMSG_DATA(h); + if ((unsigned)l < sizeof(struct nlmsgerr)) { + fprintf(stderr, "ERROR truncated\n"); + } else { + errno = -msgerr->error; + if (errno == 0) { + if (answer) + memcpy(answer, h, h->nlmsg_len); + return 0; + } + perror("RTNETLINK1 answers"); + } + return -1; + } + if (answer) { + memcpy(answer, h, h->nlmsg_len); + return 0; + } + + fprintf(stderr, "Unexpected reply!!!\n"); + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Message truncated\n"); + continue; + } + if (status) { + fprintf(stderr, "!!!Remnant of size %d\n", status); + exit(1); + } + } +} + +int rtnl_listen(struct rtnl_handle *rtnl, + rtnl_filter_t handler, + void *jarg) +{ + int status; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[8192]; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + iov.iov_base = buf; + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + perror("OVERRUN"); + continue; + } + if (status == 0) { + fprintf(stderr, "EOF on netlink\n"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + fprintf(stderr, "Sender address length == %d\n", msg.msg_namelen); + exit(1); + } + for (h = (struct nlmsghdr*)buf; (unsigned)status >= sizeof(*h); ) { + int err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l<0 || len>status) { + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Truncated message\n"); + return -1; + } + fprintf(stderr, "!!!malformed message: len=%d\n", len); + exit(1); + } + + err = handler(&nladdr, h, jarg); + if (err < 0) + return err; + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Message truncated\n"); + continue; + } + if (status) { + fprintf(stderr, "!!!Remnant of size %d\n", status); + exit(1); + } + } +} + +int rtnl_from_file(FILE *rtnl, rtnl_filter_t handler, + void *jarg) +{ + int status; + struct sockaddr_nl nladdr; + char buf[8192]; + struct nlmsghdr *h = (void*)buf; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + while (1) { + int err, len; + int l; + + status = fread(&buf, 1, sizeof(*h), rtnl); + + if (status < 0) { + if (errno == EINTR) + continue; + perror("rtnl_from_file: fread"); + return -1; + } + if (status == 0) + return 0; + + len = h->nlmsg_len; + l = len - sizeof(*h); + + if (l<0 || (unsigned)len>sizeof(buf)) { + fprintf(stderr, "!!!malformed message: len=%d @%lu\n", + len, ftell(rtnl)); + return -1; + } + + status = fread(NLMSG_DATA(h), 1, NLMSG_ALIGN(l), rtnl); + + if (status < 0) { + perror("rtnl_from_file: fread"); + return -1; + } + if (status < l) { + fprintf(stderr, "rtnl-from_file: truncated message\n"); + return -1; + } + + err = handler(&nladdr, h, jarg); + if (err < 0) + return err; + } +} + +int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *rta; + if ((int)NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { + fprintf(stderr,"addattr32: Error! max allowed bound %d exceeded\n",maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, 4); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, + int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if ((int)NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + fprintf(stderr, "addattr_l ERROR: message exceeded bound of %d\n",maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + return 0; +} + +int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len) +{ + if ((int)NLMSG_ALIGN(n->nlmsg_len) + (int)NLMSG_ALIGN(len) > maxlen) { + fprintf(stderr, "addraw_l ERROR: message exceeded bound of %d\n",maxlen); + return -1; + } + + memcpy(NLMSG_TAIL(n), data, len); + memset((void *) NLMSG_TAIL(n) + len, 0, NLMSG_ALIGN(len) - len); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len); + return 0; +} + +int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *subrta; + + if (RTA_ALIGN(rta->rta_len) + len > maxlen) { + fprintf(stderr,"rta_addattr32: Error! max allowed bound %d exceeded\n",maxlen); + return -1; + } + subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), &data, 4); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; + return 0; +} + +int rta_addattr_l(struct rtattr *rta, int maxlen, int type, + const void *data, int alen) +{ + struct rtattr *subrta; + int len = RTA_LENGTH(alen); + + if (RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len) > maxlen) { + fprintf(stderr,"rta_addattr_l: Error! max allowed bound %d exceeded\n",maxlen); + return -1; + } + subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), data, alen); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len); + return 0; +} + +int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) +{ + memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max) + tb[rta->rta_type] = rta; + rta = RTA_NEXT(rta,len); + } + if (len) + fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len, rta->rta_len); + return 0; +} + +int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, int len) +{ + int i = 0; + + memset(tb, 0, sizeof(struct rtattr *) * max); + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max && i < max) + tb[i++] = rta; + rta = RTA_NEXT(rta,len); + } + if (len) + fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len, rta->rta_len); + return i; +} diff --git a/kacpimon/libnetlink.h b/kacpimon/libnetlink.h new file mode 100644 index 0000000..6185cbc --- /dev/null +++ b/kacpimon/libnetlink.h @@ -0,0 +1,91 @@ +#ifndef __LIBNETLINK_H__ +#define __LIBNETLINK_H__ 1 + +#include +// needed by netlink.h, should be in there +#include +#include +#include + +struct rtnl_handle +{ + int fd; + struct sockaddr_nl local; + struct sockaddr_nl peer; + __u32 seq; + __u32 dump; +}; + +extern int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions); +extern int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, int protocol); +extern void rtnl_close(struct rtnl_handle *rth); +extern int rtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type); +extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len); + +typedef int (*rtnl_filter_t)(const struct sockaddr_nl *, + struct nlmsghdr *n, void *); +extern int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter, + void *arg1, + rtnl_filter_t junk, + void *arg2); +extern int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + rtnl_filter_t junk, + void *jarg); +extern int rtnl_send(struct rtnl_handle *rth, const char *buf, int); + + +extern int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data); +extern int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen); +extern int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len); +extern int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data); +extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, const void *data, int alen); + +extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len); +extern int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, int len); + +#define parse_rtattr_nested(tb, max, rta) \ + (parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta))) + +extern int rtnl_listen(struct rtnl_handle *, rtnl_filter_t handler, + void *jarg); +extern int rtnl_from_file(FILE *, rtnl_filter_t handler, + void *jarg); + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +#ifndef IFA_RTA +#define IFA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) +#endif +#ifndef IFA_PAYLOAD +#define IFA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifaddrmsg)) +#endif + +#ifndef IFLA_RTA +#define IFLA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) +#endif +#ifndef IFLA_PAYLOAD +#define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg)) +#endif + +#ifndef NDA_RTA +#define NDA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) +#endif +#ifndef NDA_PAYLOAD +#define NDA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndmsg)) +#endif + +#ifndef NDTA_RTA +#define NDTA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndtmsg)))) +#endif +#ifndef NDTA_PAYLOAD +#define NDTA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndtmsg)) +#endif + +#endif /* __LIBNETLINK_H__ */ + diff --git a/kacpimon/makefile b/kacpimon/makefile new file mode 100644 index 0000000..d5c188c --- /dev/null +++ b/kacpimon/makefile @@ -0,0 +1,56 @@ +# kacpimon makefile + +sources = acpi_ids.c kacpimon.c connection_list.c input_layer.c libnetlink.c \ + netlink.c +target = kacpimon +# debug build +#CFLAGS = -g -O0 -Wall -Wextra -Wundef -Wshadow -Werror +# release build +#CFLAGS = -O2 -Wall -Wextra -Wundef -Wshadow -Werror +# hybrid build, optimized, but with debugging symbols (Debian-style) +CFLAGS = -g -O2 -Wall -Wextra -Wundef -Wshadow -Werror + +objects := $(sources:.c=.o) + +# Default target +$(target) : $(objects) + +depends := $(sources:.c=.d) + +# Dependency files can be made from .c files like this... +# (the sed line adds the .d file itself as a target) +%.d : %.c + @set -e; \ + rm -f $@; \ + $(CPP) -MM $(CPPFLAGS) $< > $@.$$$$; \ + sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ + rm -f $@.$$$$ + +# Pull in all dependencies from the .d files. +-include $(depends) + +SBIN = /usr/sbin +install: $(target) + mkdir -p $(SBIN) + install -m 750 kacpimon $(SBIN) + +DISTTMP=/tmp +DISTNAME=kacpimon +FULLTMP = $(DISTTMP)/$(DISTNAME) +dist: + rm -rf $(FULLTMP) + mkdir -p $(FULLTMP) + cp -a * $(FULLTMP) + find $(FULLTMP) -type d -name CVS | xargs rm -rf + make -C $(FULLTMP) clean + rm -f $(FULLTMP)/cscope.out + find $(FULLTMP) -name '*~' | xargs rm -f + # Get rid of previous dist + rm -f $(FULLTMP)/$(DISTNAME).tar.gz + tar -C $(DISTTMP) -zcvf $(DISTNAME).tar.gz $(DISTNAME) + rm -rf $(FULLTMP) + +.PHONY : clean +clean : + rm -f $(depends) $(objects) $(target) + diff --git a/kacpimon/netlink.c b/kacpimon/netlink.c new file mode 100644 index 0000000..86e2612 --- /dev/null +++ b/kacpimon/netlink.c @@ -0,0 +1,205 @@ +/* + * netlink.c - Kernel ACPI Event Netlink Interface + * + * Handles the details of getting kernel ACPI events from netlink. + * + * Inspired by (and in some cases blatantly lifted from) Zhang Rui's + * acpi_genl and Alexey Kuznetsov's libnetlink. Thanks also to Yi Yang + * at intel. + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * (tabs at 4) + */ + +/* system */ +#include +#include +#include +#include + +/* local */ +#include "libnetlink.h" +#include "genetlink.h" +#include "acpi_genetlink.h" + +#include "kacpimon.h" +#include "acpi_ids.h" +#include "connection_list.h" + +/* ??? Isn't this in a system header someplace? */ +#define max(a, b) (((a)>(b))?(a):(b)) + +static void +format_netlink(struct nlmsghdr *msg) +{ + struct rtattr *tb[ACPI_GENL_ATTR_MAX + 1]; + struct genlmsghdr *ghdr = NLMSG_DATA(msg); + int len; + struct rtattr *attrs; + + len = msg->nlmsg_len; + + /* if this message doesn't have the proper family ID, drop it */ + if (msg->nlmsg_type != acpi_ids_getfamily()) { + printf("Netlink: Message received with improper family ID.\n"); + return; + } + + len -= NLMSG_LENGTH(GENL_HDRLEN); + + if (len < 0) { + printf("Netlink: Wrong controller message len: %d\n", len); + return; + } + + attrs = (struct rtattr *)((char *)ghdr + GENL_HDRLEN); + /* parse the attributes in this message */ + parse_rtattr(tb, ACPI_GENL_ATTR_MAX, attrs, len); + + /* if there's an ACPI event attribute... */ + if (tb[ACPI_GENL_ATTR_EVENT]) { + /* get the actual event struct */ + struct acpi_genl_event *event = + RTA_DATA(tb[ACPI_GENL_ATTR_EVENT]); + /* format it */ + printf("netlink: %s %s %08x %08x\n", + event->device_class, + event->bus_id, event->type, event->data); + } +} + +/* (based on rtnl_listen() in libnetlink.c) */ +void +process_netlink(int fd) +{ + int status; + struct nlmsghdr *h; + /* the address for recvmsg() */ + struct sockaddr_nl nladdr; + /* the io vector for recvmsg() */ + struct iovec iov; + /* recvmsg() parameters */ + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + /* buffer for the incoming data */ + char buf[8192]; + + /* set up the netlink address */ + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + /* set up the I/O vector */ + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + /* read the data into the buffer */ + status = recvmsg(fd, &msg, 0); + + /* if there was a problem, print a message and keep trying */ + if (status < 0) { + /* if we were interrupted by a signal, bail */ + if (errno == EINTR) + return; + printf("Netlink: recvmsg() error: %s (%d)\n", + strerror(errno), errno); + return; + } + /* if an orderly shutdown has occurred, we're done */ + if (status == 0) { + printf("Netlink: Connection closed\n"); + return; + } + /* check to see if the address length has changed */ + if (msg.msg_namelen != sizeof(nladdr)) { + printf("Netlink: Unexpected address length: %d\n", msg.msg_namelen); + return; + } + + /* for each message received */ + for (h = (struct nlmsghdr*)buf; (unsigned)status >= sizeof(*h); ) { + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l < 0 || len > status) { + if (msg.msg_flags & MSG_TRUNC) { + printf("Netlink: Message truncated (1)\n"); + return; + } + printf("Netlink: Malformed message. Length: %d\n", len); + return; + } + + /* format the message */ + format_netlink(h); + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + printf("Netlink: Message truncated (2)\n"); + return; + } + if (status) { + printf("Netlink: Remnant of size %d\n", status); + return; + } + + return; +} + +/* convert the netlink multicast group number into a bit map */ +/* (e.g. 4 => 16, 5 => 32) */ +static __u32 +nl_mgrp(__u32 group) +{ + if (group > 31) { + printf("Netlink: Use setsockopt for this group: %d\n", group); + return 0; + } + return group ? (1 << (group - 1)) : 0; +} + +void open_netlink(void) +{ + struct rtnl_handle rth; + struct connection c; + + /* some debugging */ + printf("Netlink ACPI Family ID: %hu\n", acpi_ids_getfamily()); + printf("Netlink ACPI Multicast Group ID: %hu\n", acpi_ids_getgroup()); + + /* open the appropriate netlink socket for input */ + if (rtnl_open_byproto( + &rth, nl_mgrp(acpi_ids_getgroup()), NETLINK_GENERIC) < 0) { + printf("Netlink: Cannot open generic netlink socket\n"); + return; + } + + printf("netlink opened successfully\n"); + + /* add a connection to the list */ + c.fd = rth.fd; + c.process = process_netlink; + add_connection(&c); +} diff --git a/kacpimon/netlink.h b/kacpimon/netlink.h new file mode 100644 index 0000000..9921ac2 --- /dev/null +++ b/kacpimon/netlink.h @@ -0,0 +1,31 @@ +/* + * netlink.h - Kernel ACPI Event Netlink Interface + * + * Handles the details of getting kernel ACPI events from netlink. + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * (tabs at 4) + */ + +#ifndef NETLINK_H__ +#define NETLINK_H__ + +/* open the netlink connection */ +extern void open_netlink(void); + +#endif /* NETLINK_H__ */ diff --git a/kacpimon/sample.out b/kacpimon/sample.out new file mode 100644 index 0000000..227bd0a --- /dev/null +++ b/kacpimon/sample.out @@ -0,0 +1,16 @@ +button/power PBTN 00000080 00000001 +button/power (input layer) +button/lid LID 00000080 0000000f +button/lid closed (input layer) +button/lid LID 00000080 00000010 +button/lid open (input layer) +button/sleep SBTN 00000080 00000007 +button/sleep (input layer) +ac_adapter AC 00000080 00000000 +battery BAT0 00000080 00000001 +ac_adapter ACPI0003:00 00000080 00000000 +battery PNP0C0A:00 00000080 00000001 +ac_adapter AC 00000080 00000001 +battery BAT0 00000080 00000001 +ac_adapter ACPI0003:00 00000080 00000001 +battery PNP0C0A:00 00000080 00000001 diff --git a/libnetlink.c b/libnetlink.c new file mode 100644 index 0000000..045d2d1 --- /dev/null +++ b/libnetlink.c @@ -0,0 +1,591 @@ +/* + * libnetlink.c RTnetlink service routines. + * + * 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. + * + * Authors: Alexey Kuznetsov, + * + * Modified by Ted Felix (www.tedfelix.com) to fix warnings. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libnetlink.h" + +void rtnl_close(struct rtnl_handle *rth) +{ + if (rth->fd >= 0) { + close(rth->fd); + rth->fd = -1; + } +} + +int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, + int protocol) +{ + socklen_t addr_len; + int sndbuf = 32768; + int rcvbuf = 32768; + + memset(rth, 0, sizeof(rth)); + + rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol); + if (rth->fd < 0) { + perror("Cannot open netlink socket"); + return -1; + } + + if (setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf)) < 0) { + perror("SO_SNDBUF"); + return -1; + } + + if (setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf)) < 0) { + perror("SO_RCVBUF"); + return -1; + } + + memset(&rth->local, 0, sizeof(rth->local)); + rth->local.nl_family = AF_NETLINK; + rth->local.nl_groups = subscriptions; + + if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0) { + perror("Cannot bind netlink socket"); + return -1; + } + addr_len = sizeof(rth->local); + if (getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len) < 0) { + perror("Cannot getsockname"); + return -1; + } + if (addr_len != sizeof(rth->local)) { + fprintf(stderr, "Wrong address length %d\n", addr_len); + return -1; + } + if (rth->local.nl_family != AF_NETLINK) { + fprintf(stderr, "Wrong address family %d\n", rth->local.nl_family); + return -1; + } + rth->seq = time(NULL); + return 0; +} + +int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions) +{ + return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE); +} + +int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type) +{ + struct { + struct nlmsghdr nlh; + struct rtgenmsg g; + } req; + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = type; + req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = rth->dump = ++rth->seq; + req.g.rtgen_family = family; + + return sendto(rth->fd, (void*)&req, sizeof(req), 0, + (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +int rtnl_send(struct rtnl_handle *rth, const char *buf, int len) +{ + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + return sendto(rth->fd, buf, len, 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) +{ + struct nlmsghdr nlh; + struct sockaddr_nl nladdr; + struct iovec iov[2] = { + { .iov_base = &nlh, .iov_len = sizeof(nlh) }, + { .iov_base = req, .iov_len = len } + }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = iov, + .msg_iovlen = 2, + }; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + nlh.nlmsg_len = NLMSG_LENGTH(len); + nlh.nlmsg_type = type; + nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + nlh.nlmsg_pid = 0; + nlh.nlmsg_seq = rth->dump = ++rth->seq; + + return sendmsg(rth->fd, &msg, 0); +} + +int rtnl_dump_filter(struct rtnl_handle *rth, + rtnl_filter_t filter, + void *arg1, + rtnl_filter_t junk, + void *arg2) +{ + struct sockaddr_nl nladdr; + struct iovec iov; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[16384]; + + iov.iov_base = buf; + while (1) { + int status; + struct nlmsghdr *h; + + iov.iov_len = sizeof(buf); + status = recvmsg(rth->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + perror("OVERRUN"); + continue; + } + + if (status == 0) { + fprintf(stderr, "EOF on netlink\n"); + return -1; + } + + h = (struct nlmsghdr*)buf; + while (NLMSG_OK(h, (unsigned)status)) { + int err; + + if (nladdr.nl_pid != 0 || + h->nlmsg_pid != rth->local.nl_pid || + h->nlmsg_seq != rth->dump) { + if (junk) { + err = junk(&nladdr, h, arg2); + if (err < 0) + return err; + } + goto skip_it; + } + + if (h->nlmsg_type == NLMSG_DONE) + return 0; + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *msgerr = (struct nlmsgerr*)NLMSG_DATA(h); + if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) { + fprintf(stderr, "ERROR truncated\n"); + } else { + errno = -msgerr->error; + perror("RTNETLINK answers"); + } + return -1; + } + err = filter(&nladdr, h, arg1); + if (err < 0) + return err; + +skip_it: + h = NLMSG_NEXT(h, status); + } + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Message truncated\n"); + continue; + } + if (status) { + fprintf(stderr, "!!!Remnant of size %d\n", status); + exit(1); + } + } +} + +int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + rtnl_filter_t junk, + void *jarg) +{ + int status; + unsigned seq; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov = { + .iov_base = (void*) n, + .iov_len = n->nlmsg_len + }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[16384]; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = peer; + nladdr.nl_groups = groups; + + n->nlmsg_seq = seq = ++rtnl->seq; + + if (answer == NULL) + n->nlmsg_flags |= NLM_F_ACK; + + status = sendmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + perror("Cannot talk to rtnetlink"); + return -1; + } + + memset(buf,0,sizeof(buf)); + + iov.iov_base = buf; + + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + perror("OVERRUN"); + continue; + } + if (status == 0) { + fprintf(stderr, "EOF on netlink\n"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + fprintf(stderr, "sender address length == %d\n", msg.msg_namelen); + exit(1); + } + for (h = (struct nlmsghdr*)buf; (unsigned)status >= sizeof(*h); ) { + int err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l<0 || len>status) { + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Truncated message\n"); + return -1; + } + fprintf(stderr, "!!!malformed message: len=%d\n", len); + exit(1); + } + + if (nladdr.nl_pid != (unsigned)peer || + h->nlmsg_pid != rtnl->local.nl_pid || + h->nlmsg_seq != seq) { + if (junk) { + err = junk(&nladdr, h, jarg); + if (err < 0) + return err; + } + /* Don't forget to skip that message. */ + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + continue; + } + + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *msgerr = (struct nlmsgerr*)NLMSG_DATA(h); + if ((unsigned)l < sizeof(struct nlmsgerr)) { + fprintf(stderr, "ERROR truncated\n"); + } else { + errno = -msgerr->error; + if (errno == 0) { + if (answer) + memcpy(answer, h, h->nlmsg_len); + return 0; + } + perror("RTNETLINK1 answers"); + } + return -1; + } + if (answer) { + memcpy(answer, h, h->nlmsg_len); + return 0; + } + + fprintf(stderr, "Unexpected reply!!!\n"); + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Message truncated\n"); + continue; + } + if (status) { + fprintf(stderr, "!!!Remnant of size %d\n", status); + exit(1); + } + } +} + +int rtnl_listen(struct rtnl_handle *rtnl, + rtnl_filter_t handler, + void *jarg) +{ + int status; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[8192]; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + iov.iov_base = buf; + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + perror("OVERRUN"); + continue; + } + if (status == 0) { + fprintf(stderr, "EOF on netlink\n"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + fprintf(stderr, "Sender address length == %d\n", msg.msg_namelen); + exit(1); + } + for (h = (struct nlmsghdr*)buf; (unsigned)status >= sizeof(*h); ) { + int err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l<0 || len>status) { + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Truncated message\n"); + return -1; + } + fprintf(stderr, "!!!malformed message: len=%d\n", len); + exit(1); + } + + err = handler(&nladdr, h, jarg); + if (err < 0) + return err; + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Message truncated\n"); + continue; + } + if (status) { + fprintf(stderr, "!!!Remnant of size %d\n", status); + exit(1); + } + } +} + +int rtnl_from_file(FILE *rtnl, rtnl_filter_t handler, + void *jarg) +{ + int status; + struct sockaddr_nl nladdr; + char buf[8192]; + struct nlmsghdr *h = (void*)buf; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + while (1) { + int err, len; + int l; + + status = fread(&buf, 1, sizeof(*h), rtnl); + + if (status < 0) { + if (errno == EINTR) + continue; + perror("rtnl_from_file: fread"); + return -1; + } + if (status == 0) + return 0; + + len = h->nlmsg_len; + l = len - sizeof(*h); + + if (l<0 || (unsigned)len>sizeof(buf)) { + fprintf(stderr, "!!!malformed message: len=%d @%lu\n", + len, ftell(rtnl)); + return -1; + } + + status = fread(NLMSG_DATA(h), 1, NLMSG_ALIGN(l), rtnl); + + if (status < 0) { + perror("rtnl_from_file: fread"); + return -1; + } + if (status < l) { + fprintf(stderr, "rtnl-from_file: truncated message\n"); + return -1; + } + + err = handler(&nladdr, h, jarg); + if (err < 0) + return err; + } +} + +int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *rta; + if ((int)NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { + fprintf(stderr,"addattr32: Error! max allowed bound %d exceeded\n",maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, 4); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, + int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if ((int)NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + fprintf(stderr, "addattr_l ERROR: message exceeded bound of %d\n",maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + return 0; +} + +int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len) +{ + if ((int)NLMSG_ALIGN(n->nlmsg_len) + (int)NLMSG_ALIGN(len) > maxlen) { + fprintf(stderr, "addraw_l ERROR: message exceeded bound of %d\n",maxlen); + return -1; + } + + memcpy(NLMSG_TAIL(n), data, len); + memset((void *) NLMSG_TAIL(n) + len, 0, NLMSG_ALIGN(len) - len); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len); + return 0; +} + +int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *subrta; + + if (RTA_ALIGN(rta->rta_len) + len > maxlen) { + fprintf(stderr,"rta_addattr32: Error! max allowed bound %d exceeded\n",maxlen); + return -1; + } + subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), &data, 4); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; + return 0; +} + +int rta_addattr_l(struct rtattr *rta, int maxlen, int type, + const void *data, int alen) +{ + struct rtattr *subrta; + int len = RTA_LENGTH(alen); + + if (RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len) > maxlen) { + fprintf(stderr,"rta_addattr_l: Error! max allowed bound %d exceeded\n",maxlen); + return -1; + } + subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), data, alen); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len); + return 0; +} + +int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) +{ + memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max) + tb[rta->rta_type] = rta; + rta = RTA_NEXT(rta,len); + } + if (len) + fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len, rta->rta_len); + return 0; +} + +int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, int len) +{ + int i = 0; + + memset(tb, 0, sizeof(struct rtattr *) * max); + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max && i < max) + tb[i++] = rta; + rta = RTA_NEXT(rta,len); + } + if (len) + fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len, rta->rta_len); + return i; +} diff --git a/libnetlink.h b/libnetlink.h new file mode 100644 index 0000000..8f9cb5e --- /dev/null +++ b/libnetlink.h @@ -0,0 +1,91 @@ +#ifndef __LIBNETLINK_H__ +#define __LIBNETLINK_H__ 1 + +#include +/* needed by netlink.h, should be in there */ +#include +#include +#include + +struct rtnl_handle +{ + int fd; + struct sockaddr_nl local; + struct sockaddr_nl peer; + __u32 seq; + __u32 dump; +}; + +extern int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions); +extern int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, int protocol); +extern void rtnl_close(struct rtnl_handle *rth); +extern int rtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type); +extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len); + +typedef int (*rtnl_filter_t)(const struct sockaddr_nl *, + struct nlmsghdr *n, void *); +extern int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter, + void *arg1, + rtnl_filter_t junk, + void *arg2); +extern int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + rtnl_filter_t junk, + void *jarg); +extern int rtnl_send(struct rtnl_handle *rth, const char *buf, int); + + +extern int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data); +extern int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen); +extern int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len); +extern int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data); +extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, const void *data, int alen); + +extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len); +extern int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, int len); + +#define parse_rtattr_nested(tb, max, rta) \ + (parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta))) + +extern int rtnl_listen(struct rtnl_handle *, rtnl_filter_t handler, + void *jarg); +extern int rtnl_from_file(FILE *, rtnl_filter_t handler, + void *jarg); + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +#ifndef IFA_RTA +#define IFA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) +#endif +#ifndef IFA_PAYLOAD +#define IFA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifaddrmsg)) +#endif + +#ifndef IFLA_RTA +#define IFLA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) +#endif +#ifndef IFLA_PAYLOAD +#define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg)) +#endif + +#ifndef NDA_RTA +#define NDA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) +#endif +#ifndef NDA_PAYLOAD +#define NDA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndmsg)) +#endif + +#ifndef NDTA_RTA +#define NDTA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndtmsg)))) +#endif +#ifndef NDTA_PAYLOAD +#define NDTA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndtmsg)) +#endif + +#endif /* __LIBNETLINK_H__ */ + diff --git a/log.c b/log.c new file mode 100644 index 0000000..36974f9 --- /dev/null +++ b/log.c @@ -0,0 +1,56 @@ +/* + * log.c - ACPI daemon logging + * + * Portions Copyright (C) 2000 Andrew Henroid + * Portions Copyright (C) 2001 Sun Microsystems + * Portions Copyright (C) 2004 Tim Hockin (thockin@hockin.org) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include + +#include "log.h" + +int log_debug_to_stderr = 0; + +int +#ifdef __GNUC__ +__attribute__((format(printf, 2, 3))) +#endif +acpid_log(int level, const char *fmt, ...) +{ + va_list args; + + if (level == LOG_DEBUG) { + /* if "-d" has been specified */ + if (log_debug_to_stderr) { + /* send debug output to stderr */ + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + fprintf(stderr, "\n"); + } + } else { + va_start(args, fmt); + vsyslog(level, fmt, args); + va_end(args); + } + + return 0; +} diff --git a/log.h b/log.h new file mode 100644 index 0000000..971f76e --- /dev/null +++ b/log.h @@ -0,0 +1,37 @@ +/* + * log.h - ACPI daemon logging + * + * Copyright (C) 1999-2000 Andrew Henroid + * Copyright (C) 2001 Sun Microsystems + * Portions Copyright (C) 2004 Tim Hockin (thockin@hockin.org) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LOG_H__ +#define LOG_H__ + +/* for LOG_ERR, LOG_DEBUG, LOG_INFO, etc... */ +#include + +/* + * Set to 1 to send LOG_DEBUG logging to stderr, zero to ignore LOG_DEBUG + * logging. Default is zero. + */ +extern int log_debug_to_stderr; + +extern int acpid_log(int level, const char *fmt, ...); + +#endif /* LOG_H__ */ diff --git a/netlink.c b/netlink.c new file mode 100644 index 0000000..0eb493b --- /dev/null +++ b/netlink.c @@ -0,0 +1,240 @@ +/* + * netlink.c - Kernel ACPI Event Netlink Interface + * + * Handles the details of getting kernel ACPI events from netlink. + * + * Inspired by (and in some cases blatantly lifted from) Zhang Rui's + * acpi_genl and Alexey Kuznetsov's libnetlink. Thanks also to Yi Yang + * at intel. + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * (tabs at 4) + */ + +/* system */ +#include +#include +#include +#include +#include + +/* local */ +#include "acpid.h" +#include "log.h" +#include "event.h" + +#include "libnetlink.h" +#include "genetlink.h" +#include "acpi_genetlink.h" + +#include "acpi_ids.h" +#include "connection_list.h" + +static void +format_netlink(struct nlmsghdr *msg) +{ + struct rtattr *tb[ACPI_GENL_ATTR_MAX + 1]; + struct genlmsghdr *ghdr = NLMSG_DATA(msg); + int len; + struct rtattr *attrs; + + len = msg->nlmsg_len; + + /* if this message doesn't have the proper family ID, drop it */ + if (msg->nlmsg_type != acpi_ids_getfamily()) { + if (logevents) { + acpid_log(LOG_INFO, "wrong netlink family ID."); + } + return; + } + + len -= NLMSG_LENGTH(GENL_HDRLEN); + + if (len < 0) { + acpid_log(LOG_WARNING, + "wrong netlink controller message len: %d", len); + return; + } + + attrs = (struct rtattr *)((char *)ghdr + GENL_HDRLEN); + /* parse the attributes in this message */ + parse_rtattr(tb, ACPI_GENL_ATTR_MAX, attrs, len); + + /* if there's an ACPI event attribute... */ + if (tb[ACPI_GENL_ATTR_EVENT]) { + /* get the actual event struct */ + struct acpi_genl_event *event = + RTA_DATA(tb[ACPI_GENL_ATTR_EVENT]); + char buf[64]; + + /* format it */ + snprintf(buf, sizeof(buf), "%s %s %08x %08x", + event->device_class, event->bus_id, event->type, event->data); + + /* if we're locked, don't process the event */ + if (locked()) { + if (logevents) { + acpid_log(LOG_INFO, + "lockfile present, not processing " + "netlink event \"%s\"", buf); + } + return; + } + + if (logevents) + acpid_log(LOG_INFO, + "received netlink event \"%s\"", buf); + + /* send the event off to the handler */ + acpid_handle_event(buf); + + if (logevents) + acpid_log(LOG_INFO, + "completed netlink event \"%s\"", buf); + } +} + +/* (based on rtnl_listen() in libnetlink.c) */ +void +process_netlink(int fd) +{ + int status; + struct nlmsghdr *h; + /* the address for recvmsg() */ + struct sockaddr_nl nladdr; + /* the io vector for recvmsg() */ + struct iovec iov; + /* recvmsg() parameters */ + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + /* buffer for the incoming data */ + char buf[8192]; + static int nerrs; + + /* set up the netlink address */ + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + /* set up the I/O vector */ + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + /* read the data into the buffer */ + status = recvmsg(fd, &msg, 0); + + /* if there was a problem, print a message and keep trying */ + if (status < 0) { + /* if we were interrupted by a signal, bail */ + if (errno == EINTR) + return; + + acpid_log(LOG_ERR, "netlink read error: %s (%d)", + strerror(errno), errno); + if (++nerrs >= ACPID_MAX_ERRS) { + acpid_log(LOG_ERR, + "too many errors reading via " + "netlink - aborting"); + exit(EXIT_FAILURE); + } + return; + } + /* if an orderly shutdown has occurred, we're done */ + if (status == 0) { + acpid_log(LOG_WARNING, "netlink connection closed"); + exit(EXIT_FAILURE); + } + /* check to see if the address length has changed */ + if (msg.msg_namelen != sizeof(nladdr)) { + acpid_log(LOG_WARNING, "netlink unexpected length: " + "%d expected: %d", msg.msg_namelen, sizeof(nladdr)); + return; + } + + /* for each message received */ + for (h = (struct nlmsghdr*)buf; (unsigned)status >= sizeof(*h); ) { + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l < 0 || len > status) { + if (msg.msg_flags & MSG_TRUNC) { + acpid_log(LOG_WARNING, "netlink msg truncated (1)"); + return; + } + acpid_log(LOG_WARNING, + "malformed netlink msg, length %d", len); + return; + } + + /* format the message */ + format_netlink(h); + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + acpid_log(LOG_WARNING, "netlink msg truncated (2)"); + return; + } + if (status) { + acpid_log(LOG_WARNING, "netlink remnant of size %d", status); + return; + } + + return; +} + +/* convert the netlink multicast group number into a bit map */ +/* (e.g. 4 => 16, 5 => 32) */ +static __u32 +nl_mgrp(__u32 group) +{ + if (group > 31) { + acpid_log(LOG_ERR, "Unexpected group number %d", group); + return 0; + } + return group ? (1 << (group - 1)) : 0; +} + +void open_netlink(void) +{ + struct rtnl_handle rth; + struct connection c; + + /* open the appropriate netlink socket for input */ + if (rtnl_open_byproto( + &rth, nl_mgrp(acpi_ids_getgroup()), NETLINK_GENERIC) < 0) { + acpid_log(LOG_ERR, "cannot open generic netlink socket"); + return; + } + + acpid_log(LOG_DEBUG, "netlink opened successfully"); + + /* add a connection to the list */ + c.fd = rth.fd; + c.process = process_netlink; + c.pathname = NULL; + c.kybd = 0; + add_connection(&c); +} + diff --git a/netlink.h b/netlink.h new file mode 100644 index 0000000..9921ac2 --- /dev/null +++ b/netlink.h @@ -0,0 +1,31 @@ +/* + * netlink.h - Kernel ACPI Event Netlink Interface + * + * Handles the details of getting kernel ACPI events from netlink. + * + * Copyright (C) 2008, Ted Felix (www.tedfelix.com) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * (tabs at 4) + */ + +#ifndef NETLINK_H__ +#define NETLINK_H__ + +/* open the netlink connection */ +extern void open_netlink(void); + +#endif /* NETLINK_H__ */ diff --git a/packaging/acpid b/packaging/acpid new file mode 100644 index 0000000..f0d2bfb --- /dev/null +++ b/packaging/acpid @@ -0,0 +1 @@ +OPTIONS= diff --git a/packaging/acpid-2.0.9-makefile.patch b/packaging/acpid-2.0.9-makefile.patch new file mode 100644 index 0000000..b05f23f --- /dev/null +++ b/packaging/acpid-2.0.9-makefile.patch @@ -0,0 +1,16 @@ +diff -uprN acpid-2.0.9/Makefile acpid-2.0.9-new/Makefile +--- acpid-2.0.9/Makefile 2011-03-12 08:06:19.000000000 -0800 ++++ acpid-2.0.9-new/Makefile 2011-05-25 15:58:12.990292018 -0700 +@@ -31,9 +31,9 @@ MAN8GZ = $(MAN8:.8=.8.gz) + + DOCS = COPYING Changelog README TESTPLAN TODO + +-CFLAGS = -W -Wall -Werror -Wundef -Wshadow -D_GNU_SOURCE $(OPT) \ +- -fno-strict-aliasing -g $(DEFS) +-DEFS = -DVERSION="\"$(VERSION)\"" ++CFLAGS = -W -Wall -Werror -Wundef -Wshadow -O2 -g $(DEFS) $(RPM_OPT_FLAGS) -fPIE ++LDFLAGS = -pie -Wl,-z,relro ++DEFS = -DVERSION="\"$(VERSION)\"" -D_GNU_SOURCE + + all: $(PROGS) + diff --git a/packaging/acpid-start-script b/packaging/acpid-start-script new file mode 100644 index 0000000..c75d5a5 --- /dev/null +++ b/packaging/acpid-start-script @@ -0,0 +1,4 @@ +#!/bin/sh + +/usr/sbin/acpid & +/etc/acpi/actions/start.sh diff --git a/packaging/acpid.ac.conf b/packaging/acpid.ac.conf new file mode 100644 index 0000000..e69de29 diff --git a/packaging/acpid.battery.conf b/packaging/acpid.battery.conf new file mode 100644 index 0000000..bbddf8e --- /dev/null +++ b/packaging/acpid.battery.conf @@ -0,0 +1,5 @@ +# /etc/acpi/events/battery +# Called when AC power goes away and we switch to battery + +event=battery.* +action=/etc/acpi/actions/battery.sh diff --git a/packaging/acpid.battery.sh b/packaging/acpid.battery.sh new file mode 100644 index 0000000..817ab44 --- /dev/null +++ b/packaging/acpid.battery.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +. /etc/sysconfig/acpi +. /usr/share/acpi/power-funcs + +if [ x$1 = xstop ] ; then + # When stop, we go to the AC state regardless of the actual power state. + RUN_SCRIPT_DIR=/etc/acpi/ac.d + + # However, if we have a stored power state, and that power state is already + # AC, then we don't need to do anything, and we exit immediately. + if [ -f "$POWERSTATE" ]; then + OLDSTATE=$(cat $POWERSTATE) + if [ "$OLDSTATE" = "AC" ] ; then + exit 0 + fi + fi +else + # Get the power state (AC/BATTERY) into STATE + getState; + + # Compare the power state with a stored state and exit if the state is the + # same. If not, then store the power state for comparison the next time + # around. + checkStateChanged; + + if [ "$STATE" = "BATTERY" ] ; then + RUN_SCRIPT_DIR=/etc/acpi/battery.d + else + RUN_SCRIPT_DIR=/etc/acpi/ac.d + fi +fi + +for SCRIPT in $RUN_SCRIPT_DIR/*.sh; do + if [ -f $SCRIPT ] ; then + . $SCRIPT + fi +done + diff --git a/packaging/acpid.lid.conf b/packaging/acpid.lid.conf new file mode 100644 index 0000000..0545faf --- /dev/null +++ b/packaging/acpid.lid.conf @@ -0,0 +1,5 @@ +# ACPID config to suspend machine if lidbutton is pressed, but only if +# no gnome-power-manager is running + +event=button/lid.* +action=/etc/acpi/actions/lid.sh diff --git a/packaging/acpid.lid.sh b/packaging/acpid.lid.sh new file mode 100644 index 0000000..9805df4 --- /dev/null +++ b/packaging/acpid.lid.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +PATH=/sbin:/bin:/usr/bin + +grep -q open /proc/acpi/button/lid/*/state +if [ $? == 0 ]; then + exit +fi +if [ "`who -r | awk '{print $2}'`" == "0" ]; then + exit +fi + +# Get the ID of the first active X11 session: +uid_session=$( +ck-list-sessions | \ +awk ' +/^Session[0-9]+:$/ { uid = active = x11 = "" ; next } +{ gsub(/'\''/, "", $3) } +$1 == "unix-user" { uid = $3 } +$1 == "active" { active = $3 } +$1 == "x11-display" { x11 = $3 } +active == "TRUE" && x11 != "" { + print uid + exit +}') + +# Check that there is a power manager, otherwise shut down. +[ "$uid_session" ] && +ps axo uid,cmd | \ +awk ' + $1 == '$uid_session' && + ($2 == "gnome-power-manager" || $2 == "kpowersave" || $2 == "xfce4-power-manager" || $2 ~ /dawati-power-icon/ ) \ + { found = 1; exit } + END { exit !found } +' || + echo "mem" > /sys/power/state + diff --git a/packaging/acpid.power.conf b/packaging/acpid.power.conf new file mode 100644 index 0000000..b654aa3 --- /dev/null +++ b/packaging/acpid.power.conf @@ -0,0 +1,5 @@ +# ACPID config to power down machine if powerbutton is pressed, but only if +# no gnome-power-manager is running + +event=button/power.* +action=/etc/acpi/actions/power.sh diff --git a/packaging/acpid.power.sh b/packaging/acpid.power.sh new file mode 100644 index 0000000..0996d2a --- /dev/null +++ b/packaging/acpid.power.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +PATH=/sbin:/bin:/usr/bin + +# Get the ID of the first active X11 session: +uid_session=$( +ck-list-sessions | \ +awk ' +/^Session[0-9]+:$/ { uid = active = x11 = "" ; next } +{ gsub(/'\''/, "", $3) } +$1 == "unix-user" { uid = $3 } +$1 == "active" { active = $3 } +$1 == "x11-display" { x11 = $3 } +active == "TRUE" && x11 != "" { + print uid + exit +}') + +# Check that there is a power manager, otherwise shut down. +[ "$uid_session" ] && +ps axo uid,cmd | \ +awk ' + $1 == '$uid_session' && + ($2 == "gnome-power-manager" || $2 == "kpowersave" || $2 == "xfce4-power-manager" || $2 ~ /dawati-power-icon/ ) \ + { found = 1; exit } + END { exit !found } +' || + shutdown -h now + diff --git a/packaging/acpid.service b/packaging/acpid.service new file mode 100644 index 0000000..d245e67 --- /dev/null +++ b/packaging/acpid.service @@ -0,0 +1,10 @@ +[Unit] +Description=ACPI event daemon + +[Service] +Type=forking +EnvironmentFile=/etc/sysconfig/acpid +ExecStart=/usr/sbin/acpid $OPTIONS + +[Install] +WantedBy=multi-user.target diff --git a/packaging/acpid.spec b/packaging/acpid.spec new file mode 100644 index 0000000..d8936ff --- /dev/null +++ b/packaging/acpid.spec @@ -0,0 +1,124 @@ +Name: acpid +Version: 2.0.14 +Release: 1 +License: GPLv2+ and BSD +Summary: ACPI Event Daemon +Group: System/Daemons +Source: http://tedfelix.com/linux/acpid-%{version}.tar.gz +Source2: acpid.video.conf +Source3: acpid.power.conf +Source4: acpid.power.sh +Source5: acpid.lid.conf +Source6: acpid.lid.sh +Source7: acpid.battery.sh +Source8: acpid.battery.conf +Source9: acpid.ac.conf +Source13: acpid-start-script +Source14: acpid.start.sh +Source15: acpid.service +Source16: acpid +# >> gbp-patch-tags # auto-added by gbp +Patch1: acpid-2.0.9-makefile.patch +# << gbp-patch-tags # auto-added by gbp + +Url: http://tedfelix.com/linux/acpid-netlink.html +ExclusiveArch: ia64 x86_64 %{ix86} + +%description +acpid is a daemon that dispatches ACPI events to user-space programs. + +%package extra-docs +Summary: sample docs and sample scripts for apcid +Group: Documentation +Requires: %{name} = %{version} + +%description extra-docs +Extra sample docs and scripts for acpid. + +%prep +%setup -q +# >> gbp-apply-patches # auto-added by gbp +%patch1 -p1 -b .makefile +# << gbp-apply-patches # auto-added by gbp + +%build +make %{?_smp_mflags} + + +%install +make install DESTDIR=%{buildroot} DOCDIR=%{_docdir}/%{name} + +mkdir -p %{buildroot}%{_sysconfdir}/acpi/events +mkdir -p %{buildroot}%{_sysconfdir}/acpi/actions +mkdir -p %{buildroot}%{_datadir}/acpi +chmod 755 %{buildroot}%{_sysconfdir}/acpi/events +mkdir -p %{buildroot}%{_sysconfdir}/acpi/ac.d +mkdir -p %{buildroot}%{_sysconfdir}/acpi/battery.d +mkdir -p %{buildroot}%{_sysconfdir}/acpi/start.d +mkdir -p %{buildroot}%{_sysconfdir}/pm/sleep.d +mkdir -p %{buildroot}%{_localstatedir}/lib/acpi-support +mkdir -p %{buildroot}/%{_lib}/systemd/system +install -m 644 %{SOURCE2} %{buildroot}%{_sysconfdir}/acpi/events/videoconf +install -m 644 %{SOURCE3} %{buildroot}%{_sysconfdir}/acpi/events/powerconf +install -m 755 %{SOURCE4} %{buildroot}%{_sysconfdir}/acpi/actions/power.sh +install -m 644 %{SOURCE5} %{buildroot}%{_sysconfdir}/acpi/events/lidconf +install -m 755 %{SOURCE6} %{buildroot}%{_sysconfdir}/acpi/actions/lid.sh +install -m 755 %{SOURCE7} %{buildroot}%{_sysconfdir}/acpi/actions/battery.sh +install -m 644 %{SOURCE8} %{buildroot}%{_sysconfdir}/acpi/events/batteryconf +install -m 644 %{SOURCE9} %{buildroot}%{_sysconfdir}/acpi/events/acconf +install -m 755 %{SOURCE13} %{buildroot}%{_sbindir}/acpid-start-script +install -m 755 %{SOURCE14} %{buildroot}%{_sysconfdir}/acpi/actions/start.sh +install -D -m 644 %{SOURCE15} %{buildroot}/%{_lib}/systemd/system/acpid.service +install -D -m 644 %{SOURCE16} %{buildroot}%{_sysconfdir}/sysconfig/acpid + +mkdir -p %{buildroot}/%{_lib}/systemd/system/sysinit.target.wants +ln -s ../acpid.service %{buildroot}/%{_lib}/systemd/system/sysinit.target.wants/acpid.service + + +%files +%dir %{_sysconfdir}/acpi +%dir %{_sysconfdir}/acpi/events +%dir %{_sysconfdir}/acpi/actions +%dir %{_sysconfdir}/acpi/ac.d +%dir %{_sysconfdir}/acpi/battery.d +%dir %{_localstatedir}/lib/acpi-support +/%{_lib}/systemd/system/acpid.service +/%{_lib}/systemd/system/sysinit.target.wants/acpid.service +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/acpi/events/videoconf +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/acpi/events/powerconf +%config(noreplace) %attr(0755,root,root) %{_sysconfdir}/acpi/actions/power.sh +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/acpi/events/lidconf +%config(noreplace) %attr(0755,root,root) %{_sysconfdir}/acpi/actions/lid.sh +%config(noreplace) %attr(0755,root,root) %{_sysconfdir}/acpi/actions/battery.sh +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/acpi/events/batteryconf +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/acpi/events/acconf +%config(noreplace) %attr(0755,root,root) %{_sysconfdir}/acpi/actions/start.sh +%config(noreplace) %attr(0755,root,root) %{_sysconfdir}/sysconfig/acpid + +%{_bindir}/acpi_listen +%{_sbindir}/acpid +%{_sbindir}/acpid-start-script +%{_mandir}/man8/acpid.8.gz +%{_mandir}/man8/acpi_listen.8.gz + +%files extra-docs +%defattr(-,root,root) +%doc %{_defaultdocdir}/acpid/COPYING +%doc %{_defaultdocdir}/acpid/Changelog +%doc %{_defaultdocdir}/acpid/README +%doc %{_defaultdocdir}/acpid/TESTPLAN +%doc %{_defaultdocdir}/acpid/TODO +%doc %{_defaultdocdir}/acpid/samples/acpi_handler-conf +%doc %{_defaultdocdir}/acpid/samples/acpi_handler.sh +%doc %{_defaultdocdir}/acpid/samples/battery/battery-conf +%doc %{_defaultdocdir}/acpid/samples/battery/battery.sh +%doc %{_defaultdocdir}/acpid/samples/panasonic/ac_adapt.pl +%doc %{_defaultdocdir}/acpid/samples/panasonic/ac_adapter +%doc %{_defaultdocdir}/acpid/samples/panasonic/hotkey +%doc %{_defaultdocdir}/acpid/samples/panasonic/hotkey.pl +%doc %{_defaultdocdir}/acpid/samples/power +%doc %{_defaultdocdir}/acpid/samples/power.sh +%doc %{_defaultdocdir}/acpid/samples/powerbtn/powerbtn-conf +%doc %{_defaultdocdir}/acpid/samples/powerbtn/powerbtn.sh +%doc %{_defaultdocdir}/acpid/samples/powerbtn/powerbtn.sh.old +%doc %{_defaultdocdir}/acpid/COPYING diff --git a/packaging/acpid.start.sh b/packaging/acpid.start.sh new file mode 100644 index 0000000..b849881 --- /dev/null +++ b/packaging/acpid.start.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +for SCRIPT in /etc/acpi/start.d/*.sh; do + if [ -x $SCRIPT ] ; then + . $SCRIPT + fi +done diff --git a/packaging/acpid.video.conf b/packaging/acpid.video.conf new file mode 100644 index 0000000..97507bf --- /dev/null +++ b/packaging/acpid.video.conf @@ -0,0 +1,6 @@ +# Configuration to turn on DPMS again on video activity, needed for some +# laptops. Disabled by default, uncomment if your laptop display stays blank +# after you close and open the lid. + +#event=video.* +#action=/usr/sbin/vbetool dpms on diff --git a/proc.c b/proc.c new file mode 100644 index 0000000..7d9fcd7 --- /dev/null +++ b/proc.c @@ -0,0 +1,217 @@ +/* + * proc.c - ACPI daemon proc filesystem interface + * + * Portions Copyright (C) 2000 Andrew Henroid + * Portions Copyright (C) 2001 Sun Microsystems + * Portions Copyright (C) 2004 Tim Hockin (thockin@hockin.org) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +#include "acpid.h" +#include "log.h" +#include "event.h" +#include "connection_list.h" + +const char *eventfile = ACPID_EVENTFILE; + +static char *read_line(int fd); + +static void +process_proc(int fd) +{ + char *event; + + /* read an event */ + event = read_line(fd); + + /* if we're locked, don't process the event */ + if (locked()) { + if (logevents && event != NULL) { + acpid_log(LOG_INFO, + "lockfile present, not processing " + "event \"%s\"", event); + } + return; + } + + /* handle the event */ + if (event) { + if (logevents) { + acpid_log(LOG_INFO, + "procfs received event \"%s\"", event); + } + acpid_handle_event(event); + if (logevents) { + acpid_log(LOG_INFO, + "procfs completed event \"%s\"", event); + } + } else if (errno == EPIPE) { + acpid_log(LOG_WARNING, + "events file connection closed"); + exit(EXIT_FAILURE); + } else { + static int nerrs; + if (++nerrs >= ACPID_MAX_ERRS) { + acpid_log(LOG_ERR, + "too many errors reading " + "events file - aborting"); + exit(EXIT_FAILURE); + } + } +} + +int +open_proc() +{ + int fd; + struct connection c; + + fd = open(eventfile, O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) { + acpid_log(LOG_DEBUG, "Deprecated %s was not found. " + "Trying netlink and the input layer...", eventfile); + } else { + acpid_log(LOG_ERR, "can't open %s: %s (%d)", eventfile, + strerror(errno), errno); + } + return -1; + + } + + /* Make sure scripts we exec() (in event.c) don't get our file + descriptors. */ + fcntl(fd, F_SETFD, FD_CLOEXEC); + + acpid_log(LOG_DEBUG, "proc fs opened successfully"); + + /* add a connection to the list */ + c.fd = fd; + c.process = process_proc; + c.pathname = NULL; + c.kybd = 0; + add_connection(&c); + + return 0; +} + +/* + * This depends on fixes in linux ACPI after 2.4.8 + */ +#define BUFLEN 1024 +static char * +read_line(int fd) +{ + static char buf[BUFLEN]; + int i = 0; + int r; + int searching = 1; + + while (searching) { + memset(buf+i, 0, BUFLEN-i); + + /* only go to BUFLEN-1 so there will always be a 0 at the end */ + while (i < BUFLEN-1) { + r = read(fd, buf+i, 1); + if (r < 0 && errno != EINTR) { + /* we should do something with the data */ + acpid_log(LOG_ERR, "read(): %s", + strerror(errno)); + return NULL; + } else if (r == 0) { + /* signal this in an almost standard way */ + errno = EPIPE; + return NULL; + } else if (r == 1) { + /* scan for a newline */ + if (buf[i] == '\n') { + searching = 0; + buf[i] = '\0'; + break; + } + i++; + } + } + if (i >= BUFLEN - 1) + break; + } + + return buf; +} + +#if 0 +/* This version leaks memory. The above version is simpler and leak-free. */ +/* Downside is that the above version always uses 1k of RAM. */ +/* + * This depends on fixes in linux ACPI after 2.4.8 + */ +#define MAX_BUFLEN 1024 +static char * +read_line(int fd) +{ + static char *buf; + int buflen = 64; + int i = 0; + int r; + int searching = 1; + + while (searching) { + /* ??? This memory is leaked since it is never freed */ + buf = realloc(buf, buflen); + if (!buf) { + acpid_log(LOG_ERR, "malloc(%d): %s", + buflen, strerror(errno)); + return NULL; + } + memset(buf+i, 0, buflen-i); + + while (i < buflen) { + r = read(fd, buf+i, 1); + if (r < 0 && errno != EINTR) { + /* we should do something with the data */ + acpid_log(LOG_ERR, "read(): %s", + strerror(errno)); + return NULL; + } else if (r == 0) { + /* signal this in an almost standard way */ + errno = EPIPE; + return NULL; + } else if (r == 1) { + /* scan for a newline */ + if (buf[i] == '\n') { + searching = 0; + buf[i] = '\0'; + break; + } + i++; + } + } + if (buflen >= MAX_BUFLEN) { + break; + } + buflen *= 2; + } + + return buf; +} +#endif diff --git a/proc.h b/proc.h new file mode 100644 index 0000000..fbc9022 --- /dev/null +++ b/proc.h @@ -0,0 +1,30 @@ +/* + * proc.h - ACPI daemon proc filesystem interface + * + * Portions Copyright (C) 2000 Andrew Henroid + * Portions Copyright (C) 2001 Sun Microsystems + * Portions Copyright (C) 2004 Tim Hockin (thockin@hockin.org) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PROC_H__ +#define PROC_H__ + +extern const char *eventfile; + +extern int open_proc(); + +#endif /* PROC_H__ */ diff --git a/samples/acpi_handler-conf b/samples/acpi_handler-conf new file mode 100644 index 0000000..a48b68b --- /dev/null +++ b/samples/acpi_handler-conf @@ -0,0 +1,6 @@ +# This sample acpid configuration file goes with the acpi_handler.sh +# handler script to show how one can handle all events within a single +# script. + +event=* +action=acpi_handler.sh "%e" diff --git a/samples/acpi_handler.sh b/samples/acpi_handler.sh new file mode 100755 index 0000000..b2585a5 --- /dev/null +++ b/samples/acpi_handler.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# A sample skeleton for handling ACPI events. +# See acpi_handler-conf for a configuration file that can be used to +# run this script. + +if [ $# != 1 ]; then + exit 1 +fi +set $* + +case "$1" in + button) + case "$2" in + power) /sbin/init 0 + ;; + *) logger "ACPI action $2 is not defined" + ;; + esac + ;; + + *) + logger "ACPI group $1 is not defined" + ;; +esac diff --git a/samples/battery/battery-conf b/samples/battery/battery-conf new file mode 100644 index 0000000..246d36d --- /dev/null +++ b/samples/battery/battery-conf @@ -0,0 +1,10 @@ +# /etc/acpid/events/battery +# This detects changes to AC connector status, and passes them to +# /etc/acpi/battery.sh for further processing. + +# Optionally you can specify the placeholder %e. It will pass +# through the whole kernel event message to the program you've +# specified. + +event=battery +action=/etc/acpi/battery.sh diff --git a/samples/battery/battery.sh b/samples/battery/battery.sh new file mode 100644 index 0000000..cb86156 --- /dev/null +++ b/samples/battery/battery.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# +# /etc/acpid/battery.sh +# +# written by Frank Dietrich +# +# based on default.sh in the acpid package + +# Detect AC connector plugged in or unplugged and take appropriated actions. +# +# On my notebook no event triggered if AC connector plugged in or unplugged. +# So I will use the battery event to detect new powerstate. + +# get the AC connector state from /proc filesystem. +STATE=`sed -n 's/^.*\(off\|on\)-line.*/\1/p' /proc/acpi/ac_adapter/ACAD/state` + +case "$STATE" in + on) + # AC connector plugged in + # make an entry in /var/log/daemon.log + logger "acpid: AC connector plugged in." + # deactivate standby (spindown) timeout for the drive + /sbin/hdparm -q -S 0 /dev/hda + ;; + off) + # AC connector unplugged + logger "acpid: AC connector unplugged." + # activate standby (spindown) timeout for the drive + # timeout 5 minutes (man hdparm, for more informations) + /sbin/hdparm -q -S 60 /dev/hda + ;; + *) + # AC connector in undetermined state + logger "acpid: Could not determine new AC connector state." + ;; +esac diff --git a/samples/panasonic/ac_adapt.pl b/samples/panasonic/ac_adapt.pl new file mode 100644 index 0000000..2d4367c --- /dev/null +++ b/samples/panasonic/ac_adapt.pl @@ -0,0 +1,95 @@ +#!/usr/bin/perl -w +# AC Power Handler v1.0 +# Handles AC power events for Panasonic notebooks +# +# Copyright (C) 2004 David Bronaugh +# +# Requires pcc_acpi driver +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +use strict; +use POSIX qw(ceil floor); + +our($config); +our($power_state); + +sub read_file { + my($file) = @_; + my($fh); + my($contents) = ""; + if(open($fh, $file)) { + $/ = undef; + $contents = <$fh>; + close($fh); + } else { + print "Couldn't open file " . $file . "!\n"; + } + return $contents; +} + +sub write_file { + my($file, $contents) = @_; + my($fh); + + if(open($fh, ">", $file)) { + print $fh $contents; + close($fh); + return 1; + } else { + print "Couldn't open file " . $file . "!\n"; + return 0; + } +} + +sub get_pcc_field { + my($field) = @_; + my($file) = $config->{'pcc_path'} . "/" . $power_state . "_" . $field; + + return read_file($file); +} + +sub set_pcc_field { + my($field, $contents) = @_; + my($file) = $config->{'pcc_path'} . "/" . $power_state . "_" . $field; + + if(!write_file($file, $contents)) { + print "Couldn't set pcc " . $field . " field (are you root?)\n"; + return 0; + } + return 1; +} + +sub ac_disconnect { + $power_state = "dc"; + set_pcc_field("brightness", get_pcc_field("brightness")); +} + +sub ac_connect { + $power_state = "ac"; + set_pcc_field("brightness", get_pcc_field("brightness")); +} + +my($key) = $ARGV[3]; + +my(%dispatch) = ( + "00000000" => \&ac_disconnect, + "00000001" => \&ac_connect, + ); + +$config = { + "pcc_path" => "/proc/acpi/pcc", + }; + +$dispatch{$key}(); diff --git a/samples/panasonic/ac_adapter b/samples/panasonic/ac_adapter new file mode 100644 index 0000000..7a75ecc --- /dev/null +++ b/samples/panasonic/ac_adapter @@ -0,0 +1,5 @@ +# /etc/acpi/events/hotkey +# This script handles hotkey events on Panasonic notebooks + +event=ac_adapter.* +action=/etc/acpi/ac_adapt.pl %e diff --git a/samples/panasonic/hotkey b/samples/panasonic/hotkey new file mode 100644 index 0000000..6562fbc --- /dev/null +++ b/samples/panasonic/hotkey @@ -0,0 +1,5 @@ +# /etc/acpi/events/hotkey +# This script handles hotkey events on Panasonic notebooks + +event=HKEY.* +action=/etc/acpi/hotkey.pl %e diff --git a/samples/panasonic/hotkey.pl b/samples/panasonic/hotkey.pl new file mode 100644 index 0000000..30f110d --- /dev/null +++ b/samples/panasonic/hotkey.pl @@ -0,0 +1,301 @@ +#!/usr/bin/perl -w +# Hotkey handler v1.0 +# Handles hotkey events for Panasonic notebooks +# +# Copyright (C) 2004 David Bronaugh +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +use strict; +use POSIX qw(ceil floor); + +our($config); +our($power_state); + +sub read_file { + my($file) = @_; + my($fh); + my($contents) = ""; + if(open($fh, $file)) { + $/ = undef; + $contents = <$fh>; + close($fh); + } else { + print "Couldn't open file " . $file . "!\n"; + } + return $contents; +} + +sub write_file { + my($file, $contents) = @_; + my($fh); + + if(open($fh, ">", $file)) { + print $fh $contents; + close($fh); + return 1; + } else { + print "Couldn't open file " . $file . "!\n"; + return 0; + } +} + +sub get_amixer_control_info { + my($control) = @_; + my($cmd) = $config->{'mixer_program'} . " cget name='" . $control . "'"; + my(%info); + my($fh, $field); + my($contents) = ""; + if(open($fh, $cmd . "|")) { + while(<$fh>) { + chomp; + $contents .= $_; + } + } else { + print "Couldn't run command " . $cmd . "!\n"; + } + + $contents =~ m/\; ([^\s]*)/; + + foreach(split(/,/, $+)) { + my(@foo) = split(/=/, $_); + $info{$foo[0]} = $foo[1]; + } + + $contents =~ m/\: ([^\s]*)/; + my(@foo) = split(/=/, $+); + $info{$foo[0]} = []; + @{$info{$foo[0]}} = split(/,/, $foo[1]); + + return \%info; +} + +sub set_amixer_control_info { + my($control, $values) = @_; + my($cmd) = $config->{'mixer_program'} . " -q cset name='" . $control . "' " . $values; + + if(system($cmd) == 0) { + return 1; + } else { + return 0; + } +} + +sub get_pcc_field { + my($field) = @_; + my($file) = $config->{'pcc_path'} . "/" . $power_state . "_" . $field; + + return read_file($file); +} + +sub set_pcc_field { + my($field, $contents) = @_; + my($file) = $config->{'pcc_path'} . "/" . $power_state . "_" . $field; + + if(!write_file($file, $contents)) { + print "Couldn't set pcc " . $field . " field (are you root?)\n"; + return 0; + } + return 1; +} + +sub get_brightness { + return (get_pcc_field("brightness_min"), get_pcc_field("brightness_max"), get_pcc_field("brightness")); +} + +sub set_brightness { + my($value) = @_; + return set_pcc_field("brightness", $value); +} + +sub get_mute { + my($info) = get_amixer_control_info($config->{'mute_switch'}); + + if($info->{'values'}[0] eq "on") { + return 0; + } elsif($info->{'values'}[0] eq "off") { + return 1; + } else { + print "Error getting mute status!\n"; + return -1; + } +} + +sub set_mute { + my($value) = @_; + if($value == 0) { + $value = "on"; + } elsif($value == 1) { + $value = "off"; + } + + if(set_amixer_control_info($config->{'mute_switch'}, $value)) { + return 1; + } else { + print "Couldn't set mute status!\n"; + return 0; + } +} + +sub get_volume { + my($config) = @_; + my($info) = get_amixer_control_info($config->{'volume_ctl'}); + + return ($info->{'min'}, $info->{'max'}, $info->{'values'}); +} + +sub set_volume { + my($values) = @_; + + return set_amixer_control_info($config->{'volume_ctl'}, join(",", @{$values})); +} + +sub get_power_state { + my($data) = read_file($config->{"ac_state"}); + + if($data =~ /on-line/) { + return "ac"; + } elsif($data =~ /off-line/) { + return "dc"; + } else { + print "Couldn't get power state! (is ACPI enabled?)\n"; + exit(1); + } +} + +sub adjust_brightness { + my($adjust) = @_; + my($min, $max, $bright) = get_brightness($config); + my($threshold) = $config->{'max_bright_levels'}; + my($divisor) = 1; + + $bright -= $min; + + if($max - $min > $threshold) { + $divisor = ($max - $min) / $threshold; + } + + $bright = ceil($bright / $divisor); + $bright += $adjust; + $bright = floor($bright * $divisor); + + $bright += $min; + + if($bright < $min) { + $bright = $min; + } + + if($bright > $max) { + $bright = $max; + } + + if(!set_brightness($bright)) { + print "Couldn't adjust brightness!\n"; + } + + return; +} + +sub adjust_volume { + my($increment) = @_; + my($min, $max, $volume) = get_volume($config); + + $volume->[0] += $increment; + $volume->[1] += $increment; + + $volume->[0] = ($volume->[0] < $min)?$min:$volume->[0]; + $volume->[1] = ($volume->[1] < $min)?$min:$volume->[1]; + $volume->[0] = ($volume->[0] > $max)?$max:$volume->[0]; + $volume->[1] = ($volume->[1] > $max)?$max:$volume->[1]; + + if(!set_volume($volume)) { + print "Couldn't set volume!\n"; + } + + return; +} + +# Functions which implement hotkey functions directly +sub down_brightness { + adjust_brightness(-1); +} + +sub up_brightness { + adjust_brightness(1); +} + +sub switch_monitor { + #STUB +} + +sub toggle_mute { + my($mute) = get_mute(); + + if($mute >= 0) { + set_mute($mute ^ 1); + } +} + +sub volume_up { + adjust_volume($config->{"volume_increment"}) +} + +sub volume_down { + adjust_volume(-1 * $config->{"volume_increment"}) +} + +sub suspend_to_ram { + # This space intentionally left blank (because it doesn't work here) +} + +sub spin_down_hd { + if(system("hdparm -q -y /dev/hda") != 0) { + print "Error running hdparm -- is it installed?\n"; + } +} + +sub suspend_to_disk { + system("hwclock --systohc"); + write_file($config->{'suspend_control'}, "disk"); + system("hwclock --hctosys"); +} + +my($key) = $ARGV[3]; + +my(%dispatch) = ( + "00000081" => \&down_brightness, + "00000082" => \&up_brightness, + "00000003" => \&switch_monitor, + "00000084" => \&toggle_mute, + "00000085" => \&volume_down, + "00000086" => \&volume_up, + "00000007" => \&suspend_to_ram, + "00000089" => \&spin_down_hd, + "0000000a" => \&suspend_to_disk + ); + +$config = { + "pcc_path" => "/proc/acpi/pcc", + "mixer_program" => "amixer", + "ac_state" => "/proc/acpi/ac_adapter/AC/state", + "mute_switch" => "Master Playback Switch", + "volume_ctl" => "Master Playback Volume", + "max_bright_levels" => 20, + "volume_increment" => 2, + "suspend_control" => "/sys/power/state" + }; + +$power_state = get_power_state(); + +$dispatch{$key}(); diff --git a/samples/power b/samples/power new file mode 100644 index 0000000..72c1dbd --- /dev/null +++ b/samples/power @@ -0,0 +1,7 @@ +# This is a sample acpid configuration file. See the man page for +# acpid which describes this configuration file. +# When a button/power event is received, the power.sh script is run. + +event=button/power +action=/etc/acpid/power.sh + diff --git a/samples/power.sh b/samples/power.sh new file mode 100755 index 0000000..2e02157 --- /dev/null +++ b/samples/power.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# This script is triggered by the "power" configuration file. This is +# described in the acpid man page. + +# A much more comprehensive power button handler is provided in the +# powerbtn directory in this samples directory. If you run gnome or KDE, +# it's better to use that one instead of this one. + +/sbin/shutdown -h now "Power button pressed" + diff --git a/samples/powerbtn/powerbtn-conf b/samples/powerbtn/powerbtn-conf new file mode 100644 index 0000000..8347570 --- /dev/null +++ b/samples/powerbtn/powerbtn-conf @@ -0,0 +1,13 @@ +# /etc/acpi/events/powerbtn +# This is called when the user presses the power button and calls +# /etc/acpi/powerbtn.sh for further processing. + +# Optionally you can specify the placeholder %e. It will pass +# through the whole kernel event message to the program you've +# specified. + +# We need to react on "button power.*" and "button/power.*" because +# of kernel changes. + +event=button[ /]power +action=/etc/acpi/powerbtn.sh diff --git a/samples/powerbtn/powerbtn.sh b/samples/powerbtn/powerbtn.sh new file mode 100755 index 0000000..39a3d9b --- /dev/null +++ b/samples/powerbtn/powerbtn.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# /etc/acpi/powerbtn.sh +# Taken from Debian's 2.0.4-1 diff file. This version handles KDE4. +# Power Button event handler. +# Checks to see if gnome or KDE are already handling the power button. +# If not, initiates a plain shutdown. + +# getXuser gets the X user belonging to the display in $displaynum. +# If you want the foreground X user, use getXconsole! +# Input: +# displaynum - X display number +# Output: +# XUSER - the name of the user +# XAUTHORITY - full pathname of the user's .Xauthority file +getXuser() { + user=`pinky -fw | awk '{ if ($2 == ":'$displaynum'" || $(NF) == ":'$displaynum'" ) { print $1; exit; } }'` + if [ x"$user" = x"" ]; then + startx=`pgrep -n startx` + if [ x"$startx" != x"" ]; then + user=`ps -o user --no-headers $startx` + fi + fi + if [ x"$user" != x"" ]; then + userhome=`getent passwd $user | cut -d: -f6` + export XAUTHORITY=$userhome/.Xauthority + else + export XAUTHORITY="" + fi + export XUSER=$user +} + +# Gets the X display number for the active virtual terminal. +# Output: +# DISPLAY - the X display number +# See getXuser()'s output. +getXconsole() { + console=`fgconsole`; + displaynum=`ps t tty$console | sed -n -re 's,.*/X .*:([0-9]+).*,\1,p'` + if [ x"$displaynum" != x"" ]; then + export DISPLAY=":$displaynum" + getXuser + fi +} + +# Skip if we are just in the middle of resuming. +test -f /var/lock/acpisleep && exit 0 + +# If the current X console user is running a power management daemon that +# handles suspend/resume requests, let them handle policy. + +getXconsole + +# A list of power management system process names. +PMS="gnome-power-manager kpowersave xfce4-power-manager" +PMS="$PMS guidance-power-manager.py dalston-power-applet" + +# If one of those is running or any of several others, +if pidof x $PMS > /dev/null || + ( test "$XUSER" != "" && pidof dcopserver > /dev/null && test -x /usr/bin/dcop && /usr/bin/dcop --user $XUSER kded kded loadedModules | grep -q klaptopdaemon) || + ( test "$XUSER" != "" && test -x /usr/bin/qdbus && test -r /proc/$(pidof kded4)/environ && su - $XUSER -c "eval $(echo -n 'export '; cat /proc/$(pidof kded4)/environ |tr '\0' '\n'|grep DBUS_SESSION_BUS_ADDRESS); qdbus org.kde.kded" | grep -q powerdevil) ; then + # Get out as the power manager that is running will take care of things. + exit +fi + +# No power managment system appears to be running. Just initiate a plain +# shutdown. +/sbin/shutdown -h now "Power button pressed" + diff --git a/samples/powerbtn/powerbtn.sh.old b/samples/powerbtn/powerbtn.sh.old new file mode 100755 index 0000000..e5a3e35 --- /dev/null +++ b/samples/powerbtn/powerbtn.sh.old @@ -0,0 +1,38 @@ +#!/bin/sh +# /etc/acpi/powerbtn.sh +# Taken from the 11/14/2008(ish) version from Debian. +# Power Button event handler. +# Checks to see if gnome or KDE are already handling the power button. +# If not, initiates a plain shutdown. + +# This is an older version from Debian that does not handle KDE4. + +# Skip if we are in the middle of resuming. Otherwise we may power down the +# system as it is coming back up. +# See 98-acpi-unlock.sh and 05-acpi-lock.sh in Debian. +test -f /var/lock/acpisleep && exit 0 + +# If gnome-power-manager, kpowersave or klaptopdaemon are running... +if pidof gnome-power-manager kpowersave > /dev/null || + (pidof dcopserver > /dev/null && test -x /usr/bin/dcop && /usr/bin/dcop kded kded loadedModules | grep -q klaptopdaemon) ; then + # Let them handle the power button. + exit +fi + +# If KDE is running... +if ps -Af | grep -q '[k]desktop' && pidof dcopserver > /dev/null && test -x /usr/bin/dcop ; then + # Ask it to logout. + KDESES=`pidof dcopserver | wc -w` + if [ $KDESES -eq 1 ] ; then + # single KDE session -> ask user + /usr/bin/dcop --all-sessions --all-users ksmserver ksmserver logout 1 2 0 + exit 0 + else + # more than one KDE session - just send shutdown signal to all of them + /usr/bin/dcop --all-sessions --all-users ksmserver ksmserver logout 0 2 0 && exit 0 + fi +fi + +# Initiate a plain shutdown. +/sbin/shutdown -h now "Power button pressed" + diff --git a/sock.c b/sock.c new file mode 100644 index 0000000..2359fd9 --- /dev/null +++ b/sock.c @@ -0,0 +1,181 @@ +/* + * sock.c - ACPI daemon socket interface + * + * Portions Copyright (C) 2000 Andrew Henroid + * Portions Copyright (C) 2001 Sun Microsystems + * Portions Copyright (C) 2004 Tim Hockin (thockin@hockin.org) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acpid.h" +#include "log.h" +#include "event.h" +#include "ud_socket.h" +#include "connection_list.h" + +const char *socketfile = ACPID_SOCKETFILE; +const char *socketgroup; +mode_t socketmode = ACPID_SOCKETMODE; +int clientmax = ACPID_CLIENTMAX; + +/* the number of non-root clients that are connected */ +int non_root_clients; + +/* determine if a file descriptor is in fact a socket */ +int +is_socket(int fd) +{ + int v; + socklen_t l = sizeof(int); + + return (getsockopt(fd, SOL_SOCKET, SO_TYPE, (char *)&v, &l) == 0); +} + +/* accept a new client connection */ +static void +process_sock(int fd) +{ + int cli_fd; + struct ucred creds; + char buf[32]; + static int accept_errors; + + /* accept and add to our lists */ + cli_fd = ud_accept(fd, &creds); + if (cli_fd < 0) { + acpid_log(LOG_ERR, "can't accept client: %s", + strerror(errno)); + accept_errors++; + if (accept_errors >= 5) { + acpid_log(LOG_ERR, "giving up"); + clean_exit_with_status(EXIT_FAILURE); + } + return; + } + accept_errors = 0; + + /* don't allow too many non-root clients */ + if (creds.uid != 0 && non_root_clients >= clientmax) { + close(cli_fd); + acpid_log(LOG_ERR, "too many non-root clients"); + return; + } + if (creds.uid != 0) { + non_root_clients++; + } + + /* don't leak fds when execing */ + if (fcntl(cli_fd, F_SETFD, FD_CLOEXEC) < 0) { + close(cli_fd); + acpid_log(LOG_ERR, "fcntl() on client for FD_CLOEXEC: %s", + strerror(errno)); + return; + } + + /* don't allow clients to block this */ + if (fcntl(cli_fd, F_SETFL, O_NONBLOCK) < 0) { + close(cli_fd); + acpid_log(LOG_ERR, "fcntl() on client for O_NONBLOCK: %s", + strerror(errno)); + return; + } + + snprintf(buf, sizeof(buf)-1, "%d[%d:%d]", + creds.pid, creds.uid, creds.gid); + acpid_add_client(cli_fd, buf); +} + +/* set up the socket for client connections */ +void +open_sock() +{ + int fd; + struct connection c; + + /* if this is a socket passed in via stdin by systemd */ + if (is_socket(STDIN_FILENO)) { + fd = STDIN_FILENO; + } else { + /* create our own socket */ + fd = ud_create_socket(socketfile); + if (fd < 0) { + acpid_log(LOG_ERR, "can't open socket %s: %s", + socketfile, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (chmod(socketfile, socketmode) < 0) { + close(fd); + acpid_log(LOG_ERR, "chmod() on socket %s: %s", + socketfile, strerror(errno)); + return; + } + + /* if we need to change the socket's group, do so */ + if (socketgroup) { + struct group *gr; + struct stat buf; + + gr = getgrnam(socketgroup); + if (!gr) { + acpid_log(LOG_ERR, "group %s does not exist", socketgroup); + exit(EXIT_FAILURE); + } + if (stat(socketfile, &buf) < 0) { + acpid_log(LOG_ERR, "can't stat %s: %s", + socketfile, strerror(errno)); + exit(EXIT_FAILURE); + } + if (chown(socketfile, buf.st_uid, gr->gr_gid) < 0) { + acpid_log(LOG_ERR, "can't chown %s: %s", + socketfile, strerror(errno)); + exit(EXIT_FAILURE); + } + } + } + + /* don't leak fds when execing */ + if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { + close(fd); + acpid_log(LOG_ERR, "fcntl() on socket %s for FD_CLOEXEC: %s", + socketfile, strerror(errno)); + return; + } + + /* avoid a potential hang */ + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { + close(fd); + acpid_log(LOG_ERR, "fcntl() on socket %s for O_NONBLOCK: %s", + socketfile, strerror(errno)); + return; + } + + /* add a connection to the list */ + c.fd = fd; + c.process = process_sock; + c.pathname = NULL; + c.kybd = 0; + add_connection(&c); +} diff --git a/sock.h b/sock.h new file mode 100644 index 0000000..fe37028 --- /dev/null +++ b/sock.h @@ -0,0 +1,35 @@ +/* + * sock.h - ACPI daemon socket interface + * + * Portions Copyright (C) 2000 Andrew Henroid + * Portions Copyright (C) 2001 Sun Microsystems + * Portions Copyright (C) 2004 Tim Hockin (thockin@hockin.org) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SOCK_H__ +#define SOCK_H__ + +extern const char *socketfile; +extern const char *socketgroup; +extern mode_t socketmode; +extern int clientmax; +extern int non_root_clients; + +extern int is_socket(int fd); +extern void open_sock(); + +#endif /* SOCK_H__ */ diff --git a/ud_socket.c b/ud_socket.c new file mode 100644 index 0000000..9c8ac8d --- /dev/null +++ b/ud_socket.c @@ -0,0 +1,132 @@ +/* + * $Id: ud_socket.c,v 1.6 2009/04/22 18:22:28 thockin Exp $ + * A few routines for handling UNIX domain sockets + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acpid.h" +#include "log.h" +#include "ud_socket.h" + +int +ud_create_socket(const char *name) +{ + int fd; + int r; + struct sockaddr_un uds_addr; + + if (strnlen(name, sizeof(uds_addr.sun_path)) > + sizeof(uds_addr.sun_path) - 1) { + acpid_log(LOG_ERR, "ud_create_socket(): " + "socket filename longer than %u characters: %s", + sizeof(uds_addr.sun_path) - 1, name); + errno = EINVAL; + return -1; + } + + /* JIC */ + unlink(name); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + return fd; + } + + /* setup address struct */ + memset(&uds_addr, 0, sizeof(uds_addr)); + uds_addr.sun_family = AF_UNIX; + strncpy(uds_addr.sun_path, name, sizeof(uds_addr.sun_path) - 1); + + /* bind it to the socket */ + r = bind(fd, (struct sockaddr *)&uds_addr, sizeof(uds_addr)); + if (r < 0) { + return r; + } + + /* listen - allow 10 to queue */ + r = listen(fd, 10); + if (r < 0) { + return r; + } + + return fd; +} + +int +ud_accept(int listenfd, struct ucred *cred) +{ + while (1) { + int newsock = 0; + struct sockaddr_un cliaddr; + socklen_t len = sizeof(struct sockaddr_un); + + newsock = accept(listenfd, (struct sockaddr *)&cliaddr, &len); + if (newsock < 0) { + if (errno == EINTR) { + continue; /* signal */ + } + + return newsock; + } + + if (cred) { + len = sizeof(struct ucred); + getsockopt(newsock,SOL_SOCKET,SO_PEERCRED,cred,&len); + } + + return newsock; + } +} + +int +ud_connect(const char *name) +{ + int fd; + int r; + struct sockaddr_un addr; + + if (strnlen(name, sizeof(addr.sun_path)) > sizeof(addr.sun_path) - 1) { + acpid_log(LOG_ERR, "ud_connect(): " + "socket filename longer than %u characters: %s", + sizeof(addr.sun_path) - 1, name); + errno = EINVAL; + return -1; + } + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + return fd; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + sprintf(addr.sun_path, "%s", name); + /* safer: */ + /*strncpy(addr.sun_path, name, sizeof(addr.sun_path) - 1);*/ + + r = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (r < 0) { + close(fd); + return r; + } + + return fd; +} + +int +ud_get_peercred(int fd, struct ucred *cred) +{ + socklen_t len = sizeof(struct ucred); + getsockopt(fd, SOL_SOCKET, SO_PEERCRED, cred, &len); + return 0; +} diff --git a/ud_socket.h b/ud_socket.h new file mode 100644 index 0000000..a59db95 --- /dev/null +++ b/ud_socket.h @@ -0,0 +1,16 @@ +/* + *$Id: ud_socket.h,v 1.3 2009/04/22 18:22:28 thockin Exp $ + */ + +#ifndef UD_SOCKET_H__ +#define UD_SOCKET_H__ + +#include +#include + +int ud_create_socket(const char *name); +int ud_accept(int sock, struct ucred *cred); +int ud_connect(const char *name); +int ud_get_peercred(int fd, struct ucred *cred); + +#endif -- 2.7.4