1 |
charliebrady |
1.1 |
# __init__.py |
2 |
|
|
# Entry point for anaconda storage formats subpackage. |
3 |
|
|
# |
4 |
|
|
# Copyright (C) 2009 Red Hat, Inc. |
5 |
|
|
# |
6 |
|
|
# This copyrighted material is made available to anyone wishing to use, |
7 |
|
|
# modify, copy, or redistribute it subject to the terms and conditions of |
8 |
|
|
# the GNU General Public License v.2, or (at your option) any later version. |
9 |
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT |
10 |
|
|
# ANY WARRANTY expressed or implied, including the implied warranties of |
11 |
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
12 |
|
|
# Public License for more details. You should have received a copy of the |
13 |
|
|
# GNU General Public License along with this program; if not, write to the |
14 |
|
|
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 |
|
|
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the |
16 |
|
|
# source code or documentation are not subject to the GNU General Public |
17 |
|
|
# License and may only be used or replicated with the express permission of |
18 |
|
|
# Red Hat, Inc. |
19 |
|
|
# |
20 |
|
|
# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> |
21 |
|
|
# |
22 |
|
|
|
23 |
|
|
import os |
24 |
|
|
|
25 |
|
|
from iutil import notify_kernel, get_sysfs_path_by_name |
26 |
|
|
from baseudev import udev_get_device |
27 |
|
|
from ..storage_log import log_method_call |
28 |
|
|
from ..errors import * |
29 |
|
|
from ..devicelibs.dm import dm_node_from_name |
30 |
|
|
from ..udev import udev_device_get_major, udev_device_get_minor |
31 |
|
|
|
32 |
|
|
import gettext |
33 |
|
|
_ = lambda x: gettext.ldgettext("anaconda", x) |
34 |
|
|
|
35 |
|
|
import logging |
36 |
|
|
log = logging.getLogger("storage") |
37 |
|
|
|
38 |
|
|
|
39 |
|
|
device_formats = {} |
40 |
|
|
def register_device_format(fmt_class): |
41 |
|
|
if not issubclass(fmt_class, DeviceFormat): |
42 |
|
|
raise ValueError("arg1 must be a subclass of DeviceFormat") |
43 |
|
|
|
44 |
|
|
device_formats[fmt_class._type] = fmt_class |
45 |
|
|
log.debug("registered device format class %s as %s" % (fmt_class.__name__, |
46 |
|
|
fmt_class._type)) |
47 |
|
|
|
48 |
|
|
default_fstypes = ("ext4", "ext3", "ext2") |
49 |
|
|
def get_default_filesystem_type(boot=None): |
50 |
|
|
import platform |
51 |
|
|
|
52 |
|
|
if boot: |
53 |
|
|
fstypes = [platform.getPlatform(None).defaultBootFSType] |
54 |
|
|
else: |
55 |
|
|
fstypes = default_fstypes |
56 |
|
|
|
57 |
|
|
for fstype in fstypes: |
58 |
|
|
try: |
59 |
|
|
supported = get_device_format_class(fstype).supported |
60 |
|
|
except AttributeError: |
61 |
|
|
supported = None |
62 |
|
|
|
63 |
|
|
if supported: |
64 |
|
|
return fstype |
65 |
|
|
|
66 |
|
|
raise DeviceFormatError("None of %s is supported by your kernel" % ",".join(fstypes)) |
67 |
|
|
|
68 |
|
|
def getFormat(fmt_type, *args, **kwargs): |
69 |
|
|
""" Return a DeviceFormat instance based on fmt_type and args. |
70 |
|
|
|
71 |
|
|
Given a device format type and a set of constructor arguments, |
72 |
|
|
return a DeviceFormat instance. |
73 |
|
|
|
74 |
|
|
Return None if no suitable format class is found. |
75 |
|
|
|
76 |
|
|
Arguments: |
77 |
|
|
|
78 |
|
|
fmt_type -- the name of the format type (eg: 'ext3', 'swap') |
79 |
|
|
|
80 |
|
|
Keyword Arguments: |
81 |
|
|
|
82 |
|
|
The keyword arguments may vary according to the format type, |
83 |
|
|
but here is the common set: |
84 |
|
|
|
85 |
|
|
device -- path to the device on which the format resides |
86 |
|
|
uuid -- the UUID of the (preexisting) formatted device |
87 |
|
|
exists -- whether or not the format exists on the device |
88 |
|
|
|
89 |
|
|
""" |
90 |
|
|
fmt_class = get_device_format_class(fmt_type) |
91 |
|
|
fmt = None |
92 |
|
|
if fmt_class: |
93 |
|
|
fmt = fmt_class(*args, **kwargs) |
94 |
|
|
try: |
95 |
|
|
className = fmt.__class__.__name__ |
96 |
|
|
except AttributeError: |
97 |
|
|
className = None |
98 |
|
|
log.debug("getFormat('%s') returning %s instance" % (fmt_type, className)) |
99 |
|
|
return fmt |
100 |
|
|
|
101 |
|
|
def collect_device_format_classes(): |
102 |
|
|
""" Pick up all device format classes from this directory. |
103 |
|
|
|
104 |
|
|
Note: Modules must call register_device_format(FormatClass) in |
105 |
|
|
order for the format class to be picked up. |
106 |
|
|
""" |
107 |
|
|
dir = os.path.dirname(__file__) |
108 |
|
|
for module_file in os.listdir(dir): |
109 |
|
|
# make sure we're not importing this module |
110 |
|
|
if module_file.endswith(".py") and module_file != __file__: |
111 |
|
|
mod_name = module_file[:-3] |
112 |
|
|
# imputil is deprecated in python 2.6 |
113 |
|
|
try: |
114 |
|
|
globals()[mod_name] = __import__(mod_name, globals(), locals(), [], -1) |
115 |
|
|
except ImportError, e: |
116 |
|
|
log.debug("import of device format module '%s' failed" % mod_name) |
117 |
|
|
|
118 |
|
|
def get_device_format_class(fmt_type): |
119 |
|
|
""" Return an appropriate format class based on fmt_type. """ |
120 |
|
|
if not device_formats: |
121 |
|
|
collect_device_format_classes() |
122 |
|
|
|
123 |
|
|
fmt = device_formats.get(fmt_type) |
124 |
|
|
if not fmt: |
125 |
|
|
for fmt_class in device_formats.values(): |
126 |
|
|
if fmt_type and fmt_type == fmt_class._name: |
127 |
|
|
fmt = fmt_class |
128 |
|
|
break |
129 |
|
|
elif fmt_type in fmt_class._udevTypes: |
130 |
|
|
fmt = fmt_class |
131 |
|
|
break |
132 |
|
|
|
133 |
|
|
# default to no formatting, AKA "Unknown" |
134 |
|
|
if not fmt: |
135 |
|
|
fmt = DeviceFormat |
136 |
|
|
|
137 |
|
|
return fmt |
138 |
|
|
|
139 |
|
|
class DeviceFormat(object): |
140 |
|
|
""" Generic device format. """ |
141 |
|
|
_type = None |
142 |
|
|
_name = "Unknown" |
143 |
|
|
_udevTypes = [] |
144 |
|
|
partedFlag = None |
145 |
|
|
partedSystem = None |
146 |
|
|
_formattable = False # can be formatted |
147 |
|
|
_supported = False # is supported |
148 |
|
|
_linuxNative = False # for clearpart |
149 |
|
|
_packages = [] # required packages |
150 |
|
|
_services = [] # required services |
151 |
|
|
_resizable = False # can be resized |
152 |
|
|
_bootable = False # can be used as boot |
153 |
|
|
_migratable = False # can be migrated |
154 |
|
|
_maxSize = 0 # maximum size in MB |
155 |
|
|
_minSize = 0 # minimum size in MB |
156 |
|
|
_dump = False |
157 |
|
|
_check = False |
158 |
|
|
_hidden = False # hide devices with this formatting? |
159 |
|
|
|
160 |
|
|
def __init__(self, *args, **kwargs): |
161 |
|
|
""" Create a DeviceFormat instance. |
162 |
|
|
|
163 |
|
|
Keyword Arguments: |
164 |
|
|
|
165 |
|
|
device -- path to the underlying device |
166 |
|
|
uuid -- this format's UUID |
167 |
|
|
exists -- indicates whether this is an existing format |
168 |
|
|
|
169 |
|
|
""" |
170 |
|
|
self.device = kwargs.get("device") |
171 |
|
|
self.uuid = kwargs.get("uuid") |
172 |
|
|
self.exists = kwargs.get("exists") |
173 |
|
|
self.options = kwargs.get("options") |
174 |
|
|
self._majorminor = None |
175 |
|
|
self._migrate = False |
176 |
|
|
|
177 |
|
|
# don't worry about existence if this is a DeviceFormat instance |
178 |
|
|
#if self.__class__ is DeviceFormat: |
179 |
|
|
# self.exists = True |
180 |
|
|
|
181 |
|
|
def __str__(self): |
182 |
|
|
s = ("%(classname)s instance (%(id)s) --\n" |
183 |
|
|
" type = %(type)s name = %(name)s status = %(status)s\n" |
184 |
|
|
" device = %(device)s uuid = %(uuid)s exists = %(exists)s\n" |
185 |
|
|
" options = %(options)s supported = %(supported)s" |
186 |
|
|
" formattable = %(format)s resizable = %(resize)s\n" % |
187 |
|
|
{"classname": self.__class__.__name__, "id": "%#x" % id(self), |
188 |
|
|
"type": self.type, "name": self.name, "status": self.status, |
189 |
|
|
"device": self.device, "uuid": self.uuid, "exists": self.exists, |
190 |
|
|
"options": self.options, "supported": self.supported, |
191 |
|
|
"format": self.formattable, "resize": self.resizable}) |
192 |
|
|
return s |
193 |
|
|
|
194 |
|
|
@property |
195 |
|
|
def dict(self): |
196 |
|
|
d = {"type": self.type, "name": self.name, "device": self.device, |
197 |
|
|
"uuid": self.uuid, "exists": self.exists, |
198 |
|
|
"options": self.options, "supported": self.supported, |
199 |
|
|
"resizable": self.resizable} |
200 |
|
|
return d |
201 |
|
|
|
202 |
|
|
def _setOptions(self, options): |
203 |
|
|
self._options = options |
204 |
|
|
|
205 |
|
|
def _getOptions(self): |
206 |
|
|
return self._options |
207 |
|
|
|
208 |
|
|
options = property(_getOptions, _setOptions) |
209 |
|
|
|
210 |
|
|
def _setDevice(self, devspec): |
211 |
|
|
if devspec and not devspec.startswith("/"): |
212 |
|
|
raise ValueError("device must be a fully qualified path") |
213 |
|
|
self._device = devspec |
214 |
|
|
|
215 |
|
|
def _getDevice(self): |
216 |
|
|
return self._device |
217 |
|
|
|
218 |
|
|
device = property(lambda f: f._getDevice(), |
219 |
|
|
lambda f,d: f._setDevice(d), |
220 |
|
|
doc="Full path the device this format occupies") |
221 |
|
|
|
222 |
|
|
@property |
223 |
|
|
def name(self): |
224 |
|
|
if self._name: |
225 |
|
|
name = self._name |
226 |
|
|
else: |
227 |
|
|
name = self.type |
228 |
|
|
return name |
229 |
|
|
|
230 |
|
|
@property |
231 |
|
|
def type(self): |
232 |
|
|
return self._type |
233 |
|
|
|
234 |
|
|
def probe(self): |
235 |
|
|
log_method_call(self, device=self.device, |
236 |
|
|
type=self.type, status=self.status) |
237 |
|
|
|
238 |
|
|
def notifyKernel(self): |
239 |
|
|
log_method_call(self, device=self.device, |
240 |
|
|
type=self.type) |
241 |
|
|
if not self.device: |
242 |
|
|
return |
243 |
|
|
|
244 |
|
|
if self.device.startswith("/dev/mapper/"): |
245 |
|
|
try: |
246 |
|
|
name = dm_node_from_name(os.path.basename(self.device)) |
247 |
|
|
except Exception, e: |
248 |
|
|
log.warning("failed to get dm node for %s" % self.device) |
249 |
|
|
return |
250 |
|
|
else: |
251 |
|
|
name = self.device |
252 |
|
|
|
253 |
|
|
path = get_sysfs_path_by_name(name) |
254 |
|
|
try: |
255 |
|
|
notify_kernel(path, action="change") |
256 |
|
|
except Exception, e: |
257 |
|
|
log.warning("failed to notify kernel of change: %s" % e) |
258 |
|
|
|
259 |
|
|
def cacheMajorminor(self): |
260 |
|
|
""" Cache the value of self.majorminor. |
261 |
|
|
|
262 |
|
|
Once a device node of this format's device disappears (for instance |
263 |
|
|
after a teardown), it is no longer possible to figure out the value |
264 |
|
|
of self.majorminor pseudo-unique string. Call this method before |
265 |
|
|
that happens for caching this. |
266 |
|
|
""" |
267 |
|
|
self._majorminor = None |
268 |
|
|
try: |
269 |
|
|
self.majorminor # this does the caching |
270 |
|
|
except StorageError: |
271 |
|
|
# entirely possible there's no majorminor, for instance an |
272 |
|
|
# LVMVolumeGroup has got no device node and no sysfs path. In this |
273 |
|
|
# case obviously, calling majorminor of this object later raises an |
274 |
|
|
# exception. |
275 |
|
|
pass |
276 |
|
|
return self._majorminor |
277 |
|
|
|
278 |
|
|
def create(self, *args, **kwargs): |
279 |
|
|
log_method_call(self, device=self.device, |
280 |
|
|
type=self.type, status=self.status) |
281 |
|
|
# allow late specification of device path |
282 |
|
|
device = kwargs.get("device") |
283 |
|
|
if device: |
284 |
|
|
self.device = device |
285 |
|
|
|
286 |
|
|
if not os.path.exists(self.device): |
287 |
|
|
raise FormatCreateError("invalid device specification", self.device) |
288 |
|
|
|
289 |
|
|
def destroy(self, *args, **kwargs): |
290 |
|
|
log_method_call(self, device=self.device, |
291 |
|
|
type=self.type, status=self.status) |
292 |
|
|
# zero out the 1MB at the beginning and end of the device in the |
293 |
|
|
# hope that it will wipe any metadata from filesystems that |
294 |
|
|
# previously occupied this device |
295 |
|
|
log.debug("zeroing out beginning and end of %s..." % self.device) |
296 |
|
|
fd = None |
297 |
|
|
|
298 |
|
|
try: |
299 |
|
|
fd = os.open(self.device, os.O_RDWR) |
300 |
|
|
buf = '\0' * 1024 * 1024 |
301 |
|
|
os.write(fd, buf) |
302 |
|
|
os.lseek(fd, -1024 * 1024, 2) |
303 |
|
|
os.write(fd, buf) |
304 |
|
|
os.close(fd) |
305 |
|
|
except OSError as e: |
306 |
|
|
if getattr(e, "errno", None) == 28: # No space left in device |
307 |
|
|
pass |
308 |
|
|
else: |
309 |
|
|
log.error("error zeroing out %s: %s" % (self.device, e)) |
310 |
|
|
|
311 |
|
|
if fd: |
312 |
|
|
os.close(fd) |
313 |
|
|
except Exception as e: |
314 |
|
|
log.error("error zeroing out %s: %s" % (self.device, e)) |
315 |
|
|
if fd: |
316 |
|
|
os.close(fd) |
317 |
|
|
|
318 |
|
|
self.exists = False |
319 |
|
|
|
320 |
|
|
def setup(self, *args, **kwargs): |
321 |
|
|
log_method_call(self, device=self.device, |
322 |
|
|
type=self.type, status=self.status) |
323 |
|
|
|
324 |
|
|
if not self.exists: |
325 |
|
|
raise FormatSetupError("format has not been created") |
326 |
|
|
|
327 |
|
|
if self.status: |
328 |
|
|
return |
329 |
|
|
|
330 |
|
|
# allow late specification of device path |
331 |
|
|
device = kwargs.get("device") |
332 |
|
|
if device: |
333 |
|
|
self.device = device |
334 |
|
|
|
335 |
|
|
if not self.device or not os.path.exists(self.device): |
336 |
|
|
raise FormatSetupError("invalid device specification") |
337 |
|
|
|
338 |
|
|
def teardown(self, *args, **kwargs): |
339 |
|
|
log_method_call(self, device=self.device, |
340 |
|
|
type=self.type, status=self.status) |
341 |
|
|
|
342 |
|
|
@property |
343 |
|
|
def status(self): |
344 |
|
|
return (self.exists and |
345 |
|
|
self.__class__ is not DeviceFormat and |
346 |
|
|
isinstance(self.device, str) and |
347 |
|
|
self.device and |
348 |
|
|
os.path.exists(self.device)) |
349 |
|
|
|
350 |
|
|
@property |
351 |
|
|
def formattable(self): |
352 |
|
|
""" Can we create formats of this type? """ |
353 |
|
|
return self._formattable |
354 |
|
|
|
355 |
|
|
@property |
356 |
|
|
def supported(self): |
357 |
|
|
""" Is this format a supported type? """ |
358 |
|
|
return self._supported |
359 |
|
|
|
360 |
|
|
@property |
361 |
|
|
def packages(self): |
362 |
|
|
""" Packages required to manage formats of this type. """ |
363 |
|
|
return self._packages |
364 |
|
|
|
365 |
|
|
@property |
366 |
|
|
def services(self): |
367 |
|
|
""" Services required to manage formats of this type. """ |
368 |
|
|
return self._services |
369 |
|
|
|
370 |
|
|
@property |
371 |
|
|
def resizable(self): |
372 |
|
|
""" Can formats of this type be resized? """ |
373 |
|
|
return self._resizable and self.exists |
374 |
|
|
|
375 |
|
|
@property |
376 |
|
|
def bootable(self): |
377 |
|
|
""" Is this format type suitable for a boot partition? """ |
378 |
|
|
return self._bootable |
379 |
|
|
|
380 |
|
|
@property |
381 |
|
|
def migratable(self): |
382 |
|
|
""" Can formats of this type be migrated? """ |
383 |
|
|
return self._migratable |
384 |
|
|
|
385 |
|
|
@property |
386 |
|
|
def migrate(self): |
387 |
|
|
return self._migrate |
388 |
|
|
|
389 |
|
|
@property |
390 |
|
|
def linuxNative(self): |
391 |
|
|
""" Is this format type native to linux? """ |
392 |
|
|
return self._linuxNative |
393 |
|
|
|
394 |
|
|
@property |
395 |
|
|
def mountable(self): |
396 |
|
|
""" Is this something we can mount? """ |
397 |
|
|
return False |
398 |
|
|
|
399 |
|
|
@property |
400 |
|
|
def dump(self): |
401 |
|
|
""" Whether or not this format will be dumped by dump(8). """ |
402 |
|
|
return self._dump |
403 |
|
|
|
404 |
|
|
@property |
405 |
|
|
def check(self): |
406 |
|
|
""" Whether or not this format is checked on boot. """ |
407 |
|
|
return self._check |
408 |
|
|
|
409 |
|
|
@property |
410 |
|
|
def maxSize(self): |
411 |
|
|
""" Maximum size (in MB) for this format type. """ |
412 |
|
|
return self._maxSize |
413 |
|
|
|
414 |
|
|
@property |
415 |
|
|
def minSize(self): |
416 |
|
|
""" Minimum size (in MB) for this format type. """ |
417 |
|
|
return self._minSize |
418 |
|
|
|
419 |
|
|
@property |
420 |
|
|
def hidden(self): |
421 |
|
|
""" Whether devices with this formatting should be hidden in UIs. """ |
422 |
|
|
return self._hidden |
423 |
|
|
|
424 |
|
|
@property |
425 |
|
|
def majorminor(self): |
426 |
|
|
"""A string suitable for using as a pseudo-unique ID in kickstart.""" |
427 |
|
|
if not self._majorminor: |
428 |
|
|
# If this is a device-mapper device, we have to get the DM node and |
429 |
|
|
# build the sysfs path from that. |
430 |
|
|
try: |
431 |
|
|
device = dm_node_from_name(self.device) |
432 |
|
|
except DMError: |
433 |
|
|
device = self.device |
434 |
|
|
|
435 |
|
|
try: |
436 |
|
|
sysfs_path = get_sysfs_path_by_name(device) |
437 |
|
|
except RuntimeError: |
438 |
|
|
raise StorageError("DeviceFormat.majorminor: " |
439 |
|
|
"can not get majorminor for '%s'" % device) |
440 |
|
|
|
441 |
|
|
dev = udev_get_device(sysfs_path[4:]) |
442 |
|
|
self._majorminor = "%03d%03d" %\ |
443 |
|
|
(udev_device_get_major(dev), udev_device_get_minor(dev)) |
444 |
|
|
return self._majorminor |
445 |
|
|
|
446 |
|
|
def writeKS(self, f): |
447 |
|
|
return |
448 |
|
|
|
449 |
|
|
collect_device_format_classes() |