[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

package cleanup script



I have written a (useful?) script to find all packages which have not be
used for a long time and reports this to the user (i.e., some package is
installed but none of its files have been accessed in 60 days).  Would
this be useful?  Is there some other forum where I should post this?

I have attached the script for consideration.  The only requirements are
ruby-1.8 and grep-dctrl.

-Dale Hawkins


#!/usr/bin/ruby
require 'pathname'
require 'getoptlong'

def usage
  $stderr.puts "usage: #{$0} [ -v ]* [ -r ] [ -d days(60) ] <pkg_name> [ ... ]"
  exit 1
end

days = 60
verbose = 0
recursive = false

opts = GetoptLong.new(["--days", "-d", GetoptLong::REQUIRED_ARGUMENT],
                      ["--verbose", "-v", GetoptLong::NO_ARGUMENT],
		      ["--recursive", "-r", GetoptLong::NO_ARGUMENT],
                      ["--help", "-h", GetoptLong::NO_ARGUMENT])

opts.each {|opt,val|
  case opt
  when "--days" then days = val
  when "--verbose" then verbose += 1
#  when "--recursive" then recursive = true
  else
    usage
  end
}

if days.to_i == 0
  $stderr.puts "illegal value given for days: #{days}"
end

days = days.to_i

if ARGV.empty?
  packages = IO.popen('grep-status -FStatus -sPackage -n  "install ok installed"').readlines
else
  packages = ARGV
end

class Pathname
  @@secondsPerDay = 60 * 60 * 24

  def parentInArray(paths)
    paths.any? { | p |
      fnmatch(p)
    }
  end

  def lastAccessed(t = Time.now)
    ((t - self.atime)/@@secondsPerDay).to_i
  end
end

class Package

  @@filesToIgnore = [
    '/usr/lib/menu/*',
    '/usr/share/applications/*',
    '/usr/share/man/*',
    '/usr/share/pixmaps/*',
    '/usr/share/mime-info/*',
    '/usr/lib/mime/packages/*',
    '/usr/share/doc-base/*',
  ]

  @@docDirs = [
    '/usr/share/doc/*',
  ]

  @@binaryDirs = [
    '/lib/*',
    '/usr/lib/*',
    '/bin/*',
    '/usr/bin/*',
  ]

  def initialize(name)
    @name = name
  end

  def getFiles
    if not defined? @files

      @files = IO.popen("grep-dctrl -FStatus ' installed' /var/lib/dpkg/status | grep-dctrl -PX -n -s Package #{@name}").collect { | pkg |
        IO.popen("dpkg --listfiles #{pkg}").collect { | fn |
          fn.chomp!
          if fn.empty?
            false
          else
            p = Pathname.new(fn.chomp)
          end
         }.select { | p |
          p && (p.file? && (! p.parentInArray(@@filesToIgnore)))
        }
      }.flatten

    end

    if @files.empty?
      raise "No regular files found (or package not installed)"
    end

    @files
  end

  def docFiles
    if not defined? @docFiles
      @docFiles = getFiles.select { | p |
        p.parentInArray(@@docDirs)
      }.sort {|x,y| y.atime <=> x.atime }
    end
    @docFiles
  end

  def binaryFiles
    if not defined? @binFiles
      @binFiles = getFiles.select { | p |
        p.parentInArray(@@binaryDirs)
      }.sort {|x,y| y.atime <=> x.atime }
    end
    @binFiles
  end

  def lastDocAccess
    if docFiles.empty?
      raise "No document files"
    else
      docFiles[0].lastAccessed
    end
  end

  def showlastDocumentAccess(number = 1)
    if docFiles.empty?
      raise "No doc files"
    else
      access_times = (0...([number, docFiles.size].min)).collect { | n |
        "#{docFiles[n]} was last accessed #{docFiles[n].lastAccessed} days ago"
      }
    end
  end

  def lastBinaryAccess
    if binaryFiles.empty?
      raise "No binary files"
    else
      binaryFiles[0].lastAccessed
    end
  end

  def showlastBinaryAccess(number = 1)
    if binaryFiles.empty?
      raise "No binary files"
    else
      access_times = (0...([number, binaryFiles.size].min)).collect { | n |
        "#{binaryFiles[n]} was last accessed #{binaryFiles[n].lastAccessed} days ago"
      }
    end
  end

  def getWhatDeps
    IO.popen("grep-status -n -s Package -FDepends -e #{@name}").readlines
  end

end


packages.each { | pn |

  pn.chomp!

  next if pn.empty?

  if verbose > 0
    puts "=============== #{pn} ==============="
  end

  success = false
  p = Package.new(pn)

  binDays = 10000
  begin
    binDays = p.lastBinaryAccess
    if verbose > 1
      puts p.showlastBinaryAccess(10).collect { | s |  "  " + s }
    end
    success = true
  rescue
    if verbose > 0
      $stderr.puts $!
    end
  end

  docDays = 10000
  begin
    docDays = p.lastDocAccess
    if verbose > 1
      puts p.showlastDocumentAccess(10).collect { | s |  "  " + s }
    end
    success = true
  rescue
    if verbose > 0
      $stderr.puts $!
    end
  end

  if success
    if [binDays, docDays].min > days
      puts "Recommend #{pn} for removal"
      if recursive
        puts p.getWhatDeps
      end
    else
      if verbose > 0
        puts "Keeping #{pn}"
      end
    end
  end


}


Reply to: