from abc import ABCMeta from abc import abstractmethod from bootstrapvz.common.sectors import Sectors from bootstrapvz.common.tools import log_check_call from bootstrapvz.common.fsm_proxy import FSMProxy class AbstractPartition(FSMProxy): """Abstract representation of a partiton This class is a finite state machine and represents the state of the real partition """ __metaclass__ = ABCMeta # Our states events = [{'name': 'create', 'src': 'nonexistent', 'dst': 'created'}, {'name': 'format', 'src': 'created', 'dst': 'formatted'}, {'name': 'mount', 'src': 'formatted', 'dst': 'mounted'}, {'name': 'unmount', 'src': 'mounted', 'dst': 'formatted'}, ] def __init__(self, size, filesystem, format_command): """ :param Bytes size: Size of the partition :param str filesystem: Filesystem the partition should be formatted with :param list format_command: Optional format command, valid variables are fs, device_path and size """ self.size = size self.filesystem = filesystem self.format_command = format_command # Initialize the start & end padding to 0 sectors, may be changed later self.pad_start = Sectors(0, size.sector_size) self.pad_end = Sectors(0, size.sector_size) # Path to the partition self.device_path = None # Dictionary with mount points as keys and Mount objects as values self.mounts = {} # Create the configuration for our state machine cfg = {'initial': 'nonexistent', 'events': self.events, 'callbacks': {}} super(AbstractPartition, self).__init__(cfg) def get_uuid(self): """Gets the UUID of the partition :return: The UUID of the partition :rtype: str """ [uuid] = log_check_call(['blkid', '-s', 'UUID', '-o', 'value', self.device_path]) return uuid @abstractmethod def get_start(self): pass def get_end(self): """Gets the end of the partition :return: The end of the partition :rtype: Sectors """ return self.get_start() + self.pad_start + self.size + self.pad_end def _before_format(self, e): """Formats the partition """ # If there is no explicit format_command define we simply call mkfs.fstype if self.format_command is None: format_command = ['mkfs.{fs}', '{device_path}'] else: format_command = self.format_command variables = {'fs': self.filesystem, 'device_path': self.device_path, 'size': self.size, } command = map(lambda part: part.format(**variables), format_command) # Format the partition log_check_call(command) def _before_mount(self, e): """Mount the partition """ log_check_call(['mount', '--types', self.filesystem, self.device_path, e.destination]) self.mount_dir = e.destination def _after_mount(self, e): """Mount any mounts associated with this partition """ # Make sure we mount in ascending order of mountpoint path length # This ensures that we don't mount /dev/pts before we mount /dev for destination in sorted(self.mounts.iterkeys(), key=len): self.mounts[destination].mount(self.mount_dir) def _before_unmount(self, e): """Unmount any mounts associated with this partition """ # Unmount the mounts in descending order of mounpoint path length # You cannot unmount /dev before you have unmounted /dev/pts for destination in sorted(self.mounts.iterkeys(), key=len, reverse=True): self.mounts[destination].unmount() log_check_call(['umount', self.mount_dir]) del self.mount_dir def add_mount(self, source, destination, opts=[]): """Associate a mount with this partition Automatically mounts it :param str,AbstractPartition source: The source of the mount :param str destination: The path to the mountpoint :param list opts: Any options that should be passed to the mount command """ # Create a new mount object, mount it if the partition is mounted and put it in the mounts dict from mount import Mount mount = Mount(source, destination, opts) if self.fsm.current == 'mounted': mount.mount(self.mount_dir) self.mounts[destination] = mount def remove_mount(self, destination): """Remove a mount from this partition Automatically unmounts it :param str destination: The mountpoint path of the mount that should be removed """ # Unmount the mount if the partition is mounted and delete it from the mounts dict # If the mount is already unmounted and the source is a partition, this will raise an exception if self.fsm.current == 'mounted': self.mounts[destination].unmount() del self.mounts[destination]