diff --git a/build/mac/GNUmakefile b/build/mac/GNUmakefile index f11dd04460..d09840e34d 100644 --- a/build/mac/GNUmakefile +++ b/build/mac/GNUmakefile @@ -81,7 +81,7 @@ idb: $(DIST_MANIFEST) @echo "f 644 root wheel Library/LaunchDaemons/io.pcp.pmcd.plist io.pcp.pmcd.plist replace" >> idb @echo "f 644 root wheel Library/LaunchDaemons/io.pcp.pmie.plist io.pcp.pmie.plist replace" >> idb @echo "f 644 root wheel Library/LaunchDaemons/io.pcp.pmlogger.plist io.pcp.pmlogger.plist replace" >> idb - @echo "f 644 root wheel Library/LaunchDaemons/io.pcp.pmproxy.plist io.pcp.pmlogger.plist replace" >> idb + @echo "f 644 root wheel Library/LaunchDaemons/io.pcp.pmproxy.plist io.pcp.pmproxy.plist replace" >> idb @echo idb created extrabits: idb diff --git a/build/mac/HOMEBREW_TAP_UPDATES.md b/build/mac/HOMEBREW_TAP_UPDATES.md new file mode 100644 index 0000000000..1a47d6a1e1 --- /dev/null +++ b/build/mac/HOMEBREW_TAP_UPDATES.md @@ -0,0 +1,157 @@ +# Homebrew Tap Update Recommendations + +This document outlines recommended updates to the `homebrew-performancecopilot` tap to align with the improvements made to the macOS .pkg uninstaller. + +## Repository Location +https://github.com/performancecopilot/homebrew-performancecopilot + +## Files to Update + +### 1. Casks/pcp-perf.rb + +The cask formula should be updated to ensure complete cleanup during uninstall and provide a `zap` option for deep cleanup. + +#### Current uninstall block (verify this is present): +```ruby +uninstall launchctl: [ + "io.pcp.pmcd", + "io.pcp.pmie", + "io.pcp.pmlogger", + "io.pcp.pmproxy", + ], + pkgutil: "io.pcp.performancecopilot" +``` + +#### Recommended enhancement - add explicit plist deletion: +```ruby +uninstall launchctl: [ + "io.pcp.pmcd", + "io.pcp.pmie", + "io.pcp.pmlogger", + "io.pcp.pmproxy", + ], + pkgutil: "io.pcp.performancecopilot", + delete: [ + "/Library/LaunchDaemons/io.pcp.pmcd.plist", + "/Library/LaunchDaemons/io.pcp.pmie.plist", + "/Library/LaunchDaemons/io.pcp.pmlogger.plist", + "/Library/LaunchDaemons/io.pcp.pmproxy.plist", + ] +``` + +#### Add new zap stanza for deep cleanup: +```ruby +zap trash: [ + "/etc/pcp", + "/var/lib/pcp", + "/var/log/pcp", + ] +``` + +This allows users to run `brew uninstall --zap pcp-perf` to remove all configuration and data files. + +### 2. README.md + +Update the README to clarify uninstall options for users: + +```markdown +## Uninstalling + +### Standard Uninstall +To uninstall PCP while preserving configuration and log files: + +```bash +brew uninstall pcp-perf +``` + +This will: +- Stop and remove all PCP services (pmcd, pmie, pmlogger, pmproxy) +- Remove the installed package +- Leave configuration files in `/etc/pcp/` intact +- Leave data files in `/var/lib/pcp/` intact +- Leave log files in `/var/log/pcp/` intact + +### Complete Uninstall +To completely remove PCP including all configuration and data files: + +```bash +brew uninstall --zap pcp-perf +``` + +This performs a standard uninstall plus removal of all configuration, data, and log directories. + +### Manual .pkg Uninstall +If you installed PCP using the .pkg installer (not Homebrew), you can use the bundled uninstall script: + +**Standard Uninstall (preserves config/log files):** +```bash +sudo /usr/local/libexec/pcp/bin/uninstall-pcp +``` + +This will prompt for confirmation and remove PCP, leaving configuration and log files in place with a notice about their location. + +**Complete Uninstall (removes everything):** +```bash +sudo /usr/local/libexec/pcp/bin/uninstall-pcp --force +``` + +This skips the confirmation prompt and removes all PCP files including configuration, data, and logs in `/etc/pcp/`, `/var/lib/pcp/`, and `/var/log/pcp/`. + +### 3. Version Updates + +When a new PCP version is released with these uninstaller improvements: + +1. Update the `version` field in the cask +2. Download the new .dmg and calculate the new SHA256 checksum: + ```bash + shasum -a 256 pcp-X.Y.Z-BUILD.dmg + ``` +3. Update the `sha256` field in the cask +4. Test the installation and uninstallation process + +## Testing Checklist for Homebrew Cask + +After making these updates, test the following scenarios: + +### Install Test +```bash +brew install performancecopilot/performancecopilot/pcp-perf +launchctl list | grep io.pcp # Should show 4 services +ls -la /Library/LaunchDaemons/io.pcp.* # Should show 4 plists +``` + +### Standard Uninstall Test +```bash +brew uninstall pcp-perf +launchctl list | grep io.pcp # Should show nothing +ls -la /Library/LaunchDaemons/io.pcp.* 2>&1 # Should show "No such file" +ls -d /etc/pcp /var/lib/pcp /var/log/pcp 2>&1 # Should still exist +pkgutil --pkgs | grep pcp # Should show nothing +``` + +### Zap Test +```bash +brew install performancecopilot/performancecopilot/pcp-perf +brew uninstall --zap pcp-perf +launchctl list | grep io.pcp # Should show nothing +ls -la /Library/LaunchDaemons/io.pcp.* 2>&1 # Should show "No such file" +ls -d /etc/pcp /var/lib/pcp /var/log/pcp 2>&1 # Should show "No such file" +``` + +## Alignment with .pkg Uninstaller + +The Homebrew cask and the .pkg uninstaller now follow the same principles: + +1. **Service Management**: Both use modern `launchctl` commands to stop services +2. **Package Receipts**: Both use `pkgutil --forget` for modern macOS +3. **Config/Data Preservation**: Both leave configuration and log files in place by default +4. **User Choice**: + - .pkg users can manually delete directories after uninstall + - Homebrew users can use `--zap` for automatic cleanup +5. **Complete Cleanup**: Both remove all LaunchDaemons plists + +## Notes + +- The Homebrew cask handles the `pkgutil` and `launchctl` operations automatically +- The .pkg uninstaller requires manual execution with sudo +- Both methods are now fully compatible and can cleanly uninstall PCP diff --git a/build/mac/TESTING_GUIDE.md b/build/mac/TESTING_GUIDE.md new file mode 100644 index 0000000000..8cc1be78fc --- /dev/null +++ b/build/mac/TESTING_GUIDE.md @@ -0,0 +1,487 @@ +# macOS Uninstaller Testing Guide + +This guide provides instructions for testing the updated PCP uninstaller on macOS. + +## Prerequisites + +- UTM or similar macOS VM environment +- Ability to build PCP .pkg installer +- sudo/admin access on test systems +- macOS versions to test (recommended): + - macOS 11 (Big Sur) - First Apple Silicon release + - macOS 12 (Monterey) + - macOS 13 (Ventura) + - macOS 14 (Sonoma) + - macOS 15 (Sequoia) + +## Building the Installer + +```bash +cd /path/to/pcp +./configure --prefix=/usr --libexecdir=/usr/lib --sysconfdir=/etc --localstatedir=/var +make +./Makepkgs --verbose +``` + +The .dmg file will be created in `build/mac/` directory. + +## Manual Testing Procedure + +### Test 1: Fresh Install and Uninstall + +This test verifies basic installation and uninstallation functionality. + +#### Installation Phase +```bash +# 1. Mount the DMG +hdiutil attach pcp-X.Y.Z-BUILD.dmg + +# 2. Install the .pkg +sudo installer -pkg /Volumes/pcp-X.Y.Z-BUILD/pcp-X.Y.Z-BUILD.pkg -target / + +# 3. Verify installation +launchctl list | grep io.pcp +# Expected: Should show 4 services running: +# io.pcp.pmcd +# io.pcp.pmie +# io.pcp.pmlogger +# io.pcp.pmproxy + +ls -la /Library/LaunchDaemons/io.pcp.* +# Expected: Should show 4 plist files + +pkgutil --pkgs | grep pcp +# Expected: Should show "io.pcp.performancecopilot" + +ls -d /etc/pcp /var/lib/pcp /var/log/pcp +# Expected: All three directories should exist + +ls -la /usr/local/libexec/pcp/bin/uninstall-pcp +# Expected: uninstall-pcp script should exist and be executable +``` + +#### Uninstallation Phase +```bash +# 1. Run the uninstaller +sudo /usr/local/libexec/pcp/bin/uninstall-pcp +# When prompted, type 'y' to confirm + +# 2. Verify services stopped +launchctl list | grep io.pcp +# Expected: No output (all services stopped) + +# 3. Verify plists removed +ls -la /Library/LaunchDaemons/io.pcp.* 2>&1 +# Expected: "No such file or directory" + +# 4. Verify package receipt removed +pkgutil --pkgs | grep pcp +# Expected: No output + +# 5. Verify config/log files still exist +ls -d /etc/pcp /var/lib/pcp /var/log/pcp +# Expected: All three directories should still exist + +# 6. Verify uninstall script removed itself +ls -la /usr/local/libexec/pcp/bin/uninstall-pcp 2>&1 +# Expected: "No such file or directory" + +# 7. Verify warning message was displayed +# Expected: Should have seen message about config/log files remaining +``` + +### Test 2: Service Restart Before Uninstall + +This test verifies that running services are properly stopped. + +```bash +# 1. Install PCP (see Test 1) + +# 2. Verify services are running +launchctl list io.pcp.pmcd +# Expected: Should show service info with PID + +# 3. Run uninstaller +sudo /usr/local/libexec/pcp/bin/uninstall-pcp + +# 4. Verify all services stopped (see Test 1 verification steps) +``` + +### Test 3: Multiple Install/Uninstall Cycles + +This test verifies that the uninstaller can be run multiple times without issues. + +```bash +# 1. Install PCP +# 2. Uninstall PCP +# 3. Install PCP again +# 4. Uninstall PCP again +# 5. Verify complete cleanup (see Test 1 verification steps) +``` + +### Test 4: Uninstall with Running Processes + +This test verifies behavior when PCP processes are actively running. + +```bash +# 1. Install PCP + +# 2. Start using PCP tools +pminfo -f hinv.ncpu & +pmstat 1 & + +# 3. Run uninstaller +sudo /usr/local/libexec/pcp/bin/uninstall-pcp + +# 4. Verify services stopped despite active tools +launchctl list | grep io.pcp +# Expected: No output + +# Note: PCP client tools may continue running briefly, but services should stop +``` + +### Test 5: Force Mode Uninstall + +This test verifies that --force flag removes all PCP files including config/log directories. + +```bash +# 1. Install PCP (see Test 1) + +# 2. Run uninstaller with --force flag (no prompt) +sudo /usr/local/libexec/pcp/bin/uninstall-pcp --force +# Expected: No "Are you sure?" prompt + +# 3. Verify complete removal +ls -d /etc/pcp /var/lib/pcp /var/log/pcp 2>&1 +# Expected: "No such file or directory" for all three + +# 4. Verify services stopped (see Test 1 verification steps) +launchctl list | grep io.pcp +# Expected: No output + +# 5. Verify plists removed +ls -la /Library/LaunchDaemons/io.pcp.* 2>&1 +# Expected: "No such file or directory" +``` + +### Test 6: Manual Config/Log Cleanup (Without Force) + +This test verifies that users can manually clean up remaining files after standard uninstall. + +```bash +# 1. Install and uninstall PCP without --force (see Test 1) + +# 2. Verify config/log directories still exist +ls -d /etc/pcp /var/lib/pcp /var/log/pcp +# Expected: All three directories exist + +# 3. Manually remove config/log directories +sudo rm -rf /etc/pcp /var/lib/pcp /var/log/pcp + +# 4. Verify complete removal +ls -d /etc/pcp /var/lib/pcp /var/log/pcp 2>&1 +# Expected: "No such file or directory" +``` + +### Test 7: Uninstall from Different Working Directories + +This test verifies the safety check that prevents running from BINADM_DIR. + +```bash +# 1. Install PCP + +# 2. Try running from home directory (should work) +cd ~ +sudo /usr/local/libexec/pcp/bin/uninstall-pcp +# Expected: Should proceed normally + +# 3. Install again + +# 4. Try running from BINADM_DIR (should fail) +cd /usr/local/libexec/pcp/bin +sudo ./uninstall-pcp +# Expected: Should display error "Do not run ... from BINADM_DIR" +``` + +### Test 8: Uninstall as Non-Root User + +This test verifies the root user check. + +```bash +# 1. Install PCP + +# 2. Try running without sudo +/usr/local/libexec/pcp/bin/uninstall-pcp +# Expected: Should display error about needing root and suggest using sudo + +# 3. Run with sudo (should work) +sudo /usr/local/libexec/pcp/bin/uninstall-pcp +``` + +## Automated Testing Ideas + +### Option 1: Shell Script Test Suite + +Create a test script that automates the verification steps: + +```bash +#!/bin/bash +# test-uninstaller.sh + +set -e + +PKG_PATH="$1" +if [ -z "$PKG_PATH" ]; then + echo "Usage: $0 /path/to/pcp-X.Y.Z-BUILD.pkg" + exit 1 +fi + +echo "=== Test: Install PCP ===" +sudo installer -pkg "$PKG_PATH" -target / +sleep 5 + +echo "=== Verify: Services running ===" +if ! launchctl list | grep -q io.pcp.pmcd; then + echo "FAIL: pmcd not running" + exit 1 +fi +echo "PASS: Services running" + +echo "=== Test: Uninstall PCP ===" +echo "y" | sudo /usr/local/libexec/pcp/bin/uninstall-pcp +sleep 5 + +echo "=== Verify: Services stopped ===" +if launchctl list | grep -q io.pcp; then + echo "FAIL: Services still running" + exit 1 +fi +echo "PASS: Services stopped" + +echo "=== Verify: Plists removed ===" +if ls /Library/LaunchDaemons/io.pcp.* >/dev/null 2>&1; then + echo "FAIL: Plists still present" + exit 1 +fi +echo "PASS: Plists removed" + +echo "=== Verify: Package receipt removed ===" +if pkgutil --pkgs | grep -q pcp; then + echo "FAIL: Package receipt still present" + exit 1 +fi +echo "PASS: Package receipt removed" + +echo "=== Verify: Config/log files preserved ===" +if [ ! -d /etc/pcp ] || [ ! -d /var/lib/pcp ] || [ ! -d /var/log/pcp ]; then + echo "FAIL: Config/log files removed" + exit 1 +fi +echo "PASS: Config/log files preserved" + +echo "" +echo "=== ALL TESTS PASSED ===" +echo "" +echo "Cleaning up config/log files..." +sudo rm -rf /etc/pcp /var/lib/pcp /var/log/pcp +echo "Done" +``` + +### Option 2: UTM Snapshot-Based Testing + +For testing across multiple macOS versions using UTM: + +1. **Setup Phase**: + - Create UTM VMs for each macOS version + - Take a clean snapshot of each VM (before any PCP installation) + - Name snapshots: `clean-macos-11`, `clean-macos-12`, etc. + +2. **Test Execution**: + ```bash + # For each macOS version: + # 1. Restore to clean snapshot + # 2. Copy .pkg to VM (use shared folder or scp) + # 3. SSH into VM and run test script + # 4. Review results + # 5. Restore to clean snapshot for next test + ``` + +3. **Automation Script** (run on host): + ```bash + #!/bin/bash + # test-all-macos-versions.sh + + VERSIONS=("11" "12" "13" "14" "15") + PKG_PATH="/path/to/pcp-X.Y.Z-BUILD.pkg" + + for ver in "${VERSIONS[@]}"; do + echo "Testing macOS $ver..." + + # Restore VM to clean snapshot (UTM CLI would be used here) + # Start VM + # Copy package to VM + # SSH and run test script + # Collect results + # Stop VM + + echo "macOS $ver test complete" + done + ``` + +### Option 3: Bats Testing Framework + +Use [Bats](https://github.com/bats-core/bats-core) for more structured testing: + +```bash +# test-uninstaller.bats + +setup() { + PKG_PATH="${PKG_PATH:-/tmp/pcp.pkg}" + sudo installer -pkg "$PKG_PATH" -target / + sleep 5 +} + +teardown() { + sudo rm -rf /etc/pcp /var/lib/pcp /var/log/pcp 2>/dev/null || true +} + +@test "Services are running after install" { + run launchctl list io.pcp.pmcd + [ "$status" -eq 0 ] +} + +@test "Uninstaller stops all services" { + echo "y" | sudo /usr/local/libexec/pcp/bin/uninstall-pcp + run launchctl list io.pcp.pmcd + [ "$status" -ne 0 ] +} + +@test "Uninstaller removes plists" { + echo "y" | sudo /usr/local/libexec/pcp/bin/uninstall-pcp + run ls /Library/LaunchDaemons/io.pcp.pmcd.plist + [ "$status" -ne 0 ] +} + +@test "Uninstaller preserves config files" { + echo "y" | sudo /usr/local/libexec/pcp/bin/uninstall-pcp + [ -d /etc/pcp ] + [ -d /var/lib/pcp ] + [ -d /var/log/pcp ] +} +``` + +## Regression Testing + +After any changes to the uninstaller or build process: + +1. Run Test 1 (Fresh Install and Uninstall) on at least two macOS versions +2. Run Test 2 (Service Restart) on one macOS version +3. Run Test 5 (Force Mode Uninstall) to verify complete cleanup +4. Run Test 8 (Non-Root User) to verify security checks + +## Known Issues and Edge Cases + +### Issue: Old PCP Installations +If testing on a system with a very old PCP installation (pre-launchd), manual cleanup may be required before testing. + +### Issue: Disk Space +Ensure VMs have at least 5GB free space for installation and logging. + +### Issue: VM Network +Some tests may require network access for downloading dependencies during PCP build. + +## Test Results Template + +``` +Test Date: YYYY-MM-DD +Tester: [Your Name] +PCP Version: X.Y.Z-BUILD +macOS Version: [Version and Build Number] + +Test 1 (Fresh Install/Uninstall): [PASS/FAIL] + Notes: + +Test 2 (Service Restart): [PASS/FAIL] + Notes: + +Test 3 (Multiple Cycles): [PASS/FAIL] + Notes: + +Test 4 (Running Processes): [PASS/FAIL] + Notes: + +Test 5 (Force Mode Uninstall): [PASS/FAIL] + Notes: + +Test 6 (Manual Cleanup): [PASS/FAIL] + Notes: + +Test 7 (Working Directory): [PASS/FAIL] + Notes: + +Test 8 (Non-Root User): [PASS/FAIL] + Notes: + +Overall Result: [PASS/FAIL] +Additional Comments: +``` + +## Continuous Integration Ideas + +For future automation, consider: + +1. **GitHub Actions with macOS Runners**: + - Use GitHub's macOS runners + - Run automated tests on every PR + - Test on latest macOS only (to stay within CI budget) + +2. **Tart VMs** (macOS on Apple Silicon): + - Use [Tart](https://github.com/cirruslabs/tart) for lightweight macOS VMs + - Automate VM creation and testing + - Works well in CI/CD pipelines + +3. **Homebrew Cask Testing**: + - Use `brew install --cask` in CI + - Run automated uninstall tests + - Verify formula syntax with `brew audit` + +## Quick Smoke Test (5 minutes) + +For rapid testing during development: + +### Standard Uninstall Test +```bash +# Build and install +sudo installer -pkg /path/to/pcp.pkg -target / + +# Verify services running +launchctl list | grep io.pcp | wc -l # Should be 4 + +# Uninstall (standard mode) +echo "y" | sudo /usr/local/libexec/pcp/bin/uninstall-pcp + +# Verify cleanup +launchctl list | grep io.pcp | wc -l # Should be 0 +ls /Library/LaunchDaemons/io.pcp.* 2>&1 | grep -q "No such file" # Should pass +[ -d /etc/pcp ] && echo "Config preserved" || echo "FAIL: Config removed" + +# Manual cleanup +sudo rm -rf /etc/pcp /var/lib/pcp /var/log/pcp +``` + +### Force Mode Uninstall Test +```bash +# Build and install +sudo installer -pkg /path/to/pcp.pkg -target / + +# Verify services running +launchctl list | grep io.pcp | wc -l # Should be 4 + +# Uninstall (force mode - no prompt, complete removal) +sudo /usr/local/libexec/pcp/bin/uninstall-pcp --force + +# Verify complete cleanup +launchctl list | grep io.pcp | wc -l # Should be 0 +ls /Library/LaunchDaemons/io.pcp.* 2>&1 | grep -q "No such file" # Should pass +[ -d /etc/pcp ] && echo "FAIL: Config not removed" || echo "Complete removal successful" +``` diff --git a/build/mac/uninstall-pcp.in b/build/mac/uninstall-pcp.in index 5c33d1101e..5425704c9e 100755 --- a/build/mac/uninstall-pcp.in +++ b/build/mac/uninstall-pcp.in @@ -1,22 +1,47 @@ #!/bin/sh # # Copyright (c) 2003-2004 Silicon Graphics, Inc. All Rights Reserved. -# +# # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2 of the License, or (at your # option) any later version. -# +# # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. -# +# # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # +# Parse command line arguments +FORCE=false +while [ $# -gt 0 ]; do + case "$1" in + -f|--force) + FORCE=true + shift + ;; + -h|--help) + echo "Usage: $0 [-f|--force] [-h|--help]" + echo "" + echo "Options:" + echo " -f, --force Skip confirmation prompt and remove all PCP" + echo " configuration, data, and log directories" + echo " -h, --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + if [ $USER != "root" ] then echo "Only the root user can uninstall PCP, try the following command instead:" @@ -24,13 +49,17 @@ then exit 1 fi -@echo@ @echo_n@ "Are you sure you want to uninstall PCP? [y/n] @echo_c@" -read answer - -if [ "X$answer" != "Xy" -a "X$answer" != "XY" ] +# Interactive confirmation unless force mode +if ! $FORCE then - echo "Uninstall cancelled." - exit 1 + @echo@ @echo_n@ "Are you sure you want to uninstall PCP? [y/n] @echo_c@" + read answer + + if [ "X$answer" != "Xy" -a "X$answer" != "XY" ] + then + echo "Uninstall cancelled." + exit 1 + fi fi here=`pwd` @@ -42,30 +71,105 @@ then exit 1 fi -echo "Stopping PCP..." -/Library/StartupItems/pcp/pmlogger stop -/Library/StartupItems/pcp/pmcd stop -sleep 5 +echo "Stopping PCP services..." + +# Unload and remove launchd services +for service in io.pcp.pmcd io.pcp.pmie io.pcp.pmlogger io.pcp.pmproxy +do + if launchctl list "$service" >/dev/null 2>&1; then + echo " Stopping $service..." + # Try modern bootout first (macOS 10.11+), fall back to unload for older systems + launchctl bootout system "/Library/LaunchDaemons/${service}.plist" 2>/dev/null || \ + launchctl unload "/Library/LaunchDaemons/${service}.plist" 2>/dev/null || true + fi +done + +sleep 2 if [ -x $BINADM_DIR/install-pcp -a -r $BINADM_DIR/idb ] then - echo "Removing Receipts" + echo "Removing package receipts..." + + # Modern macOS package receipt removal + if pkgutil --pkgs | grep -q "io.pcp.performancecopilot" 2>/dev/null; then + pkgutil --forget io.pcp.performancecopilot 2>/dev/null || true + fi + + # Also remove old-style receipts if they exist (for upgrades from old versions) if [ -d /Library/Receipts/pcp-*.pkg ]; then rm -rf /Library/Receipts/pcp-*.pkg fi + + echo "Removing LaunchDaemons plists..." + for plist in io.pcp.pmcd io.pcp.pmie io.pcp.pmlogger io.pcp.pmproxy + do + if [ -f "/Library/LaunchDaemons/${plist}.plist" ]; then + rm -f "/Library/LaunchDaemons/${plist}.plist" + fi + done + echo "Uninstall in background to remove all running scripts" - # Copy to /tmp since inst will remove itself and this script - cp $BINADM_DIR/install-pcp $BINADM_DIR/idb /tmp - # create tidy up script - echo '#!/bin/sh' > /tmp/_pcp_tidy.sh - echo '/tmp/install-pcp -r -l /var/log/pcp_inst.log /tmp/idb && /bin/rm -rf /Library/Receipts/pcp-*' >> /tmp/_pcp_tidy.sh - echo '/bin/rm -f /tmp/install-pcp /tmp/idb /tmp/_pcp_tidy.sh' >> /tmp/_pcp_tidy.sh - echo 'echo Done' >> /tmp/_pcp_tidy.sh - chmod u+x /tmp/_pcp_tidy.sh + + # Use mktemp for security instead of hardcoded /tmp paths + tmpdir=`mktemp -d /tmp/pcp-uninstall.XXXXXXXXX` || exit 1 + + # Copy to temp dir since install-pcp will remove itself and this script + cp $BINADM_DIR/install-pcp $BINADM_DIR/idb "$tmpdir/" + + # Create tidy up script in temp directory + tidyscript="$tmpdir/_pcp_tidy.sh" + echo '#!/bin/sh' > "$tidyscript" + echo "tmpdir=\"$tmpdir\"" >> "$tidyscript" + echo 'cd "$tmpdir"' >> "$tidyscript" + + # In force mode, suppress "Directory not empty" warnings from install-pcp + # since we'll be removing those directories with rm -rf anyway + if $FORCE + then + echo './install-pcp -r -l /var/log/pcp_inst.log ./idb 2>/dev/null' >> "$tidyscript" + else + echo './install-pcp -r -l /var/log/pcp_inst.log ./idb' >> "$tidyscript" + fi + + echo 'pkgutil --forget io.pcp.performancecopilot 2>/dev/null || true' >> "$tidyscript" + echo '/bin/rm -rf /Library/Receipts/pcp-* 2>/dev/null || true' >> "$tidyscript" + + # If force mode, aggressively remove PCP directories + if $FORCE + then + echo 'echo ""' >> "$tidyscript" + echo 'echo "Force mode: Removing all PCP configuration, data, and log files..."' >> "$tidyscript" + echo 'rm -rf /etc/pcp 2>/dev/null || true' >> "$tidyscript" + echo 'rm -rf /var/lib/pcp 2>/dev/null || true' >> "$tidyscript" + echo 'rm -rf /var/log/pcp 2>/dev/null || true' >> "$tidyscript" + echo 'echo "Complete removal finished."' >> "$tidyscript" + fi + + # Clean up temp directory and script + echo 'cd /' >> "$tidyscript" + echo "rm -rf \"$tmpdir\"" >> "$tidyscript" + echo 'echo ""' >> "$tidyscript" + echo 'echo "PCP uninstall complete."' >> "$tidyscript" + + # Show appropriate message based on force mode + if ! $FORCE + then + echo 'echo ""' >> "$tidyscript" + echo 'echo "Note: Configuration and log files have been left in place:"' >> "$tidyscript" + echo 'echo " - /etc/pcp/"' >> "$tidyscript" + echo 'echo " - /var/lib/pcp/"' >> "$tidyscript" + echo 'echo " - /var/log/pcp/"' >> "$tidyscript" + echo 'echo ""' >> "$tidyscript" + echo 'echo "You may manually remove these directories if desired,"' >> "$tidyscript" + echo 'echo "or run this script with --force to remove everything."' >> "$tidyscript" + fi + + echo 'echo ""' >> "$tidyscript" + chmod u+x "$tidyscript" + # Run remove, overriding this shell - exec /tmp/_pcp_tidy.sh + exec "$tidyscript" else echo "Unable to find $BINADM_DIR/install-pcp. Remove failed" exit 1 fi -