pkgpurge 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b45d7148c766962be92c49e134e3b910af1b86994f3918a18be3fb7779256e4d
4
+ data.tar.gz: 10f4b4cc4c9163d9afc3519f2e8cc50b91036f9918c10adfe50ece85e590953f
5
+ SHA512:
6
+ metadata.gz: a4949cde5ab1aac508265d49735f0f49154e26be5001fd250e75f5cacbc7cd858dc7cea50e8c23f79d9b6d79ac64942c90a3bd1d2228c861050af5bb10682251
7
+ data.tar.gz: 640543ed8133e7dd3b08d82dfe2d977711ec1ca778170e86e6762ea2dffea9a10e486c49a942c29fd9b7beb5dd7dbfe907b3fb4d8b20d4b25799d7b5166f0afd
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2019 Yoshimasa Niwa
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ pkgpurge
2
+ ========
3
+
4
+ NAME
5
+ ----
6
+
7
+ `pkgpurge` -- A simple helper tool to purge `installer(8)` packages.
8
+
9
+ SYNOPSIS
10
+ --------
11
+
12
+ pkgpurge COMMAND [OPTIONS...] [ARGS...]
13
+
14
+ DESCRIPTION
15
+ -----------
16
+
17
+ `pkgpurge` is a Ruby gem that provides a command line interface and a Ruby library
18
+ to purge `install(8)` packages.
19
+
20
+ `pkgpurge` command line interface takes next commands.
21
+
22
+ ### `ls PATH`
23
+
24
+ List entries for given receipt plist at `PATH`.
25
+ Similar to `lsbom -pfmugsct PATH_TO_BOM_FILE`
26
+
27
+ ### `verify PATH`
28
+
29
+ Verify entries for given receipt plist at `PATH`.
30
+ Print modified or missing entries.
31
+
32
+ * `--checksum`
33
+
34
+ Verify checksum of each entry. Slow.
35
+ Directories and symbolic links are ignored.
36
+ This command executes `cksum(1)` on each regular file entry.
37
+
38
+ * `--mtime`
39
+
40
+ Verify mtime of each entry. Directories are ignored.
41
+
42
+ ### `ls-purge PATH`
43
+
44
+ List entries that can be purged for given receipt plist at PATH.
45
+ Entries are not modified and if it's a directory, must be empty before listed.
46
+
47
+ * `--checksum`
48
+
49
+ Verify checksum of each entry. Slow.
50
+ Directories and symbolic links are ignored.
51
+ This command executes `cksum(1)` on each regular file entry.
52
+
53
+ * `--mtime`
54
+
55
+ Verify mtime of each entry. Directories are ignored.
data/bin/pkgpurge ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
4
+
5
+ require "pkgpurge"
6
+
7
+ Pkgpurge::CLI.start(ARGV)
@@ -0,0 +1,124 @@
1
+ require "rubygems"
2
+ require "thor"
3
+
4
+ module Pkgpurge
5
+ class CLI < Thor
6
+ desc "ls PATH", "List entries for given receipt plist at PATH"
7
+ def ls(path)
8
+ root = Receipt.new(path).root
9
+
10
+ Entry.traverse_entry_with_path(root, "/") do |entry, path|
11
+ report path, "%o" % entry.mode, entry.uid, entry.gid, entry.size, entry.checksum
12
+ end
13
+ end
14
+
15
+ desc "verify PATH", "Verify entries for given receipt plist at PATH"
16
+ option :checksum, type: :boolean, banner: "Verify checksum of each entry. Slow."
17
+ option :mtime, type: :boolean, banner: "Verify mtime of each entry."
18
+ def verify(path)
19
+ root = Receipt.new(path).root
20
+
21
+ Entry.traverse_entry_with_path(root, "/") do |entry, path|
22
+ verify_entry(entry, path, options).each do |modification|
23
+ report path, *modification
24
+ end
25
+ end
26
+ end
27
+
28
+ desc "ls-purge PATH", "List entries that can be purged for given receipt plist at PATH"
29
+ option :checksum, type: :boolean, banner: "Verify checksum of each entry. Slow."
30
+ option :mtime, type: :boolean, banner: "Verify mtime of each entry."
31
+ def ls_purge(path)
32
+ root = Receipt.new(path).root
33
+
34
+ traverse_purge_entry_with_path(root, "/") do |entry, path|
35
+ if verify_entry(entry, path, options).empty?
36
+ puts path
37
+ true
38
+ else
39
+ false
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def traverse_purge_entry_with_path(entry, parent_path, &block)
47
+ path = File.expand_path(File.join(parent_path, entry.name))
48
+ unless File.exists?(path)
49
+ return true
50
+ end
51
+
52
+ children = if !File.symlink?(path) && File.directory?(path)
53
+ Dir.children(path)
54
+ else
55
+ []
56
+ end
57
+
58
+ empty_children = []
59
+ entry.entries.each do |_, entry|
60
+ if traverse_purge_entry_with_path(entry, path, &block)
61
+ empty_children << entry.name
62
+ end
63
+ end
64
+
65
+ if children.sort == empty_children.sort
66
+ # All children are empty, so we can purge this path
67
+ yield(entry, path)
68
+ else
69
+ false
70
+ end
71
+ end
72
+
73
+ def verify_entry(entry, path, options = {})
74
+ modifications = []
75
+
76
+ unless File.exists?(path)
77
+ modifications << ["missing"]
78
+ return modifications
79
+ end
80
+
81
+ stat = File.lstat(path)
82
+ unless stat.mode == entry.mode
83
+ modifications << ["mode", "%o" % entry.mode, "%o" % stat.mode]
84
+ end
85
+ unless stat.uid == entry.uid
86
+ modifications << ["uid", entry.uid, stat.uid]
87
+ end
88
+ unless stat.gid == entry.gid
89
+ modifications << ["gid", entry.gid, stat.gid]
90
+ end
91
+
92
+ if entry.size
93
+ unless stat.size == entry.size
94
+ modifications << ["size", entry.size, stat.size]
95
+ end
96
+ end
97
+
98
+ if options[:checksum] && entry.checksum && !entry.symlink?
99
+ checksum = cksum(path)
100
+ if checksum != entry.checksum
101
+ modifications << ["checksum", entry.checksum, checksum]
102
+ end
103
+ end
104
+
105
+ if options[:mtime] && entry.mtime
106
+ unless stat.mtime == entry.mtime
107
+ modifications << ["mtime", entry.mtime, stat.mtime]
108
+ end
109
+ end
110
+
111
+ modifications
112
+ end
113
+
114
+ CKSUM = "/usr/bin/cksum".freeze
115
+
116
+ def cksum(path)
117
+ Command.run(CKSUM, path).split(/\s/).first.to_i
118
+ end
119
+
120
+ def report(*messages)
121
+ puts messages.join("\t")
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,7 @@
1
+ module Pkgpurge
2
+ module Command
3
+ def self.run(*command)
4
+ IO.popen(command){|io| io.read}
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,43 @@
1
+ module Pkgpurge
2
+ class Entry
3
+ def self.traverse_entry_with_path(entry, parent_path = nil, &block)
4
+ if parent_path
5
+ path = File.join(parent_path, entry.name)
6
+ else
7
+ path = entry.name
8
+ end
9
+
10
+ yield(entry, File.expand_path(path))
11
+
12
+ entry.entries.each do |_, entry|
13
+ traverse_entry_with_path(entry, path, &block)
14
+ end
15
+ end
16
+
17
+ attr_reader :name, :mode, :uid, :gid, :size, :checksum, :mtime, :entries
18
+
19
+ def initialize(name, mode, uid, gid, size, checksum, mtime)
20
+ @name = name
21
+ @mode = mode
22
+ @uid = uid
23
+ @gid = gid
24
+ @size = size
25
+ @checksum = checksum
26
+ @mtime = mtime
27
+ @entries = {}
28
+ end
29
+
30
+ # See `/usr/includde/sys/_types/_s_ifmt.h`
31
+ S_IFMT = 0170000
32
+ S_IFDIR = 0040000
33
+ S_IFLNK = 0120000
34
+
35
+ def directory?
36
+ (@mode & S_IFMT) == S_IFDIR
37
+ end
38
+
39
+ def symlink?
40
+ (@mode & S_IFMT) == S_IFLNK
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,80 @@
1
+ require "rubygems"
2
+ require "plist"
3
+ require "pathname"
4
+
5
+ module Pkgpurge
6
+ class Receipt
7
+ def initialize(path)
8
+ @path = path
9
+ end
10
+
11
+ def root
12
+ @root ||= parse!
13
+ end
14
+
15
+ private
16
+
17
+ def parse!
18
+ relative_install_prefix_path = Pathname.new(install_prefix_path).relative_path_from("/").to_s
19
+
20
+ root = nil
21
+ lsbom.lines.each do |line|
22
+ # size and checksum are optional.
23
+ path, mode, uid, gid, size, checksum, mtime = line.chomp.split("\t")
24
+ mode = mode.to_i(8)
25
+ uid = uid.to_i
26
+ gid = gid.to_i
27
+ size = size && size.to_i
28
+ checksum = checksum && checksum.to_i
29
+ mtime = mtime && Time.at(mtime.to_i)
30
+
31
+ current = nil
32
+ path.split("/").each do |path_component|
33
+ if path_component == "."
34
+ unless current
35
+ if !root
36
+ root = Entry.new(path_component, mode, uid, gid, size, checksum, mtime)
37
+ end
38
+ current = root
39
+ next
40
+ end
41
+ raise "Unexpected path: #{path}"
42
+ end
43
+
44
+ entry = current.entries[path_component]
45
+ unless entry
46
+ entry = Entry.new(path_component, mode, uid, gid, size, checksum, mtime)
47
+ current.entries[path_component] = entry
48
+ break
49
+ end
50
+
51
+ current = entry
52
+ end
53
+ end
54
+
55
+ root
56
+ end
57
+
58
+ LSBOM = "/usr/bin/lsbom".freeze
59
+ PLIST_BUDDY = "/usr/libexec/PlistBuddy".freeze
60
+
61
+ def lsbom
62
+ @lsbom ||= begin
63
+ bompath = "#{File.join(File.dirname(@path), File.basename(@path, ".plist"))}.bom"
64
+ # file, mode, uid, gid, size, checksum, mtime
65
+ Command.run(LSBOM, "-pfmugsct", bompath)
66
+ end
67
+ end
68
+
69
+ def install_prefix_path
70
+ plist["InstallPrefixPath"]
71
+ end
72
+
73
+ def plist
74
+ @plist ||= begin
75
+ plist_xml = Command.run(PLIST_BUDDY, "-x", "-c", "Print", @path)
76
+ Plist.parse_xml(plist_xml)
77
+ end
78
+ end
79
+ end
80
+ end
data/lib/pkgpurge.rb ADDED
@@ -0,0 +1,6 @@
1
+ module Pkgpurge
2
+ autoload :CLI, "pkgpurge/cli"
3
+ autoload :Command, "pkgpurge/command"
4
+ autoload :Entry, "pkgpurge/entry"
5
+ autoload :Receipt, "pkgpurge/receipt"
6
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pkgpurge
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yoshimasa Niwa
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-02-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: plist
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A simple helper tool to purge `intaller(8)` packages
70
+ email:
71
+ - niw@niw.at
72
+ executables:
73
+ - pkgpurge
74
+ extensions: []
75
+ extra_rdoc_files:
76
+ - LICENSE
77
+ - README.md
78
+ files:
79
+ - LICENSE
80
+ - README.md
81
+ - bin/pkgpurge
82
+ - lib/pkgpurge.rb
83
+ - lib/pkgpurge/cli.rb
84
+ - lib/pkgpurge/command.rb
85
+ - lib/pkgpurge/entry.rb
86
+ - lib/pkgpurge/receipt.rb
87
+ homepage: https://github.com/niw/pkgpurge
88
+ licenses: []
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubygems_version: 3.0.1
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: A simple helper tool to purge `intaller(8)` packages
109
+ test_files: []