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() |