[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