[PATCH] Add tmpfs support (#918621)
David Lehman
dlehman at redhat.com
Fri Oct 4 16:18:29 UTC 2013
On Thu, 2013-10-03 at 17:41 +0200, Martin Kolman wrote:
> 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>
Looks good in general, but I have a few comments that didn't occur to me
until this reading. See below.
> ---
> 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):
Please drop the chunk above that only removes a blank line.
> 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)
Please log a message if the udev lookup fails since that's a serious
problem for anything other than tmpfs.
>
> 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"
Just return self.type or self._type here -- one less string.
> +
> +
> 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. """
Hey, that's nice -- glad to see that comment go away after all these
years. While you're at, please change it to just "Filesystem classes."
> 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
Resize (_resizable, _minInstanceSize) are only relevant for preexisting
filesystems, which is impossible with tmpfs, no? If I'm right, let's
simplify by just saying they're not resizable.
> +
> + 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"
If self.device is always "tmpfs" let's just return that from here -- one
less string laying around.
> +
> + @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)
>
More information about the anaconda-patches
mailing list