[blivet][PATCH 2/5] Add tmpfs support (#918621)
Martin Kolman
mkolman at redhat.com
Thu Nov 7 17:52:12 UTC 2013
Add support for creating tmpfs mounts, mounting/unmounting
them, checking free space on them and adding them to fstab.
Tmpfs is a filesystem that lives in the Kernel page cache
and stores data primarily in RAM and in swap once RAM runs out.
Data stored on tmpfs mounts will not survive a system
reboot/crash/shutdown.
About tmpfs mount size:
- if no size is specified, the size will be 50% by default
- if size is specified, the mount will have this size
-> there is no limit on the size on tmpfs mounts
-> just note that once it fills RAM (and any swap),
the system will grind to a halt
- grow and maxsize are not supported
- you can use use filesystem options to set size in percent:
fsopts == "size=25%"
This will make the tmpfs mount take up
to 25% percent of system RAM.
What operations are supported:
-> adding tmpfs mounts to fstab
-> mounting and unmounting tmpfs mounts
-> checking free space on mounted tmpfs mounts
-> resizing mounted tmpfs mounts
Resolves: rhbz#472127
Signed-off-by: Martin Kolman <mkolman at redhat.com>
---
blivet/__init__.py | 4 ++
blivet/deviceaction.py | 10 ++-
blivet/devices.py | 27 ++++++++
blivet/formats/fs.py | 170 +++++++++++++++++++++++++++++++++++++++++++++++--
4 files changed, 202 insertions(+), 9 deletions(-)
diff --git a/blivet/__init__.py b/blivet/__init__.py
index 327274c..d7c1572 100644
--- a/blivet/__init__.py
+++ b/blivet/__init__.py
@@ -1186,6 +1186,10 @@ class Blivet(object):
kwargs["subvol"] = True
return self.newBTRFS(*args, **kwargs)
+ def newTmpFS(self, *args, **kwargs):
+ """ Return a new TmpFSDevice. """
+ return TmpFSDevice(*args, **kwargs)
+
def createDevice(self, device):
""" Schedule creation of a device.
diff --git a/blivet/deviceaction.py b/blivet/deviceaction.py
index 202a66c..a53fdb5 100644
--- a/blivet/deviceaction.py
+++ b/blivet/deviceaction.py
@@ -473,8 +473,14 @@ class ActionCreateFormat(DeviceAction):
udev_settle()
self.device.updateSysfsPath()
info = udev_get_block_device(self.device.sysfsPath)
- self.device.format.uuid = udev_device_get_uuid(info)
- self.device.deviceLinks = udev_device_get_symlinks(info)
+ # only do this if the format has a device known to udev
+ # (the format might not have a normal device at all)
+ if info:
+ self.device.format.uuid = udev_device_get_uuid(info)
+ self.device.deviceLinks = udev_device_get_symlinks(info)
+ elif self.device.format.type != "tmpfs":
+ # udev lookup failing is a serious issue for anything other than tmpfs
+ log.error("udev lookup failed for device: %s" % self.device)
def cancel(self):
self.device.format = self.origFormat
diff --git a/blivet/devices.py b/blivet/devices.py
index d620eaf..5794d95 100644
--- a/blivet/devices.py
+++ b/blivet/devices.py
@@ -3768,6 +3768,33 @@ class NoDevice(StorageDevice):
self._preDestroy()
+class TmpFSDevice(NoDevice):
+ """ A nodev device for a tmpfs filesystem. """
+ _type = "tmpfs"
+
+ def __init__(self, *args, **kwargs):
+ """Create a tmpfs device"""
+ format = kwargs.get('format')
+ NoDevice.__init__(self, format)
+ # the tmpfs device does not exist until mounted
+ self.exists = False
+ self._size = kwargs["size"]
+ self._targetSize = self._size
+
+ @property
+ def size(self):
+ if self._size is not None:
+ return self._size
+ elif self.format:
+ return self.format.size
+ else:
+ return 0
+
+ @property
+ def fstabSpec(self):
+ return self._type
+
+
class FileDevice(StorageDevice):
""" A file on a filesystem.
diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py
index 04270f0..20ea1ac 100644
--- a/blivet/formats/fs.py
+++ b/blivet/formats/fs.py
@@ -21,11 +21,7 @@
# David Cantrell <dcantrell at redhat.com>
#
-""" Filesystem classes for use by anaconda.
-
- TODO:
- - bug 472127: allow creation of tmpfs filesystems (/tmp, /var/tmp, &c)
-"""
+""" Filesystem classes. """
import math
import os
import sys
@@ -39,6 +35,7 @@ from .. import platform
from ..flags import flags
from parted import fileSystemType
from ..storage_log import log_method_call
+from .. import arch
import logging
log = logging.getLogger("blivet")
@@ -54,6 +51,8 @@ except OSError:
# these are for converting to/from SI for ntfsresize
mb = 1000 * 1000.0
mib = 1024 * 1024.0
+gib = 1024 * 1024 * 1024.0
+
fs_configs = {}
@@ -429,7 +428,8 @@ class FS(DeviceFormat):
if not self.resizefsProg:
return
- if not os.path.exists(self.device):
+ # tmpfs mounts don't need an existing device node
+ if not self.device == "tmpfs" and not os.path.exists(self.device):
raise FSResizeError("device does not exist", self.device)
self.doCheck()
@@ -1405,7 +1405,7 @@ class NoDevFS(FS):
def __init__(self, *args, **kwargs):
FS.__init__(self, *args, **kwargs)
self.exists = True
- self.device = self.type
+ self.device = self._type
def _setDevice(self, devspec):
self._device = devspec
@@ -1450,6 +1450,162 @@ register_device_format(SysFS)
class TmpFS(NoDevFS):
_type = "tmpfs"
+ _supported = True
+ _resizable = True
+ # remounting can be used to change
+ # the size of a live tmpfs mount
+ _resizefs = "mount"
+ # as tmpfs is part of the Linux kernel,
+ # it is Linux-native
+ _linuxNative = True
+ # empty tmpfs has zero overhead
+ _minInstanceSize = 0
+ # tmpfs really does not occupy any space by itself
+ _minSize = 0
+ # in a sense, I guess tmpfs is formattable
+ # in the regard that the format is automatically created
+ # once mounted
+ _formattable = True
+
+ def __init__(self, *args, **kwargs):
+ NoDevFS.__init__(self, *args, **kwargs)
+ self.exists = True
+ self._device = "tmpfs"
+
+ # according to the following Kernel ML thread:
+ # http://www.gossamer-threads.com/lists/linux/kernel/875278
+ # maximum tmpfs mount size is 16TB on 32 bit systems
+ # and 16EB on 64 bit systems
+ bits = arch.bits()
+ if bits == 32:
+ self._maxSize = 16 * 1024 * 1024
+ elif bits == 64:
+ self._maxSize = 16 * 1024 * 1024 * 1024 * 1024
+ # if the architecture is other than 32 or 64 bit or unknown
+ # just use the default maxsize, which is 0, this disables
+ # resizing but other operations such as mounting should work fine
+
+ # check if fixed filesystem size has been specified,
+ # if no size is specified, tmpfs will by default
+ # be limited to half of the system RAM
+ # (sizes of all tmpfs mounts are independent)
+ fsoptions = kwargs.get("mountopts")
+ system_ram = util.total_memory() / 1024 # kB to MB
+ self._size_option = ""
+ if fsoptions:
+ # some mount options were specified, replace the default value
+ self._options = fsoptions
+ if self._size:
+ # absolute size for the tmpfs mount has been specified
+ self._size_option = "size=%dm" % self._size
+ else:
+ # if no size option is specified, the tmpfs mount size will be 50%
+ # of system RAM by default
+ self._size = system_ram*0.5
+
+ def create(self, *args, **kwargs):
+ """ A filesystem is created automatically once tmpfs is mounted. """
+ pass
+
+ def destroy(self, *args, **kwargs):
+ """ The device and its filesystem are automatically destroyed once the
+ mountpoint is unmounted.
+ """
+ pass
+
+ @property
+ def mountable(self):
+ return True
+
+ def _getOptions(self):
+ # if the size option string is defined,
+ # append it to options
+ # (we need to separate the size option string
+ # from the other options, as the size might change
+ # when the filesystem is resized;
+ # replacing the size option in the otherwise free-form
+ # options string would get messy fast)
+ opts = ",".join([o for o in [self._options, self._size_option] if o])
+ return opts or "defaults"
+
+ def _setOptions(self, options):
+ self._options = options
+
+ # override the options property
+ # so that the size and other options
+ # are correctly added to fstab
+ options = property(_getOptions, _setOptions)
+
+ @property
+ def size(self):
+ return self._size
+
+ @property
+ def minSize(self):
+ """ The minimum filesystem size in megabytes. """
+ return self._minInstanceSize
+
+ @property
+ def free(self):
+ free_space = 0
+ if self._mountpoint:
+ # If self._mountpoint is defined, it means this tmpfs mount
+ # has been mounted and there is a path we can use as a handle to
+ # look-up the free space on the filesystem.
+ # When running with changeroot, such as during installation,
+ # self._mountpoint is set to the full changeroot path once mounted,
+ # so even with changeroot, statvfs should still work fine.
+ st = os.statvfs(self._mountpoint)
+ free_space = st.f_bavail * st.f_frsize/1024/1024 # blocks to MB
+ else:
+ # Free might be called even if the tmpfs mount has not been
+ # mounted yet, in this case just return the size set for the mount.
+ # Once mounted, the tmpfs mount will be empty
+ # and therefore free space will correspond to its size.
+ free_space = self._size
+ return free_space
+
+ def _getDevice(self):
+ """ All the tmpfs mounts use the same "tmpfs" device. """
+ return self._type
+
+ def _setDevice(self, value):
+ # the DeviceFormat parent class does a
+ # self.device = kwargs["device"]
+ # assignment, so we need a setter for the
+ # device property, but as the device is always the
+ # same, nothing actually needs to be set
+ pass
+
+ device = property(_getDevice, _setDevice)
+
+ @property
+ def resizeArgs(self):
+ # a live tmpfs mount can be resized by remounting it with
+ # the mount command
+
+ # add the remount flag, size and any options
+ remount_options = 'remount,size=%dm' % self.targetSize
+ # if any mount options are defined, append them
+ if self._options:
+ remount_options = "%s,%s" % (remount_options, self._options)
+ return ['-o', remount_options, self._type, self._mountpoint]
+
+ def doResize(self):
+ # we need to override doResize, because the
+ # self._size_option string needs to be updated in case
+ # the tmpfs mount is successfully resized, using the new
+ # self._size value
+ original_size = self._size
+ FS.doResize(self)
+ # after a successful resize, self._size
+ # is set to be equal to self.targetSize,
+ # so it can be used to check if resize took place
+ if original_size != self._size:
+ # update the size option string
+ # -> please note that resizing always sets the
+ # size of this tmpfs mount to an absolute value
+ self._size_option = "size=%dm" % self._size
register_device_format(TmpFS)
--
1.8.3.1
More information about the anaconda-patches
mailing list