reguire gnupg1 on f>=31; sense the system to use gpg1 when installed
[bootmanager.git] / source / utils.py
1 #
2 # Copyright (c) 2003 Intel Corporation
3 # All rights reserved.
4 #
5 # Copyright (c) 2004-2006 The Trustees of Princeton University
6 # All rights reserved.
7 # expected /proc/partitions format
8
9 # pylint: disable=c0111
10
11 from __future__ import print_function
12
13 import sys
14 import os
15 import shutil
16 import subprocess
17 import shlex
18 import socket
19 import fcntl
20 # handling breakpoints in the startup process
21 import select
22
23 from Exceptions import *
24
25 ####################
26 # the simplest way to debug is to let the node take off,
27 # ssh into it as root using the debug ssh key in /etc/planetlab
28 # then go to /tmp/source
29 # edit this file locally to turn on breakpoints if needed, then run
30 # ./BootManager.py
31 ####################
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 = sys.stdin.readline().strip()
61         else:
62             sys.stdout.write("\nTimed-out ({}s)".format(PROMPT_TIMEOUT))
63         if answer:
64             BREAKPOINT_MODE = (answer in 'yY')
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:
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 not in (0, 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     return sysexec("chroot {} {}".format(path, cmd), log, shell=shell)
200
201
202 def sysexec_chroot_noerr(path, cmd, log=None, shell=False):
203     """
204     same as sysexec_chroot, but capture boot manager exceptions
205     """
206     try:
207         rc = 0
208         rc = sysexec_chroot(path, cmd, log, shell=shell)
209     except BootManagerException as e:
210         pass
211
212     return rc
213
214
215 def sysexec_noerr(cmd, log=None, shell=False):
216     """
217     same as sysexec, but capture boot manager exceptions
218     """
219     try:
220         rc = 0
221         rc = sysexec(cmd, log, shell=shell)
222     except BootManagerException as e:
223         pass
224
225     return rc
226
227
228
229 def chdir(dir):
230     """
231     change to a directory, return 1 if successful, a BootManagerException if failure
232     """
233     try:
234         os.chdir(dir)
235     except OSError:
236         raise BootManagerException("Unable to change to directory: {}".format(dir))
237
238     return 1
239
240
241
242 def removefile(filepath):
243     """
244     removes a file, return 1 if successful, 0 if failure
245     """
246     try:
247         os.remove(filepath)
248     except OSError:
249         raise BootManagerException("Unable to remove file: {}".format(filepath))
250
251     return 1
252
253
254
255 # from: http://forums.devshed.com/archive/t-51149/
256 #              Ethernet-card-address-Through-Python-or-C
257
258 def get_mac_from_interface(ifname):
259     """
260     given a device name, like eth0, return its mac_address.
261     return None if the device doesn't exist.
262     """
263
264     SIOCGIFHWADDR = 0x8927 # magic number
265
266     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
267     ifname = ifname.strip()
268     ifr = ifname + '\0'*(32-len(ifname))
269
270     try:
271         r = fcntl.ioctl(s.fileno(), SIOCGIFHWADDR,ifr)
272         ret = ':'.join(["{:02x}".format(ord(n)) for n in r[18:24]])
273     except IOError as e:
274         ret = None
275
276     return ret
277
278 def check_file_hash(filename, hash_filename):
279     """Check the file's integrity with a given hash."""
280     return sha1_file(filename) == open(hash_filename).read().split()[0].strip()
281
282 def sha1_file(filename):
283     """Calculate sha1 hash of file."""
284     try:
285         try:
286             import hashlib
287             m = hashlib.sha1()
288         except:
289             import sha
290             m = sha.new()
291         f = file(filename, 'rb')
292         while True:
293             # 256 KB seems ideal for speed/memory tradeoff
294             # It wont get much faster with bigger blocks, but
295             # heap peak grows
296             block = f.read(256 * 1024)
297             if len(block) == 0:
298                 # end of file
299                 break
300             m.update(block)
301             # Simple trick to keep total heap even lower
302             # Delete the previous block, so while next one is read
303             # we wont have two allocated blocks with same size
304             del block
305         return m.hexdigest()
306     except IOError:
307         raise BootManagerException("Cannot calculate SHA1 hash of {}".format(filename))
308
309 def display_disks_status(PARTITIONS, message, log):
310     log.write("{} - PARTITIONS status - BEG\n".format(message))
311     sysexec_noerr("vgdisplay", log)
312     sysexec_noerr("pvdisplay", log)
313     for name, path in PARTITIONS.items():
314         log.write("PARTITIONS[{}]={}\n".format(name,path))
315         sysexec_noerr("ls -l {}".format(path), log)
316     log.write("{} - PARTITIONS status - END\n".format(message))