class Cask::Pkg SYSTEM_DIRS = [ '/', '/Applications', '/Applications/Utilities', '/Incompatible Software', '/Library', '/Library/Application Support', '/Library/Audio', '/Library/Caches', '/Library/ColorPickers', '/Library/ColorSync', '/Library/Components', '/Library/Compositions', '/Library/Contextual Menu Items', '/Library/CoreMediaIO', '/Library/Desktop Pictures', '/Library/Developer', '/Library/Dictionaries', '/Library/DirectoryServices', '/Library/Documentation', '/Library/Extensions', '/Library/Filesystems', '/Library/Fonts', '/Library/Frameworks', '/Library/Graphics', '/Library/Image Capture', '/Library/Input Methods', '/Library/Internet Plug-Ins', '/Library/Java', '/Library/Keyboard Layouts', '/Library/Keychains', '/Library/LaunchAgents', '/Library/LaunchDaemons', '/Library/Logs', '/Library/Messages', '/Library/Modem Scripts', '/Library/OpenDirectory', '/Library/PDF Services', '/Library/Perl', '/Library/PreferencePanes', '/Library/Preferences', '/Library/Printers', '/Library/PrivilegedHelperTools', '/Library/Python', '/Library/QuickLook', '/Library/QuickTime', '/Library/Receipts', '/Library/Ruby', '/Library/Sandbox', '/Library/Screen Savers', '/Library/ScriptingAdditions', '/Library/Scripts', '/Library/Security', '/Library/Speech', '/Library/Spelling', '/Library/Spotlight', '/Library/StartupItems', '/Library/SystemProfiler', '/Library/Updates', '/Library/User Pictures', '/Library/Video', '/Library/WebServer', '/Library/Widgets', '/Library/iTunes', '/Network', '/System', '/System/Library', '/System/Library/Accessibility', '/System/Library/Accounts', '/System/Library/Address Book Plug-Ins', '/System/Library/Assistant', '/System/Library/Automator', '/System/Library/BridgeSupport', '/System/Library/Caches', '/System/Library/ColorPickers', '/System/Library/ColorSync', '/System/Library/Colors', '/System/Library/Components', '/System/Library/Compositions', '/System/Library/CoreServices', '/System/Library/DTDs', '/System/Library/DirectoryServices', '/System/Library/Displays', '/System/Library/Extensions', '/System/Library/Filesystems', '/System/Library/Filters', '/System/Library/Fonts', '/System/Library/Frameworks', '/System/Library/Graphics', '/System/Library/IdentityServices', '/System/Library/Image Capture', '/System/Library/Input Methods', '/System/Library/InternetAccounts', '/System/Library/Java', '/System/Library/KerberosPlugins', '/System/Library/Keyboard Layouts', '/System/Library/Keychains', '/System/Library/LaunchAgents', '/System/Library/LaunchDaemons', '/System/Library/LinguisticData', '/System/Library/LocationBundles', '/System/Library/LoginPlugins', '/System/Library/Messages', '/System/Library/Metadata', '/System/Library/MonitorPanels', '/System/Library/OpenDirectory', '/System/Library/OpenSSL', '/System/Library/Password Server Filters', '/System/Library/PerformanceMetrics', '/System/Library/Perl', '/System/Library/PreferencePanes', '/System/Library/Printers', '/System/Library/PrivateFrameworks', '/System/Library/QuickLook', '/System/Library/QuickTime', '/System/Library/QuickTimeJava', '/System/Library/Recents', '/System/Library/SDKSettingsPlist', '/System/Library/Sandbox', '/System/Library/Screen Savers', '/System/Library/ScreenReader', '/System/Library/ScriptingAdditions', '/System/Library/ScriptingDefinitions', '/System/Library/Security', '/System/Library/Services', '/System/Library/Sounds', '/System/Library/Speech', '/System/Library/Spelling', '/System/Library/Spotlight', '/System/Library/StartupItems', '/System/Library/SyncServices', '/System/Library/SystemConfiguration', '/System/Library/SystemProfiler', '/System/Library/Tcl', '/System/Library/TextEncodings', '/System/Library/User Template', '/System/Library/UserEventPlugins', '/System/Library/Video', '/System/Library/WidgetResources', '/User Information', '/Users', '/Volumes', '/bin', '/boot', '/cores', '/dev', '/etc', '/etc/X11', '/etc/opt', '/etc/sgml', '/etc/xml', '/home', '/libexec', '/lost+found', '/media', '/mnt', '/net', '/opt', '/private', '/private/etc', '/private/tftpboot', '/private/tmp', '/private/var', '/proc', '/root', '/sbin', '/srv', '/tmp', '/usr', '/usr/X11R6', '/usr/bin', '/usr/etc', '/usr/include', '/usr/lib', '/usr/libexec', '/usr/local', '/usr/local/Cellar', '/usr/local/Frameworks', '/usr/local/Library', '/usr/local/bin', '/usr/local/etc', '/usr/local/include', '/usr/local/lib', '/usr/local/libexec', '/usr/local/opt', '/usr/local/share', '/usr/local/share/man', '/usr/local/share/man/man1', '/usr/local/share/man/man2', '/usr/local/share/man/man3', '/usr/local/share/man/man4', '/usr/local/share/man/man5', '/usr/local/share/man/man6', '/usr/local/share/man/man7', '/usr/local/share/man/man8', '/usr/local/share/man/man9', '/usr/local/share/man/mann', '/usr/local/var', '/usr/local/var/lib', '/usr/local/var/lock', '/usr/local/var/run', '/usr/sbin', '/usr/share', '/usr/share/man', '/usr/share/man/man1', '/usr/share/man/man2', '/usr/share/man/man3', '/usr/share/man/man4', '/usr/share/man/man5', '/usr/share/man/man6', '/usr/share/man/man7', '/usr/share/man/man8', '/usr/share/man/man9', '/usr/share/man/mann', '/usr/src', '/var', '/var/cache', '/var/lib', '/var/lock', '/var/log', '/var/mail', '/var/run', '/var/spool', '/var/spool/mail', '/var/tmp', ].map{|x| Pathname.new(x)} def self.all_matching(regexp, command) command.run('/usr/sbin/pkgutil', :args => [%Q{--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=Cask::SystemCommand) @package_id = package_id @command = command end def uninstall odebug "Deleting pkg files" pkgutil_bom_files.each_slice(500) do |file_slice| @command.run('/bin/rm', :args => file_slice.unshift('-f', '--'), :sudo => true) end odebug "Deleting pkg symlinks and special files" pkgutil_bom_specials.each_slice(500) do |file_slice| @command.run('/bin/rm', :args => file_slice.unshift('-f', '--'), :sudo => true) end odebug "Deleting pkg directories" _deepest_path_first(pkgutil_bom_dirs).each do |dir| if dir.exist? and !SYSTEM_DIRS.include?(dir) _with_full_permissions(dir) do _clean_broken_symlinks(dir) _clean_ds_store(dir) _rmdir(dir) end end 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 @command.run!('/usr/sbin/pkgutil', :args => ['--only-files', '--files', package_id] ).stdout.split("\n").map { |path| root.join(path) } end def pkgutil_bom_dirs @command.run!('/usr/sbin/pkgutil', :args => ['--only-dirs', '--files', package_id] ).stdout.split("\n").map { |path| root.join(path) } end def pkgutil_bom_all @command.run!('/usr/sbin/pkgutil', :args => ['--files', package_id] ).stdout.split("\n").map { |path| root.join(path) } end def pkgutil_bom_specials pkgutil_bom_all - pkgutil_bom_files - pkgutil_bom_dirs end def root @root ||= Pathname(info.fetch('volume')).join(info.fetch('install-location')) end def info @command.run!('/usr/sbin/pkgutil', :args => ['--pkg-info-plist', package_id] ).plist end def _rmdir(path) if path.children.empty? @command.run!('/bin/rmdir', :args => ['--', path], :sudo => true) end end def _with_full_permissions(path, &block) original_mode = (path.stat.mode % 01000).to_s(8) # todo: similarly read and restore OS X flags (cf man chflags) @command.run!('/bin/chmod', :args => ['--', '777', path], :sudo => true) block.call ensure if path.exist? # block may have removed dir @command.run!('/bin/chmod', :args => ['--', original_mode, path], :sudo => true) end end def _deepest_path_first(paths) paths.sort do |path_a, path_b| path_b.to_s.split('/').count <=> path_a.to_s.split('/').count end end # Some pkgs leave broken symlinks hanging around; we clean them out before # attempting to rmdir to prevent extra cruft from lying around after # uninstall def _clean_broken_symlinks(dir) dir.children.each do |child| if _broken_symlink?(child) @command.run!('/bin/rm', :args => ['--', child], :sudo => true) end end end def _clean_ds_store(dir) ds_store = dir.join('.DS_Store') @command.run!('/bin/rm', :args => ['--', ds_store], :sudo => true) if ds_store.exist? end def _broken_symlink?(path) path.symlink? and !path.exist? end end