Cask upgrade hangs during uninstall

I have been having some issues with brew cask upgrade hanging for a long time (10+ mins) during the “Uninstall” phase. The cask is “pdk” is in the puppetlabs/puppet tap. When I try to upgrade using brew cask, it tries to uninstall first (1). This uninstall step takes quite a long time, and really runs the CPU fans while it is going, making me think it is doing something complicated. I ran top during this hanging process and it seemed like “opendirectory” was really working hard. That seems a bit strange to me, so I wanted to see if anyone else had seen something similar. PDK has over 77,000 files in the package, so it may be the sort of thing that doesn’t really show itself on “smaller” packages. Maybe it is running some sort of “lstat” or whatever against each file, and because my system is bound to AD, it is causing a “lookup” for each of the 77,000+ files? I am honestly just speculating, and for all I know, it could be the (explicative) Symantec Endpoint Protection they force us to use or some combination of issues.

(1) I am guessing doing the “Uninstall” first is a best practice? I can’t see anything in their cask configuration that indicates that it needs to be uninstalled before upgrading. I certainly wouldn’t uninstall before doing an upgrade by hand (just double click the new pkg file).

EDIT: Found that puppet-agent also needed upgraded, so I ran it with --debug --verbose, and wow, there is some badness here…

==> Uninstalling packages:
/usr/sbin/pkgutil --pkgs=com.puppetlabs.puppet-agent
com.puppetlabs.puppet-agent
/usr/sbin/pkgutil --files com.puppetlabs.puppet-agent
/usr/sbin/pkgutil --pkg-info-plist com.puppetlabs.puppet-agent
==> Deleting pkg files
/usr/bin/sudo -E -- /usr/bin/xargs -0 -- /bin/rm --
==> Deleting pkg symlinks and special files
/usr/bin/sudo -E -- /usr/bin/xargs -0 -- /bin/rm --
==> Deleting pkg directories
/usr/bin/stat -f \%Of -- /opt/puppetlabs/puppet/lib/ruby/gems/2.5.0/gems/test-unit-3.2.7/lib/test/unit/ui/xml
/usr/bin/sudo -E -- /bin/chmod -- 777 /opt/puppetlabs/puppet/lib/ruby/gems/2.5.0/gems/test-unit-3.2.7/lib/test/unit/ui/xml
/usr/bin/sudo -E -- /bin/rmdir -- /opt/puppetlabs/puppet/lib/ruby/gems/2.5.0/gems/test-unit-3.2.7/lib/test/unit/ui/xml
/usr/bin/stat -f \%Of -- /opt/puppetlabs/puppet/lib/ruby/gems/2.5.0/gems/gettext-3.2.2/samples/cgi/locale/ja/LC_MESSAGES
/usr/bin/sudo -E -- /bin/chmod -- 777 /opt/puppetlabs/puppet/lib/ruby/gems/2.5.0/gems/gettext-3.2.2/samples/cgi/locale/ja/LC_MESSAGES
(((SNIP)))
/usr/bin/stat -f \%Of -- /opt/puppetlabs/pxp-agent
/usr/bin/sudo -E -- /bin/chmod -- 777 /opt/puppetlabs/pxp-agent
/usr/bin/sudo -E -- /bin/rmdir -- /opt/puppetlabs/pxp-agent
/usr/bin/stat -f \%Of -- /opt/puppetlabs/facter
/usr/bin/sudo -E -- /bin/chmod -- 777 /opt/puppetlabs/facter
/usr/bin/sudo -E -- /bin/rmdir -- /opt/puppetlabs/facter
/usr/bin/stat -f \%Of -- /var/log/puppetlabs
/usr/bin/sudo -E -- /bin/chmod -- 777 /var/log/puppetlabs
/usr/bin/sudo -E -- /bin/chmod -- 755 /var/log/puppetlabs
/usr/bin/sudo -E -- /usr/bin/chflags -- 0 /var/log/puppetlabs
/usr/bin/stat -f \%Of -- /opt/puppetlabs
/usr/bin/sudo -E -- /bin/chmod -- 777 /opt/puppetlabs
/usr/bin/sudo -E -- /bin/chmod -- 755 /opt/puppetlabs
/usr/bin/sudo -E -- /usr/bin/chflags -- 0 /opt/puppetlabs
/usr/bin/stat -f \%Of -- /etc/paths.d
/usr/bin/sudo -E -- /bin/chmod -- 777 /etc/paths.d
/usr/bin/sudo -E -- /bin/chmod -- 755 /etc/paths.d
/usr/bin/sudo -E -- /usr/bin/chflags -- 0 /etc/paths.d
==> Unregistering pkg receipt (aka forgetting)
/usr/bin/sudo -E -- /usr/sbin/pkgutil --forget com.puppetlabs.puppet-agent
==> Post-uninstalling artifact of class Cask::Artifact::Uninstall

There has to be a more efficient way than running 2-3+ commands (as sudo) for each directory in the package. The files and “special” (links) went super fast. There are obviously other commands between these, since sometimes it chowns it back to 755?

EDIT 2: After further investigation, it is potentially way more than 2-3 commands as sudo :wink:

Basic Algorithm:
*Sort a list of directories in the package list (Example list if you have PDK installed: pkgutil --only-dirs --files pdk or ~10,800 directories) by depth (deepest fist)

  • For each DIR in the list of directories
    • Save original_permissions (using ruby method)
    • Save original_flags (using /usr/bin/stat -f %Of -- DIR)
    • Run: sudo chmod 777 --- DIR
    • For each FILE in the directory DIR:
      • if FILE is a symlink and its destination does not exist, Run: sudo rm -- FILE
    • If the .DS_Store exists, Run sudo rm -- DIR/.DS_Store
    • If the directory is empty:
      • and If the DIR is actually a symlink, Run sudo rm -f -- DIR
      • otherwise, Run: sudo rmdir -- DIR
    • If the directory still exists (because it was not empty):
      • Run: sudo chmod (original_perms) -- DIR
      • Run: sudo chflags (original_flags) -- DIR

Now, let’s brainstorm a way to reduce the number of commands. Firstly, I am not 100% sure that it is the package manager’s job to remove broken symlinks and .DS_Store files left behind by the UI. Running ~30,000-40,000 sudo commands, just to remove the directories one package seems like we could have some improvement.

Perhaps there is a way to implement some of the algorithm using shell commands, such that it would be a single sudo command? and we wouldn’t have to mess with the permissions? :man_shrugging:

Perhaps we could call a stub script that runs the current algorithm, but the whole thing runs as root, so there is not a need to run so many sudo commands.

DISCUSS! :slight_smile:

Thanks in advance,
Tommy