diff --git a/lib/hbc.rb b/lib/hbc.rb index 97e69581d29d..7dee87b3a5fe 100644 --- a/lib/hbc.rb +++ b/lib/hbc.rb @@ -56,7 +56,7 @@ def self.init unless caskroom.exist? ohai "We need to make Caskroom for the first time at #{caskroom}" ohai "We'll set permissions properly so we won't need sudo in the future" - current_user = Etc.getpwuid(Process.euid).name + current_user = Hbc::Utils.current_user if caskroom.parent.writable? system '/bin/mkdir', '--', caskroom else diff --git a/lib/hbc/artifact/moved.rb b/lib/hbc/artifact/moved.rb index ef6006521e1b..d78a5642f1bd 100644 --- a/lib/hbc/artifact/moved.rb +++ b/lib/hbc/artifact/moved.rb @@ -22,7 +22,7 @@ def install_phase each_artifact do |artifact| load_specification(artifact) next unless preflight_checks - delete if File.exist?(target) + delete if File.exist?(target) && force move end end @@ -91,7 +91,15 @@ def warning_target_exists def delete ohai "Removing #{english_name}: '#{target}'" - target.rmtree + case + when Hbc::MacOS.undeletable?(target) + raise Hbc::CaskError.new( + "Cannot remove undeletable #{english_name}") + when force + Hbc::Utils.permissions_rmtree(target, command: @command) + else + target.rmtree + end end def summarize_artifact(artifact_spec) diff --git a/lib/hbc/artifact/uninstall_base.rb b/lib/hbc/artifact/uninstall_base.rb index b3bcf375f654..1b46eaae12e2 100644 --- a/lib/hbc/artifact/uninstall_base.rb +++ b/lib/hbc/artifact/uninstall_base.rb @@ -1,4 +1,3 @@ -require 'set' require 'pathname' class Hbc::Artifact::UninstallBase < Hbc::Artifact::Base @@ -8,138 +7,6 @@ class Hbc::Artifact::UninstallBase < Hbc::Artifact::Base PATH_ARG_SLICE_SIZE = 500 - # todo: There should be a way to specify a containing - # directory under which nothing can be deleted. - # - # This set should be merged with the SYSTEM_DIRS - # set found in lib/cask/pkg.rb. - UNDELETABLE_PATHS = Set.new [ - '~/', - '~/Applications', - '~/Desktop', - '~/Documents', - '~/Downloads', - '~/Mail', - '~/Movies', - '~/Music', - '~/Music/iTunes', - '~/Music/iTunes/iTunes Music', - '~/Music/iTunes/Album Artwork', - '~/News', - '~/Pictures', - '~/Pictures/Desktops', - '~/Pictures/Photo Booth', - '~/Pictures/iChat Icons', - '~/Pictures/iPhoto Library', - '~/Public', - '~/Sites', - '~/Library', - '~/Library/.localized', - '~/Library/Accessibility', - '~/Library/Accounts', - '~/Library/Address Book Plug-Ins', - '~/Library/Application Scripts', - '~/Library/Application Support', - '~/Library/Application Support/Apple', - '~/Library/Application Support/com.apple.AssistiveControl', - '~/Library/Application Support/com.apple.QuickLook', - '~/Library/Application Support/com.apple.TCC', - '~/Library/Assistants', - '~/Library/Audio', - '~/Library/Automator', - '~/Library/Autosave Information', - '~/Library/Caches', - '~/Library/Calendars', - '~/Library/ColorPickers', - '~/Library/ColorSync', - '~/Library/Colors', - '~/Library/Components', - '~/Library/Compositions', - '~/Library/Containers', - '~/Library/Contextual Menu Items', - '~/Library/Cookies', - '~/Library/DTDs', - '~/Library/Desktop Pictures', - '~/Library/Developer', - '~/Library/Dictionaries', - '~/Library/DirectoryServices', - '~/Library/Displays', - '~/Library/Documentation', - '~/Library/Extensions', - '~/Library/Favorites', - '~/Library/FileSync', - '~/Library/Filesystems', - '~/Library/Filters', - '~/Library/FontCollections', - '~/Library/Fonts', - '~/Library/Frameworks', - '~/Library/GameKit', - '~/Library/Graphics', - '~/Library/Group Containers', - '~/Library/Icons', - '~/Library/IdentityServices', - '~/Library/Image Capture', - '~/Library/Images', - '~/Library/Input Methods', - '~/Library/Internet Plug-Ins', - '~/Library/InternetAccounts', - '~/Library/iTunes', - '~/Library/KeyBindings', - '~/Library/Keyboard Layouts', - '~/Library/Keychains', - '~/Library/LaunchAgents', - '~/Library/LaunchDaemons', - '~/Library/LocationBundles', - '~/Library/LoginPlugins', - '~/Library/Logs', - '~/Library/Mail', - '~/Library/Mail Downloads', - '~/Library/Messages', - '~/Library/Metadata', - '~/Library/Mobile Documents', - '~/Library/MonitorPanels', - '~/Library/OpenDirectory', - '~/Library/PDF Services', - '~/Library/PhonePlugins', - '~/Library/Phones', - '~/Library/PreferencePanes', - '~/Library/Preferences', - '~/Library/Printers', - '~/Library/PrivateFrameworks', - '~/Library/PubSub', - '~/Library/QuickLook', - '~/Library/QuickTime', - '~/Library/Receipts', - '~/Library/Recent Servers', - '~/Library/Recents', - '~/Library/Safari', - '~/Library/Saved Application State', - '~/Library/Screen Savers', - '~/Library/ScreenReader', - '~/Library/ScriptingAdditions', - '~/Library/ScriptingDefinitions', - '~/Library/Scripts', - '~/Library/Security', - '~/Library/Services', - '~/Library/Sounds', - '~/Library/Speech', - '~/Library/Spelling', - '~/Library/Spotlight', - '~/Library/StartupItems', - '~/Library/StickiesDatabase', - '~/Library/Sync Services', - '~/Library/SyncServices', - '~/Library/SyncedPreferences', - '~/Library/TextEncodings', - '~/Library/User Pictures', - '~/Library/Video', - '~/Library/Voices', - '~/Library/WebKit', - '~/Library/WidgetResources', - '~/Library/Widgets', - '~/Library/Workflows', - ].map{|x| %r{\A~}.match(x) ? Pathname.new(x).expand_path : Pathname.new(x)} - ORDERED_DIRECTIVES = [ :early_script, :launchctl, @@ -175,7 +42,7 @@ def self.remove_relative_path_strings(action, path_strings) def self.remove_undeletable_path_strings(action, path_strings) undeletable = path_strings.map do |path_string| - path_string if UNDELETABLE_PATHS.include?(Pathname.new(path_string)) + path_string if Hbc::MacOS.undeletable?(Pathname.new(path_string)) end.compact undeletable.each do |path_string| opoo %Q{Skipping #{action} for undeletable path #{path_string}} diff --git a/lib/hbc/installer.rb b/lib/hbc/installer.rb index 1489ac6e60ab..7a46a57a3a83 100644 --- a/lib/hbc/installer.rb +++ b/lib/hbc/installer.rb @@ -312,41 +312,8 @@ def zap purge_caskroom_path end - # this feels like a class method, but uses @command def permissions_rmtree(path) - if path.respond_to?(:rmtree) and path.exist? - tried_permissions = false - tried_ownership = false - begin - path.rmtree - rescue StandardError => e - # in case of permissions problems - unless tried_permissions - # todo Better handling for the case where path is a symlink. - # The -h and -R flags cannot be combined, and behavior is - # dependent on whether the file argument has a trailing - # slash. This should do the right thing, but is fragile. - @command.run!('/usr/bin/chflags', :args => ['-R', '--', '000', path]) - @command.run!('/bin/chmod', :args => ['-R', '--', 'u+rwx', path]) - @command.run!('/bin/chmod', :args => ['-R', '-N', path]) - tried_permissions = true - retry # rmtree - end - unless tried_ownership - # in case of ownership problems - # todo Further examine files to see if ownership is the problem - # before using sudo+chown - ohai "Using sudo to gain ownership of path '#{path}'" - current_user = Etc.getpwuid(Process.euid).name - @command.run('/usr/sbin/chown', :args => ['-R', '--', current_user, path], - :sudo => true) - tried_ownership = true - # retry chflags/chmod after chown - tried_permissions = false - retry # rmtree - end - end - end + Hbc::Utils.permissions_rmtree(path, command: @command) end def purge_versioned_files diff --git a/lib/hbc/macos.rb b/lib/hbc/macos.rb index bd564065df01..0ac3e01930ef 100644 --- a/lib/hbc/macos.rb +++ b/lib/hbc/macos.rb @@ -1,10 +1,377 @@ # originally from Homebrew +require 'set' + require 'hbc/macos/release' module Hbc::MacOS extend self + 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) }.freeze + + # todo: There should be a way to specify a containing + # directory under which nothing can be deleted. + UNDELETABLE_DIRS = Set.new [ + '~/', + '~/Applications', + '~/Desktop', + '~/Documents', + '~/Downloads', + '~/Mail', + '~/Movies', + '~/Music', + '~/Music/iTunes', + '~/Music/iTunes/iTunes Music', + '~/Music/iTunes/Album Artwork', + '~/News', + '~/Pictures', + '~/Pictures/Desktops', + '~/Pictures/Photo Booth', + '~/Pictures/iChat Icons', + '~/Pictures/iPhoto Library', + '~/Public', + '~/Sites', + '~/Library', + '~/Library/.localized', + '~/Library/Accessibility', + '~/Library/Accounts', + '~/Library/Address Book Plug-Ins', + '~/Library/Application Scripts', + '~/Library/Application Support', + '~/Library/Application Support/Apple', + '~/Library/Application Support/com.apple.AssistiveControl', + '~/Library/Application Support/com.apple.QuickLook', + '~/Library/Application Support/com.apple.TCC', + '~/Library/Assistants', + '~/Library/Audio', + '~/Library/Automator', + '~/Library/Autosave Information', + '~/Library/Caches', + '~/Library/Calendars', + '~/Library/ColorPickers', + '~/Library/ColorSync', + '~/Library/Colors', + '~/Library/Components', + '~/Library/Compositions', + '~/Library/Containers', + '~/Library/Contextual Menu Items', + '~/Library/Cookies', + '~/Library/DTDs', + '~/Library/Desktop Pictures', + '~/Library/Developer', + '~/Library/Dictionaries', + '~/Library/DirectoryServices', + '~/Library/Displays', + '~/Library/Documentation', + '~/Library/Extensions', + '~/Library/Favorites', + '~/Library/FileSync', + '~/Library/Filesystems', + '~/Library/Filters', + '~/Library/FontCollections', + '~/Library/Fonts', + '~/Library/Frameworks', + '~/Library/GameKit', + '~/Library/Graphics', + '~/Library/Group Containers', + '~/Library/Icons', + '~/Library/IdentityServices', + '~/Library/Image Capture', + '~/Library/Images', + '~/Library/Input Methods', + '~/Library/Internet Plug-Ins', + '~/Library/InternetAccounts', + '~/Library/iTunes', + '~/Library/KeyBindings', + '~/Library/Keyboard Layouts', + '~/Library/Keychains', + '~/Library/LaunchAgents', + '~/Library/LaunchDaemons', + '~/Library/LocationBundles', + '~/Library/LoginPlugins', + '~/Library/Logs', + '~/Library/Mail', + '~/Library/Mail Downloads', + '~/Library/Messages', + '~/Library/Metadata', + '~/Library/Mobile Documents', + '~/Library/MonitorPanels', + '~/Library/OpenDirectory', + '~/Library/PDF Services', + '~/Library/PhonePlugins', + '~/Library/Phones', + '~/Library/PreferencePanes', + '~/Library/Preferences', + '~/Library/Printers', + '~/Library/PrivateFrameworks', + '~/Library/PubSub', + '~/Library/QuickLook', + '~/Library/QuickTime', + '~/Library/Receipts', + '~/Library/Recent Servers', + '~/Library/Recents', + '~/Library/Safari', + '~/Library/Saved Application State', + '~/Library/Screen Savers', + '~/Library/ScreenReader', + '~/Library/ScriptingAdditions', + '~/Library/ScriptingDefinitions', + '~/Library/Scripts', + '~/Library/Security', + '~/Library/Services', + '~/Library/Sounds', + '~/Library/Speech', + '~/Library/Spelling', + '~/Library/Spotlight', + '~/Library/StartupItems', + '~/Library/StickiesDatabase', + '~/Library/Sync Services', + '~/Library/SyncServices', + '~/Library/SyncedPreferences', + '~/Library/TextEncodings', + '~/Library/User Pictures', + '~/Library/Video', + '~/Library/Voices', + '~/Library/WebKit', + '~/Library/WidgetResources', + '~/Library/Widgets', + '~/Library/Workflows', + ].map { |x| %r{\A~}.match(x) ? + Pathname.new(x).expand_path + : Pathname.new(x) + } + .concat(SYSTEM_DIRS) + .freeze + + def undeletable?(dir) + UNDELETABLE_DIRS.any? { |u| File.identical?(u, dir) } + end + # These Comparable instances can be compared to numerics, strings, or symbols def release_with_patchlevel @@release_with_patchlevel ||= diff --git a/lib/hbc/pkg.rb b/lib/hbc/pkg.rb index 3e7805bd85c6..5934ccee976a 100644 --- a/lib/hbc/pkg.rb +++ b/lib/hbc/pkg.rb @@ -1,231 +1,4 @@ class Hbc::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) @@ -250,7 +23,7 @@ def uninstall end odebug "Deleting pkg directories" _deepest_path_first(pkgutil_bom_dirs).each do |dir| - if dir.exist? and !SYSTEM_DIRS.include?(dir) + if dir.exist? and !Hbc::MacOS.undeletable?(dir) _with_full_permissions(dir) do _clean_broken_symlinks(dir) _clean_ds_store(dir) diff --git a/lib/hbc/staged.rb b/lib/hbc/staged.rb index 9eda64b9e913..fbce3f6a2995 100644 --- a/lib/hbc/staged.rb +++ b/lib/hbc/staged.rb @@ -41,7 +41,7 @@ def set_ownership(paths, user: current_user, group: 'staff') end def current_user - Etc.getpwuid(Process.euid).name + Hbc::Utils.current_user end private diff --git a/lib/hbc/utils.rb b/lib/hbc/utils.rb index c343e7a5fa39..d50567b68971 100644 --- a/lib/hbc/utils.rb +++ b/lib/hbc/utils.rb @@ -159,6 +159,49 @@ def self.rmdir_if_possible(dir) end end + def self.permissions_rmtree(path, options = {}) + command = options.fetch(:command, Hbc::SystemCommand) + if path.respond_to?(:rmtree) and path.exist? + tried_permissions = false + tried_ownership = false + begin + path.rmtree + rescue StandardError => e + # in case of permissions problems + unless tried_permissions + # todo Better handling for the case where path is a symlink. + # The -h and -R flags cannot be combined, and behavior is + # dependent on whether the file argument has a trailing + # slash. This should do the right thing, but is fragile. + command.run('/usr/bin/chflags', must_succeed: false, + args: ['-R', '--', '000', path]) + command.run('/bin/chmod', must_succeed: false, + args: ['-R', '--', 'u+rwx', path]) + command.run('/bin/chmod', must_succeed: false, + args: ['-R', '-N', path]) + tried_permissions = true + retry # rmtree + end + unless tried_ownership + # in case of ownership problems + # todo Further examine files to see if ownership is the problem + # before using sudo+chown + ohai "Using sudo to gain ownership of path '#{path}'" + command.run('/usr/sbin/chown', :args => ['-R', '--', current_user, path], + :sudo => true) + tried_ownership = true + # retry chflags/chmod after chown + tried_permissions = false + retry # rmtree + end + end + end + end + + def self.current_user + Etc.getpwuid(Process.euid).name + end + # originally from Homebrew abv def self.cabv(dir) output = '' diff --git a/spec/cask/macos_spec.rb b/spec/cask/macos_spec.rb new file mode 100644 index 000000000000..ec7c803ee41f --- /dev/null +++ b/spec/cask/macos_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe Hbc::MacOS do + it "says '/' is undeletable" do + expect(Hbc::MacOS).to be_undeletable( + '/') + expect(Hbc::MacOS).to be_undeletable( + '/.') + expect(Hbc::MacOS).to be_undeletable( + '/usr/local/Library/Taps/../../../..') + end + + it "says '/Applications' is undeletable" do + expect(Hbc::MacOS).to be_undeletable( + '/Applications') + expect(Hbc::MacOS).to be_undeletable( + '/Applications/') + expect(Hbc::MacOS).to be_undeletable( + '/Applications/.') + expect(Hbc::MacOS).to be_undeletable( + '/Applications/Mail.app/..') + end + + it "says the home directory is undeletable" do + expect(Hbc::MacOS).to be_undeletable( + Dir.home) + expect(Hbc::MacOS).to be_undeletable( + "#{ Dir.home }/") + expect(Hbc::MacOS).to be_undeletable( + "#{ Dir.home }/Documents/..") + end + + it "says the user library directory is undeletable" do + expect(Hbc::MacOS).to be_undeletable( + "#{ Dir.home }/Library") + expect(Hbc::MacOS).to be_undeletable( + "#{ Dir.home }/Library/") + expect(Hbc::MacOS).to be_undeletable( + "#{ Dir.home }/Library/.") + expect(Hbc::MacOS).to be_undeletable( + "#{ Dir.home }/Library/Preferences/..") + end + + it "says '/Applications/.app' is deletable" do + expect(Hbc::MacOS).not_to be_undeletable( + '/Applications/.app') + end + + it "says '/Applications/SnakeOil Professional.app' is deletable" do + expect(Hbc::MacOS).not_to be_undeletable( + '/Applications/SnakeOil Professional.app') + end +end diff --git a/test/cask/artifact/app_test.rb b/test/cask/artifact/app_test.rb index c326cd60b80b..3276d6c333c3 100644 --- a/test/cask/artifact/app_test.rb +++ b/test/cask/artifact/app_test.rb @@ -67,19 +67,114 @@ File.exist?(cask.staged_path.join('CaffeineAgain.app')).must_equal true end - it "avoids clobbering an existing app" do - cask = local_caffeine + describe "when the target already exists" do + let(:target_path) { + target_path = Hbc.appdir.join('Caffeine.app') + target_path.mkpath + target_path + } + + it "avoids clobbering an existing app" do + cask = local_caffeine - existing_app_path = Hbc.appdir.join('Caffeine.app') - existing_app_path.mkpath + TestHelper.must_output(self, lambda { + Hbc::Artifact::App.new(cask).install_phase + }, "==> It seems there is already an App at '#{target_path}'; not moving.") - TestHelper.must_output(self, lambda { - Hbc::Artifact::App.new(cask).install_phase - }, "==> It seems there is already an App at '#{existing_app_path}'; not moving.") + source_path = cask.staged_path.join('Caffeine.app') + + File.identical?(source_path, target_path).must_equal false + + contents_path = target_path.join('Contents/Info.plist') + File.exist?(contents_path).must_equal false + end + + describe "given the force option" do + let(:install_phase) { + lambda { |given_options = {}| + options = { force: true }.merge(given_options) + Hbc::Artifact::App.new(local_caffeine, options).install_phase + } + } + + let(:chmod_cmd) { + ['/bin/chmod', '-R', '--', 'u+rwx', target_path] + } - source_path = cask.staged_path.join('Caffeine.app') + let(:chmod_n_cmd) { + ['/bin/chmod', '-R', '-N', target_path] + } - File.identical?(source_path, existing_app_path).must_equal false + let(:chflags_cmd) { + ['/usr/bin/chflags', '-R', '--', '000', target_path] + } + + before do + Hbc::Utils.stubs(current_user: 'fake_user') + end + + describe "target is both writable and user-owned" do + it "overwrites the existing app" do + cask = local_caffeine + + expected = [ + "==> It seems there is already an App at '#{target_path}'; overwriting.", + "==> Removing App: '#{target_path}'", + "==> Moving App 'Caffeine.app' to '#{target_path}'" + ] + TestHelper.must_output(self, install_phase, + expected.join("\n")) + + source_path = cask.staged_path.join('Caffeine.app') + + File.exist?(source_path).must_equal false + File.ftype(target_path).must_equal 'directory' + + contents_path = target_path.join('Contents/Info.plist') + File.exist?(contents_path).must_equal true + end + end + + describe "target is user-owned but contains read-only files" do + before do + system '/usr/bin/touch', '--', "#{ target_path }/foo" + system '/bin/chmod', '--', '0555', target_path + end + + it "tries to make the target world-writable" do + Hbc::FakeSystemCommand.expect_and_pass_through(chflags_cmd) + Hbc::FakeSystemCommand.expect_and_pass_through(chmod_cmd) + Hbc::FakeSystemCommand.expect_and_pass_through(chmod_n_cmd) + shutup do + install_phase.call(command: Hbc::FakeSystemCommand) + end + end + + it "overwrites the existing app" do + cask = local_caffeine + + expected = [ + "==> It seems there is already an App at '#{target_path}'; overwriting.", + "==> Removing App: '#{target_path}'", + "==> Moving App 'Caffeine.app' to '#{target_path}'" + ] + TestHelper.must_output(self, install_phase, + expected.join("\n")) + + source_path = cask.staged_path.join('Caffeine.app') + + File.exist?(source_path).must_equal false + File.ftype(target_path).must_equal 'directory' + + contents_path = target_path.join('Contents/Info.plist') + File.exist?(contents_path).must_equal true + end + + after do + system '/bin/chmod', '--', '0755', target_path + end + end + end end it "gives a warning if the source doesn't exist" do diff --git a/test/support/fake_system_command.rb b/test/support/fake_system_command.rb index a98686dcae97..310f36e41286 100644 --- a/test/support/fake_system_command.rb +++ b/test/support/fake_system_command.rb @@ -26,6 +26,13 @@ def self.expects_command(command, response='', times=1) expectations[command] = times end + def self.expect_and_pass_through(command, times=1) + pass_through = lambda do |command, options| + Hbc::SystemCommand.run(command, options) + end + expects_command(command, pass_through, times) + end + def self.verify_expectations! expectations.each do |command, times| unless system_calls[command] == times @@ -34,13 +41,19 @@ def self.verify_expectations! end end - def self.run(command, options={}) - command = Hbc::SystemCommand._process_options(command, options) + def self.run(command_string, options={}) + command = Hbc::SystemCommand._process_options(command_string, options) unless responses.key?(command) fail("no response faked for #{command.inspect}, faked responses are: #{responses.inspect}") end system_calls[command] += 1 - Hbc::SystemCommand::Result.new(command, responses[command], '', 0) + + response = responses[command] + if response.respond_to?(:call) + response.call(command_string, options) + else + Hbc::SystemCommand::Result.new(command, response, '', 0) + end end def self.run!(command, options={}) diff --git a/test/test_helper.rb b/test/test_helper.rb index 3a832eac8bd5..9ee1c6e2eb50 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -98,12 +98,15 @@ def self.fake_response_for(*args) Hbc::FakeFetcher.fake_response_for(*args) end - def self.must_output(test, lambda, expected) + def self.must_output(test, lambda, expected = nil) out, err = test.capture_subprocess_io do lambda.call end - if expected.is_a? Regexp + case + when block_given? + yield (out+err).chomp + when expected.is_a?(Regexp) (out+err).chomp.must_match expected else (out+err).chomp.must_equal expected.gsub(/^ */, '')