[PATCH] Add tmpfs support (#918621)
Martin Kolman
mkolman at redhat.com
Thu Oct 3 15:41:44 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 supported
-> just using grow without maxsize will set the size to 100% RAM
-> if maxsize is > RAM, the size will be 100% RAM
-> if maxize is < RAM, the size will be a corresponding percentage of RAM
Signed-off-by: Martin Kolman <mkolman at redhat.com>
---
blivet/__init__.py | 5 +-
blivet/deviceaction.py | 7 ++-
blivet/devices.py | 27 ++++++++++
blivet/formats/fs.py | 141 ++++++++++++++++++++++++++++++++++++++++++++++---
4 files changed, 171 insertions(+), 9 deletions(-)
diff --git a/blivet/__init__.py b/blivet/__init__.py
index d15a0d0..04c7534 100644
--- a/blivet/__init__.py
+++ b/blivet/__init__.py
@@ -1192,6 +1192,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.
@@ -1437,7 +1441,6 @@ class Blivet(object):
free += device.format.free
else:
free += device.size
-
return free
def sanityCheck(self):
diff --git a/blivet/deviceaction.py b/blivet/deviceaction.py
index 202a66c..7160cce 100644
--- a/blivet/deviceaction.py
+++ b/blivet/deviceaction.py
@@ -473,8 +473,11 @@ 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)
def cancel(self):
self.device.format = self.origFormat
diff --git a/blivet/devices.py b/blivet/devices.py
index 6650086..dbad590 100644
--- a/blivet/devices.py
+++ b/blivet/devices.py
@@ -3760,6 +3760,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 "tmpfs"
+
+
class FileDevice(StorageDevice):
""" A file on a filesystem.
diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py
index 13896c9..88d1035 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 for use by anaconda. """
import math
import os
import sys
@@ -1406,7 +1402,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
@@ -1451,6 +1447,139 @@ register_device_format(SysFS)
class TmpFS(NoDevFS):
_type = "tmpfs"
+ _supported = True
+ _resizable = True
+ # as tmpfs is part of the Linux kernel,
+ # I guess 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"
+
+ # 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")
+ grow = kwargs.get("grow")
+ maxsize = kwargs.get("maxsize")
+ system_ram = util.total_memory() / 1024 # kB to MB
+ size_option = ""
+ if fsoptions:
+ self._appendOptions(fsoptions)
+ if self._size:
+ # absolute size for the tmpfs mount has been specified
+ self._appendOptions("size=%dm" % self._size)
+ elif grow:
+ # grow to 100% RAM
+ size_option = "size=100%%"
+ fs_size = system_ram
+ # check if maxsize is defined and < RAM
+ if maxsize and maxsize < system_ram:
+ # as maxsize is smaller than RAM, filesystem size = maxsize
+ fs_size = maxsize
+ # maxsize is less than size of RAM
+ # -> convert to % of RAM
+ size_fraction = maxsize / float(system_ram)
+ # "size=%1.0f%%" % (0.5555*100) -> "size=56%"
+ size_option = "size=%1.0f%%" % (size_fraction * 100)
+ self._size = fs_size
+ else:
+ # if no size option is specified, the tmpfs mount size will be 50%
+ # of system RAM
+ self._size = system_ram*0.5
+ if size_option:
+ self._appendOptions(size_option)
+
+ 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 self._options:
+ return self._options
+ else:
+ return "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, df 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
+
+ @property
+ def device(self):
+ """All the tmpfs mounts use the same "tmpfs" device"""
+ return "tmpfs"
+
+ @device.setter
+ def device(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
+
+ def _appendOptions(self, new_options):
+ """Append free form option string
+ to the options (used among others in fstab)
+ """
+ if self._options: # append to existing options
+ self._options = "%s,%s" % (self._options, new_options)
+ else: # not yet set, just assign it
+ self._options = new_options
register_device_format(TmpFS)
--
1.8.3.1
More information about the anaconda-patches
mailing list