bugfix in sysexec_chroot_noerr, path not passed along
[bootmanager.git] / source / utils.py
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2003 Intel Corporation
4 # All rights reserved.
5 #
6 # Copyright (c) 2004-2006 The Trustees of Princeton University
7 # All rights reserved.
8 # expected /proc/partitions format
9
10 from __future__ import print_function
11
12 import os, sys, shutil
13 import subprocess
14 import shlex
15 import socket
16 import fcntl
17 import string
18 import exceptions
19
20 from Exceptions import *
21
22 ####################
23 # the simplest way to debug is to let the node take off, 
24 # ssh into it as root using the debug ssh key in /etc/planetlab
25 # then go to /tmp/source 
26 # edit this file locally to turn on breakpoints if needed, then run
27 # ./BootManager.py
28 ####################
29
30 ### handling breakpoints in the startup process
31 import select, sys, string
32
33 ### global debugging settings
34
35 # enabling this will cause the node to ask for breakpoint-mode at startup
36 # production code should read False/False
37 PROMPT_MODE = False
38 # default for when prompt is turned off, or it's on but the timeout triggers
39 BREAKPOINT_MODE = False
40
41 # verbose mode is just fine
42 VERBOSE_MODE = True
43 # in seconds : if no input, proceed
44 PROMPT_TIMEOUT = 5
45
46 def prompt_for_breakpoint_mode():
47
48     global BREAKPOINT_MODE
49     if PROMPT_MODE:
50         default_answer = BREAKPOINT_MODE
51         answer = ''
52         if BREAKPOINT_MODE:
53             display = "[y]/n"
54         else:
55             display = "y/[n]"
56         sys.stdout.write ("Want to run in breakpoint mode ? {} ".format(display))
57         sys.stdout.flush()
58         r, w, e = select.select ([sys.stdin], [], [], PROMPT_TIMEOUT)
59         if r:
60             answer = string.strip(sys.stdin.readline())
61         else:
62             sys.stdout.write("\nTimed-out ({}s)".format(PROMPT_TIMEOUT))
63         if answer:
64             BREAKPOINT_MODE = (answer == "y" or answer == "Y")
65         else:
66             BREAKPOINT_MODE = default_answer
67     label = "Off"
68     if BREAKPOINT_MODE:
69         label = "On"
70     sys.stdout.write("\nCurrent BREAKPOINT_MODE is {}\n".format(label))
71
72 def breakpoint (message, cmd = None):
73
74     if BREAKPOINT_MODE:
75
76         if cmd is None:
77             cmd = "/bin/sh"
78             message = message + " -- Entering bash - type ^D to proceed"
79
80         print(message)
81         os.system(cmd)
82
83     else:
84         print("Ignoring breakpoint (BREAKPOINT_MODE=False) : {}".format(message))    
85
86
87 ########################################
88 def makedirs(path):
89     """
90     from python docs for os.makedirs:
91     Throws an error exception if the leaf directory
92     already exists or cannot be created.
93
94     That is real useful. Instead, we'll create the directory, then use a
95     separate function to test for its existance.
96
97     Return 1 if the directory exists and/or has been created, a BootManagerException
98     otherwise. Does not test the writability of said directory.
99     """
100     try:
101         os.makedirs(path)
102     except OSError:
103         pass
104     try:
105         os.listdir(path)
106     except OSError:
107         raise BootManagerException("Unable to create directory tree: {}".format(path))
108     
109     return 1
110
111
112
113 def removedir(path):
114     """
115     remove a directory tree, return 1 if successful, a BootManagerException
116     if failure.
117     """
118     try:
119         os.listdir(path)
120     except OSError:
121         return 1
122
123     try:
124         shutil.rmtree(path)
125     except OSError as desc:
126         raise BootManagerException("Unable to remove directory tree: {}".format(path))
127     
128     return 1
129
130
131 def sysexec(cmd, log=None, fsck=False, shell=False):
132     """
133     execute a system command, output the results to the logger
134     if log <> None
135
136     return 1 if command completed (return code of non-zero),
137     0 if failed. A BootManagerException is raised if the command
138     was unable to execute or was interrupted by the user with Ctrl+C
139     """
140     try:
141         # Thierry - Jan. 6 2011
142         # would probably make sense to look for | here as well
143         # however this is fragile and hard to test thoroughly
144         # let the caller set 'shell' when that is desirable
145         if shell or cmd.__contains__(">"):
146             prog = subprocess.Popen(cmd, shell=True)
147             if log is not None:
148                 log.write("sysexec (shell mode) >>> {}".format(cmd))
149             if VERBOSE_MODE:
150                 print("sysexec (shell mode) >>> {}".format(cmd))
151         else:
152             prog = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
153             if log is not None:
154                 log.write("sysexec >>> {}\n".format(cmd))
155             if VERBOSE_MODE:
156                 print("sysexec >>> {}".format(cmd))
157     except OSError:
158         raise BootManagerException(
159               "Unable to create instance of subprocess.Popen "
160               "for command: {}".format(cmd))
161     try:
162         (stdoutdata, stderrdata) = prog.communicate()
163     except KeyboardInterrupt:
164         raise BootManagerException("Interrupted by user")
165
166     # log stdout & stderr
167     if log is not None:
168         if stdoutdata:
169             log.write("==========stdout\n" + stdoutdata)
170         if stderrdata:
171             log.write("==========stderr\n" + stderrdata)
172
173     returncode = prog.wait()
174
175     if fsck:
176        # The exit code returned by fsck is the sum of the following conditions:
177        #      0    - No errors
178        #      1    - File system errors corrected
179        #      2    - System should be rebooted
180        #      4    - File system errors left uncorrected
181        #      8    - Operational error
182        #      16   - Usage or syntax error
183        #      32   - Fsck canceled by user request
184        #      128  - Shared library error
185        if returncode != 0 and returncode != 1:
186             raise BootManagerException("Running {} failed (rc={})".format(cmd, returncode))
187     else:
188         if returncode != 0:
189             raise BootManagerException("Running {} failed (rc={})".format(cmd, returncode))
190
191     prog = None
192     return 1
193
194
195 def sysexec_chroot(path, cmd, log=None, shell=False):
196     """
197     same as sysexec, but inside a chroot
198     """
199     preload = ""
200     release = os.uname()[2]
201     # 2.6.12 kernels need this
202     if release[:5] == "2.6.1":
203         library = "{}/lib/libc-opendir-hack.so".format(path)
204         if not os.path.exists(library):
205             shutil.copy("./libc-opendir-hack.so", library)
206         preload = "/bin/env LD_PRELOAD=/lib/libc-opendir-hack.so"
207     sysexec("chroot {} {} {}".format(path, preload, cmd), log, shell=shell)
208
209
210 def sysexec_chroot_noerr(path, cmd, log=None, shell=False):
211     """
212     same as sysexec_chroot, but capture boot manager exceptions
213     """
214     try:
215         rc = 0
216         rc = sysexec_chroot(path, cmd, log, shell=shell)
217     except BootManagerException as e:
218         pass
219
220     return rc
221
222
223 def sysexec_noerr(cmd, log=None, shell=False):
224     """
225     same as sysexec, but capture boot manager exceptions
226     """
227     try:
228         rc = 0
229         rc = sysexec(cmd, log, shell=shell)
230     except BootManagerException as e:
231         pass
232
233     return rc
234
235
236
237 def chdir(dir):
238     """
239     change to a directory, return 1 if successful, a BootManagerException if failure
240     """
241     try:
242         os.chdir(dir)
243     except OSError:
244         raise BootManagerException("Unable to change to directory: {}".format(dir))
245
246     return 1
247
248
249
250 def removefile(filepath):
251     """
252     removes a file, return 1 if successful, 0 if failure
253     """
254     try:
255         os.remove(filepath)
256     except OSError:
257         raise BootManagerException("Unable to remove file: {}".format(filepath))
258
259     return 1
260
261
262
263 # from: http://forums.devshed.com/archive/t-51149/
264 #              Ethernet-card-address-Through-Python-or-C
265
266 def get_mac_from_interface(ifname):
267     """
268     given a device name, like eth0, return its mac_address.
269     return None if the device doesn't exist.
270     """
271     
272     SIOCGIFHWADDR = 0x8927 # magic number
273
274     s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
275     ifname = string.strip(ifname)
276     ifr = ifname + '\0'*(32-len(ifname))
277
278     try:
279         r = fcntl.ioctl(s.fileno(), SIOCGIFHWADDR,ifr)
280         ret = ':'.join(["{:02x}".format(ord(n)) for n in r[18:24]])
281     except IOError as e:
282         ret = None
283         
284     return ret
285
286 def check_file_hash(filename, hash_filename):
287     """Check the file's integrity with a given hash."""
288     return sha1_file(filename) == open(hash_filename).read().split()[0].strip()
289
290 def sha1_file(filename):
291     """Calculate sha1 hash of file."""
292     try:
293         try:
294             import hashlib
295             m = hashlib.sha1()
296         except:
297             import sha
298             m = sha.new()
299         f = file(filename, 'rb')
300         while True:
301             # 256 KB seems ideal for speed/memory tradeoff
302             # It wont get much faster with bigger blocks, but
303             # heap peak grows
304             block = f.read(256 * 1024)
305             if len(block) == 0:
306                 # end of file
307                 break
308             m.update(block)
309             # Simple trick to keep total heap even lower
310             # Delete the previous block, so while next one is read
311             # we wont have two allocated blocks with same size
312             del block
313         return m.hexdigest()
314     except IOError:
315         raise BootManagerException("Cannot calculate SHA1 hash of {}".format(filename))
316
317 def display_disks_status(PARTITIONS, message, log):
318     log.write("{} - PARTITIONS status - BEG\n".format(message))
319     sysexec_noerr("vgdisplay", log)
320     sysexec_noerr("pvdisplay", log)
321     for name, path in PARTITIONS.items():
322         log.write("PARTITIONS[{}]={}\n".format(name,path))
323         sysexec_noerr("ls -l {}".format(path), log)
324     log.write("{} - PARTITIONS status - END\n".format(message))