]> git.neil.brown.name Git - susman.git/commitdiff
lsusd - a Linux SUSpend Daemon
authorNeilBrown <neilb@suse.de>
Fri, 21 Oct 2011 04:49:17 +0000 (15:49 +1100)
committerNeilBrown <neilb@suse.de>
Fri, 21 Oct 2011 05:21:49 +0000 (16:21 +1100)
Initial commit.

Signed-off-by: NeilBrown <neilb@suse.de>
20 files changed:
COPYING [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
alarm_test.c [new file with mode: 0644]
block.sh [new file with mode: 0644]
block_test.c [new file with mode: 0644]
dnotify.py [new file with mode: 0644]
event_test.c [new file with mode: 0644]
libsus.h [new file with mode: 0644]
lsusd.c [new file with mode: 0644]
lsused.c [new file with mode: 0644]
request_suspend.c [new file with mode: 0644]
suspend.py [new file with mode: 0644]
suspend_block.c [new file with mode: 0644]
test_block.sh [new file with mode: 0644]
wakealarm.c [new file with mode: 0644]
wakealarmd.c [new file with mode: 0644]
wakeevent.c [new file with mode: 0644]
watch_test.c [new file with mode: 0644]
watcher.c [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..d159169
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..6371a81
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,47 @@
+# Copyright (C) 2011 Neil Brown <neilb@suse.de>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+PROGS = lsusd lsused request_suspend wakealarmd
+TESTS = block_test watch_test event_test alarm_test
+LIBS = suspend_block.o watcher.o wakeevent.o wakealarm.o
+
+all: $(PROGS) $(TESTS)
+
+lsusd: lsusd.o
+
+lsused: lsused.o libsus.a
+       $(CC) -o lsused lsused.o libsus.a -levent
+
+wakealarmd: wakealarmd.o libsus.a
+       $(CC) -o wakealarmd wakealarmd.o libsus.a -levent
+
+request_suspend: request_suspend.o
+
+block_test: block_test.o libsus.a
+       $(CC) -o block_test block_test.o libsus.a
+watch_test: watch_test.o libsus.a
+       $(CC) -o watch_test watch_test.o libsus.a -levent
+event_test: event_test.o libsus.a
+       $(CC) -o event_test event_test.o libsus.a -levent
+alarm_test: alarm_test.o libsus.a
+       $(CC) -o alarm_test alarm_test.o libsus.a -levent
+
+libsus.a: $(LIBS)
+       ar cr libsus.a $(LIBS)
+
+clean:
+       rm -f *.o *.a *.pyc $(PROGS) $(TESTS)
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..89b1c1e
--- /dev/null
+++ b/README
@@ -0,0 +1,104 @@
+
+This directory contains a prototype proof-of-concept system
+for managing suspend in Linux.
+Thus the Linux SUSpend Daemon.
+
+It contains:
+
+ lsusd:
+    The main daemon.  It is written to run a tight loop and blocks as
+     required.  It obeys the wakeup_count protocol to get race-free
+     suspend and allows clients to register to find out about
+     suspend and to block it either briefly or longer term.
+    It uses files in /var/run/suspend for all communication.
+
+    File are:
+
+      disabled:  This file always exists.  If any process holds a
+        shared flock(), suspend will not happen.
+      immediate:  If this file exists, lsusd will try to suspend whenever
+        possible.
+      request:  If this is created, then lsusd will try to suspend
+        once, and will remove the file when suspend completes or aborts.
+      watching:  This is normally empty.  Any process wanting to know
+        about suspend should take a shared flock and check the file is
+        still empty, and should watch for modification.
+        When suspend is imminent, lsusd creates 'watching-next', writes
+         a byte to 'watching' and waits for an exclusive lock on 'watching'.
+        Clients should move their lock to 'watching-next' when ready for
+        suspend.
+        When suspend completes, another byte (or 2) is written to
+        'watching', and 'watching-next' is renamed over it.  Clients can
+        use either of these to know that resume has happened.
+
+      watching-next: The file that will be 'watching' in the next awake cycle.
+
+    lsusd does not try to be event-loop based because:
+      - /sys/power/wakeup_count is not pollable.  This could probably be
+        'fixed' but I want code to work with today's kernel.  It will probably
+        only block 100msec at most, but that might be too long???
+      - I cannot get an event notification when a lock is removed from a
+        file. :-(  And I think locks are an excellent light-weight
+        mechanism for blocking suspend.
+
+  lsused:
+      This is an event-loop based daemon that can therefore easily handle
+      socket connections and client protocols which need prompt
+      response.  It communicates with lsusd and provides extra
+      services to client.
+
+      lsused (which needs a better name) listens on the socket
+            /var/run/suspend/registration
+      A client may connect and send a list of file descriptors.
+      When a suspend is immanent, if any file descriptor is readable,
+      lsused will send a 'S' message to the client and await an 'R' reply
+      (S == suspend, R == ready).  When all replies are in, lsused will
+      allow the suspend to complete.  When it does (or aborts), it will send
+      'A' (awake) to those clients to which it sent 'S'.
+
+      This allows a client to get a chance to handle any wakeup events,
+      but not to be woken unnecessarily on every suspend.
+
+   wakealarmd:
+      This allows clients to register on the socket.
+             /var/run/suspend/wakealarm
+      They write a timestamp in seconds since epoch, and will receive
+      a 'Now' message when that time arrives.
+      Between the time the connection is made and the time a "seconds"
+      number is written, suspend will be blocked.
+      Also between the time that "Now" is sent and when the socket is
+      closed, suspend is also blocked.
+
+   request_suspend:
+      A simple tool to create the 'request' file and then wait for it
+      to be removed.
+
+   libsus.a:  A library of client-side interfaces.
+      suspend_open, suspend_block, suspend_allow, suspend_close:
+           easy interface to blocking suspend
+      suspend_watch, suspend_unwatch:
+           For use in libevent program to get notifications of
+           suspend and resume via the 'watching' file.
+      wake_set, wake_destory:
+           create a libevent event for an fd which is protected from
+           suspend. Whenever it is readable, suspend will not be entered.
+      wakealarm_set, wakealarm_destroy:
+           create a libevent event for a particular time which will
+           trigger even if system is suspend, and will protect against
+           suspend while event is happening.
+
+
+   block_test watch_test event_test alarm_test
+        simple test programs for the above interfaces.
+
+
+    suspend.py  dnotify.py:
+       Sample code for detecting suspend/resume from python
+    block.sh test_block.sh:
+       Sample code for disabling suspend from shell.
+
+All code is available under GPLv2+.  However if you ask for a different
+license I am unlikely to refuse (at least with the early prototype).
+
+Patches and comment are welcome, but please also feel free to include
+any of this in some more complete framework.
diff --git a/alarm_test.c b/alarm_test.c
new file mode 100644 (file)
index 0000000..37c30fa
--- /dev/null
@@ -0,0 +1,57 @@
+
+
+/* Test wakealarm.
+ * argv is a number of seconds to wait for.
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include "libsus.h"
+
+void callback(int fd, short ev, void *data)
+{
+       printf("Ping - got the event\n");
+       suspend_block(-1);
+       event_loopbreak();
+}
+
+int main(int argc, char *argv[])
+{
+       time_t now;
+       time_t then;
+       int diff;
+       struct event *ev;
+
+       if (argc != 2) {
+               fprintf(stderr, "Usage: alarm_test seconds\n");
+               exit(1);
+       }
+       diff = atoi(argv[1]);
+       time(&now);
+       then = now + diff;
+       event_init();
+
+       ev = wakealarm_set(then, callback, NULL);
+
+       event_loop(0);
+       printf("Hold off suspend for a while...\n");
+       sleep(4);
+       printf("OK - done\n");
+       exit(0);
+}
diff --git a/block.sh b/block.sh
new file mode 100644 (file)
index 0000000..50767d0
--- /dev/null
+++ b/block.sh
@@ -0,0 +1,29 @@
+
+# shell functions to allow suspend to be blocked.
+#
+# Copyright (C) 2011 Neil Brown <neilb@suse.de>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+block_suspend() {
+    exec 9< /var/run/suspend/disabled
+    flock --shared 9
+}
+
+enable_suspend() {
+    flock --unlock 9
+    exec 9<&-
+}
+
diff --git a/block_test.c b/block_test.c
new file mode 100644 (file)
index 0000000..ea5430b
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * block_test  - test program for blocking suspend
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "libsus.h"
+
+main(int argc, char *argv[])
+{
+       int sleep_time = 5;
+       int handle;
+       if (argc == 2)
+               sleep_time = atoi(argv[1]);
+
+       handle = suspend_block(-1);
+       printf("Have the block - waiting\n");
+       sleep(sleep_time);
+       printf("Releasing...\n");
+       suspend_allow(handle);
+       sleep(1);
+       handle = suspend_block(handle);
+       printf("Blocked again\n");
+       sleep(sleep_time);
+       suspend_close(handle);
+       printf("OK all done\n");
+       exit(1);
+}
diff --git a/dnotify.py b/dnotify.py
new file mode 100644 (file)
index 0000000..2746f9a
--- /dev/null
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+
+# class to allow watching multiple files and
+# calling a callback when any change (size or mtime)
+#
+# We take exclusive use of SIGIO and maintain a global list of
+# watched files.
+# As we cannot get siginfo in python, we check every file
+# every time we get a signal.
+# we report change is size, mtime, or ino of the file (given by name)
+
+# Copyright (C) 2011 Neil Brown <neilb@suse.de>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+import os, fcntl, signal
+
+
+dirlist = []
+def notified(sig, stack):
+    for d in dirlist:
+        fcntl.fcntl(d.fd, fcntl.F_NOTIFY, (fcntl.DN_MODIFY|fcntl.DN_RENAME|
+                                           fcntl.DN_CREATE|fcntl.DN_DELETE))
+        d.check()
+
+class dir():
+    def __init__(self, dname):
+        self.dname = dname
+        self.fd = os.open(dname, 0)
+        self.files = []
+        self.callbacks = []
+        fcntl.fcntl(self.fd, fcntl.F_NOTIFY, (fcntl.DN_MODIFY|fcntl.DN_RENAME|
+                                              fcntl.DN_CREATE|fcntl.DN_DELETE))
+        if not dirlist:
+            signal.signal(signal.SIGIO, notified)
+        dirlist.append(self)
+
+    def watch(self, fname, callback):
+        f = file(os.path.join(self.dname, fname), callback)
+        self.files.append(f)
+        return f
+
+    def watchall(self, callback):
+        self.callbacks.append(callback)
+
+    def check(self):
+        newlist = []
+        for c in self.callbacks:
+            if c():
+                newlist.append(c)
+        self.callbacks = newlist
+
+        for f in self.files:
+            f.check()
+
+    def cancel(self, victim):
+        if victim in self.files:
+            self.files.remove(victim)
+
+class file():
+    def __init__(self, fname, callback):
+        self.name = fname
+        try:
+            stat = os.stat(self.name)
+        except OSError:
+            self.ino = 0
+            self.size = 0
+            self.mtime = 0
+        else:
+            self.ino = stat.st_ino
+            self.size = stat.st_size
+            self.mtime = stat.st_mtime
+        self.callback = callback
+
+    def check(self):
+        try:
+            stat = os.stat(self.name)
+        except OSError:
+            if self.ino == 0:
+                return False
+            self.size = 0
+            self.mtime = 0
+            self.ino = 0
+        else:
+            if stat.st_size == self.size and stat.st_mtime == self.mtime \
+                   and stat.st_ino == self.ino:
+                return False
+            self.size = stat.st_size
+            self.mtime = stat.st_mtime
+            self.ino = stat.st_ino
+
+        self.callback(self)
+        return True
+
+    def cancel(self):
+        global dirlist
+        for d in dirlist:
+            d.cancel(self)
+
+
+if __name__ == "__main__" :
+    import signal
+
+
+    ##
+    def ping(f): print "got ", f.name
+
+    d = dir("/tmp/n")
+    a = d.watch("a", ping)
+    b = d.watch("b", ping)
+    c = d.watch("c", ping)
+
+    while True:
+        signal.pause()
diff --git a/event_test.c b/event_test.c
new file mode 100644 (file)
index 0000000..3039cd7
--- /dev/null
@@ -0,0 +1,69 @@
+
+/*
+ * test handling wakeup events on fd.
+ * You will need to strace things and watch to get any real
+ * feedback as this only does anything interesting when there
+ * is a race.
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <event.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include "libsus.h"
+
+void read_event(int fd, short ev, void *data)
+{
+       char buf[80];
+       int n;
+       int i;
+
+       printf("Can read now .. give it a moment though\n");
+       sleep(5);
+       n = read(fd, buf, 80);
+       if (n < 0)
+               exit(1);
+
+       for (i = 0; i < n ; i++)
+               printf(" %02x", buf[i] & 0xff);
+       printf("\n");
+}
+
+main(int argc, char *argv[])
+{
+       int fd;
+       struct event *ev;
+
+       if (argc != 2) {
+               fprintf(stderr, "Usage: event_test devicename\n");
+               exit(1);
+       }
+       fd = open(argv[1], O_RDONLY);
+       if (fd < 0) {
+               perror(argv[1]);
+               exit(1);
+       }
+
+       event_init();
+       event_priority_init(3);
+       ev = wake_set(fd, read_event, NULL, 1);
+
+       event_loop(0);
+       exit(0);
+}
diff --git a/libsus.h b/libsus.h
new file mode 100644 (file)
index 0000000..56e4432
--- /dev/null
+++ b/libsus.h
@@ -0,0 +1,39 @@
+/* headers for libsus - supporting suspend management.
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+int suspend_open();
+int suspend_block(int handle);
+void suspend_allow(int handle);
+int suspend_close(int handle);
+
+
+void *suspend_watch(int (*will_suspend)(void *data),
+                   void (*did_resume)(void *data),
+                   void *data);
+void suspend_ok(void *han);
+void suspend_unwatch(void *v);
+
+struct event *wake_set(int fd, void(*fn)(int,short,void*),
+                      void *data, int prio);
+void wake_destroy(struct event *ev);
+
+struct event *wakealarm_set(time_t when, void(*fn)(int, short, void*),
+                           void *data);
+void wakealarm_destroy(struct event *ev);
+
+
diff --git a/lsusd.c b/lsusd.c
new file mode 100644 (file)
index 0000000..91b0512
--- /dev/null
+++ b/lsusd.c
@@ -0,0 +1,196 @@
+/*
+ * lsusd - Linus SUSpend daemon.
+ * This daemon enters suspend when required and allows clients
+ * to block suspend, request suspend, or be notified of suspend.
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#define _GNU_SOURCE
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <dirent.h>
+
+static void alert_watchers(void)
+{
+       int fd;
+       char zero = 0;
+
+       fd = open("/var/run/suspend/watching-next",
+                 O_RDWR|O_CREAT|O_TRUNC, 0640);
+       if (fd < 0)
+               return;
+       close(fd);
+       fd = open("/var/run/suspend/watching",
+                 O_RDWR|O_CREAT|O_TRUNC, 0640);
+       if (fd < 0)
+               return;
+       if (write(fd, &zero, 1) != 1)
+               return;
+       flock(fd, LOCK_EX);
+       /* all watches must have moved to next file */
+       close(fd);
+}
+
+static void cycle_watchers(void)
+{
+       int fd;
+       char zero[2];
+
+       fd = open("/var/run/suspend/watching", O_RDWR|O_CREAT, 0640);
+       if (fd < 0)
+               return;
+       zero[0] = zero[1] = 0;
+       if (write(fd, zero, 2) != 2) {
+               close(fd);
+               return;
+       }
+       close(fd);
+       rename("/var/run/suspend/watching-next",
+              "/var/run/suspend/watching");
+}
+
+static int read_wakeup_count()
+{
+       int fd;
+       int n;
+       char buf[20];
+
+       fd = open("/sys/power/wakeup_count", O_RDONLY);
+       if (fd < 0)
+               return -1;
+       n = read(fd, buf, sizeof(buf)-1);
+       close(fd);
+       if (n < 0)
+               return -1;
+       buf[n] = 0;
+       return atoi(buf);
+}
+
+static int set_wakeup_count(int count)
+{
+       int fd;
+       char buf[20];
+       int n;
+
+       if (count < 0)
+               return 1; /* Something wrong - just suspend */
+
+       fd = open("/sys/power/wakeup_count", O_RDWR);
+       if (fd < 0)
+               return 1;
+
+       snprintf(buf, sizeof(buf), "%d", count);
+       n = write(fd, buf, strlen(buf));
+       close(fd);
+       if (n < 0)
+               return 0;
+       return 1;
+}
+
+static void catch(int sig)
+{
+       return;
+}
+
+static void wait_request(int dirfd)
+{
+       int found_immediate = 0;
+       int found_request = 0;
+
+       do {
+               DIR *dir;
+               struct dirent *de;
+               sigset_t set, oldset;
+               sigemptyset(&set);
+               sigaddset(&set, SIGIO);
+
+               sigprocmask(SIG_BLOCK, &set, &oldset);
+               signal(SIGIO, catch);
+
+               fcntl(dirfd, F_NOTIFY, DN_CREATE);
+
+               lseek(dirfd, 0L, 0);
+               dir = fdopendir(dup(dirfd));
+               while ((de = readdir(dir)) != NULL) {
+                       if (strcmp(de->d_name, "immediate") == 0)
+                               found_immediate = 1;
+                       if (strcmp(de->d_name, "request") == 0)
+                               found_request = 1;
+               }
+
+               if (!found_request && !found_immediate)
+                       sigsuspend(&oldset);
+               closedir(dir);
+               signal(SIGIO, SIG_DFL);
+               sigprocmask(SIG_UNBLOCK, &set, &oldset);
+       } while (!found_immediate && !found_request);
+}
+
+static void do_suspend(void)
+{
+       int fd = open("/sys/power/state", O_RDWR);
+       if (fd >= 0) {
+               write(fd, "mem\n", 4);
+               close(fd);
+       } else
+               sleep(5);
+}
+
+main(int argc, char *argv)
+{
+       int dir;
+       int disable;
+
+       mkdir("/var/run/suspend", 0770);
+
+       dir = open("/var/run/suspend", O_RDONLY);
+       disable = open("/var/run/suspend/disabled", O_RDWR|O_CREAT, 0640);
+
+       if (dir < 0 || disable < 0)
+               exit(1);
+
+       while (1) {
+               int count;
+
+               /* Don't accept an old request */
+               unlink("/var/run/suspend/request");
+               wait_request(dir);
+               if (flock(disable, LOCK_EX|LOCK_NB) != 0) {
+                       flock(disable, LOCK_EX);
+                       flock(disable, LOCK_UN);
+                       unlink("/var/run/suspend/request");
+                       /* blocked - so need to ensure request still valid */
+                       continue;
+               }
+               /* we got that without blocking but are not holding it */
+
+               /* Next two might block, but that doesn't abort suspend */
+               count = read_wakeup_count();
+               alert_watchers();
+
+               if (flock(disable, LOCK_EX|LOCK_NB) == 0
+                   && set_wakeup_count(count))
+                       do_suspend();
+               flock(disable, LOCK_UN);
+               cycle_watchers();
+       }
+}
diff --git a/lsused.c b/lsused.c
new file mode 100644 (file)
index 0000000..99179a8
--- /dev/null
+++ b/lsused.c
@@ -0,0 +1,264 @@
+
+/*
+ * lsused - Linux SUSpend Event monitoring Daemon
+ *
+ * apps and services can send fds to this daemon.
+ * We will not allow suspend to happen if any fd is readable.
+ * A client should also notice that it is readable, take a
+ * shared lock on the suspend/disabled file and then read the event.
+ *
+ * The client opens connects on a unix domain socket to
+ * /var/run/suspend/registration
+ * It sets 'W' with some fds attached to be watched.
+ * On notification if any fds are readable we send by 'S' to say
+ * Suspend Soon and wait for 'R' to say 'Ready'.
+ * We don't bother checking the fds again until the next suspend
+ * attempt.
+ *
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <event.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include <errno.h>
+#include "libsus.h"
+
+
+struct handle {
+       struct event    ev;
+       int             sent;           /* 'S' has been sent */
+       int             suspending;     /* ... 'R' hasn't been received yet */
+       struct handle   *next;
+       struct state    *state;
+};
+
+struct state {
+       int             waiting;        /* Number of replies waiting for */
+       struct handle   *handles;       /* linked list of handles */
+       struct pollfd   *fds;           /* for 'poll' */
+       struct handle   **hans;         /* aligned with fds */
+       int             nfds;           /* number of active 'fds' */
+       int             fdsize;         /* allocated size of fds array */
+       void            *sus;           /* handle from suspend_watch */
+};
+
+static void del_fd(struct state *state, int i)
+{
+       state->fds[i] = state->fds[state->nfds - 1];
+       state->hans[i] = state->hans[state->nfds - 1];
+       state->nfds--;
+}
+
+static void add_fd(struct state *state, struct handle *han,
+                  int fd, short events)
+{
+       int n = state->nfds;
+       if (state->nfds >= state->fdsize) {
+               /*need to make bigger */
+               int need = 16;
+               while (need < n+1)
+                       need *= 2;
+               state->fds = realloc(state->fds,
+                                    need * sizeof(struct pollfd));
+               state->hans = realloc(state->hans,
+                                     need * sizeof(struct handle *));
+       }
+       state->hans[n] = han;
+       state->fds[n].fd = fd;
+       state->fds[n].events = events;
+       state->nfds++;
+}
+
+static void add_han(struct handle *han, struct state *state)
+{
+
+       han->next = state->handles;
+       state->handles = han;
+}
+
+static void del_han(struct handle *han)
+{
+       struct state *state = han->state;
+       struct handle **hanp = &state->handles;
+       struct handle *h;
+       int i;
+
+       /* First remove the fds; */
+       for (i = 0; i < state->nfds ; i++)
+               if (state->hans[i] == han) {
+                       del_fd(state, i);
+                       i--;
+               }
+
+       /* Then remove the han */
+       for (h = *hanp; h; hanp = &h->next, h = *hanp) {
+               if (h == han) {
+                       *hanp = h->next;
+                       break;
+               }
+       }
+}
+
+static void do_read(int fd, short ev, void *data)
+{
+       struct handle *han = data;
+       char buf;
+       struct msghdr msg;
+       struct cmsghdr *cm;
+       struct iovec iov;
+       char mbuf[100];
+
+       buf = 0;
+       msg.msg_name = NULL;
+       msg.msg_namelen = 0;
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       iov.iov_base = &buf;
+       iov.iov_len = 1;
+       msg.msg_control = mbuf;
+       msg.msg_controllen = sizeof(mbuf);
+       msg.msg_flags = 0;
+
+       if (recvmsg(fd, &msg, MSG_CMSG_CLOEXEC|MSG_DONTWAIT) < 0
+           && errno == EAGAIN)
+               return;
+
+       switch (buf) {
+       case 'W':
+               for (cm = CMSG_FIRSTHDR(&msg);
+                    cm != NULL;
+                    cm = CMSG_NXTHDR(&msg, cm))
+                       if (cm->cmsg_level == SOL_SOCKET &&
+                           cm->cmsg_type == SCM_RIGHTS) {
+                               int *fdptr = (int*)CMSG_DATA(cm);
+                               int n = (cm->cmsg_len -
+                                        CMSG_ALIGN(sizeof(struct cmsghdr)))
+                                       / sizeof(int);
+                               int i;
+                               for (i = 0; i < n; i++)
+                                       add_fd(han->state, han, fdptr[i],
+                                               POLLIN|POLLPRI);
+                       }
+               write(fd, "A", 1);
+               break;
+
+       case 'R':
+               if (han->suspending) {
+                       han->suspending = 0;
+                       han->state->waiting--;
+                       if (han->state->waiting == 0)
+                               suspend_ok(han->state->sus);
+               }
+               break;
+
+       default:
+               event_del(&han->ev);
+               del_han(han);
+               close(fd);
+       }
+}
+
+static void do_accept(int fd, short ev, void *data)
+{
+       struct state *state = data;
+       struct handle *han;
+       int newfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
+       if (newfd < 0)
+               return;
+
+       han = malloc(sizeof(*han));
+       if (!han) {
+               close(newfd);
+               return;
+       }
+       han->sent = 0;
+       han->suspending = 0;
+       han->state = state;
+       add_han(han, state);
+       event_set(&han->ev, newfd, EV_READ | EV_PERSIST, do_read, han);
+       event_add(&han->ev, NULL);
+       write(newfd, "A", 1);
+}
+
+static int do_suspend(void *data)
+{
+       struct state *state = data;
+       struct handle *han;
+       int n;
+       int i;
+
+       n = poll(state->fds, state->nfds, 0);
+       if (n == 0)
+               /* nothing happening */
+               return 1;
+       for (han = state->handles ; han ; han = han->next)
+               han->sent = 0;
+       state->waiting = 1;
+       for (i = 0; i < state->nfds; i++)
+               if (state->fds[i].revents) {
+                       han = state->hans[i];
+                       if (!han->sent) {
+                               han->sent = 1;
+                               han->suspending = 1;
+                               write(EVENT_FD(&han->ev), "S", 1);
+                               state->waiting++;
+                       }
+               }
+       state->waiting--;
+       return (state->waiting == 0);
+}
+
+static void did_resume(void *data)
+{
+       struct state *state = data;
+       struct handle *han;
+
+       for (han = state->handles ; han ; han = han->next)
+               if (han->sent)
+                       write(EVENT_FD(&han->ev), "A", 1);
+}
+
+main(int argc, char *argv[])
+{
+       struct sockaddr_un addr;
+       struct state state;
+       struct event ev;
+       int s;
+
+       memset(&state, 0, sizeof(state));
+
+       s = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
+       addr.sun_family = AF_UNIX;
+       strcpy(addr.sun_path, "/var/run/suspend/registration");
+       unlink("/var/run/suspend/registration");
+       bind(s, (struct sockaddr *)&addr, sizeof(addr));
+       listen(s, 20);
+
+       event_init();
+
+       state.sus = suspend_watch(do_suspend, did_resume, &state);
+       event_set(&ev, s, EV_READ | EV_PERSIST, do_accept, &state);
+       event_add(&ev, NULL);
+
+       event_loop(0);
+       exit(0);
+}
diff --git a/request_suspend.c b/request_suspend.c
new file mode 100644 (file)
index 0000000..3a60341
--- /dev/null
@@ -0,0 +1,72 @@
+/* Request suspend
+ * Create the suspend-request file, then wait for it to be deleted.
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#define _GNU_SOURCE
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <signal.h>
+
+static void catch(int sig)
+{
+       return;
+}
+
+main(int argc, char *argv[])
+{
+       int dirfd = open("/var/run/suspend", O_RDONLY);
+       int fd_request = open("/var/run/suspend/request",
+                             O_RDWR|O_CREAT, 0640);
+       if (fd_request < 0)
+               exit(1);
+
+
+       if (dirfd < 0)
+               exit(1);
+       /* Wait for unlink */
+       while (1) {
+               int fd;
+               struct stat stat;
+               sigset_t set, oldset;
+               sigemptyset(&set);
+               sigaddset(&set, SIGIO);
+
+               sigprocmask(SIG_BLOCK, &set, &oldset);
+               signal(SIGIO, catch);
+
+               fcntl(dirfd, F_NOTIFY, DN_DELETE);
+
+               if (fstat(fd_request, &stat) != 0
+                   || stat.st_nlink == 0)
+                       exit(0);
+               fd = open("/var/run/suspend/disabled", O_RDONLY);
+               if (fd >= 0) {
+                       if (flock(fd, LOCK_EX|LOCK_NB) != 0) {
+                               /* Someone is blocking suspend */
+                               unlink("/var/run/suspend/request");
+                               exit(1);
+                       }
+                       close(fd);
+               }
+
+               sigsuspend(&oldset);
+               sigprocmask(SIG_UNBLOCK, &set, &oldset);
+       }
+}
diff --git a/suspend.py b/suspend.py
new file mode 100644 (file)
index 0000000..2de18b4
--- /dev/null
@@ -0,0 +1,88 @@
+
+#
+# interact with lsusd to provide suspend notification
+#
+# Copyright (C) 2011 Neil Brown <neilb@suse.de>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import dnotify, fcntl, os
+
+lock_watcher = None
+
+class monitor:
+    def __init__(self, suspend_callback, resume_callback):
+        """
+        Arrange that suspend_callback is called before we suspend, and
+        resume_callback is called when we resume.
+        If suspend_callback returns False, it must have arranged for
+        'release' to be called soon to allow suspend to continue.
+        """
+        global lock_watcher
+        if not lock_watcher:
+            lock_watcher = dnotify.dir('/var/run/suspend')
+
+        self.f = open('/var/run/suspend/watching', 'r')
+        self.getlock()
+        while os.fstat(self.f.fileno()).st_nlink == 0:
+            self.f.close()
+            self.f = open('/var/run/suspend/watching', 'r')
+            self.getlock()
+
+        self.suspended = False
+        self.suspend = suspend_callback
+        self.resume = resume_callback
+        self.watch = lock_watcher.watch("watching", self.change)
+
+    def getlock(self):
+        # lock file, protecting againt getting IOError when we get signalled.
+        locked = False
+        while not locked:
+            try:
+                fcntl.flock(self.f, fcntl.LOCK_SH)
+                locked = True
+            except IOError:
+                pass
+
+    def change(self, watched):
+        if os.fstat(self.f.fileno()).st_size == 0:
+            if self.suspended and os.stat('/var/run/suspend/watching').st_size == 0:
+                self.suspended = False
+                if self.resume:
+                    self.resume()
+            return
+        if not self.suspended and (not self.suspend or self.suspend()):
+            # ready for suspend
+            self.release()
+
+    def release(self):
+        # ready for suspend
+        old = self.f
+        self.f = open('/var/run/suspend/watching-next', 'r')
+        self.getlock()
+        self.suspended = True
+        fcntl.flock(old, fcntl.LOCK_UN)
+        old.close()
+
+
+
+if __name__ == '__main__':
+    import signal
+    def sus(): print "Suspending"; return True
+    def res(): print "Resuming"
+    monitor(sus, res)
+    print "ready"
+    while True:
+        signal.pause()
diff --git a/suspend_block.c b/suspend_block.c
new file mode 100644 (file)
index 0000000..e50c1f3
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Library routine to block and re-enable suspend.
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include "libsus.h"
+
+int suspend_open()
+{
+       return open("/var/run/suspend/disabled", O_RDONLY|O_CLOEXEC);
+}
+
+int suspend_block(int handle)
+{
+       if (handle < 0)
+               handle = suspend_open();
+       if (handle < 0)
+               return handle;
+
+       flock(handle, LOCK_SH);
+       return handle;
+}
+
+void suspend_allow(int handle)
+{
+       flock(handle, LOCK_UN);
+}
+
+int suspend_close(int handle)
+{
+       if (handle >= 0)
+               close(handle);
+}
+
diff --git a/test_block.sh b/test_block.sh
new file mode 100644 (file)
index 0000000..19d306c
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+# Copyright (C) 2011 Neil Brown <neilb@suse.de>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+source ./block.sh
+
+block_suspend
+
+echo now blocked.
+sleep 10
+echo allowing now.
+
+enable_suspend
diff --git a/wakealarm.c b/wakealarm.c
new file mode 100644 (file)
index 0000000..65f83f7
--- /dev/null
@@ -0,0 +1,103 @@
+
+/*
+ * Library code to allow libevent app to register for a wake alarm
+ * and register with wakealarmd to keep suspend at bay for the time.
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <event.h>
+#include <fcntl.h>
+#include <errno.h>
+#include "libsus.h"
+
+struct han {
+       struct event    ev;
+       int             sock;
+       int             disable;
+       void            (*fn)(int,short,void*);
+       void            *data;
+};
+
+static void alarm_clock(int fd, short ev, void *data)
+{
+       char buf[20];
+       int n;
+       struct han *h = data;
+
+       n = read(fd, buf, sizeof(buf)-1);
+       if (n < 0 && errno == EAGAIN)
+               return;
+       if (n <= 0 ||
+           strncmp(buf, "Now", 3) == 0) {
+               h->fn(-1, ev, h->data);
+               wakealarm_destroy(&h->ev);
+       }
+       /* Some other message, keep waiting */
+}
+
+struct event *wakealarm_set(time_t when, void(*fn)(int, short, void*),
+                           void *data)
+{
+       struct sockaddr_un addr;
+       struct han *h = malloc(sizeof(*h));
+       char buf[20];
+
+       if (!h)
+               return NULL;
+
+       h->fn = fn;
+       h->data = data;
+       h->disable = suspend_open();
+       h->sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+       if (h->sock < 0 || h->disable < 0)
+               goto abort;
+
+       addr.sun_family = AF_UNIX;
+       strcpy(addr.sun_path, "/var/run/suspend/wakealarm");
+       if (connect(h->sock, (struct sockaddr*)&addr, sizeof(addr)) != 0)
+               goto abort;
+
+       fcntl(h->sock, F_SETFL, fcntl(h->sock, F_GETFL, 0) | O_NONBLOCK);
+       sprintf(buf, "%lld\n", (long long)when);
+       write(h->sock, buf, strlen(buf));
+
+       event_set(&h->ev, h->sock, EV_READ|EV_PERSIST, alarm_clock, h);
+       event_add(&h->ev, NULL);
+
+       return &h->ev;
+
+abort:
+       suspend_close(h->disable);
+       if (h->sock >= 0)
+               close(h->sock);
+       free(h);
+       return NULL;
+}
+
+void wakealarm_destroy(struct event *ev)
+{
+       struct han *h = (struct han *)ev;
+       event_del(&h->ev);
+       close(h->sock);
+       suspend_close(h->disable);
+       free(h);
+}
diff --git a/wakealarmd.c b/wakealarmd.c
new file mode 100644 (file)
index 0000000..494e96e
--- /dev/null
@@ -0,0 +1,225 @@
+
+/* wake alarm service.
+ * We provide a wakeup service to use the rtc wakealarm
+ * to ensure the system is running at given times and to
+ * alert clients.
+ *
+ * Client can connect and register a time as seconds since epoch
+ * We echo back the time and then when the time comes we echo "Now".
+ * We keep system awake until another time is written, or until
+ * connection is closed.
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <event.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include "libsus.h"
+
+struct conn {
+       struct event    ev;
+       struct event    tev;
+       time_t          stamp;  /* When to wake */
+       int             active; /* stamp has passed */
+       struct conn     *next;  /* sorted by 'stamp' */
+       struct state    *state;
+};
+
+struct state {
+       struct event    ev;
+       int             disablefd;
+       int             disabled;
+       void            *watcher;
+       struct conn     *conns;
+       int             active_count;
+};
+
+static void add_han(struct conn *han)
+{
+       struct state *state = han->state;
+       struct conn **hanp = &state->conns;
+       struct timeval tv;
+       time_t now;
+
+       while (*hanp &&
+              (*hanp)->stamp < han->stamp)
+               hanp = &(*hanp)->next;
+       han->next = *hanp;
+       *hanp = han;
+
+       time(&now);
+       if (now < han->stamp) {
+               tv.tv_sec = han->stamp - now;
+               tv.tv_usec = 0;
+               evtimer_add(&han->tev, &tv);
+               han->active = 0;
+       } else {
+               if (!han->active) {
+                       han->active = 1;
+                       state->active_count++;
+               }
+       }
+}
+
+static void del_han(struct conn *han)
+{
+       struct state *state = han->state;
+       struct conn **hanp = &state->conns;
+
+       while (*hanp &&
+              *hanp != han)
+               hanp = &(*hanp)->next;
+       if (*hanp == han) {
+               *hanp = han->next;
+               if (han->active) {
+                       state->active_count--;
+                       if (state->active_count == 0
+                           && state->disabled) {
+                               suspend_allow(state->disablefd);
+                               state->disabled = 0;
+                       }
+               }
+       }
+}
+
+static void destroy_han(struct conn *han)
+{
+       event_del(&han->ev);
+       event_del(&han->tev);
+       free(han);
+}
+
+static void do_read(int fd, short ev, void *data)
+{
+       struct conn *han = data;
+       char buf[20];
+       int n;
+
+       n = read(fd, buf, sizeof(buf)-1);
+       if (n < 0 && errno == EAGAIN)
+               return;
+       if (n <= 0) {
+               del_han(han);
+               destroy_han(han);
+               return;
+       }
+       del_han(han);
+       han->stamp = atol(buf);
+       add_han(han);
+       sprintf(buf, "%lld\n", (long long)han->stamp);
+       write(fd, buf, strlen(buf));
+}
+
+static void do_timeout(int fd, short ev, void *data)
+{
+       struct conn *han = data;
+
+       if (!han->active) {
+               han->active = 1;
+               han->state->active_count++;
+       }
+       write(EVENT_FD(&han->ev), "Now\n", 4);
+}
+
+static void do_accept(int fd, short ev, void *data)
+{
+       struct state *state = data;
+       struct conn *han;
+       int newfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
+
+       if (newfd < 0)
+               return;
+       han = malloc(sizeof(*han));
+       if (!han) {
+               close(newfd);
+               return;
+       }
+       han->state = state;
+       han->stamp = 0;
+       han->active = 1;
+       state->active_count++;
+       han->next = state->conns;
+       state->conns = han;
+
+       evtimer_set(&han->tev, do_timeout, han);
+       event_set(&han->ev, newfd, EV_READ | EV_PERSIST, do_read, han);
+       event_add(&han->ev, NULL);
+       write(newfd, "0\n", 2);
+}
+
+static int do_suspend(void *data)
+{
+       struct state *state = data;
+       time_t now;
+
+       time(&now);
+
+       /* active_count must be zero */
+       if (state->conns == NULL)
+               return 1;
+
+       if (state->conns->stamp > now + 4) {
+               int fd = open("/sys/class/rtc/rtc0/wakealarm", O_WRONLY);
+               if (fd >= 0) {
+                       char buf[20];
+                       sprintf(buf, "%lld\n",
+                               (long long)state->conns->stamp - 2);
+                       write(fd, buf, strlen(buf));
+                       close(fd);
+               }
+               return 1;
+       }
+       /* too close to next wakeup */
+       if (state->disabled) {
+               suspend_block(state->disablefd);
+               state->disabled = 1;
+       }
+       return 1;
+}
+
+int main(int argc, char *argv[])
+{
+       struct state st;
+       struct sockaddr_un addr;
+       int s;
+
+       st.disablefd = suspend_open();
+       st.disabled = 0;
+       st.conns = NULL;
+       st.active_count = 0;
+
+       s = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
+       addr.sun_family = AF_UNIX;
+       strcpy(addr.sun_path, "/var/run/suspend/wakealarm");
+       unlink("/var/run/suspend/wakealarm");
+       bind(s, (struct sockaddr *)&addr, sizeof(addr));
+       listen(s, 20);
+
+       event_init();
+       st.watcher = suspend_watch(do_suspend, NULL, &st);
+       event_set(&st.ev, s, EV_READ | EV_PERSIST, do_accept, &st);
+       event_add(&st.ev, NULL);
+
+       event_loop(0);
+       exit(0);
+}
diff --git a/wakeevent.c b/wakeevent.c
new file mode 100644 (file)
index 0000000..3640e28
--- /dev/null
@@ -0,0 +1,148 @@
+
+/* Register an fd which produces wake events with
+ * eventlib.
+ * Whenever the fd is readable, we block suspend,
+ * call the handler, then allow suspend.
+ * Meanwhile we open a socket to the event daemon passing
+ * it the same fd.
+ * At a lower priority, when we read 'S' from the daemon we reply
+ * with 'R'.
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <event.h>
+#include <fcntl.h>
+#include <errno.h>
+#include "libsus.h"
+
+struct han {
+       struct event    ev;
+       struct event    sev;
+       int             sock;
+       int             disable;
+       void            (*fn)(int,short,void*);
+       void            *data;
+};
+
+static void wakeup_call(int fd, short ev, void *data)
+{
+       /* A (potential) wakeup event can be read from this fd.
+        * We won't go to sleep because we haven't replied to
+        * 'S' yet as that is handle with a lower priority.
+        */
+       struct han *han = data;
+       han->fn(fd, ev, han->data);
+}
+
+static void wakeup_sock(int fd, short ev, void *data)
+{
+       char buf;
+       struct han *han = data;
+       int n = read(fd, &buf, 1);
+
+       if (n < 0 && errno == EAGAIN)
+               return;
+       if (n != 1) {
+               /* How do I signal an error ?*/
+               event_del(&han->sev);
+               return;
+       }
+       if (buf == 'S')
+               /* As we are at a lower priority (higher number)
+                * than the main event, we must have handled everything
+                */
+               write(fd, "R", 1);
+}
+
+static void send_fd(int sock, int fd)
+{
+       struct msghdr msg = {0};
+       struct iovec iov;
+       struct cmsghdr *cmsg;
+       int myfds[1];
+       char buf[CMSG_SPACE(sizeof myfds)];
+       int *fdptr;
+
+       msg.msg_control = buf;
+       msg.msg_controllen = sizeof buf;
+       cmsg = CMSG_FIRSTHDR(&msg);
+       cmsg->cmsg_level = SOL_SOCKET;
+       cmsg->cmsg_type = SCM_RIGHTS;
+       cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+       fdptr = (int*)CMSG_DATA(cmsg);
+       fdptr[0] = fd;
+       msg.msg_controllen = cmsg->cmsg_len;
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       iov.iov_base = "W";
+       iov.iov_len = 1;
+       sendmsg(sock, &msg, 0);
+}
+
+struct event *wake_set(int fd, void(*fn)(int,short,void*), void *data, int prio)
+{
+       struct sockaddr_un addr;
+       struct han *h = malloc(sizeof(*h));
+
+       if (!h)
+               return NULL;
+
+       h->fn = fn;
+       h->data = data;
+       h->disable = suspend_open();
+       h->sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+       if (h->sock < 0 || h->disable < 0)
+               goto abort;
+       addr.sun_family = AF_UNIX;
+       strcpy(addr.sun_path, "/var/run/suspend/registration");
+       if (connect(h->sock, (struct sockaddr*)&addr, sizeof(addr)) != 0)
+               goto abort;
+
+       fcntl(h->sock, F_SETFL, fcntl(h->sock, F_GETFL, 0) | O_NONBLOCK);
+
+       send_fd(h->sock, fd);
+
+       event_set(&h->ev, fd, EV_READ|EV_PERSIST, wakeup_call, h);
+       event_set(&h->sev, h->sock, EV_READ|EV_PERSIST, wakeup_sock, h);
+       event_priority_set(&h->ev, prio);
+       event_priority_set(&h->sev, prio+1);
+       event_add(&h->ev, NULL);
+       event_add(&h->sev, NULL);
+
+       return &h->ev;
+
+abort:
+       suspend_close(h->disable);
+       if (h->sock >= 0)
+               close(h->sock);
+       free(h);
+       return NULL;
+}
+
+void wake_destroy(struct event *ev)
+{
+       struct han *h = (struct han *)ev;
+       event_del(&h->ev);
+       event_del(&h->sev);
+       close(h->sock);
+       suspend_close(h->disable);
+       free(h);
+}
diff --git a/watch_test.c b/watch_test.c
new file mode 100644 (file)
index 0000000..cbd2352
--- /dev/null
@@ -0,0 +1,51 @@
+
+/* Test watching for suspend notifications
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <stdlib.h>
+#include <event.h>
+#include <stdio.h>
+#include "libsus.h"
+
+static int suspend(void *m)
+{
+       char *msg = m;
+       printf("Suspend: %s\n", msg);
+       printf("Hang on while I tie my shoe laces...\n");
+       sleep(3);
+       printf("... OK, Done\n");
+       return 1;
+}
+
+static void resume(void *m)
+{
+       char *msg = m;
+       printf("Resume: %s\n", msg);
+}
+
+
+main(int argc, char *argv[])
+{
+
+       event_init();
+
+       suspend_watch(suspend, resume, "I saw that");
+
+       event_loop(0);
+       exit(0);
+}
diff --git a/watcher.c b/watcher.c
new file mode 100644 (file)
index 0000000..1049e8c
--- /dev/null
+++ b/watcher.c
@@ -0,0 +1,143 @@
+
+/*
+ * Use libevent to watch for suspends and take action.
+ * The calling program must already have a libevent loop running.
+ * One or two callbacks are registered with suspend_watch.
+ * The first is required and gets called just before suspend.
+ * It must return promptly but may call suspend_block first.
+ * The second is options and will get called after resume.
+ *
+ *
+ * Copyright (C) 2011 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License along
+ *    with this program; if not, write to the Free Software Foundation, Inc.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <event.h>
+#include <sys/stat.h>
+#include <malloc.h>
+
+struct cb {
+       int (*will_suspend)(void *data);
+       void (*did_resume)(void *data);
+       void *data;
+       int dirfd;
+       int fd, nextfd;
+       struct event ev;
+};
+
+static void checkdir(int efd, short ev, void *vp)
+{
+       struct cb *han = vp;
+       struct stat stb;
+       int fd;
+       int rv;
+
+       if (han->fd < 0)
+               /* too early */
+               return;
+
+       if (han->nextfd >= 0) {
+               /*suspended - maybe not any more */
+               fstat(han->fd, &stb);
+               if (stb.st_size <= 0)
+                       /*false alarm */
+                       return;
+               /* back from resume */
+               close(han->fd);
+               han->fd = han->nextfd;
+               han->nextfd = -1;
+               if (han->did_resume)
+                       han->did_resume(han->data);
+               /* Fall through incase suspend has started again */
+       }
+
+       /* not suspended yet */
+       if (fstat(han->fd, &stb) == 0
+           && stb.st_size == 0)
+               /* false alarm */
+               return;
+
+       /* We need to move on now. */
+       fd = open("/var/run/suspend/watching-next", O_RDONLY|O_CLOEXEC);
+       flock(fd, LOCK_SH);
+       han->nextfd = fd;
+       rv = han->will_suspend(han->data);
+       if (rv)
+               suspend_ok(han);
+}
+
+int suspend_ok(void *v)
+{
+       struct cb *han = v;
+       flock(han->fd, LOCK_UN);
+       return 1;
+}
+
+void *suspend_watch(int (*will_suspend)(void *data),
+                   void (*did_resume)(void *data),
+                   void *data)
+{
+       struct cb *han = malloc(sizeof(*han));
+       struct stat stb;
+       int fd = -1;
+       if (!han)
+               return NULL;
+
+       han->data = data;
+       han->will_suspend = will_suspend;
+       han->did_resume = did_resume;
+       han->fd = -1;
+       han->nextfd = -1;
+       signal_set(&han->ev, SIGIO, checkdir, han);
+       signal_add(&han->ev, NULL);
+       han->dirfd = open("/var/run/suspend", O_RDONLY|O_CLOEXEC);
+       if (han->dirfd < 0)
+               goto abort;
+       fcntl(han->dirfd, F_NOTIFY, DN_MODIFY | DN_MULTISHOT);
+again:
+       fd = open("/var/run/suspend/watching", O_RDONLY|O_CLOEXEC);
+       flock(fd, LOCK_SH);
+       han->fd = fd;
+       checkdir(0, 0, han);
+       /* OK, he won't suspend until I say OK. */
+
+       return han;
+abort:
+       signal_del(&han->ev);
+       if (fd >= 0)
+               close(fd);
+       if (han->dirfd >= 0)
+               close(han->dirfd);
+       free(han);
+}
+
+void suspend_unwatch(void *v)
+{
+       struct cb *han = v;
+       if (han->dirfd >= 0)
+               close(han->dirfd);
+       signal_del(&han->ev);
+       if (han->fd >= 0)
+               close(han->fd);
+       if (han->nextfd >= 0)
+               close(han->nextfd);
+       free(han);
+}
+