forked from planetlab/NodeManager
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tools.py
195 lines (174 loc) · 6.61 KB
/
tools.py
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
# $Id$
# $URL$
"""A few things that didn't seem to fit anywhere else."""
import os, os.path
import pwd
import tempfile
import fcntl
import errno
import threading
import subprocess
import logger
PID_FILE = '/var/run/nodemanager.pid'
####################
def get_default_if():
interface = get_if_from_hwaddr(get_hwaddr_from_plnode())
if not interface: interface = "eth0"
return interface
def get_hwaddr_from_plnode():
try:
for line in open("/usr/boot/plnode.txt", 'r').readlines():
if line.startswith("NET_DEVICE"):
return line.split("=")[1].strip().strip('"')
except:
pass
return None
def get_if_from_hwaddr(hwaddr):
import sioc
devs = sioc.gifconf()
for dev in devs:
dev_hwaddr = sioc.gifhwaddr(dev)
if dev_hwaddr == hwaddr: return dev
return None
####################
# daemonizing
def as_daemon_thread(run):
"""Call function <run> with no arguments in its own thread."""
thr = threading.Thread(target=run)
thr.setDaemon(True)
thr.start()
def close_nonstandard_fds():
"""Close all open file descriptors other than 0, 1, and 2."""
_SC_OPEN_MAX = 4
for fd in range(3, os.sysconf(_SC_OPEN_MAX)):
try: os.close(fd)
except OSError: pass # most likely an fd that isn't open
# after http://www.erlenstar.demon.co.uk/unix/faq_2.html
def daemon():
"""Daemonize the current process."""
if os.fork() != 0: os._exit(0)
os.setsid()
if os.fork() != 0: os._exit(0)
os.chdir('/')
os.umask(0022)
devnull = os.open(os.devnull, os.O_RDWR)
os.dup2(devnull, 0)
# xxx fixme - this is just to make sure that nothing gets stupidly lost - should use devnull
crashlog = os.open('/var/log/nodemanager.daemon', os.O_RDWR | os.O_APPEND | os.O_CREAT, 0644)
os.dup2(crashlog, 1)
os.dup2(crashlog, 2)
def fork_as(su, function, *args):
"""fork(), cd / to avoid keeping unused directories open, close all nonstandard file descriptors (to avoid capturing open sockets), fork() again (to avoid zombies) and call <function> with arguments <args> in the grandchild process. If <su> is not None, set our group and user ids appropriately in the child process."""
child_pid = os.fork()
if child_pid == 0:
try:
os.chdir('/')
close_nonstandard_fds()
if su:
pw_ent = pwd.getpwnam(su)
os.setegid(pw_ent[3])
os.seteuid(pw_ent[2])
child_pid = os.fork()
if child_pid == 0: function(*args)
except:
os.seteuid(os.getuid()) # undo su so we can write the log file
os.setegid(os.getgid())
logger.log_exc("tools: fork_as")
os._exit(0)
else: os.waitpid(child_pid, 0)
####################
# manage files
def pid_file():
"""We use a pid file to ensure that only one copy of NM is running at a given time.
If successful, this function will write a pid file containing the pid of the current process.
The return value is the pid of the other running process, or None otherwise."""
other_pid = None
if os.access(PID_FILE, os.F_OK): # check for a pid file
handle = open(PID_FILE) # pid file exists, read it
other_pid = int(handle.read())
handle.close()
# check for a process with that pid by sending signal 0
try: os.kill(other_pid, 0)
except OSError, e:
if e.errno == errno.ESRCH: other_pid = None # doesn't exist
else: raise # who knows
if other_pid == None:
# write a new pid file
write_file(PID_FILE, lambda f: f.write(str(os.getpid())))
return other_pid
def write_file(filename, do_write, **kw_args):
"""Write file <filename> atomically by opening a temporary file, using <do_write> to write that file, and then renaming the temporary file."""
os.rename(write_temp_file(do_write, **kw_args), filename)
def write_temp_file(do_write, mode=None, uidgid=None):
fd, temporary_filename = tempfile.mkstemp()
if mode: os.chmod(temporary_filename, mode)
if uidgid: os.chown(temporary_filename, *uidgid)
f = os.fdopen(fd, 'w')
try: do_write(f)
finally: f.close()
return temporary_filename
# replace a target file with a new contents - checks for changes
# can handle chmod if requested
# can also remove resulting file if contents are void, if requested
# performs atomically:
# writes in a tmp file, which is then renamed (from sliverauth originally)
# returns True if a change occurred, or the file is deleted
def replace_file_with_string (target, new_contents, chmod=None, remove_if_empty=False):
try:
current=file(target).read()
except:
current=""
if current==new_contents:
# if turns out to be an empty string, and remove_if_empty is set,
# then make sure to trash the file if it exists
if remove_if_empty and not new_contents and os.path.isfile(target):
logger.verbose("tools.replace_file_with_string: removing file %s"%target)
try: os.unlink(target)
finally: return True
return False
# overwrite target file: create a temp in the same directory
path=os.path.dirname(target) or '.'
fd, name = tempfile.mkstemp('','repl',path)
os.write(fd,new_contents)
os.close(fd)
if os.path.exists(target):
os.unlink(target)
os.rename(name,target)
if chmod: os.chmod(target,chmod)
return True
####################
# utilities functions to get (cached) information from the node
# get node_id from /etc/planetlab/node_id and cache it
_node_id=None
def node_id():
global _node_id
if _node_id is None:
try:
_node_id=int(file("/etc/planetlab/node_id").read())
except:
_node_id=""
return _node_id
_root_context_arch=None
def root_context_arch():
global _root_context_arch
if not _root_context_arch:
sp=subprocess.Popen(["uname","-i"],stdout=subprocess.PIPE)
(_root_context_arch,_)=sp.communicate()
_root_context_arch=_root_context_arch.strip()
return _root_context_arch
####################
class NMLock:
def __init__(self, file):
logger.log("tools: Lock %s initialized." % file, 2)
self.fd = os.open(file, os.O_RDWR|os.O_CREAT, 0600)
flags = fcntl.fcntl(self.fd, fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self.fd, fcntl.F_SETFD, flags)
def __del__(self):
os.close(self.fd)
def acquire(self):
logger.log("tools: Lock acquired.", 2)
fcntl.lockf(self.fd, fcntl.LOCK_SH)
def release(self):
logger.log("tools: Lock released.", 2)
fcntl.lockf(self.fd, fcntl.LOCK_UN)