File: psnotify.go

package info (click to toggle)
golang-github-cloudfoundry-gosigar 0.0~git20180907.50ddd08-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, buster, forky, sid, trixie
  • size: 352 kB
  • sloc: sh: 26; makefile: 4
file content (159 lines) | stat: -rw-r--r-- 3,647 bytes parent folder | download
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
152
153
154
155
156
157
158
159
// +build darwin freebsd linux netbsd openbsd

package psnotify

import (
	"errors"
	"fmt"
	"sync"
)

type ProcEventFork struct {
	ParentPid int // Pid of the process that called fork()
	ChildPid  int // Child process pid created by fork()
}

type ProcEventExec struct {
	Pid int // Pid of the process that called exec()
}

type ProcEventExit struct {
	Pid int // Pid of the process that called exit()
}

type watch struct {
	flags uint32 // Saved value of Watch() flags param
}

type eventListener interface {
	close() error // Watch.Close() closes the OS specific listener
}

type Watcher struct {
	listener     eventListener  // OS specifics (kqueue or netlink)
	watches      map[int]*watch // Map of watched process ids
	watchesMutex *sync.Mutex

	Error chan error          // Errors are sent on this channel
	Fork  chan *ProcEventFork // Fork events are sent on this channel
	Exec  chan *ProcEventExec // Exec events are sent on this channel
	Exit  chan *ProcEventExit // Exit events are sent on this channel
	done  chan bool           // Used to stop the readEvents() goroutine

	isClosed    bool // Set to true when Close() is first called
	closedMutex *sync.Mutex
}

// Initialize event listener and channels
func NewWatcher() (*Watcher, error) {
	listener, err := createListener()

	if err != nil {
		return nil, err
	}

	w := &Watcher{
		listener:     listener,
		watches:      make(map[int]*watch),
		watchesMutex: &sync.Mutex{},
		Fork:         make(chan *ProcEventFork),
		Exec:         make(chan *ProcEventExec),
		Exit:         make(chan *ProcEventExit),
		Error:        make(chan error),
		done:         make(chan bool, 1),
		closedMutex:  &sync.Mutex{},
	}

	go w.readEvents()
	return w, nil
}

// Close event channels when done message is received
func (w *Watcher) finish() {
	close(w.Fork)
	close(w.Exec)
	close(w.Exit)
	close(w.Error)
}

// Closes the OS specific event listener,
// removes all watches and closes all event channels.
func (w *Watcher) Close() error {
	w.closedMutex.Lock()
	defer w.closedMutex.Unlock()

	if w.isClosed {
		return nil
	}
	w.isClosed = true

	w.watchesMutex.Lock()
	for pid := range w.watches {
		delete(w.watches, pid)
		w.unregister(pid)
	}
	w.watchesMutex.Unlock()

	w.done <- true

	w.listener.close()

	return nil
}

// Add pid to the watched process set.
// The flags param is a bitmask of process events to capture,
// must be one or more of: PROC_EVENT_FORK, PROC_EVENT_EXEC, PROC_EVENT_EXIT
func (w *Watcher) Watch(pid int, flags uint32) error {
	w.closedMutex.Lock()
	closed := w.isClosed
	w.closedMutex.Unlock()

	if closed {
		return errors.New("psnotify watcher is closed")
	}

	watchEntry, found := w.watches[pid]

	if found {
		watchEntry.flags |= flags
	} else {
		if err := w.register(pid, flags); err != nil {
			return err
		}

		w.watchesMutex.Lock()
		w.watches[pid] = &watch{flags: flags}
		w.watchesMutex.Unlock()
	}

	return nil
}

// Remove pid from the watched process set.
func (w *Watcher) RemoveWatch(pid int) error {
	w.watchesMutex.Lock()
	defer w.watchesMutex.Unlock()

	_, ok := w.watches[pid]
	if !ok {
		msg := fmt.Sprintf("watch for pid=%d does not exist", pid)
		return errors.New(msg)
	}
	delete(w.watches, pid)
	return w.unregister(pid)
}

// Internal helper to check if there is a message on the "done" channel.
// The "done" message is sent by the Close() method; when received here,
// the Watcher.finish method is called to close all channels and return
// true - in which case the caller should break from the readEvents loop.
func (w *Watcher) isDone() bool {
	var done bool
	select {
	case done = <-w.done:
		w.finish()
	default:
	}
	return done
}