/[smeserver]/cdrom.image/sme9/updates/storage/formats/fs.py
ViewVC logotype

Contents of /cdrom.image/sme9/updates/storage/formats/fs.py

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.2 - (show annotations) (download) (as text)
Sun Dec 22 04:27:52 2013 UTC (10 years, 6 months ago) by wellsi
Branch: MAIN
CVS Tags: HEAD
Changes since 1.1: +0 -0 lines
Content type: text/x-python
FILE REMOVED
anaconda updates now handled via patches

1 # filesystems.py
2 # Filesystem classes for anaconda's storage configuration module.
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 # David Cantrell <dcantrell@redhat.com>
22 #
23
24 """ Filesystem classes for use by anaconda.
25
26 TODO:
27 - migration
28 - bug 472127: allow creation of tmpfs filesystems (/tmp, /var/tmp, &c)
29 """
30 import math
31 import os
32 import sys
33 import tempfile
34 import selinux
35 import isys
36
37 from ..errors import *
38 from . import DeviceFormat, register_device_format
39 import iutil
40 from flags import flags
41 from parted import fileSystemType
42 from ..storage_log import log_method_call
43
44 import logging
45 log = logging.getLogger("storage")
46
47 import gettext
48 _ = lambda x: gettext.ldgettext("anaconda", x)
49
50 try:
51 lost_and_found_context = selinux.matchpathcon("/lost+found", 0)[1]
52 except OSError:
53 lost_and_found_context = None
54
55 fs_configs = {}
56
57 def get_kernel_filesystems():
58 fs_list = []
59 for line in open("/proc/filesystems").readlines():
60 fs_list.append(line.split()[-1])
61 return fs_list
62
63 global kernel_filesystems
64 kernel_filesystems = get_kernel_filesystems()
65
66 def fsConfigFromFile(config_file):
67 """ Generate a set of attribute name/value pairs with which a
68 filesystem type can be defined.
69
70 The following config file would define a filesystem identical to
71 the static Ext3FS class definition:
72
73 type = ext3
74 mkfs = "mke2fs"
75 resizefs = "resize2fs"
76 labelfs = "e2label"
77 fsck = "e2fsck"
78 packages = ["e2fsprogs"]
79 formattable = True
80 supported = True
81 resizable = True
82 bootable = True
83 linuxNative = True
84 maxSize = 8 * 1024 * 1024
85 minSize = 0
86 defaultFormatOptions = "-t ext3"
87 defaultMountOptions = "defaults"
88
89 """
90 # XXX NOTUSED
91 lines = open(config_file).readlines()
92 fs_attrs = {}
93 for line in lines:
94 (key, value) = [t.strip() for t in line.split("=")]
95 if not hasattr(FS, "_" + key):
96 print "invalid key: %s" % key
97 continue
98
99 fs_attrs[key] = value
100
101 if not fs_attrs.has_key("type"):
102 raise ValueError, _("filesystem configuration missing a type")
103
104 # XXX what's the policy about multiple configs for a given type?
105 fs_configs[fs_attrs['type']] = fs_attrs
106
107 class FS(DeviceFormat):
108 """ Filesystem class. """
109 _type = "Abstract Filesystem Class" # fs type name
110 _mountType = None # like _type but for passing to mount
111 _name = None
112 _mkfs = "" # mkfs utility
113 _modules = [] # kernel modules required for support
114 _resizefs = "" # resize utility
115 _labelfs = "" # labeling utility
116 _fsck = "" # fs check utility
117 _fsckErrors = {} # fs check command error codes & msgs
118 _migratefs = "" # fs migration utility
119 _infofs = "" # fs info utility
120 _defaultFormatOptions = [] # default options passed to mkfs
121 _defaultMountOptions = ["defaults"] # default options passed to mount
122 _defaultLabelOptions = []
123 _defaultCheckOptions = []
124 _defaultMigrateOptions = []
125 _defaultInfoOptions = []
126 _migrationTarget = None
127 _existingSizeFields = []
128 _fsProfileSpecifier = None # mkfs option specifying fsprofile
129
130 def __init__(self, *args, **kwargs):
131 """ Create a FS instance.
132
133 Keyword Args:
134
135 device -- path to the device containing the filesystem
136 mountpoint -- the filesystem's mountpoint
137 label -- the filesystem label
138 uuid -- the filesystem UUID
139 mountopts -- mount options for the filesystem
140 size -- the filesystem's size in MiB
141 exists -- indicates whether this is an existing filesystem
142
143 """
144 if self.__class__ is FS:
145 raise TypeError("FS is an abstract class.")
146
147 DeviceFormat.__init__(self, *args, **kwargs)
148 self.mountpoint = kwargs.get("mountpoint")
149 self.mountopts = kwargs.get("mountopts")
150 self.label = kwargs.get("label")
151 self.fsprofile = kwargs.get("fsprofile")
152
153 # filesystem size does not necessarily equal device size
154 self._size = kwargs.get("size", 0)
155 self._minInstanceSize = None # min size of this FS instance
156 self._mountpoint = None # the current mountpoint when mounted
157 if self.exists and self.supported:
158 self._size = self._getExistingSize()
159 foo = self.minSize # force calculation of minimum size
160
161 self._targetSize = self._size
162
163 if self.supported:
164 self.loadModule()
165
166 def __str__(self):
167 s = DeviceFormat.__str__(self)
168 s += (" mountpoint = %(mountpoint)s mountopts = %(mountopts)s\n"
169 " label = %(label)s size = %(size)s"
170 " targetSize = %(targetSize)s\n" %
171 {"mountpoint": self.mountpoint, "mountopts": self.mountopts,
172 "label": self.label, "size": self._size,
173 "targetSize": self.targetSize})
174 return s
175
176 @property
177 def dict(self):
178 d = super(FS, self).dict
179 d.update({"mountpoint": self.mountpoint, "size": self._size,
180 "label": self.label, "targetSize": self.targetSize,
181 "mountable": self.mountable,
182 "migratable": self.migratable})
183 return d
184
185 def _setTargetSize(self, newsize):
186 """ Set a target size for this filesystem. """
187 if not self.exists:
188 raise FSError("filesystem has not been created")
189
190 if newsize is None:
191 # unset any outstanding resize request
192 self._targetSize = self._size
193 return
194
195 if not self.minSize <= newsize < self.maxSize:
196 raise ValueError("invalid target size request")
197
198 self._targetSize = newsize
199
200 def _getTargetSize(self):
201 """ Get this filesystem's target size. """
202 return self._targetSize
203
204 targetSize = property(_getTargetSize, _setTargetSize,
205 doc="Target size for this filesystem")
206
207 def _getSize(self):
208 """ Get this filesystem's size. """
209 size = self._size
210 if self.resizable and self.targetSize != size:
211 size = self.targetSize
212 return size
213
214 size = property(_getSize, doc="This filesystem's size, accounting "
215 "for pending changes")
216
217 def _getExistingSize(self):
218 """ Determine the size of this filesystem. Filesystem must
219 exist. Each filesystem varies, but the general procedure
220 is to run the filesystem dump or info utility and read
221 the block size and number of blocks for the filesystem
222 and compute megabytes from that.
223
224 The loop that reads the output from the infofsProg is meant
225 to be simple, but take in to account variations in output.
226 The general procedure:
227 1) Capture output from infofsProg.
228 2) Iterate over each line of the output:
229 a) Trim leading and trailing whitespace.
230 b) Break line into fields split on ' '
231 c) If line begins with any of the strings in
232 _existingSizeFields, start at the end of
233 fields and take the first one that converts
234 to a long. Store this in the values list.
235 d) Repeat until the values list length equals
236 the _existingSizeFields length.
237 3) If the length of the values list equals the length
238 of _existingSizeFields, compute the size of this
239 filesystem by multiplying all of the values together
240 to get bytes, then convert to megabytes. Return
241 this value.
242 4) If we were unable to capture all fields, return 0.
243
244 The caller should catch exceptions from this method. Any
245 exception raised indicates a need to change the fields we
246 are looking for, the command to run and arguments, or
247 something else. If you catch an exception from this method,
248 assume the filesystem cannot be resized.
249 """
250 size = self._size
251
252 if self.infofsProg and self.mountable and self.exists and not size:
253 try:
254 values = []
255 argv = self._defaultInfoOptions + [ self.device ]
256
257 buf = iutil.execWithCapture(self.infofsProg, argv,
258 stderr="/dev/tty5")
259
260 for line in buf.splitlines():
261 found = False
262
263 line = line.strip()
264 tmp = line.split(' ')
265 tmp.reverse()
266
267 for field in self._existingSizeFields:
268 if line.startswith(field):
269 for subfield in tmp:
270 try:
271 values.append(long(subfield))
272 found = True
273 break
274 except ValueError:
275 continue
276
277 if found:
278 break
279
280 if len(values) == len(self._existingSizeFields):
281 break
282
283 if len(values) != len(self._existingSizeFields):
284 return 0
285
286 size = 1
287 for value in values:
288 size *= value
289
290 # report current size as megabytes
291 size = math.floor(size / 1024.0 / 1024.0)
292 except Exception as e:
293 log.error("failed to obtain size of filesystem on %s: %s"
294 % (self.device, e))
295
296 return size
297
298 @property
299 def currentSize(self):
300 """ The filesystem's current actual size. """
301 size = 0
302 if self.exists:
303 size = self._size
304 return float(size)
305
306 def _getFormatOptions(self, options=None):
307 argv = []
308 if options and isinstance(options, list):
309 argv.extend(options)
310 argv.extend(self.defaultFormatOptions)
311 if self._fsProfileSpecifier and self.fsprofile:
312 argv.extend([self._fsProfileSpecifier, self.fsprofile])
313 argv.append(self.device)
314 return argv
315
316 def doFormat(self, *args, **kwargs):
317 """ Create the filesystem.
318
319 Arguments:
320
321 None
322
323 Keyword Arguments:
324
325 intf -- InstallInterface instance
326 options -- list of options to pass to mkfs
327
328 """
329 log_method_call(self, type=self.mountType, device=self.device,
330 mountpoint=self.mountpoint)
331
332 intf = kwargs.get("intf")
333 options = kwargs.get("options")
334
335 if self.exists:
336 raise FormatCreateError("filesystem already exists", self.device)
337
338 if not self.formattable:
339 return
340
341 if not self.mkfsProg:
342 return
343
344 if self.exists:
345 return
346
347 if not os.path.exists(self.device):
348 raise FormatCreateError("device does not exist", self.device)
349
350 argv = self._getFormatOptions(options=options)
351
352 w = None
353 if intf:
354 w = intf.progressWindow(_("Formatting"),
355 _("Creating %s filesystem on %s")
356 % (self.type, self.device),
357 100, pulse = True)
358
359 try:
360 rc = iutil.execWithPulseProgress(self.mkfsProg,
361 argv,
362 stdout="/dev/tty5",
363 stderr="/dev/tty5",
364 progress=w)
365 except Exception as e:
366 raise FormatCreateError(e, self.device)
367 finally:
368 if w:
369 w.pop()
370
371 if rc:
372 raise FormatCreateError("format failed: %s" % rc, self.device)
373
374 self.exists = True
375 self.notifyKernel()
376
377 if self.label:
378 self.writeLabel(self.label)
379
380 def doMigrate(self, intf=None):
381 if not self.exists:
382 raise FSError("filesystem has not been created")
383
384 if not self.migratable or not self.migrate:
385 return
386
387 if not os.path.exists(self.device):
388 raise FSError("device does not exist")
389
390 # if journal already exists skip
391 if isys.ext2HasJournal(self.device):
392 log.info("Skipping migration of %s, has a journal already."
393 % self.device)
394 return
395
396 argv = self._defaultMigrateOptions[:]
397 argv.append(self.device)
398 try:
399 rc = iutil.execWithRedirect(self.migratefsProg,
400 argv,
401 stdout = "/dev/tty5",
402 stderr = "/dev/tty5")
403 except Exception as e:
404 raise FSMigrateError("filesystem migration failed: %s" % e,
405 self.device)
406
407 if rc:
408 raise FSMigrateError("filesystem migration failed: %s" % rc,
409 self.device)
410
411 # the other option is to actually replace this instance with an
412 # instance of the new filesystem type.
413 self._type = self.migrationTarget
414 self._mountType = self.migrationTarget
415
416 @property
417 def resizeArgs(self):
418 argv = [self.device, "%d" % (self.targetSize,)]
419 return argv
420
421 def doResize(self, *args, **kwargs):
422 """ Resize this filesystem to new size @newsize.
423
424 Arguments:
425
426 None
427
428 Keyword Arguments:
429
430 intf -- InstallInterface instance
431
432 """
433 intf = kwargs.get("intf")
434
435 if not self.exists:
436 raise FSResizeError("filesystem does not exist", self.device)
437
438 if not self.resizable:
439 raise FSResizeError("filesystem not resizable", self.device)
440
441 if self.targetSize == self.currentSize:
442 return
443
444 if not self.resizefsProg:
445 return
446
447 if not os.path.exists(self.device):
448 raise FSResizeError("device does not exist", self.device)
449
450 self.doCheck(intf=intf)
451
452 w = None
453 if intf:
454 w = intf.progressWindow(_("Resizing"),
455 _("Resizing filesystem on %s")
456 % (self.device,),
457 100, pulse = True)
458
459 try:
460 rc = iutil.execWithPulseProgress(self.resizefsProg,
461 self.resizeArgs,
462 stdout="/dev/tty5",
463 stderr="/dev/tty5",
464 progress=w)
465 except Exception as e:
466 raise FSResizeError(e, self.device)
467 finally:
468 if w:
469 w.pop()
470
471 if rc:
472 raise FSResizeError("resize failed: %s" % rc, self.device)
473
474 self.doCheck(intf=intf)
475
476 # XXX must be a smarter way to do this
477 self._size = self.targetSize
478 self.notifyKernel()
479
480 def _getCheckArgs(self):
481 argv = []
482 argv.extend(self.defaultCheckOptions)
483 argv.append(self.device)
484 return argv
485
486 def _fsckFailed(self, rc):
487 return False
488
489 def _fsckErrorMessage(self, rc):
490 return _("Unknown return code: %d.") % (rc,)
491
492 def doCheck(self, intf=None):
493 if not self.exists:
494 raise FSError("filesystem has not been created")
495
496 if not self.fsckProg:
497 return
498
499 if not os.path.exists(self.device):
500 raise FSError("device does not exist")
501
502 w = None
503 if intf:
504 w = intf.progressWindow(_("Checking"),
505 _("Checking filesystem on %s")
506 % (self.device),
507 100, pulse = True)
508
509 try:
510 rc = iutil.execWithPulseProgress(self.fsckProg,
511 self._getCheckArgs(),
512 stdout="/dev/tty5",
513 stderr="/dev/tty5",
514 progress = w)
515 except Exception as e:
516 raise FSError("filesystem check failed: %s" % e)
517 finally:
518 if w:
519 w.pop()
520
521 if self._fsckFailed(rc):
522 hdr = _("%(type)s filesystem check failure on %(device)s: ") % \
523 {"type": self.type, "device": self.device}
524
525 msg = self._fsckErrorMessage(rc)
526
527 if intf:
528 help = _("Errors like this usually mean there is a problem "
529 "with the filesystem that will require user "
530 "interaction to repair. Before restarting "
531 "installation, reboot to rescue mode or another "
532 "system that allows you to repair the filesystem "
533 "interactively. Restart installation after you "
534 "have corrected the problems on the filesystem.")
535
536 intf.messageWindow(_("Unrecoverable Error"),
537 hdr + "\n\n" + msg + "\n\n" + help,
538 custom_icon='error')
539 sys.exit(0)
540 else:
541 raise FSError(hdr + msg)
542
543 def loadModule(self):
544 """Load whatever kernel module is required to support this filesystem."""
545 global kernel_filesystems
546
547 if not self._modules or self.mountType in kernel_filesystems:
548 return
549
550 for module in self._modules:
551 try:
552 rc = iutil.execWithRedirect("modprobe", [module],
553 stdout="/dev/tty5", stderr="/dev/tty5")
554 except Exception as e:
555 log.error("Could not load kernel module %s: %s" % (module, e))
556 self._supported = False
557 return
558
559 if rc:
560 log.error("Could not load kernel module %s" % module)
561 self._supported = False
562 return
563
564 # If we successfully loaded a kernel module, for this filesystem, we
565 # also need to update the list of supported filesystems.
566 kernel_filesystems = get_kernel_filesystems()
567
568 def mount(self, *args, **kwargs):
569 """ Mount this filesystem.
570
571 Arguments:
572
573 None
574
575 Keyword Arguments:
576
577 options -- mount options (overrides all other option strings)
578 chroot -- prefix to apply to mountpoint
579 mountpoint -- mountpoint (overrides self.mountpoint)
580 """
581 options = kwargs.get("options", "")
582 chroot = kwargs.get("chroot", "/")
583 mountpoint = kwargs.get("mountpoint")
584
585 if not self.exists:
586 raise FSError("filesystem has not been created")
587
588 if not mountpoint:
589 mountpoint = self.mountpoint
590
591 if not mountpoint:
592 raise FSError("no mountpoint given")
593
594 if self.status:
595 return
596
597 if not isinstance(self, NoDevFS) and not os.path.exists(self.device):
598 raise FSError("device %s does not exist" % self.device)
599
600 # XXX os.path.join is FUBAR:
601 #
602 # os.path.join("/mnt/foo", "/") -> "/"
603 #
604 #mountpoint = os.path.join(chroot, mountpoint)
605 chrootedMountpoint = os.path.normpath("%s/%s" % (chroot, mountpoint))
606 iutil.mkdirChain(chrootedMountpoint)
607 if flags.selinux:
608 ret = isys.resetFileContext(mountpoint, chroot)
609 log.info("set SELinux context for mountpoint %s to %s" \
610 % (mountpoint, ret))
611
612 # passed in options override default options
613 if not options or not isinstance(options, str):
614 options = self.options
615
616 try:
617 rc = isys.mount(self.device, chrootedMountpoint,
618 fstype=self.mountType,
619 options=options,
620 bindMount=isinstance(self, BindFS))
621 except Exception as e:
622 raise FSError("mount failed: %s" % e)
623
624 if rc:
625 raise FSError("mount failed: %s" % rc)
626
627 if flags.selinux:
628 ret = isys.resetFileContext(mountpoint, chroot)
629 log.info("set SELinux context for newly mounted filesystem "
630 "root at %s to %s" %(mountpoint, ret))
631 isys.setFileContext("%s/lost+found" % mountpoint,
632 lost_and_found_context, chroot)
633
634 self._mountpoint = chrootedMountpoint
635
636 def remount(self, *args, **kwargs):
637 """ Remount the filesystem with new options """
638 options = kwargs.get("options", "")
639 log.info("remounting %s on %s", self.device, self._mountpoint)
640
641 if not self.exists:
642 raise FSError("filesystem has not been created")
643
644 if not self._mountpoint:
645 # not mounted
646 return
647
648 if not os.path.exists(self._mountpoint):
649 raise FSError("mountpoint does not exist")
650
651 # passed in options override default options
652 if not options or not isinstance(options, str):
653 options = self.options
654
655 try:
656 rc = isys.mount(self.device, self._mountpoint,
657 fstype=self.mountType,
658 options=options, remount=True,
659 bindMount=isinstance(self, BindFS))
660 except Exception as e:
661 raise FSError("mount failed: %s" % e)
662
663 if rc:
664 raise FSError("mount failed: %s" % rc)
665
666 if flags.selinux:
667 ret = isys.resetFileContext(self._mountpoint, "")
668 log.info("set SELinux context for newly mounted filesystem "
669 "root at %s to %s" %(self._mountpoint, ret))
670 isys.setFileContext("%s/lost+found" % self._mountpoint,
671 lost_and_found_context, "")
672
673
674 def unmount(self):
675 """ Unmount this filesystem. """
676 if not self.exists:
677 raise FSError("filesystem has not been created")
678
679 if not self._mountpoint:
680 # not mounted
681 return
682
683 if not os.path.exists(self._mountpoint):
684 raise FSError("mountpoint does not exist")
685
686 rc = isys.umount(self._mountpoint, removeDir = False)
687 if rc:
688 raise FSError("umount failed")
689
690 self._mountpoint = None
691
692 def _getLabelArgs(self, label):
693 argv = []
694 argv.extend(self.defaultLabelOptions)
695 argv.extend([self.device, label])
696 return argv
697
698 def writeLabel(self, label):
699 """ Create a label for this filesystem. """
700 if not self.exists:
701 raise FSError("filesystem has not been created")
702
703 if not self.labelfsProg:
704 return
705
706 if not os.path.exists(self.device):
707 raise FSError("device does not exist")
708
709 argv = self._getLabelArgs(label)
710 rc = iutil.execWithRedirect(self.labelfsProg,
711 argv,
712 stderr="/dev/tty5")
713 if rc:
714 raise FSError("label failed")
715
716 self.label = label
717 self.notifyKernel()
718
719 @property
720 def isDirty(self):
721 return False
722
723 @property
724 def mkfsProg(self):
725 """ Program used to create filesystems of this type. """
726 return self._mkfs
727
728 @property
729 def fsckProg(self):
730 """ Program used to check filesystems of this type. """
731 return self._fsck
732
733 @property
734 def resizefsProg(self):
735 """ Program used to resize filesystems of this type. """
736 return self._resizefs
737
738 @property
739 def labelfsProg(self):
740 """ Program used to manage labels for this filesystem type. """
741 return self._labelfs
742
743 @property
744 def migratefsProg(self):
745 """ Program used to migrate filesystems of this type. """
746 return self._migratefs
747
748 @property
749 def infofsProg(self):
750 """ Program used to get information for this filesystem type. """
751 return self._infofs
752
753 @property
754 def migrationTarget(self):
755 return self._migrationTarget
756
757 @property
758 def utilsAvailable(self):
759 # we aren't checking for fsck because we shouldn't need it
760 for prog in [self.mkfsProg, self.resizefsProg, self.labelfsProg,
761 self.infofsProg]:
762 if not prog:
763 continue
764
765 if not filter(lambda d: os.access("%s/%s" % (d, prog), os.X_OK),
766 os.environ["PATH"].split(":")):
767 return False
768
769 return True
770
771 @property
772 def supported(self):
773 log_method_call(self, supported=self._supported)
774 return self._supported and self.utilsAvailable
775
776 @property
777 def mountable(self):
778 canmount = (self.mountType in kernel_filesystems) or \
779 (os.access("/sbin/mount.%s" % (self.mountType,), os.X_OK))
780 modpath = os.path.realpath(os.path.join("/lib/modules", os.uname()[2]))
781 modname = "%s.ko" % self.mountType
782
783 if not canmount and os.path.isdir(modpath):
784 for root, dirs, files in os.walk(modpath):
785 have = filter(lambda x: x.startswith(modname), files)
786 if len(have) == 1 and have[0].startswith(modname):
787 return True
788
789 return canmount
790
791 @property
792 def resizable(self):
793 """ Can formats of this filesystem type be resized? """
794 return super(FS, self).resizable and self.utilsAvailable
795
796 @property
797 def defaultFormatOptions(self):
798 """ Default options passed to mkfs for this filesystem type. """
799 # return a copy to prevent modification
800 return self._defaultFormatOptions[:]
801
802 @property
803 def defaultMountOptions(self):
804 """ Default options passed to mount for this filesystem type. """
805 # return a copy to prevent modification
806 return self._defaultMountOptions[:]
807
808 @property
809 def defaultLabelOptions(self):
810 """ Default options passed to labeler for this filesystem type. """
811 # return a copy to prevent modification
812 return self._defaultLabelOptions[:]
813
814 @property
815 def defaultCheckOptions(self):
816 """ Default options passed to checker for this filesystem type. """
817 # return a copy to prevent modification
818 return self._defaultCheckOptions[:]
819
820 def _getOptions(self):
821 options = ",".join(self.defaultMountOptions)
822 if self.mountopts:
823 # XXX should we clobber or append?
824 options = self.mountopts
825 return options
826
827 def _setOptions(self, options):
828 self.mountopts = options
829
830 options = property(_getOptions, _setOptions)
831
832 def _isMigratable(self):
833 """ Can filesystems of this type be migrated? """
834 return bool(self._migratable and self.migratefsProg and
835 filter(lambda d: os.access("%s/%s"
836 % (d, self.migratefsProg,),
837 os.X_OK),
838 os.environ["PATH"].split(":")) and
839 self.migrationTarget)
840
841 migratable = property(_isMigratable)
842
843 def _setMigrate(self, migrate):
844 if not migrate:
845 self._migrate = migrate
846 return
847
848 if self.migratable and self.exists:
849 self._migrate = migrate
850 else:
851 raise ValueError("cannot set migrate on non-migratable filesystem")
852
853 migrate = property(lambda f: f._migrate, lambda f,m: f._setMigrate(m))
854
855 @property
856 def type(self):
857 _type = self._type
858 if self.migrate:
859 _type = self.migrationTarget
860
861 return _type
862
863 @property
864 def mountType(self):
865 if not self._mountType:
866 self._mountType = self._type
867
868 return self._mountType
869
870 # These methods just wrap filesystem-specific methods in more
871 # generically named methods so filesystems and formatted devices
872 # like swap and LVM physical volumes can have a common API.
873 def create(self, *args, **kwargs):
874 if self.exists:
875 raise FSError("filesystem already exists")
876
877 DeviceFormat.create(self, *args, **kwargs)
878
879 return self.doFormat(*args, **kwargs)
880
881 def setup(self, *args, **kwargs):
882 """ Mount the filesystem.
883
884 The filesystem will be mounted at the directory indicated by
885 self.mountpoint.
886 """
887 return self.mount(**kwargs)
888
889 def teardown(self, *args, **kwargs):
890 return self.unmount(*args, **kwargs)
891
892 @property
893 def status(self):
894 # FIXME check /proc/mounts or similar
895 if not self.exists:
896 return False
897 return self._mountpoint is not None
898
899 def writeKS(self, f):
900 f.write("%s --fstype=%s" % (self.mountpoint, self.type))
901
902 if self.label:
903 f.write(" --label=\"%s\"" % self.label)
904
905 if self.fsprofile:
906 f.write(" --fsprofile=\"%s\"" % self.fsprofile)
907
908
909 class Ext2FS(FS):
910 """ ext2 filesystem. """
911 _type = "ext2"
912 _mkfs = "mke2fs"
913 _modules = ["ext2"]
914 _resizefs = "resize2fs"
915 _labelfs = "e2label"
916 _fsck = "e2fsck"
917 _fsckErrors = {4: _("File system errors left uncorrected."),
918 8: _("Operational error."),
919 16: _("Usage or syntax error."),
920 32: _("e2fsck cancelled by user request."),
921 128: _("Shared library error.")}
922 _packages = ["e2fsprogs"]
923 _formattable = True
924 _supported = True
925 _resizable = True
926 _bootable = True
927 _linuxNative = True
928 _maxSize = 8 * 1024 * 1024
929 _minSize = 0
930 _defaultFormatOptions = []
931 _defaultMountOptions = ["defaults"]
932 _defaultCheckOptions = ["-f", "-p", "-C", "0"]
933 _dump = True
934 _check = True
935 _migratable = True
936 _migrationTarget = "ext3"
937 _migratefs = "tune2fs"
938 _defaultMigrateOptions = ["-j"]
939 _infofs = "dumpe2fs"
940 _defaultInfoOptions = ["-h"]
941 _existingSizeFields = ["Block count:", "Block size:"]
942 _fsProfileSpecifier = "-T"
943 partedSystem = fileSystemType["ext2"]
944
945 def _fsckFailed(self, rc):
946 for errorCode in self._fsckErrors.keys():
947 if rc & errorCode:
948 return True
949 return False
950
951 def _fsckErrorMessage(self, rc):
952 msg = ''
953
954 for errorCode in self._fsckErrors.keys():
955 if rc & errorCode:
956 msg += "\n" + self._fsckErrors[errorCode]
957
958 return msg.strip()
959
960 def doMigrate(self, intf=None):
961 FS.doMigrate(self, intf=intf)
962 self.tuneFS()
963
964 def doFormat(self, *args, **kwargs):
965 FS.doFormat(self, *args, **kwargs)
966 self.tuneFS()
967
968 def tuneFS(self):
969 if not isys.ext2HasJournal(self.device):
970 # only do this if there's a journal
971 return
972
973 try:
974 rc = iutil.execWithRedirect("tune2fs",
975 ["-c0", "-i0",
976 "-ouser_xattr,acl", self.device],
977 stdout = "/dev/tty5",
978 stderr = "/dev/tty5")
979 except Exception as e:
980 log.error("failed to run tune2fs on %s: %s" % (self.device, e))
981
982 @property
983 def minSize(self):
984 """ Minimum size for this filesystem in MB. """
985 if self._minInstanceSize is None:
986 # try once in the beginning to get the minimum size for an
987 # existing filesystem.
988 size = self._minSize
989 blockSize = None
990
991 if self.exists and os.path.exists(self.device):
992 # get block size
993 buf = iutil.execWithCapture(self.infofsProg,
994 ["-h", self.device],
995 stderr="/dev/tty5")
996 for line in buf.splitlines():
997 if line.startswith("Block size:"):
998 blockSize = int(line.split(" ")[-1])
999 break
1000
1001 if blockSize is None:
1002 raise FSError("failed to get block size for %s filesystem "
1003 "on %s" % (self.mountType, self.device))
1004
1005 # get minimum size according to resize2fs
1006 buf = iutil.execWithCapture(self.resizefsProg,
1007 ["-P", self.device],
1008 stderr="/dev/tty5")
1009 for line in buf.splitlines():
1010 if "minimum size of the filesystem:" not in line:
1011 continue
1012
1013 # line will look like:
1014 # Estimated minimum size of the filesystem: 1148649
1015 #
1016 # NOTE: The minimum size reported is in blocks. Convert
1017 # to bytes, then megabytes, and finally round up.
1018 (text, sep, minSize) = line.partition(": ")
1019 size = long(minSize) * blockSize
1020 size = math.ceil(size / 1024.0 / 1024.0)
1021 break
1022
1023 if size is None:
1024 log.warning("failed to get minimum size for %s filesystem "
1025 "on %s" % (self.mountType, self.device))
1026
1027 self._minInstanceSize = size
1028
1029 return self._minInstanceSize
1030
1031 @property
1032 def isDirty(self):
1033 return isys.ext2IsDirty(self.device)
1034
1035 @property
1036 def resizeArgs(self):
1037 argv = ["-p", self.device, "%dM" % (self.targetSize,)]
1038 return argv
1039
1040 register_device_format(Ext2FS)
1041
1042
1043 class Ext3FS(Ext2FS):
1044 """ ext3 filesystem. """
1045 _type = "ext3"
1046 _defaultFormatOptions = ["-t", "ext3"]
1047 _migratable = False
1048 _modules = ["ext3"]
1049 _defaultMigrateOptions = ["-O", "extents"]
1050 partedSystem = fileSystemType["ext3"]
1051
1052 # It is possible for a user to specify an fsprofile that defines a blocksize
1053 # smaller than the default of 4096 bytes and therefore to make liars of us
1054 # with regard to this maximum filesystem size, but if they're doing such
1055 # things they should know the implications of their chosen block size.
1056 _maxSize = 16 * 1024 * 1024
1057
1058 register_device_format(Ext3FS)
1059
1060
1061 class Ext4FS(Ext3FS):
1062 """ ext4 filesystem. """
1063 _type = "ext4"
1064 _defaultFormatOptions = ["-t", "ext4"]
1065 _migratable = False
1066 _modules = ["ext4"]
1067 partedSystem = fileSystemType["ext4"]
1068
1069 register_device_format(Ext4FS)
1070
1071
1072 class FATFS(FS):
1073 """ FAT filesystem. """
1074 _type = "vfat"
1075 _mkfs = "mkdosfs"
1076 _modules = ["vfat"]
1077 _labelfs = "dosfslabel"
1078 _fsck = "dosfsck"
1079 _fsckErrors = {1: _("Recoverable errors have been detected or dosfsck has "
1080 "discovered an internal inconsistency."),
1081 2: _("Usage error.")}
1082 _supported = True
1083 _formattable = True
1084 _maxSize = 1024 * 1024
1085 _packages = [ "dosfstools" ]
1086 _defaultMountOptions = ["umask=0077", "shortname=winnt"]
1087 # FIXME this should be fat32 in some cases
1088 partedSystem = fileSystemType["fat16"]
1089
1090 def _fsckFailed(self, rc):
1091 if rc >= 1:
1092 return True
1093 return False
1094
1095 def _fsckErrorMessage(self, rc):
1096 return self._fsckErrors[rc]
1097
1098 register_device_format(FATFS)
1099
1100
1101 class EFIFS(FATFS):
1102 _type = "efi"
1103 _mountType = "vfat"
1104 _modules = ["vfat"]
1105 _name = "EFI System Partition"
1106 _minSize = 50
1107 _bootable = True
1108
1109 @property
1110 def supported(self):
1111 import platform
1112 p = platform.getPlatform(None)
1113 return (isinstance(p, platform.EFI) and
1114 p.isEfi and
1115 self.utilsAvailable)
1116
1117 register_device_format(EFIFS)
1118
1119
1120 class BTRFS(FS):
1121 """ btrfs filesystem """
1122 _type = "btrfs"
1123 _mkfs = "mkfs.btrfs"
1124 _modules = ["btrfs"]
1125 _resizefs = "btrfsctl"
1126 _formattable = True
1127 _linuxNative = True
1128 _bootable = False
1129 _maxLabelChars = 256
1130 _supported = False
1131 _dump = True
1132 _check = True
1133 _packages = ["btrfs-progs"]
1134 _maxSize = 16 * 1024 * 1024
1135 # FIXME parted needs to be thaught about btrfs so that we can set the
1136 # partition table type correctly for btrfs partitions
1137 # partedSystem = fileSystemType["btrfs"]
1138
1139 def _getFormatOptions(self, options=None):
1140 argv = []
1141 if options and isinstance(options, list):
1142 argv.extend(options)
1143 argv.extend(self.defaultFormatOptions)
1144 if self.label:
1145 argv.extend(["-L", self.label])
1146 argv.append(self.device)
1147 return argv
1148
1149 @property
1150 def resizeArgs(self):
1151 argv = ["-r", "%dm" % (self.targetSize,), self.device]
1152 return argv
1153
1154 @property
1155 def supported(self):
1156 """ Is this filesystem a supported type? """
1157 supported = self._supported
1158 if flags.cmdline.has_key("btrfs"):
1159 supported = self.utilsAvailable
1160
1161 return supported
1162
1163 register_device_format(BTRFS)
1164
1165
1166 class GFS2(FS):
1167 """ gfs2 filesystem. """
1168 _type = "gfs2"
1169 _mkfs = "mkfs.gfs2"
1170 _modules = ["dlm", "gfs2"]
1171 _formattable = True
1172 _defaultFormatOptions = ["-j", "1", "-p", "lock_nolock", "-O"]
1173 _linuxNative = True
1174 _supported = False
1175 _dump = True
1176 _check = True
1177 _packages = ["gfs2-utils"]
1178 # FIXME parted needs to be thaught about btrfs so that we can set the
1179 # partition table type correctly for btrfs partitions
1180 # partedSystem = fileSystemType["gfs2"]
1181
1182 @property
1183 def supported(self):
1184 """ Is this filesystem a supported type? """
1185 supported = self._supported
1186 if flags.cmdline.has_key("gfs2"):
1187 supported = self.utilsAvailable
1188
1189 return supported
1190
1191 register_device_format(GFS2)
1192
1193
1194 class JFS(FS):
1195 """ JFS filesystem """
1196 _type = "jfs"
1197 _mkfs = "mkfs.jfs"
1198 _modules = ["jfs"]
1199 _labelfs = "jfs_tune"
1200 _defaultFormatOptions = ["-q"]
1201 _defaultLabelOptions = ["-L"]
1202 _maxLabelChars = 16
1203 _maxSize = 8 * 1024 * 1024
1204 _formattable = True
1205 _linuxNative = True
1206 _supported = False
1207 _dump = True
1208 _check = True
1209 _infofs = "jfs_tune"
1210 _defaultInfoOptions = ["-l"]
1211 _existingSizeFields = ["Aggregate block size:", "Aggregate size:"]
1212 partedSystem = fileSystemType["jfs"]
1213
1214 @property
1215 def supported(self):
1216 """ Is this filesystem a supported type? """
1217 supported = self._supported
1218 if flags.cmdline.has_key("jfs"):
1219 supported = self.utilsAvailable
1220
1221 return supported
1222
1223 register_device_format(JFS)
1224
1225
1226 class ReiserFS(FS):
1227 """ reiserfs filesystem """
1228 _type = "reiserfs"
1229 _mkfs = "mkreiserfs"
1230 _resizefs = "resize_reiserfs"
1231 _labelfs = "reiserfstune"
1232 _modules = ["reiserfs"]
1233 _defaultFormatOptions = ["-f", "-f"]
1234 _defaultLabelOptions = ["-l"]
1235 _maxLabelChars = 16
1236 _maxSize = 16 * 1024 * 1024
1237 _formattable = True
1238 _linuxNative = True
1239 _supported = False
1240 _dump = True
1241 _check = True
1242 _packages = ["reiserfs-utils"]
1243 _infofs = "debugreiserfs"
1244 _defaultInfoOptions = []
1245 _existingSizeFields = ["Count of blocks on the device:", "Blocksize:"]
1246 partedSystem = fileSystemType["reiserfs"]
1247
1248 @property
1249 def supported(self):
1250 """ Is this filesystem a supported type? """
1251 supported = self._supported
1252 if flags.cmdline.has_key("reiserfs"):
1253 supported = self.utilsAvailable
1254
1255 return supported
1256
1257 @property
1258 def resizeArgs(self):
1259 argv = ["-s", "%dM" % (self.targetSize,), self.device]
1260 return argv
1261
1262 register_device_format(ReiserFS)
1263
1264
1265 class XFS(FS):
1266 """ XFS filesystem """
1267 _type = "xfs"
1268 _mkfs = "mkfs.xfs"
1269 _modules = ["xfs"]
1270 _labelfs = "xfs_admin"
1271 _defaultFormatOptions = ["-f"]
1272 _defaultLabelOptions = ["-L"]
1273 _maxLabelChars = 16
1274 _maxSize = 16 * 1024 * 1024
1275 _formattable = True
1276 _linuxNative = True
1277 _supported = True
1278 _dump = True
1279 _check = True
1280 _packages = ["xfsprogs"]
1281 _infofs = "xfs_db"
1282 _defaultInfoOptions = ["-c", "\"sb 0\"", "-c", "\"p dblocks\"",
1283 "-c", "\"p blocksize\""]
1284 _existingSizeFields = ["dblocks =", "blocksize ="]
1285 partedSystem = fileSystemType["xfs"]
1286
1287 def _getLabelArgs(self, label):
1288 argv = []
1289 argv.extend(self.defaultLabelOptions)
1290 argv.extend([label, self.device])
1291 return argv
1292
1293 register_device_format(XFS)
1294
1295
1296 class HFS(FS):
1297 _type = "hfs"
1298 _mkfs = "hformat"
1299 _modules = ["hfs"]
1300 _formattable = True
1301 partedSystem = fileSystemType["hfs"]
1302
1303 register_device_format(HFS)
1304
1305
1306 class AppleBootstrapFS(HFS):
1307 _type = "appleboot"
1308 _mountType = "hfs"
1309 _name = "Apple Bootstrap"
1310 _bootable = True
1311 _minSize = 800.00 / 1024.00
1312 _maxSize = 1
1313
1314 @property
1315 def supported(self):
1316 import platform
1317 return (isinstance(platform.getPlatform(None), platform.NewWorldPPC)
1318 and self.utilsAvailable)
1319
1320 def writeKS(self, f):
1321 f.write("appleboot --fstype=%s" % self.type)
1322
1323 register_device_format(AppleBootstrapFS)
1324
1325
1326 # this doesn't need to be here
1327 class HFSPlus(FS):
1328 _type = "hfs+"
1329 _modules = ["hfsplus"]
1330 _udevTypes = ["hfsplus"]
1331 partedSystem = fileSystemType["hfs+"]
1332
1333 register_device_format(HFSPlus)
1334
1335
1336 class NTFS(FS):
1337 """ ntfs filesystem. """
1338 _type = "ntfs"
1339 _resizefs = "ntfsresize"
1340 _fsck = "ntfsresize"
1341 _resizable = True
1342 _minSize = 1
1343 _maxSize = 16 * 1024 * 1024
1344 _defaultMountOptions = ["defaults", "ro"]
1345 _defaultCheckOptions = ["-c"]
1346 _packages = ["ntfsprogs"]
1347 _infofs = "ntfsinfo"
1348 _defaultInfoOptions = ["-m"]
1349 _existingSizeFields = ["Cluster Size:", "Volume Size in Clusters:"]
1350 partedSystem = fileSystemType["ntfs"]
1351
1352 def _fsckFailed(self, rc):
1353 if rc != 0:
1354 return True
1355 return False
1356
1357 @property
1358 def minSize(self):
1359 """ The minimum filesystem size in megabytes. """
1360 if self._minInstanceSize is None:
1361 # we try one time to determine the minimum size.
1362 size = self._minSize
1363 if self.exists and os.path.exists(self.device):
1364 minSize = None
1365 buf = iutil.execWithCapture(self.resizefsProg,
1366 ["-m", self.device],
1367 stderr = "/dev/tty5")
1368 for l in buf.split("\n"):
1369 if not l.startswith("Minsize"):
1370 continue
1371 try:
1372 min = l.split(":")[1].strip()
1373 minSize = int(min) + 250
1374 except Exception, e:
1375 minSize = None
1376 log.warning("Unable to parse output for minimum size on %s: %s" %(self.device, e))
1377
1378 if minSize is None:
1379 log.warning("Unable to discover minimum size of filesystem "
1380 "on %s" %(self.device,))
1381 else:
1382 size = minSize
1383
1384 self._minInstanceSize = size
1385
1386 return self._minInstanceSize
1387
1388 @property
1389 def resizeArgs(self):
1390 # You must supply at least two '-f' options to ntfsresize or
1391 # the proceed question will be presented to you.
1392 argv = ["-ff", "-s", "%dM" % (self.targetSize,), self.device]
1393 return argv
1394
1395
1396 register_device_format(NTFS)
1397
1398
1399 # if this isn't going to be mountable it might as well not be here
1400 class NFS(FS):
1401 """ NFS filesystem. """
1402 _type = "nfs"
1403 _modules = ["nfs"]
1404
1405 def _deviceCheck(self, devspec):
1406 if devspec is not None and ":" not in devspec:
1407 raise ValueError("device must be of the form <host>:<path>")
1408
1409 @property
1410 def mountable(self):
1411 return False
1412
1413 def _setDevice(self, devspec):
1414 self._deviceCheck(devspec)
1415 self._device = devspec
1416
1417 def _getDevice(self):
1418 return self._device
1419
1420 device = property(lambda f: f._getDevice(),
1421 lambda f,d: f._setDevice(d),
1422 doc="Full path the device this format occupies")
1423
1424 register_device_format(NFS)
1425
1426
1427 class NFSv4(NFS):
1428 """ NFSv4 filesystem. """
1429 _type = "nfs4"
1430 _modules = ["nfs4"]
1431
1432 register_device_format(NFSv4)
1433
1434
1435 class Iso9660FS(FS):
1436 """ ISO9660 filesystem. """
1437 _type = "iso9660"
1438 _formattable = False
1439 _supported = True
1440 _resizable = False
1441 _bootable = False
1442 _linuxNative = False
1443 _dump = False
1444 _check = False
1445 _migratable = False
1446 _defaultMountOptions = ["ro"]
1447
1448 def writeKS(self, f):
1449 return
1450
1451 register_device_format(Iso9660FS)
1452
1453
1454 class NoDevFS(FS):
1455 """ nodev filesystem base class """
1456 _type = "nodev"
1457
1458 def __init__(self, *args, **kwargs):
1459 FS.__init__(self, *args, **kwargs)
1460 self.exists = True
1461 self.device = self.type
1462
1463 def _setDevice(self, devspec):
1464 self._device = devspec
1465
1466 def _getExistingSize(self):
1467 pass
1468
1469 def writeKS(self, f):
1470 return
1471
1472 register_device_format(NoDevFS)
1473
1474
1475 class DevPtsFS(NoDevFS):
1476 """ devpts filesystem. """
1477 _type = "devpts"
1478 _defaultMountOptions = ["gid=5", "mode=620"]
1479
1480 register_device_format(DevPtsFS)
1481
1482
1483 # these don't really need to be here
1484 class ProcFS(NoDevFS):
1485 _type = "proc"
1486
1487 register_device_format(ProcFS)
1488
1489
1490 class SysFS(NoDevFS):
1491 _type = "sysfs"
1492
1493 register_device_format(SysFS)
1494
1495
1496 class TmpFS(NoDevFS):
1497 _type = "tmpfs"
1498
1499 register_device_format(TmpFS)
1500
1501
1502 class BindFS(FS):
1503 _type = "bind"
1504
1505 @property
1506 def mountable(self):
1507 return True
1508
1509 def _getExistingSize(self):
1510 pass
1511
1512 def writeKS(self, f):
1513 return
1514
1515 register_device_format(BindFS)
1516
1517

admin@koozali.org
ViewVC Help
Powered by ViewVC 1.2.1 RSS 2.0 feed