-
Notifications
You must be signed in to change notification settings - Fork 1
/
library.go
151 lines (134 loc) · 3.45 KB
/
library.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package libspector
import (
"bufio"
"bytes"
"fmt"
"os/exec"
"strings"
"time"
)
type library struct {
path string
pkgName string
pkg Package
ctime *time.Time
}
// Path returns the full path of this library file.
func (lib *library) Path() string {
return lib.path
}
func (lib *library) Package() (Package, error) {
if lib.pkg != nil {
// Is the package already loaded?
return lib.pkg, nil
}
if lib.pkgName != "" {
// Do we have the package name?
pkg, err := findPackage(lib.pkgName)
if err != nil {
return nil, err
}
lib.pkg = pkg
return pkg, nil
}
// Find the package from scratch.
libs, err := FindLibrary(lib.path)
if err != nil {
return nil, err
}
if len(libs) != 1 {
return nil, fmt.Errorf("failed to uniquely identify path %q, found %d results", lib.path, len(libs))
}
return libs[0].Package()
}
// Outdated compares the ctime of the library path against the timestamp of when the process was started.
func (lib *library) Outdated(proc Process) bool {
mtime, err := lib.Ctime()
if err != nil {
// Library path could not be queried, must be outdated.
return true
}
stime, err := proc.Started()
if err != nil {
// Process start time could not be queried, must have been killed so can't be outdated.
return false
}
return stime.Before(mtime)
}
// parseFindLibrary parses output produced by commands run within FindLibrary,
// separated out for testing.
func parseFindLibrary(buf *bytes.Buffer) ([]Library, error) {
libs := []Library{}
scanner := bufio.NewScanner(buf)
for scanner.Scan() {
// Each line should look like:
// somelib-1.0:maybearch: /usr/lib/somelib.so
line := scanner.Text()
parts := strings.Split(line, ":")
if len(parts) < 2 {
return nil, ErrParse(line)
}
pkg, path := parts[0], strings.TrimSpace(parts[len(parts)-1])
libs = append(libs, &library{path: path, pkgName: pkg})
}
if err := scanner.Err(); err != nil {
return libs, err
}
return libs, nil
}
// FindLibrary uses `dpkg -S` to find libraries with the given path substring.
func FindLibrary(path string) ([]Library, error) {
cmd := exec.Command("dpkg", "-S", path)
buf := new(bytes.Buffer)
cmd.Stdout = buf
if err := cmd.Run(); err != nil {
return nil, err
}
return parseFindLibrary(buf)
}
func parseFindLibraryByPID(buf *bytes.Buffer) ([]Library, error) {
// First line looks like this:
// 1234: nginx: master process /usr/sbin/nginx
// Then every following line is:
// 0000000000400000 788K r-x-- /usr/sbin/nginx
libs := []Library{}
scanner := bufio.NewScanner(buf)
scanner.Scan() // Skip first line
seen := map[string]bool{}
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, " ") {
// Finished parsing, found " total: ..." line
break
}
parts := strings.Fields(line)
if len(parts) < 4 {
return nil, ErrParse(line)
}
path := parts[3]
if !strings.HasPrefix(path, "/") {
// Skip anon lines etc.
continue
}
if _, ok := seen[path]; ok {
// Already seen, skip
continue
}
seen[path] = true
libs = append(libs, &library{path: path})
}
if err := scanner.Err(); err != nil {
return libs, err
}
return libs, nil
}
// findLibraryByPID uses `pmap -p $PID` to find libraries that are being used by a given PID.
func findLibraryByPID(pid int) ([]Library, error) {
cmd := exec.Command("pmap", "-p", fmt.Sprintf("%d", pid))
buf := new(bytes.Buffer)
cmd.Stdout = buf
if err := cmd.Run(); err != nil {
return nil, err
}
return parseFindLibraryByPID(buf)
}