aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/cask/lib/hbc/pkg.rb
blob: 902d564494dfb73e8e93a80083b54155a6f3d3d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
module Hbc
  class Pkg
    def self.all_matching(regexp, command)
      command.run("/usr/sbin/pkgutil", args: ["--pkgs=#{regexp}"]).stdout.split("\n").map do |package_id|
        new(package_id.chomp, command)
      end
    end

    attr_reader :package_id

    def initialize(package_id, command = SystemCommand)
      @package_id = package_id
      @command = command
    end

    def uninstall
      unless pkgutil_bom_files.empty?
        odebug "Deleting pkg files"
        @command.run("/usr/bin/xargs", args: ["-0", "--", "/bin/rm", "--"], input: pkgutil_bom_files.join("\0"), sudo: true)
      end

      unless pkgutil_bom_specials.empty?
        odebug "Deleting pkg symlinks and special files"
        @command.run("/usr/bin/xargs", args: ["-0", "--", "/bin/rm", "--"], input: pkgutil_bom_specials.join("\0"), sudo: true)
      end

      unless pkgutil_bom_dirs.empty?
        odebug "Deleting pkg directories"
        deepest_path_first(pkgutil_bom_dirs).each do |dir|
          next if MacOS.undeletable?(dir)

          with_full_permissions(dir) do
            clean_broken_symlinks(dir)
            clean_ds_store(dir)
            rmdir(dir)
          end
        end
      end

      if root.directory? && !MacOS.undeletable?(root)
        clean_ds_store(root)
        rmdir(root)
      end

      forget
    end

    def forget
      odebug "Unregistering pkg receipt (aka forgetting)"
      @command.run!("/usr/sbin/pkgutil", args: ["--forget", package_id], sudo: true)
    end

    def pkgutil_bom_files
      @pkgutil_bom_files ||= pkgutil_bom_all.select(&:file?) - pkgutil_bom_specials
    end

    def pkgutil_bom_specials
      @pkgutil_bom_specials ||= pkgutil_bom_all.select(&method(:special?))
    end

    def pkgutil_bom_dirs
      @pkgutil_bom_dirs ||= pkgutil_bom_all.select(&:directory?) - pkgutil_bom_specials
    end

    def pkgutil_bom_all
      @pkgutil_bom_all ||= info.fetch("paths").keys.map { |p| root.join(p) }
    end

    def root
      @root ||= Pathname.new(info.fetch("volume")).join(info.fetch("install-location"))
    end

    def info
      @info ||= @command.run!("/usr/sbin/pkgutil", args: ["--export-plist", package_id])
                        .plist
    end

    private

    def special?(path)
      path.symlink? || path.chardev? || path.blockdev?
    end

    def rmdir(path)
      return unless path.children.empty?
      if path.symlink?
        @command.run!("/bin/rm", args: ["-f", "--", path], sudo: true)
      else
        @command.run!("/bin/rmdir", args: ["--", path], sudo: true)
      end
    end

    def with_full_permissions(path)
      original_mode = (path.stat.mode % 01000).to_s(8)
      original_flags = @command.run!("/usr/bin/stat", args: ["-f", "%Of", "--", path]).stdout.chomp

      @command.run!("/bin/chmod", args: ["--", "777", path], sudo: true)
      yield
    ensure
      if path.exist? # block may have removed dir
        @command.run!("/bin/chmod", args: ["--", original_mode, path], sudo: true)
        @command.run!("/usr/bin/chflags", args: ["--", original_flags, path], sudo: true)
      end
    end

    def deepest_path_first(paths)
      paths.sort_by { |path| -path.to_s.split(File::SEPARATOR).count }
    end

    def clean_ds_store(dir)
      return unless (ds_store = dir.join(".DS_Store")).exist?
      @command.run!("/bin/rm", args: ["--", ds_store], sudo: true)
    end

    # Some packages leave broken symlinks around; we clean them out before
    # attempting to `rmdir` to prevent extra cruft from lying around.
    def clean_broken_symlinks(dir)
      dir.children.select(&method(:broken_symlink?)).each do |path|
        @command.run!("/bin/rm", args: ["--", path], sudo: true)
      end
    end

    def broken_symlink?(path)
      path.symlink? && !path.exist?
    end
  end
end