1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
|
#!/usr/bin/python
#
# powernapd - monitor a system process table; if IDLE amount of time
# goes by with no MONITORED_PROCESSES running, run ACTION
#
# Copyright (C) 2009-2011 Canonical Ltd.
#
# Authors: Dustin Kirkland <kirkland@canonical.com>
# Andres Rodriguez <andreserl@canonical.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, version 3 of the License.
#
# 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, see <http://www.gnu.org/licenses/>.
# Imports
import commands
import logging, logging.handlers
import os
import re
import signal
import sys
import time
import socket, traceback
import struct
from powernap import powernap
# Initialize Powernap. This initialization loads the config file.
try:
powernap = powernap.PowerNap()
os.putenv("ACTION_METHOD", str(powernap.ACTION_METHOD))
except:
print("Unable to initialize PowerNap")
sys.exit(1)
# Define globals
global LOCK, CONFIG, MONITORS, in_powersave_mode
LOCK = "/var/run/%s.pid" % powernap.PKG
LOG = "/var/log/%s.log" % powernap.PKG
in_powersave_mode = False
logging.basicConfig(filename=LOG, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d_%H:%M:%S', level=logging.DEBUG,)
# Generic debug function
def debug(level, msg):
if level >= (logging.ERROR - 10*powernap.DEBUG):
logging.log(level, msg)
# Generic error function
def error(msg):
debug(logging.ERROR, msg)
sys.exit(1)
# Lock function, using a pidfile in /var/run
def establish_lock():
if os.path.exists(LOCK):
f = open(LOCK,'r')
pid = f.read()
f.close()
error("Another instance is running [%s]" % pid)
else:
try:
f = open(LOCK,'w')
except:
error("Administrative privileges are required to run %s" % powernap.PKG);
f.write(str(os.getpid()))
f.close()
# Set signal handlers
signal.signal(signal.SIGHUP, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGQUIT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGIO, signal.SIG_IGN)
#signal.signal(signal.SIGIO, input_singal_handler)
signal.signal(signal.SIGUSR1, take_action_handler)
signal.signal(signal.SIGUSR2, take_recover_action_handler)
# Clean up lock file on termination signals
def signal_handler(signal, frame):
if os.path.exists(LOCK):
os.remove(LOCK)
debug(logging.INFO, "Stopping %s" % powernap.PKG)
sys.exit(1)
# Handler warns users when an USB Input device has been connected or
# disconnected. This is only useful for [InputMonitor] to avoid errors.
def input_signal_handler(signal, frame):
debug(logging.WARNING, "A monitored [InputMonitor] device has been disconnected or reconnected.")
# Send a message to system users, that we're about to take an action,
# and sleep for a grace period
def warn_users():
timestamp = time.strftime("%Y-%m-%d_%H:%M:%S")
msg1 = "[%s] PowerNap will take the following action in [%s] seconds: [%s]" % (timestamp, powernap.GRACE_SECONDS, powernap.ACTION)
msg2 = "To cancel this operation, press any key in any terminal"
debug(logging.WARNING, msg1)
if powernap.WARN:
commands.getoutput("echo '%s\n%s' | wall" % (msg1, msg2))
# TODO: notify authorities about action taken
def notify_authorities():
debug(logging.WARNING, "Taking action [%s]" % powernap.ACTION)
# Recover action for ACTION_METHOD=0 (pm-powersave).
def take_recover_action():
debug(logging.WARNING, "Taking recover action [%s]" % powernap.RECOVER_ACTION)
os.system(powernap.RECOVER_ACTION)
debug(logging.DEBUG, "Reseting counters after taking recover action")
for monitor in MONITORS:
monitor._absent_seconds = 0
# Handler for asynchronous external signals
def take_recover_action_handler(signal, frame):
global in_powersave_mode
if powernap.ACTION_METHOD == 0 and in_powersave_mode:
take_recover_action()
in_powersave_mode = False
# Zero the counters and take the action
def take_action():
notify_authorities()
debug(logging.DEBUG, "Reseting counters prior to taking action")
for monitor in MONITORS:
monitor._absent_seconds = 0
os.system(powernap.ACTION)
# Handler for asynchronous external signals
def take_action_handler(signal, frame):
global in_powersave_mode
take_action()
if powernap.ACTION_METHOD == 0:
in_powersave_mode = True
def powernapd_loop():
global in_powersave_mode
# Starting the Monitors
for monitor in MONITORS:
debug(logging.DEBUG, "Starting [%s:%s]" % (monitor._type, monitor._name))
monitor.start()
grace_seconds = powernap.GRACE_SECONDS
in_powersave_mode = False
users_warned = False
while 1:
debug(logging.DEBUG, "Sleeping [%d] seconds" % powernap.INTERVAL_SECONDS)
time.sleep(powernap.INTERVAL_SECONDS)
# Examine monitor activity, compute absent time of each monitored monitor process
debug(logging.DEBUG, "Examining Monitors")
absent_monitors = 0
grace_monitors = 0
for monitor in MONITORS:
debug(logging.DEBUG, " Looking for [%s] %s" % (monitor._name, monitor._type))
if monitor.active():
monitor._absent_seconds = 0
grace_seconds = powernap.GRACE_SECONDS
users_warned = False
debug(logging.DEBUG, " Activity found, reset absent time [%d/%d]" % (monitor._absent_seconds, powernap.ABSENT_SECONDS))
if in_powersave_mode and powernap.ACTION_METHOD == 0:
take_recover_action()
in_powersave_mode = False
break
elif in_powersave_mode:
# activity during POWERSAVE mode. Only increments absent_seconds and provides logging.
monitor._absent_seconds += powernap.INTERVAL_SECONDS
debug(logging.DEBUG, " Activity not found, increment absent time [%d/%d]" % (monitor._absent_seconds, powernap.ABSENT_SECONDS))
else:
# activity not found, increment absent time
monitor._absent_seconds += powernap.INTERVAL_SECONDS
debug(logging.DEBUG, " Activity not found, increment absent time [%d/%d]" % (monitor._absent_seconds, powernap.ABSENT_SECONDS))
if monitor._absent_seconds >= (powernap.ABSENT_SECONDS - powernap.GRACE_SECONDS):
# If monitor is absent for (ABSENT_SECONDS - GRACE_SECONDS), we consider it in GRACE PERIOD.
grace_monitors += 1
if monitor._absent_seconds >= powernap.ABSENT_SECONDS:
# activity missing for >= absent_seconds threshold, mark absent
debug(logging.DEBUG, " Activity absent for >= threshold, so mark absent")
absent_monitors += 1
# GRACE PERIOD: Time between ABSENT_SECONDS and (ABSENT_SECONDS - GRACE_SECONDS)
# If all monitors are in their own GRACE period
if grace_monitors > 0 and grace_monitors == len(MONITORS):
if users_warned is False and powernap.ACTION_METHOD != 0:
# Only display warn_users() wall message if action is something other than
# powersave, and only when initially entering to GRACE_PERIOD.
# If in GRACE_PERIOD but a warn_users() already issued, then ignore condition.
warn_users()
users_warned = True
debug(logging.WARNING, "Entered into GRACE PERIOD. Action [%s] will be taken in [%d] seconds" % (powernap.ACTION, grace_seconds))
grace_seconds -= powernap.INTERVAL_SECONDS
if grace_seconds == -1:
# Reset flags to original if powerwake finished GRACE PERIOD and ready to take action
grace_seconds = powernap.GRACE_SECONDS
users_warned = False
# Determine if action needs to be taken
if absent_monitors > 0 and absent_monitors == len(MONITORS):
take_action()
# If ACTION_METHOD is PowerSave then flag. Use is when tracking Monitor Activity
if powernap.ACTION_METHOD == 0:
in_powersave_mode = True
# "Forking a Daemon Process on Unix" from The Python Cookbook
def daemonize (stdin="/dev/null", stdout="/var/log/%s.log" % powernap.PKG, stderr="/var/log/%s.err" % powernap.PKG):
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError, e:
sys.stderr.write("fork #1 failed: (%d) %sn" % (e.errno, e.strerror))
sys.exit(1)
os.chdir("/")
os.setsid()
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError, e:
sys.stderr.write("fork #2 failed: (%d) %sn" % (e.errno, e.strerror))
sys.exit(1)
f = open(LOCK,'w')
f.write(str(os.getpid()))
f.close()
for f in sys.stdout, sys.stderr: f.flush()
si = file(stdin, 'r')
so = file(stdout, 'a+')
se = file(stderr, 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# Main program
if __name__ == '__main__':
# Ensure that only one instance runs
establish_lock()
daemonize()
try:
# Run the main powernapd loop
MONITORS = powernap.get_monitors()
debug(logging.INFO, "Starting %s" % powernap.PKG)
powernapd_loop()
finally:
# Clean up the lock file
if os.path.exists(LOCK):
os.remove(LOCK)
|