[PATCH] Add tmpfs support (#918621)

Martin Kolman mkolman at redhat.com
Mon Oct 7 15:17:50 UTC 2013


On Fri, 2013-10-04 at 11:18 -0500, David Lehman wrote:
> 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.
Sure. :)

> 
> > 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.
OK.
> 
> >  
> >      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.
OK.
> 
> > +
> > +
> >  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."
Yeah, makes sense. :)

> 
> >  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.
It actually is resizable - once mounted, it can be remounted with a
different size. [1][2]
As it is implemented over the kernel page cache, I guess this just
changes the cap on number of pages it can use. I'll have to check what
it supports setting to limit to a smaller value and also what it does if
you try to resize a full mount.

The question is if this needs to be supported at this stage. At least
for use during the installation, this is not really needed.

Any idea if the OpenLMI people might want this ?

[1]
http://ihazem.wordpress.com/2011/03/08/increase-size-of-tmpfs-file-system/
[2] http://philippe.dupriez.be/2010/07/01/linux-hot-resize-tmpfs/

> 
> > +
> > +    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.
OK.
> 
> > +
> > +    @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)
> >  
> 
> 
> _______________________________________________
> anaconda-patches mailing list
> anaconda-patches at lists.fedorahosted.org
> https://lists.fedorahosted.org/mailman/listinfo/anaconda-patches




More information about the anaconda-patches mailing list