1 |
# -*- coding: utf-8 -*- |
2 |
# |
3 |
# Copyright © 2008 Red Hat, Inc. All rights reserved. |
4 |
# Copyright © 2009 Mandriva. All rights reserved. |
5 |
# |
6 |
# This copyrighted material is made available to anyone wishing to use, modify, |
7 |
# copy, or redistribute it subject to the terms and conditions of the GNU |
8 |
# General Public License v.2. This program is distributed in the hope that it |
9 |
# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the |
10 |
# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
11 |
# See the GNU General Public License for more details. You should have |
12 |
# received a copy of the GNU General Public License along with this program; if |
13 |
# not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth |
14 |
# Floor, Boston, MA 02110-1301, USA. Any Red Hat trademarks that are |
15 |
# incorporated in the source code or documentation are not subject to the GNU |
16 |
# General Public License and may only be used or replicated with the express |
17 |
# permission of Red Hat, Inc. |
18 |
# |
19 |
# Author(s): Luke Macken <lmacken@redhat.com> |
20 |
# Aurelien Lefebvre <alefebvre@mandriva.com> |
21 |
# Luis Medinas <luis.medinas@caixamagica.pt> |
22 |
|
23 |
import subprocess |
24 |
import logging |
25 |
import os |
26 |
import re |
27 |
import signal |
28 |
|
29 |
from datetime import datetime |
30 |
from stat import ST_SIZE |
31 |
from threading import Thread |
32 |
from time import sleep |
33 |
|
34 |
from liveusb import _ |
35 |
|
36 |
class LiveUSBError(Exception): |
37 |
_message = "" |
38 |
def __init__(self, message): self._message = message |
39 |
def _get_message(self): return self._message |
40 |
def _set_message(self, message): self._message = message |
41 |
message = property(_get_message, _set_message) |
42 |
|
43 |
class LiveUSBCreator(object): |
44 |
""" An OS-independent parent class for Live USB Creators """ |
45 |
|
46 |
iso = None |
47 |
drives = {} |
48 |
pids = [] |
49 |
isosize = 0 |
50 |
log = None |
51 |
drive = None |
52 |
|
53 |
def __init__(self, opts): |
54 |
self.opts = opts |
55 |
self._setup_logger() |
56 |
|
57 |
def _setup_logger(self): |
58 |
self.log = logging.getLogger(__name__) |
59 |
level = logging.INFO |
60 |
if self.opts.verbose: |
61 |
level = logging.DEBUG |
62 |
self.log.setLevel(level) |
63 |
self.handler = logging.StreamHandler() |
64 |
self.handler.setLevel(level) |
65 |
formatter = logging.Formatter("[%(module)s:%(lineno)s] %(message)s") |
66 |
self.handler.setFormatter(formatter) |
67 |
self.log.addHandler(self.handler) |
68 |
|
69 |
def detect_removable_drives(self): |
70 |
""" This method should populate self.drives with removable devices """ |
71 |
raise NotImplementedError |
72 |
|
73 |
def terminate(self): |
74 |
""" Terminate any subprocesses that we have spawned """ |
75 |
raise NotImplementedError |
76 |
|
77 |
def set_iso(self, iso): |
78 |
""" Select the given ISO """ |
79 |
self.iso = self._to_unicode(iso) |
80 |
self.isosize = os.stat(self.iso)[ST_SIZE] |
81 |
|
82 |
def _to_unicode(self, obj, encoding='utf-8'): |
83 |
if hasattr(obj, 'toUtf8'): # PyQt4.QtCore.QString |
84 |
obj = str(obj.toUtf8()) |
85 |
#if isinstance(obj, basestring): |
86 |
# if not isinstance(obj, unicode): |
87 |
# obj = unicode(obj, encoding, 'replace') |
88 |
return obj |
89 |
|
90 |
def _test_hybrid_1(self, iso): |
91 |
hybrid = False |
92 |
for i in range(0,512): |
93 |
if not iso.read(1) == '\x00': |
94 |
hybrid = True |
95 |
if hybrid: |
96 |
break |
97 |
return hybrid |
98 |
|
99 |
def _test_hybrid_2(self, iso): |
100 |
hybrid = False |
101 |
iso.seek(0x1fe) |
102 |
if iso.read(1) == '\x55': |
103 |
iso.seek(0x1ff) |
104 |
if iso.read(1) == '\xaa': |
105 |
hybrid = True |
106 |
return hybrid |
107 |
|
108 |
def _test_hybrid_3(self, iso): |
109 |
hybrid = True |
110 |
iso.seek(0x200) |
111 |
for i in range(0x200,0x8000): |
112 |
if iso.read(1) != '\x00': |
113 |
hybrid = False |
114 |
break |
115 |
return hybrid |
116 |
|
117 |
def is_hybrid(self, iso): |
118 |
isofile = open(iso, "rb") |
119 |
hybrid = self._test_hybrid_1(isofile) and self._test_hybrid_2(isofile) and self._test_hybrid_3(isofile) |
120 |
isofile.close() |
121 |
return hybrid |
122 |
|
123 |
class LinuxLiveUSBCreator(LiveUSBCreator): |
124 |
|
125 |
bus = None # the dbus.SystemBus |
126 |
hal = None # the org.freedesktop.Hal.Manager dbus.Interface |
127 |
|
128 |
def detect_removable_drives(self): |
129 |
""" Detect all removable USB storage devices using HAL via D-Bus """ |
130 |
import dbus |
131 |
self.drives = {} |
132 |
self.bus = dbus.SystemBus() |
133 |
hal_obj = self.bus.get_object("org.freedesktop.Hal", |
134 |
"/org/freedesktop/Hal/Manager") |
135 |
self.hal = dbus.Interface(hal_obj, "org.freedesktop.Hal.Manager") |
136 |
|
137 |
devices = [] |
138 |
devices = self.hal.FindDeviceByCapability("storage") |
139 |
|
140 |
for device in devices: |
141 |
try: |
142 |
dev = self._get_device(device) |
143 |
if dev.GetProperty("storage.bus") == "usb": |
144 |
self._add_device(dev) |
145 |
except dbus.exceptions.DBusException: |
146 |
pass |
147 |
|
148 |
if not len(self.drives): |
149 |
raise LiveUSBError(_("Unable to find any USB drives")) |
150 |
|
151 |
def _add_device(self, dev, parent=None): |
152 |
model = None |
153 |
capacity = None |
154 |
try: |
155 |
model = dev.GetProperty('storage.model') |
156 |
capacity = str(dev.GetProperty('storage.removable.media_size')) |
157 |
except Exception: |
158 |
pass |
159 |
|
160 |
self.drives[dev.GetProperty('block.device')] = { |
161 |
'name' : dev.GetProperty('block.device'), |
162 |
'model' : model, |
163 |
'capacity' : capacity |
164 |
} |
165 |
|
166 |
def _get_device(self, udi): |
167 |
""" Return a dbus Interface to a specific HAL device UDI """ |
168 |
import dbus |
169 |
dev_obj = self.bus.get_object("org.freedesktop.Hal", udi) |
170 |
return dbus.Interface(dev_obj, "org.freedesktop.Hal.Device") |
171 |
|
172 |
def build_disk(self, progress_thread): |
173 |
|
174 |
isosize = float(self.isosize) |
175 |
pattern = "^([0-9]+) bytes .*" |
176 |
patternCompiled = re.compile(pattern) |
177 |
|
178 |
pidPattern = "^DDPID=([0-9]+).*" |
179 |
pidPatternCompiled = re.compile(pidPattern) |
180 |
|
181 |
drive_infos = self.drives[self.drive] |
182 |
if drive_infos.has_key("capacity") and drive_infos["capacity"]: |
183 |
self.log.debug("Iso size = %s" % str(isosize)) |
184 |
self.log.debug("Device capacity = %s " % str(drive_infos['capacity'])) |
185 |
if int(drive_infos['capacity']) < int(isosize): |
186 |
raise LiveUSBError(_("Selected iso is too large for the selected device")) |
187 |
|
188 |
dd = '/usr/sbin/liveusb-dd.sh "' + self.iso + '" "' + self.drive + '"' |
189 |
self.log.debug(dd) |
190 |
p = subprocess.Popen(dd, |
191 |
shell=True, |
192 |
stdout=subprocess.PIPE, |
193 |
stderr=subprocess.STDOUT, |
194 |
env={"LC_ALL": "C"}) |
195 |
|
196 |
ppid = p.pid |
197 |
ddpid = None |
198 |
|
199 |
self.pids.append(ppid) |
200 |
|
201 |
# Updating progress bar by parsing /bin/dd output |
202 |
while True: |
203 |
line = p.stdout.readline() |
204 |
if not line: |
205 |
break |
206 |
|
207 |
if not ddpid: |
208 |
pidm = re.findall(pidPatternCompiled, line) |
209 |
if len(pidm) > 0: |
210 |
ddpid = int(pidm[0]) |
211 |
self.pids.append(ddpid) |
212 |
|
213 |
m = re.findall(patternCompiled, line) |
214 |
if len(m) > 0: |
215 |
current = float(m[0]) |
216 |
progress = (current/isosize)*100 |
217 |
progress = round(progress, 2) |
218 |
progress_thread.update(progress) |
219 |
|
220 |
self.pids.remove(ppid) |
221 |
self.pids.remove(ddpid) |
222 |
|
223 |
def terminate(self): |
224 |
for pid in self.pids: |
225 |
try: |
226 |
os.kill(pid, signal.SIGHUP) |
227 |
self.log.debug("Killed process %d" % pid) |
228 |
except OSError: |
229 |
pass |
230 |
|
231 |
class WindowsLiveUSBCreator(LiveUSBCreator): |
232 |
|
233 |
def detect_removable_drives(self): |
234 |
import win32file, win32api, pywintypes, wmi |
235 |
self.drives = {} |
236 |
|
237 |
c = wmi.WMI() |
238 |
|
239 |
for physical_disk in c.Win32_DiskDrive (): |
240 |
if physical_disk.InterfaceType == "USB": |
241 |
if len(physical_disk.Capabilities): |
242 |
for objElem in physical_disk.Capabilities: |
243 |
if objElem == 7: |
244 |
self.drives[physical_disk.DeviceID] = { |
245 |
'name' : physical_disk.DeviceID, |
246 |
'model' : physical_disk.Model, |
247 |
'device' : physical_disk.DeviceID, |
248 |
'capacity': physical_disk.Size |
249 |
} |
250 |
break |
251 |
if not len(self.drives): |
252 |
raise LiveUSBError(_("There isn't any removable drive available")) |
253 |
|
254 |
def _get_device(self, drive): |
255 |
if not self.drives[drive]: |
256 |
return None |
257 |
return self.drives[drive]['device'] |
258 |
|
259 |
def build_disk(self, progress_thread): |
260 |
|
261 |
isosize = float(self.isosize) |
262 |
pattern = "^([0-9,]+).*" |
263 |
patternCompiled = re.compile(pattern) |
264 |
|
265 |
# f = subprocess.Popen(['format', self.drive, '/q', '/y', '/fs:fat32'], shell=True) |
266 |
# self.log.debug("Formating %s" % self.drive) |
267 |
# f.wait() |
268 |
# self.log.debug("Done, exit code: %d" % f.wait()) |
269 |
|
270 |
dd = os.path.join("tools", "dd.exe") |
271 |
dd += " bs=8M" + " if=\"%s\" of=%s --progress" % (self.iso, self._get_device(self.drive)) |
272 |
self.log.debug(dd) |
273 |
p = subprocess.Popen(dd, |
274 |
shell=True, |
275 |
stdout=subprocess.PIPE, |
276 |
stderr=subprocess.STDOUT, |
277 |
universal_newlines=True) |
278 |
|
279 |
ppid = p.pid |
280 |
|
281 |
self.pids.append(ppid) |
282 |
|
283 |
# Updating progress bar by parsing /bin/dd output |
284 |
while True: |
285 |
line = p.stdout.readline() |
286 |
if not line: |
287 |
break |
288 |
|
289 |
m = re.findall(patternCompiled, line) |
290 |
if len(m) > 0: |
291 |
current = float(re.sub(",", "", m[0])) |
292 |
progress = (current/isosize)*100 |
293 |
progress = round(progress, 2) |
294 |
progress_thread.update(progress) |
295 |
|
296 |
self.pids.remove(ppid) |
297 |
|
298 |
def terminate(self): |
299 |
""" Terminate any subprocesses that we have spawned """ |
300 |
import win32api, win32con, pywintypes |
301 |
for pid in self.pids: |
302 |
try: |
303 |
handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE, |
304 |
False, pid) |
305 |
self.log.debug("Terminating process %s" % pid) |
306 |
win32api.TerminateProcess(handle, -2) |
307 |
win32api.CloseHandle(handle) |
308 |
except pywintypes.error: |
309 |
pass |