mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
213 lines
4.8 KiB
Go
213 lines
4.8 KiB
Go
package lsof
|
|
|
|
// Copyright 2015 Keybase, Inc. All rights reserved. Use of
|
|
// this source code is governed by the included BSD license.
|
|
|
|
// CREDIT: https://github.com/keybase/client/tree/master/go/lsof
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
// Process defines a process using an open file. Properties here are strings
|
|
// for compatibility with different platforms.
|
|
type Process struct {
|
|
PID string
|
|
Command string
|
|
UserID string
|
|
FileDescriptors []FileDescriptor
|
|
}
|
|
|
|
func (p Process) String() string {
|
|
var fds []string
|
|
for _, fd := range p.FileDescriptors {
|
|
fds = append(fds, fmt.Sprintf("\n %s", fd))
|
|
}
|
|
return fmt.Sprintf(
|
|
"pid=%s, uid=%s '%s'%s",
|
|
p.PID, p.UserID, p.Command, strings.Join(fds, ""),
|
|
)
|
|
}
|
|
|
|
// FileType defines the type of file in use by a process
|
|
type FileType string
|
|
|
|
const (
|
|
FileTypeUnknown FileType = ""
|
|
FileTypeDir FileType = "DIR"
|
|
FileTypeFile FileType = "REG"
|
|
)
|
|
|
|
// FileDescriptor defines a file in use by a process
|
|
type FileDescriptor struct {
|
|
FD string
|
|
Type FileType
|
|
Name string
|
|
}
|
|
|
|
func (f FileDescriptor) String() string {
|
|
return fmt.Sprintf("fd=%s, type=%s, name=%s", f.FD, f.Type, f.Name)
|
|
}
|
|
|
|
// ExecError is an error running lsof
|
|
type ExecError struct {
|
|
command string
|
|
args []string
|
|
output string
|
|
err error
|
|
}
|
|
|
|
func (e ExecError) Error() string {
|
|
return fmt.Sprintf("error running %s %s: %s (%s)", e.command, e.args, e.err, e.output)
|
|
}
|
|
|
|
// MountPoint returns processes using the mountpoint "lsof /dir"
|
|
func MountPoint(dir string) ([]Process, error) {
|
|
// TODO: Fix lsof to not return error on exit status 1 since it isn't
|
|
// really any error, only an indication that there was no use of the
|
|
// mount.
|
|
return run([]string{"-F", "pcuftn", dir})
|
|
}
|
|
|
|
func fileTypeFromString(s string) FileType {
|
|
switch s {
|
|
case "DIR":
|
|
return FileTypeDir
|
|
case "REG":
|
|
return FileTypeFile
|
|
default:
|
|
return FileTypeUnknown
|
|
}
|
|
}
|
|
|
|
func (p *Process) fillField(s string) error {
|
|
if s == "" {
|
|
return fmt.Errorf("empty field")
|
|
}
|
|
// See Output for Other Programs at http://linux.die.net/man/8/lsof
|
|
key := s[0]
|
|
value := s[1:]
|
|
switch key {
|
|
case 'p':
|
|
p.PID = value
|
|
case 'c':
|
|
p.Command = value
|
|
case 'u':
|
|
p.UserID = value
|
|
default:
|
|
// Skip unhandled field
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *FileDescriptor) fillField(s string) error {
|
|
// See Output for Other Programs at http://linux.die.net/man/8/lsof
|
|
key := s[0]
|
|
value := s[1:]
|
|
switch key {
|
|
case 't':
|
|
f.Type = fileTypeFromString(value)
|
|
case 'f':
|
|
f.FD = value
|
|
case 'n':
|
|
f.Name = value
|
|
default:
|
|
// Skip unhandled field
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Process) parseFileLines(lines []string) error {
|
|
file := FileDescriptor{}
|
|
for _, line := range lines {
|
|
if strings.HasPrefix(line, "f") && file.FD != "" {
|
|
// New file
|
|
p.FileDescriptors = append(p.FileDescriptors, file)
|
|
file = FileDescriptor{}
|
|
}
|
|
err := file.fillField(line)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if file.FD != "" {
|
|
p.FileDescriptors = append(p.FileDescriptors, file)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseProcessLines(lines []string) (Process, error) {
|
|
p := Process{}
|
|
for index, line := range lines {
|
|
if strings.HasPrefix(line, "f") {
|
|
err := p.parseFileLines(lines[index:])
|
|
if err != nil {
|
|
return p, err
|
|
}
|
|
break
|
|
} else {
|
|
err := p.fillField(line)
|
|
if err != nil {
|
|
return p, err
|
|
}
|
|
}
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func parseAppendProcessLines(processes []Process, linesChunk []string) ([]Process, []string, error) {
|
|
if len(linesChunk) == 0 {
|
|
return processes, linesChunk, nil
|
|
}
|
|
process, err := parseProcessLines(linesChunk)
|
|
if err != nil {
|
|
return processes, linesChunk, err
|
|
}
|
|
processesAfter := processes
|
|
processesAfter = append(processesAfter, process)
|
|
linesChunkAfter := []string{}
|
|
return processesAfter, linesChunkAfter, nil
|
|
}
|
|
|
|
func parse(s string) ([]Process, error) {
|
|
lines := strings.Split(s, "\n")
|
|
linesChunk := []string{}
|
|
processes := []Process{}
|
|
var err error
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) == "" {
|
|
continue
|
|
}
|
|
|
|
// End of process, let's parse those lines
|
|
if strings.HasPrefix(line, "p") && len(linesChunk) > 0 {
|
|
processes, linesChunk, err = parseAppendProcessLines(processes, linesChunk)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
linesChunk = append(linesChunk, line)
|
|
}
|
|
processes, _, err = parseAppendProcessLines(processes, linesChunk)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return processes, nil
|
|
}
|
|
|
|
func run(args []string) ([]Process, error) {
|
|
// Some systems (Arch, Debian) install lsof in /usr/bin and others (centos)
|
|
// install it in /usr/sbin, even though regular users can use it too. FreeBSD,
|
|
// on the other hand, puts it in /usr/local/sbin. So do not specify absolute path.
|
|
command := "lsof"
|
|
args = append([]string{"-w"}, args...)
|
|
output, err := exec.Command(command, args...).Output()
|
|
if err != nil {
|
|
return nil, ExecError{command: command, args: args, output: string(output), err: err}
|
|
}
|
|
return parse(string(output))
|
|
}
|