Setting tag nodemanager-2.0-38
[nodemanager.git] / tools.py
1 """A few things that didn't seem to fit anywhere else."""
2
3 import os, os.path
4 import pwd
5 import tempfile
6 import fcntl
7 import errno
8 import threading
9 import subprocess
10
11 import logger
12
13 PID_FILE = '/var/run/nodemanager.pid'
14
15 ####################
16 def get_default_if():
17     interface = get_if_from_hwaddr(get_hwaddr_from_plnode())
18     if not interface: interface = "eth0"
19     return interface
20
21 def get_hwaddr_from_plnode():
22     try:
23         for line in open("/usr/boot/plnode.txt", 'r').readlines():
24             if line.startswith("NET_DEVICE"):
25                 return line.split("=")[1].strip().strip('"')
26     except:
27         pass
28     return None
29
30 def get_if_from_hwaddr(hwaddr):
31     import sioc
32     devs = sioc.gifconf()
33     for dev in devs:
34         dev_hwaddr = sioc.gifhwaddr(dev)
35         if dev_hwaddr == hwaddr: return dev
36     return None
37
38 ####################
39 # daemonizing
40 def as_daemon_thread(run):
41     """Call function <run> with no arguments in its own thread."""
42     thr = threading.Thread(target=run)
43     thr.setDaemon(True)
44     thr.start()
45
46 def close_nonstandard_fds():
47     """Close all open file descriptors other than 0, 1, and 2."""
48     _SC_OPEN_MAX = 4
49     for fd in range(3, os.sysconf(_SC_OPEN_MAX)):
50         try: os.close(fd)
51         except OSError: pass  # most likely an fd that isn't open
52
53 # after http://www.erlenstar.demon.co.uk/unix/faq_2.html
54 def daemon():
55     """Daemonize the current process."""
56     if os.fork() != 0: os._exit(0)
57     os.setsid()
58     if os.fork() != 0: os._exit(0)
59     os.chdir('/')
60     os.umask(0022)
61     devnull = os.open(os.devnull, os.O_RDWR)
62     os.dup2(devnull, 0)
63     # xxx fixme - this is just to make sure that nothing gets stupidly lost - should use devnull
64     crashlog = os.open('/var/log/nodemanager.daemon', os.O_RDWR | os.O_APPEND | os.O_CREAT, 0644)
65     os.dup2(crashlog, 1)
66     os.dup2(crashlog, 2)
67
68 def fork_as(su, function, *args):
69     """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."""
70     child_pid = os.fork()
71     if child_pid == 0:
72         try:
73             os.chdir('/')
74             close_nonstandard_fds()
75             if su:
76                 pw_ent = pwd.getpwnam(su)
77                 os.setegid(pw_ent[3])
78                 os.seteuid(pw_ent[2])
79             child_pid = os.fork()
80             if child_pid == 0: function(*args)
81         except:
82             os.seteuid(os.getuid())  # undo su so we can write the log file
83             os.setegid(os.getgid())
84             logger.log_exc("tools: fork_as")
85         os._exit(0)
86     else: os.waitpid(child_pid, 0)
87
88 ####################
89 # manage files
90 def pid_file():
91     """We use a pid file to ensure that only one copy of NM is running at a given time.
92 If successful, this function will write a pid file containing the pid of the current process.
93 The return value is the pid of the other running process, or None otherwise."""
94     other_pid = None
95     if os.access(PID_FILE, os.F_OK):  # check for a pid file
96         handle = open(PID_FILE)  # pid file exists, read it
97         other_pid = int(handle.read())
98         handle.close()
99         # check for a process with that pid by sending signal 0
100         try: os.kill(other_pid, 0)
101         except OSError, e:
102             if e.errno == errno.ESRCH: other_pid = None  # doesn't exist
103             else: raise  # who knows
104     if other_pid == None:
105         # write a new pid file
106         write_file(PID_FILE, lambda f: f.write(str(os.getpid())))
107     return other_pid
108
109 def write_file(filename, do_write, **kw_args):
110     """Write file <filename> atomically by opening a temporary file, using <do_write> to write that file, and then renaming the temporary file."""
111     os.rename(write_temp_file(do_write, **kw_args), filename)
112
113 def write_temp_file(do_write, mode=None, uidgid=None):
114     fd, temporary_filename = tempfile.mkstemp()
115     if mode: os.chmod(temporary_filename, mode)
116     if uidgid: os.chown(temporary_filename, *uidgid)
117     f = os.fdopen(fd, 'w')
118     try: do_write(f)
119     finally: f.close()
120     return temporary_filename
121
122 # replace a target file with a new contents - checks for changes
123 # can handle chmod if requested
124 # can also remove resulting file if contents are void, if requested
125 # performs atomically:
126 #    writes in a tmp file, which is then renamed (from sliverauth originally)
127 # returns True if a change occurred, or the file is deleted
128 def replace_file_with_string (target, new_contents, chmod=None, remove_if_empty=False):
129     try:
130         current=file(target).read()
131     except:
132         current=""
133     if current==new_contents:
134         # if turns out to be an empty string, and remove_if_empty is set,
135         # then make sure to trash the file if it exists
136         if remove_if_empty and not new_contents and os.path.isfile(target):
137             logger.verbose("tools.replace_file_with_string: removing file %s"%target)
138             try: os.unlink(target)
139             finally: return True
140         return False
141     # overwrite target file: create a temp in the same directory
142     path=os.path.dirname(target) or '.'
143     fd, name = tempfile.mkstemp('','repl',path)
144     os.write(fd,new_contents)
145     os.close(fd)
146     if os.path.exists(target):
147         os.unlink(target)
148     os.rename(name,target)
149     if chmod: os.chmod(target,chmod)
150     return True
151
152
153 ####################
154 # utilities functions to get (cached) information from the node
155
156 # get node_id from /etc/planetlab/node_id and cache it
157 _node_id=None
158 def node_id():
159     global _node_id
160     if _node_id is None:
161         try:
162             _node_id=int(file("/etc/planetlab/node_id").read())
163         except:
164             _node_id=""
165     return _node_id
166
167 _root_context_arch=None
168 def root_context_arch():
169     global _root_context_arch
170     if not _root_context_arch:
171         sp=subprocess.Popen(["uname","-i"],stdout=subprocess.PIPE)
172         (_root_context_arch,_)=sp.communicate()
173         _root_context_arch=_root_context_arch.strip()
174     return _root_context_arch
175
176
177 ####################
178 class NMLock:
179     def __init__(self, file):
180         logger.log("tools: Lock %s initialized." % file, 2)
181         self.fd = os.open(file, os.O_RDWR|os.O_CREAT, 0600)
182         flags = fcntl.fcntl(self.fd, fcntl.F_GETFD)
183         flags |= fcntl.FD_CLOEXEC
184         fcntl.fcntl(self.fd, fcntl.F_SETFD, flags)
185     def __del__(self):
186         os.close(self.fd)
187     def acquire(self):
188         logger.log("tools: Lock acquired.", 2)
189         fcntl.lockf(self.fd, fcntl.LOCK_SH)
190     def release(self):
191         logger.log("tools: Lock released.", 2)
192         fcntl.lockf(self.fd, fcntl.LOCK_UN)