Skip to content

Instantly share code, notes, and snippets.

@vasi
Created February 10, 2025 01:57
Show Gist options
  • Save vasi/27a1a69f08a36aef6b21d571f36b4069 to your computer and use it in GitHub Desktop.
Save vasi/27a1a69f08a36aef6b21d571f36b4069 to your computer and use it in GitHub Desktop.
NetBSD orphan finding
#!/usr/bin/env ruby
keepers = IO.readlines(File.join(Dir.home, 'keepers'))
.map(&:chomp)
.map {|s| s.sub(/\s*#.*/, '')}
.reject(&:empty?)
installed = IO.popen(['pkg_info', '-u'])
.readlines
.map {|s| /^(\S+)-[^\s-]+\s/.match(s)[1] }
(installed - keepers).sort.each {|s| puts s}
#!/usr/bin/env ruby
require 'pathname'
require 'inifile'
class PackageSpec
attr_reader :name, :version
def initialize(spec)
@spec = spec
@name, @version = /^(.*)-([^-]*)$/.match(@spec).captures
end
def to_s
@spec
end
end
class Package
def initialize(dir)
@dir = dir
@spec = PackageSpec.new(@dir.basename.to_s)
end
def name
@spec.name
end
def version
@spec.version
end
def installed_info
@installed_info ||= begin
path = @dir.join('+INSTALLED_INFO')
path.exist? ? IniFile.load(path.to_s) : IniFile.new
end
end
def automatic?
installed_info['global']['automatic'] == 'yes'
end
def required_by
@required_by ||= begin
path = @dir.join('+REQUIRED_BY')
path.exist? ? IO.readlines(path.to_s).map(&:chomp).map {|s| PackageSpec.new(s)} : []
end
end
def orphan?
automatic? && required_by.empty?
end
end
class PackageDB
def initialize(dir)
@dir = Pathname.new(dir)
end
def by_name
@by_name ||= @dir.children.select {|c| c.directory?}.each_with_object({}) do |d,h|
p = Package.new(d)
h[p.name] = p
end
end
def packages
by_name.values
end
def [](pkg)
by_name[pkg]
end
def orphans
packages.select(&:orphan?)
end
end
db = PackageDB.new('/usr/pkg/pkgdb')
db.orphans.map(&:name).sort.each {|n| puts n }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment