Nach langer Zeit will ich mich auch mal wieder hier blicken lassen und ein kleines Script von mir zur Begutachtung vorstellen. Ich habe einen kleinen cron geschrieben, der platformunabhängig läuft und sowohl Python callables als auch andere Programme ausführen kann. Unter *nix kann er das auch in einem definierbaren Userkontext, sodass con als root laufen kann, gestartete Programme aber nur mit eingeschränkten Rechten laufen werden.
Ich möchte den auf einem System einsetzen, auf dem man nur sehr beschränkt Dinge nachinstallieren kann und das KEINEN crond hat. Würdet ihr mir das empfehlen oder habe ich etwas wichtiges übersehen?
Code: Alles auswählen
#!/usr/bin/python2
#coding: utf-8
#file : pycron.py
#original code from https://github.com/idning/pcl/blob/master/pcl/crontab.py
#cron, we do the task in the main thread, so do not add task run more than 1 minutes
#you can start thread in your task
import argparse
from datetime import datetime
import logging
import os
import shlex
from StringIO import StringIO
import subprocess
import sys
import time
import thread
try:
import pwd
import grp
except ImportError:
pass
DETACHED_PROCESS = 0x00000008
class Cron(object):
def __init__(self):
self.events = []
def add_Event(self, e):
if isinstance(e,Event):
self.events.append(e)
def run(self):
last_run = 0
while True:
#wait to a new minute start
t = time.time()
next_minute = t - t%60 + 60
while t < next_minute:
sleeptime = 60 - t%60
logging.debug('current time: %s, we will sleep %.2f seconds' %(t, sleeptime))
time.sleep(sleeptime)
t = time.time()
if last_run and next_minute - last_run != 60:
logging.warn('Cron Ignored: last_run: %s, this_time:%s' % (last_run, next_minute) )
last_run = next_minute
current = datetime(*datetime.now().timetuple()[:5])
for e in self.events:
e.check(current)
time.sleep(0.001)
class Event(object):
def __init__(self,desc, use_thread=False):
"""
desc: min hour day month dow
day: 1 - num days
month: 1 - 12
dow: mon = 1, sun = 7
"""
self.desc = desc
self.use_thread = use_thread
#support:
# *
# 59
# 10,20,30
def _match(self, value, expr):
#print 'match', value, expr
if expr == '*':
return True
values = expr.split(',')
for v in values:
if int(v) == value:
return True
return False
def matchtime(self, t):
mins, hour, day, month, dow = self.desc.split()
return self._match(t.minute , mins) and\
self._match(t.hour , hour) and\
self._match(t.day , day) and\
self._match(t.month , month) and\
self._match(t.isoweekday() , dow)
def check(self,t):
if self.matchtime(t):
if self.use_thread:
thread.start_new_thread(self.run,tuple(),{})
else:
try:
self.run()
except Exception, e:
logging.exception(e)
def run(self):
pass
class PyEvent(Event):
def __init__(self,desc, func, args=(), kwargs={}, use_thread=False):
Event.__init__(self,desc,use_thread=use_thread)
self.func = func
self.args = args
self.kwargs = kwargs
def run(self):
self.func(*self.args, **self.kwargs)
class ProcessEvent(Event):
def __init__(self,desc,cmd,use_thread=False):
Event.__init__(self,desc,use_thread=use_thread)
if isinstance(cmd,basestring):
self.cmd=tuple(shlex.split(cmd))
elif hasattr(cmd,"__iter__"):
self.cmd=tuple(cmd)
else:
raise TypeError, "cmd must be a string or an iterable command list like shlex.split returns"
def run(self):
if os.name=="posix":
Null=file("/dev/null","w+b")
elif os.name=="nt":
Null=file("NUL","w+b")
else:
Null=None
subprocess.Popen(self.cmd,stdout=Null,stderr=Null,cwd=os.path.abspath(os.sep))
if hasattr(Null,"close"):
Null.close()
if os.name == "posix":
class PosixProcessEvent(ProcessEvent):
def __init__(self,desc,cmd,username=None,groupname=None,use_thread=False):
ProcessEvent.__init__(self,desc,cmd,use_thread=False)
uid=os.getuid()
gid=os.getgid()
self.username=username
self.groupname=groupname
if username is not None:
try:
p=pwd.getpwnam(username)
self.uid=p.pw_uid
self.gid=p.pw_gid
except KeyError:
raise KeyError, "no user %s found" % username
if uid != 0 and self.uid != uid:
raise OSError, "must be superuser to change user/uid to %s/%i" % (self.username,self.uid)
else:
self.uid=uid
if groupname is not None:
try:
self.gid=grp.getgrnam(groupname).gr_gid
except KeyError:
raise KeyError, "no group %s found" % groupname
if uid != 0 and self.gid != gid:
raise OSError, "must be superuser to change groupname/gid to %s/%i" % (self.groupname,self.gid)
else:
self.gid=gid
def run(self):
def ChangeChildUIDandGID():
os.setgid(self.gid)
os.setuid(self.uid)
Null=file("/dev/null","w+b")
subprocess.Popen(self.cmd,preexec_fn=ChangeChildUIDandGID,stdout=Null,stderr=Null,cwd=os.path.abspath(os.sep))
Null.close()
class NullFile(StringIO):
def write(self,text):
self.truncate(0)
if __name__ == "__main__":
#if running as main program, not as module
def detach():
if os.name == "posix":
try:
pid=os.fork()
if pid > 0:
#exit parent process
sys.exit(0)
except OSError,e:
print >> sys.stderr, "fork failed: %d (%s)" % (e.errno, e.strerror)
sys.exit(1)
# Configure the child processes environment
os.chdir("/")
os.setsid()
os.umask(0)
sys.stdin.close()
f=NullFile()
sys.stdout=sys.stderr=f
main()
elif os.name == "nt":
pid = subprocess.Popen([sys.executable, sys.argv[0]],creationflags=DETACHED_PROCESS,close_fds=True).pid
sys.exit(0)
else:
sys.exit("no daemon mode available for this platform")
def main2():
def long_task():
time.sleep(61)
print 'long task @ %s' % time.time()
#we will got warnning
cron = Cron()
cron.add_Event(PyEvent('* * * * *' , long_task)) # every minute
cron.run()
def main():
def minute_task():
print 'minute_task @ %s' % time.time()
def day_task():
print 'day_task @ %s' % time.time()
cron = Cron()
cron.add_Event(PyEvent('* * * * *' , minute_task)) # every minute
cron.add_Event(PyEvent('* * * * *' , minute_task, use_thread=True)) # every minute
cron.add_Event(PyEvent('33 * * * *' , day_task)) # every hour
cron.add_Event(PyEvent('34 18 * * *' , day_task)) # every day
cron.run()
SCRIPTNAME=sys.argv[0]
parser = argparse.ArgumentParser(description='Cron program written in python. Support for all cron features. If run by root, it can call commands in any specific user context.')
group = parser.add_mutually_exclusive_group()
group.add_argument("-b","--background",dest="daemon",action='store_true',help="run in background, behave like a daemon")
group.add_argument("-f","--foreground",dest="daemon",action='store_false',help="run in foreground, do not run in daemon mode (DEFAULT)")
p=parser.parse_args()
if p.daemon:
detach()
else:
main()