]> git.ipfire.org Git - ipfire-3.x.git/blob - src/pomona/storage/formats/__init__.py
Daily checkin.
[ipfire-3.x.git] / src / pomona / storage / formats / __init__.py
1 #!/usr/bin/python
2
3 import os
4 import copy
5
6 device_formats = {}
7 def register_device_format(fmt_class):
8 if not issubclass(fmt_class, DeviceFormat):
9 raise ValueError("Argument must be a subclass of DeviceFormat")
10 device_formats[fmt_class._type] = fmt_class
11
12 def getFormat(fmt_type, *args, **kwargs):
13 """ Return a DeviceFormat instance based on fmt_type and args.
14
15 Given a device format type and a set of constructor arguments,
16 return a DeviceFormat instance.
17
18 Return None if no suitable format class is found.
19
20 Arguments:
21
22 fmt_type -- the name of the format type (eg: 'ext3', 'swap')
23
24 Keyword Arguments:
25
26 The keyword arguments may vary according to the format type,
27 but here is the common set:
28
29 device -- path to the device on which the format resides
30 uuid -- the UUID of the (preexisting) formatted device
31 exists -- whether or not the format exists on the device
32
33 """
34
35 #installer = kwargs["installer"]
36
37 fmt_class = get_device_format_class(fmt_type)
38 fmt = None
39 if fmt_class:
40 fmt = fmt_class(*args, **kwargs)
41 try:
42 className = fmt.__class__.__name__
43 except AttributeError:
44 className = None
45 #installer.log.debug("getFormat('%s') returning %s instance" % (fmt_type, className))
46 return fmt
47
48 def get_device_format_class(fmt_type):
49 """ Return an appropriate format class based on fmt_type. """
50 if not device_formats:
51 collect_device_format_classes()
52
53 fmt = device_formats.get(fmt_type)
54 if not fmt:
55 for fmt_class in device_formats.values():
56 if fmt_type and fmt_type == fmt_class._name:
57 fmt = fmt_class
58 break
59 elif fmt_type in fmt_class._udevTypes:
60 fmt = fmt_class
61 break
62
63 # default to no formatting, AKA "Unknown"
64 if not fmt:
65 fmt = DeviceFormat
66 return fmt
67
68 def collect_device_format_classes():
69 """ Pick up all device format classes from this directory.
70
71 Note: Modules must call register_device_format(FormatClass) in
72 order for the format class to be picked up.
73 """
74 dir = os.path.dirname(__file__)
75 for module_file in os.listdir(dir):
76 # make sure we're not importing this module
77 if module_file.endswith(".py") and module_file != __file__:
78 mod_name = module_file[:-3]
79 try:
80 globals()[mod_name] = __import__(mod_name, globals(), locals(), [], -1)
81 except ImportError, e:
82 pass
83
84 default_fstypes = ("ext4", "ext3", "ext2")
85 default_boot_fstypes = ("ext3", "ext2")
86 def get_default_filesystem_type(boot=None):
87 if boot:
88 fstypes = default_boot_fstypes
89 else:
90 fstypes = default_fstypes
91
92 for fstype in fstypes:
93 try:
94 supported = get_device_format_class(fstype).supported
95 except AttributeError:
96 supported = None
97
98 if supported:
99 return fstype
100
101 raise DeviceFormatError("None of %s is supported by your kernel" % ",".join(fstypes))
102
103 class DeviceFormat(object):
104 """ Generic device format. """
105 _type = None
106 _name = "Unknown"
107 _udevTypes = []
108 partedFlag = None
109 _formattable = False # can be formatted
110 _supported = False # is supported
111 _resizable = False # can be resized
112 _bootable = False # can be used as boot
113 _migratable = False # can be migrated
114 _maxSize = 0 # maximum size in MB
115 _minSize = 0 # minimum size in MB
116 _dump = False
117 _check = False
118
119 def __init__(self, installer, *args, **kwargs):
120 """ Create a DeviceFormat instance.
121
122 Keyword Arguments:
123
124 device -- path to the underlying device
125 uuid -- this format's UUID
126 exists -- indicates whether this is an existing format
127
128 """
129 self.installer = installer
130
131 self.device = kwargs.get("device")
132 self.uuid = kwargs.get("uuid")
133 self.exists = kwargs.get("exists")
134 self.options = kwargs.get("options")
135 self._migrate = False
136
137 def __deepcopy__(self, memo):
138 new = self.__class__.__new__(self.__class__)
139 memo[id(self)] = new
140 shallow_copy_attrs = ('installer', 'screen')
141 for (attr, value) in self.__dict__.items():
142 if attr in shallow_copy_attrs:
143 setattr(new, attr, copy.copy(value))
144 else:
145 setattr(new, attr, copy.deepcopy(value, memo))
146
147 return new
148
149 def _setOptions(self, options):
150 self._options = options
151
152 def _getOptions(self):
153 return self._options
154
155 options = property(_getOptions, _setOptions)
156
157 def _setDevice(self, devspec):
158 if devspec and not devspec.startswith("/"):
159 raise ValueError("device must be a fully qualified path")
160 self._device = devspec
161
162 def _getDevice(self):
163 return self._device
164
165 device = property(lambda f: f._getDevice(),
166 lambda f,d: f._setDevice(d),
167 doc="Full path the device this format occupies")
168
169 @property
170 def name(self):
171 if self._name:
172 name = self._name
173 else:
174 name = self.type
175 return name
176
177 @property
178 def type(self):
179 return self._type
180
181 def probe(self):
182 pass
183
184 def notifyKernel(self):
185 if not self.device:
186 return
187
188 if self.device.startswith("/dev/mapper/"):
189 try:
190 name = dm_node_from_name(os.path.basename(self.device))
191 except Exception, e:
192 self.installer.log.warning("Failed to get dm node for %s" % self.device)
193 return
194 elif self.device:
195 name = os.path.basename(self.device)
196
197 path = get_sysfs_path_by_name(name)
198 try:
199 notify_kernel(path, action="change")
200 except Exception, e:
201 self.installer.log.warning("Failed to notify kernel of change: %s" % e)
202
203 def create(self, *args, **kwargs):
204 # allow late specification of device path
205 device = kwargs.get("device")
206 if device:
207 self.device = device
208
209 if not os.path.exists(self.device):
210 raise FormatCreateError("invalid device specification")
211
212 def destroy(self, *args, **kwargs):
213 # zero out the 1MB at the beginning and end of the device in the
214 # hope that it will wipe any metadata from filesystems that
215 # previously occupied this device
216 self.installer.log.debug("Zeroing out beginning and end of %s..." % self.device)
217 try:
218 fd = os.open(self.device, os.O_RDWR)
219 buf = '\0' * 1024 * 1024
220 os.write(fd, buf)
221 os.lseek(fd, -1024 * 1024, 2)
222 os.write(fd, buf)
223 os.close(fd)
224 except OSError as e:
225 if getattr(e, "errno", None) == 28: # No space left in device
226 pass
227 else:
228 self.installer.log.error("Error zeroing out %s: %s" % (self.device, e))
229 os.close(fd)
230 except Exception as e:
231 self.installer.log.error("Error zeroing out %s: %s" % (self.device, e))
232 os.close(fd)
233
234 self.exists = False
235
236 def setup(self, *args, **kwargs):
237 if not self.exists:
238 raise FormatSetupError("format has not been created")
239
240 if self.status:
241 return
242
243 # allow late specification of device path
244 device = kwargs.get("device")
245 if device:
246 self.device = device
247
248 if not self.device or not os.path.exists(self.device):
249 raise FormatSetupError("invalid device specification")
250
251 def teardown(self, *args, **kwargs):
252 pass
253
254 @property
255 def status(self):
256 return (self.exists and
257 self.__class__ is not DeviceFormat and
258 isinstance(self.device, str) and
259 self.device and
260 os.path.exists(self.device))
261
262 @property
263 def formattable(self):
264 """ Can we create formats of this type? """
265 return self._formattable
266
267 @property
268 def supported(self):
269 """ Is this format a supported type? """
270 return self._supported
271
272 @property
273 def resizable(self):
274 """ Can formats of this type be resized? """
275 return self._resizable
276
277 @property
278 def bootable(self):
279 """ Is this format type suitable for a boot partition? """
280 return self._bootable
281
282 @property
283 def migratable(self):
284 """ Can formats of this type be migrated? """
285 return self._migratable
286
287 @property
288 def migrate(self):
289 return self._migrate
290
291 @property
292 def linuxNative(self):
293 """ Is this format type native to linux? """
294 return self._linuxNative
295
296 @property
297 def mountable(self):
298 """ Is this something we can mount? """
299 return False
300
301 @property
302 def dump(self):
303 """ Whether or not this format will be dumped by dump(8). """
304 return self._dump
305
306 @property
307 def check(self):
308 """ Whether or not this format is checked on boot. """
309 return self._check
310
311 @property
312 def maxSize(self):
313 """ Maximum size (in MB) for this format type. """
314 return self._maxSize
315
316 @property
317 def minSize(self):
318 """ Minimum size (in MB) for this format type. """
319 return self._minSize
320
321
322 collect_device_format_classes()