Files
wehub-resource-sync 36b3af2e3d
PR Check / Code Quality: Format (push) Failing after 1s
PR Check / Code Quality: Lint (darwin) (push) Failing after 0s
PR Check / Code Quality: Lint (freebsd) (push) Failing after 1s
PR Check / Code Quality: Lint (windows) (push) Failing after 1s
PR Check / Code Quality: Lint (linux) (push) Failing after 1s
PR Check / Security: Vulnerability Scan (push) Failing after 0s
Update Documentation / update-docs (push) Failing after 2s
PR Check / Code Quality: Vendor (push) Failing after 1s
PR Check / Code Quality: Coverage (push) Failing after 0s
PR Check / Tests: Unit (macos-latest) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04-arm) (push) Has been cancelled
PR Check / Tests: Unit (windows-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:22:06 +08:00

157 lines
4.1 KiB
Go

//go:build darwin
package proc
import (
"os/exec"
"slices"
"strconv"
"strings"
"github.com/pranshuparmar/witr/pkg/model"
)
// GetFileContext returns file descriptor and lock info for a process
func GetFileContext(pid int) *model.FileContext {
ctx := &model.FileContext{}
// Get open file count
openFiles, fileLimit := getOpenFileCount(pid)
ctx.OpenFiles = openFiles
ctx.FileLimit = fileLimit
// Get locked files
ctx.LockedFiles = getLockedFiles(pid)
// Only return if we have meaningful data to show
// Show if: high file usage (>50% of limit) or has locks
if len(ctx.LockedFiles) > 0 {
return ctx
}
if ctx.FileLimit > 0 && ctx.OpenFiles > 0 {
usagePercent := float64(ctx.OpenFiles) / float64(ctx.FileLimit) * 100
if usagePercent > 50 {
return ctx
}
}
return nil
}
// getOpenFileCount returns the number of open files and the limit for a process
func getOpenFileCount(pid int) (int, int) {
// Use lsof to count open files
// lsof -p <pid> returns all open files. lsof may exit non-zero when one
// of the process's FDs is inaccessible while still emitting valid data
// on stdout; salvage stdout when present.
out, err := exec.Command("lsof", "-p", strconv.Itoa(pid)).Output()
if err != nil && len(out) == 0 {
return 0, 0
}
// Count lines (subtract 1 for header)
openFiles := 0
for line := range strings.Lines(string(out)) {
if strings.TrimSpace(line) != "" {
openFiles++
}
}
if openFiles > 0 {
openFiles-- // Subtract header line
}
// Get file limit using launchctl or ulimit
fileLimit := getFileLimit(pid)
return openFiles, fileLimit
}
// getFileLimit returns the file descriptor limit for a process
func getFileLimit(pid int) int {
// Try to get per-process limit
// On macOS, we can use launchctl limit or check /proc equivalent
// Default to common macOS limits
// Try launchctl limit (system-wide soft limit)
out, err := exec.Command("launchctl", "limit", "maxfiles").Output()
if err == nil {
// Format: maxfiles 256 unlimited
fields := strings.Fields(string(out))
if len(fields) >= 2 {
limit, err := strconv.Atoi(fields[1])
if err == nil {
return limit
}
}
}
// Default macOS limit
return 256
}
// getLockedFiles returns files with locks held by the process
func getLockedFiles(pid int) []string {
var locked []string
// Use lsof to find locked files
// -p <pid> for specific process
// Look for lock indicators in the output
// lsof may exit non-zero when one of the process's FDs is inaccessible
// while still emitting valid data on stdout; salvage stdout when present.
out, err := exec.Command("lsof", "-p", strconv.Itoa(pid), "-F", "fn").Output()
if err != nil && len(out) == 0 {
return locked
}
// Parse lsof -F output
// f = file descriptor info
// n = file name
var currentFD string
for line := range strings.Lines(string(out)) {
if len(line) == 0 {
continue
}
switch line[0] {
case 'f':
currentFD = strings.TrimSpace(line[1:])
case 'n':
fileName := strings.TrimSpace(line[1:])
// Check if this FD indicates a lock
// Common lock indicators: .lock files, fcntl locks shown with 'l' type
if strings.HasSuffix(fileName, ".lock") ||
strings.HasSuffix(fileName, ".pid") ||
strings.Contains(fileName, "/lock") {
if !slices.Contains(locked, fileName) {
locked = append(locked, fileName)
}
}
_ = currentFD // Used for future lock type detection
}
}
// Also check for actual fcntl/flock locks using lsof -F with lock info
out2, err := exec.Command("lsof", "-p", strconv.Itoa(pid)).Output()
if err == nil || len(out2) > 0 {
for line := range strings.Lines(string(out2)) {
fields := strings.Fields(line)
// Look for lock type indicators (varies by lsof version)
// Typically shows "r" for read lock, "w" for write lock, "R" for read lock on entire file
if len(fields) >= 5 {
lockType := fields[4]
if lockType == "r" || lockType == "w" || lockType == "R" || lockType == "W" {
// This file has a lock
if len(fields) >= 9 {
fileName := fields[8]
if !slices.Contains(locked, fileName) {
locked = append(locked, fileName)
}
}
}
}
}
}
return locked
}