ManageIQ/manageiq-smartstate

View on GitHub
lib/fs/MiqFS/MiqFS.rb

Summary

Maintainability
F
3 days
Test Coverage
F
38%
require 'stringio'
require 'find'

require 'fs/MiqFS/FsProbe'

class MiqFS
  attr_accessor :fsType, :dobj, :fsId, :volName

  #
  # Class method to instantiate a MiqFS object
  # to handle the file system type detected on
  # the given disk.
  #
  def self.getFS(dobj, probes = nil)
    return(nil)   if dobj.fs == :none
    return(dobj.fs) if dobj.fs

    if probes.nil? && dobj.dInfo.localDev
      require 'fs/modules/NativeFS'
      return(new(NativeFS, dobj)) if NativeFS.supported?(dobj)
      return(nil)
    end

    if (fsm = FsProbe.getFsMod(dobj, probes))
      fs = new(fsm, dobj)
      dobj.fs = fs
      return(fs)
    end
    dobj.fs = :none if probes.nil? # only if we performed a full probe
    (nil)
  end

  def initialize(fsm, dobj)
    extend(fsm)
    @dobj = dobj
    @cwd = "/"
    @fsId = ''
    @volName = ''
    fs_init
    @fsId = @fsId.to_s

    @findEachPrune = false
    @findEachYield = false
  end

  def umount
    fs_umount if self.respond_to?(:fs_umount)
    nil
  end

  # Return free space in file system.
  def freeBytes
    fs_freeBytes
  end

  #
  # Directory instance methods
  #

  def chdir(dir)
    twd = normalizePath(dir)
    raise "Directory not found: #{twd}" unless fileDirectory?(twd)
    @cwd = twd
  end

  def dirEntries(*dir)
    fs_dirEntries(optDir(dir))
  end

  def dirForeach(*dir, &block)
    ents = fs_dirEntries(optDir(dir))
    ents.each { |e| block.call(e) }
  end

  def pwd
    @cwd
  end

  GLOB_CHARS = '*?[{'
  def isGlob?(str)
    str.count(GLOB_CHARS) != 0
  end

  def dirGlob(glb, *flags, &block)
    return([glb]) unless isGlob?(glb)

    if glb[0, 1] == '/'
      dir = '/'
      glb = glb[1..-1]
    else
      dir = @cwd
    end

    matches = doGlob(glb.split('/'), dir, flags)
    return(matches) unless block_given?

    matches.each do |e|
      block.call(e)
    end
    (false)
  end

  def doGlob(glbArr, dir, flags)
    return [] if !glbArr || glbArr.length == 0

    retArr = []
    glb = glbArr[0]

    dirForeach(dir) do |e|
      if flags.length == 0
        match = File.fnmatch(glb, e)
      else
        match = File.fnmatch(glb, e, flags)
      end
      if match
        if glbArr.length == 1
          retArr << File.join(dir, e)
        else
          next unless fileDirectory?(nf = File.join(dir, e))
          retArr.concat(doGlob(glbArr[1..-1], nf, flags))
        end
      end
    end
    (retArr)
  end

  def dirMkdir(dir)
    fs_dirMkdir(normalizePath(dir))
  end

  def dirRmdir(dir)
    fs_dirRmdir(normalizePath(dir))
  end

  #
  # File instance methods
  #

  def fileExists?(f)
    fs_fileExists?(normalizePath(f))
  end

  def fileFile?(f)
    fs_fileFile?(normalizePath(f))
  end

  def fileDirectory?(f)
    fs_fileDirectory?(normalizePath(f))
  end

  def fileSymLink?(f)
    p = normalizePath(f)
    (fs_isSymLink?(p))
  end

  def fileOpen(f, mode = "r", &block)
    fpf = normalizePath(f)
    fo = fileOpenCommon(fpf, mode, &block)
    (fo)
  end

  def fileOpenLink(f, mode = "r", &block)
    fpf = normalizePath(f)
    fileOpenCommon(fpf, mode, &block)
  end

  def fileOpenCommon(fpf, mode = "r", &block)
    fobj = fs_fileOpen(fpf, mode)
    return(nil) unless fobj
    fo = MiqFile.new(self, fobj)

    if self.respond_to?(:fs_fileObjExtend)
      fo = fs_fileObjExtend(fo)
    end

    if block_given?
      begin
        rv = block.call(fo)
        return(rv)
      ensure
        fileClose(fo)
      end
    end
    (fo)
  end

  def fileClose(mfobj)
    fs_fileClose(mfobj.fobj)
  end

  def fileDelete(f)
    fs_fileDelete(normalizePath(f))
  end

  def fileSize(f)
    fs_fileSize(normalizePath(f))
  end

  def fileBasename(f, *sfx)
    return(File.basename(f)) if sfx.length == 0
    (File.basename(f, sfx[0]))
  end

  def fileDirname(f)
    File.dirname(f)
  end

  def fileExtname(f)
    File.extname(f)
  end

  def fileFnmatch(glb, pth, *flags)
    return(File.fnmatch(glb, pth)) if flags.length == 0
    (File.fnmatch(glb, pth, flags))
  end

  def fileAtime(f)
    fs_fileAtime(normalizePath(f))
  end

  def fileCtime(f)
    fs_fileCtime(normalizePath(f))
  end

  def fileMtime(f)
    fs_fileMtime(normalizePath(f))
  end

  def fileAtime_obj(fo)
    fs_fileAtime_obj(fo)
  end

  def fileCtime_obj(fo)
    fs_fileCtime_obj(fo)
  end

  def fileMtime_obj(fo)
    fs_fileMtime_obj(fo)
  end

  def find(dir, depth = nil, level = 0)
    return if depth && level > depth
    foundFiles = []

    dirEntries = self.dirEntries(dir)
    dirEntries.each do |de|
      next if de == '.' || de == '..'
      fp = File.join(dir, de)
      foundFiles << fp
      foundFiles.concat(find(fp, depth, level + 1)) if self.fileDirectory?(fp)
    end
    (foundFiles)
  end

  def findEach(dir, depth = nil, level = 0, &block)
    return if depth && level > depth
    return unless (dirEntries = self.dirEntries(dir))
    dirEntries.each do |de|
      next if de == '.' || de == '..'
      fp = File.join(dir, de)
      @findEachYield = true
      begin
        yield(fp)
      ensure
        @findEachYield = false
      end
      findEach(fp, depth, level + 1, &block) if self.fileDirectory?(fp) && !@findEachPrune
      @findEachPrune = false
    end
  end

  def findEachPrune
    raise "MiqFS.findEachPrune: findEach not in progress" unless @findEachYield
    @findEachPrune = true
  end

  def rmBranch(dir)
    raise "rmBranch: #{dir} does not exist"   unless self.fileExists?(dir)
    raise "rmBranch: #{dir} is not a directory" unless self.fileDirectory?(dir)

    dirEntries = self.dirEntries(dir)

    dirEntries.each do |de|
      next if de == '.' || de == '..'
      fp = File.join(dir, de)
      if self.fileDirectory?(fp)
        rmBranch(fp)
      else
        fileDelete(fp)
      end
    end if dirEntries
    dirRmdir(dir)
  end

  #
  # Copy files and directories from the VM to the host.
  #
  # FILE -> FILE
  # FILE -> DIR
  # DIR  -> DIR (recursive = true)
  #
  def copyOut(from, to, recursive = false)
    allTargets = []
    from = [from] unless from.kind_of?(Array)
    from.each { |t| allTargets.concat(dirGlob(t)) }

    raise "copyOut: no source files matched" if allTargets.length == 0
    if allTargets.length > 1 || recursive
      raise "copyOut: destination directory does not exist" unless File.exist?(to)
      raise "copyOut: destination must be a directory for multi-file copy" unless File.directory?(to)
    end

    allTargets.each do |f|
      #
      # Copy plain files.
      #
      if fileFile?(f)
        if fileDirectory?(to)
          tf = File.join(to, File.basename(f))
        else
          tf = to
        end
        copyOutSingle(f, tf)
        next
      end

      #
      # If the recursive flag is not set, skip directories.
      #
      next unless recursive

      #
      # Recursively copy directory sub-tree.
      #
      owd = @cwd
      chdir(f)
      td = File.join(to, f)
      Dir.mkdir(td) unless File.exist?(td)
      findEach('.') do |ff|
        tf = File.join(td, ff)
        if fileDirectory?(ff)
          Dir.mkdir(tf)
        elsif fileFile?(ff)
          copyOutSingle(ff, tf)
        end
      end # findEach
      chdir(owd)
    end # allTargets.each
  end

  def copyOutSingle(ff, tf)
    fileOpen(ff) do |ffo|
      tfo = File.new(tf, "wb")
      while (buf = ffo.read(1024))
        tfo.write(buf)
      end
      tfo.close
    end
  end

  #
  # Copy files and directories from the host to the VM.
  #
  # FILE -> FILE
  # FILE -> DIR
  # DIR  -> DIR (recursive = true)
  #
  def copyIn(from, to, recursive = false)
    allTargets = []
    from = [from] unless from.kind_of?(Array)
    from.each { |t| allTargets.concat(Dir.glob(t)) }

    raise "copyIn: no source files matched" if allTargets.length == 0
    if allTargets.length > 1 || recursive
      raise "copyIn: destination directory does not exist" unless self.fileExists?(to)
      raise "copyIn: destination must be a directory for multi-file copy" unless self.fileDirectory?(to)
    end

    allTargets.each do |f|
      #
      # Copy plain files.
      #
      if File.file?(f)
        if self.fileDirectory?(to)
          tf = File.join(to, File.basename(f))
        else
          tf = to
        end
        copyInSingle(f, tf)
        next
      end

      #
      # If the recursive flag is not set, skip directories.
      #
      next unless recursive

      #
      # Recursively copy directory sub-tree.
      #
      owd = Dir.pwd
      Dir.chdir(f)
      td = File.join(to, f)
      dirMkdir(td) unless self.fileExists?(td)
      Find.find('.') do |ff|
        tf = File.join(td, ff)
        if File.directory?(ff)
          dirMkdir(tf) unless self.fileExists?(tf)
        elsif File.file?(ff)
          copyInSingle(ff, tf)
        end
      end # Find.find
      Dir.chdir(owd)
    end # allTargets.each
  end

  def copyInSingle(ff, tf)
    File.open(ff) do |ffo|
      tfo = fileOpen(tf, "wb")
      while (buf = ffo.read(1024))
        tfo.write(buf)
      end
      tfo.close
    end
  end

  def normalizePath(p)
    # At the base FS level, we should never see a drive letter.
    np = File.expand_path(p, @cwd).gsub(/^[a-zA-Z]:/, "")
    # puts "MiqFS::normalizePath: p = #{p}, np = #{np}"
    (np)
  end

  def expandPath(path, dir)
    if path[0, 1] == '/'
      tPath = path
    else
      tPath = File.join(dir, path)
    end

    tpa = tPath.slice('/')
    return(tPath) if tpa.empty?

    rpa = []
    tpa.each do |d|
      case d
      when ".." then rpa.pop
      when "."  then next
      when ""   then next
      else rpa << d
      end
    end
    ("/" + rpa.join("/"))
  end

  def optDir(dir)
    return(@cwd) if dir.length == 0
    (normalizePath(dir[0]))
  end

  #
  # Overridden by fs module if the fs supports symbolic links.
  #
  def fs_supportsSymLinks
    false
  end

  def fs_isSymLink?(_f)
    false
  end

  private :normalizePath, :optDir, :fileOpenCommon
end

class MiqFile
  attr_accessor :fs, :fobj

  def initialize(fs, fobj)
    @fs = fs
    @fobj = fobj
  end

  def seek(amt, whence = IO::SEEK_SET)
    @fs.fs_fileSeek(@fobj, amt, whence)
  end

  def read(len = -1)
    return(@fs.fs_fileRead(@fobj, len)) if len >= 0
    (@fs.fs_fileRead(@fobj, size))
  end

  def write(buf, len = buf.length)
    return(@fs.fs_fileWrite(@fobj, buf, len)) if len >= 0
  end

  def close
    @fs.fs_fileClose(@fobj)
  end

  def size
    @fs.fs_fileSize_obj(@fobj)
  end

  def atime
    @fs.fileAtime_obj(@fobj)
  end

  def ctime
    @fs.fileCtime_obj(@fobj)
  end

  def mtime
    @fs.fileMtime_obj(@fobj)
  end
end