Skip to content

Instantly share code, notes, and snippets.

@keithchambers
Last active November 17, 2024 07:33
Show Gist options
  • Select an option

  • Save keithchambers/d15b629bcc2f65ac478ed1363824b4fc to your computer and use it in GitHub Desktop.

Select an option

Save keithchambers/d15b629bcc2f65ac478ed1363824b4fc to your computer and use it in GitHub Desktop.
macvolumes
// macvolumes is a tool to classify macOS volumes by their physical connectivity
// and whether they are permanently installed or removable. It distinguishes between:
// - System volumes (the macOS boot volume)
// - Fixed volumes (built-in or permanently installed drives)
// - Removable volumes (external drives that can be disconnected)
//
// The tool uses macOS's standardized disk management structures to ensure
// consistent behavior across different Mac models.
package main
import (
"context"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"time"
)
// ConnectionType represents a volume's physical connection characteristics
type ConnectionType struct {
Attachment string // "fixed" or "removable"
Transport string // "system", "fabric", "pcie", "thunderbolt", "usb"
}
// Constants representing different transport types
const (
appleFabric = "apple fabric"
pcie = "pcie"
nvme = "nvme"
thunderbolt = "thunderbolt"
usb = "usb"
)
var diskIDPattern = regexp.MustCompile(disk\d+)
// runCommand executes a system command with a timeout and returns its output
func runCommand(name string, args ...string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, name, args...)
output, err := cmd.Output()
if ctx.Err() == context.DeadlineExceeded {
return "", fmt.Errorf("command %s timed out", name)
}
if err != nil {
return "", fmt.Errorf("error running %s: %v", name, err)
}
return string(output), nil
}
// getDiskInfo retrieves and parses diskutil info for a given disk
// Returns a map of lowercase key-value pairs from diskutil output
func getDiskInfo(diskID string) (map[string]string, error) {
info := make(map[string]string)
output, err := runCommand("diskutil", "info", diskID)
if err != nil {
return nil, err
}
lines := strings.Split(output, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.Contains(line, ":") {
parts := strings.SplitN(line, ":", 2)
key := strings.TrimSpace(strings.ToLower(parts[0]))
value := strings.TrimSpace(strings.ToLower(parts[1]))
info[key] = value
}
}
return info, nil
}
// isTimeMachineVolume checks if a volume is used for Time Machine backups
// This checks both the volume name and specific Time Machine flags
func isTimeMachineVolume(diskID string) bool {
info, err := getDiskInfo(diskID)
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting disk info for %s: %v\n", diskID, err)
return false
}
// Check volume name and properties
volName := strings.ToLower(info["volume name"])
return strings.Contains(volName, "time machine") ||
strings.Contains(volName, "backup") ||
strings.Contains(info["file system personality"], "com.apple.backup")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment