1// +build linux freebsd openbsd darwin solaris
2
3package process
4
5import (
6	"context"
7	"fmt"
8	"os"
9	"os/user"
10	"path/filepath"
11	"strconv"
12	"strings"
13	"syscall"
14
15	"github.com/shirou/gopsutil/internal/common"
16	"golang.org/x/sys/unix"
17)
18
19// POSIX
20func getTerminalMap() (map[uint64]string, error) {
21	ret := make(map[uint64]string)
22	var termfiles []string
23
24	d, err := os.Open("/dev")
25	if err != nil {
26		return nil, err
27	}
28	defer d.Close()
29
30	devnames, err := d.Readdirnames(-1)
31	if err != nil {
32		return nil, err
33	}
34	for _, devname := range devnames {
35		if strings.HasPrefix(devname, "/dev/tty") {
36			termfiles = append(termfiles, "/dev/tty/"+devname)
37		}
38	}
39
40	var ptsnames []string
41	ptsd, err := os.Open("/dev/pts")
42	if err != nil {
43		ptsnames, _ = filepath.Glob("/dev/ttyp*")
44		if ptsnames == nil {
45			return nil, err
46		}
47	}
48	defer ptsd.Close()
49
50	if ptsnames == nil {
51		defer ptsd.Close()
52		ptsnames, err = ptsd.Readdirnames(-1)
53		if err != nil {
54			return nil, err
55		}
56		for _, ptsname := range ptsnames {
57			termfiles = append(termfiles, "/dev/pts/"+ptsname)
58		}
59	} else {
60		termfiles = ptsnames
61	}
62
63	for _, name := range termfiles {
64		stat := unix.Stat_t{}
65		if err = unix.Stat(name, &stat); err != nil {
66			return nil, err
67		}
68		rdev := uint64(stat.Rdev)
69		ret[rdev] = strings.Replace(name, "/dev", "", -1)
70	}
71	return ret, nil
72}
73
74// isMount is a port of python's os.path.ismount()
75// https://github.com/python/cpython/blob/08ff4369afca84587b1c82034af4e9f64caddbf2/Lib/posixpath.py#L186-L216
76// https://docs.python.org/3/library/os.path.html#os.path.ismount
77func isMount(path string) bool {
78	// Check symlinkness with os.Lstat; unix.DT_LNK is not portable
79	fileInfo, err := os.Lstat(path)
80	if err != nil {
81		return false
82	}
83	if fileInfo.Mode() & os.ModeSymlink != 0 {
84		return false
85	}
86	var stat1 unix.Stat_t
87	if err := unix.Lstat(path, &stat1); err != nil {
88		return false
89	}
90	parent := filepath.Join(path, "..")
91	var stat2 unix.Stat_t
92	if err := unix.Lstat(parent, &stat2); err != nil {
93		return false
94	}
95	return stat1.Dev != stat2.Dev || stat1.Ino == stat2.Ino
96}
97
98func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) {
99	if pid <= 0 {
100		return false, fmt.Errorf("invalid pid %v", pid)
101	}
102	proc, err := os.FindProcess(int(pid))
103	if err != nil {
104		return false, err
105	}
106
107	if isMount(common.HostProc()) { // if /<HOST_PROC>/proc exists and is mounted, check if /<HOST_PROC>/proc/<PID> folder exists
108		_, err := os.Stat(common.HostProc(strconv.Itoa(int(pid))))
109		if os.IsNotExist(err) {
110			return false, nil
111		}
112		return err == nil, err
113	}
114
115	// procfs does not exist or is not mounted, check PID existence by signalling the pid
116	err = proc.Signal(syscall.Signal(0))
117	if err == nil {
118		return true, nil
119	}
120	if err.Error() == "os: process already finished" {
121		return false, nil
122	}
123	errno, ok := err.(syscall.Errno)
124	if !ok {
125		return false, err
126	}
127	switch errno {
128	case syscall.ESRCH:
129		return false, nil
130	case syscall.EPERM:
131		return true, nil
132	}
133
134	return false, err
135}
136
137func (p *Process) SendSignalWithContext(ctx context.Context, sig syscall.Signal) error {
138	process, err := os.FindProcess(int(p.Pid))
139	if err != nil {
140		return err
141	}
142
143	err = process.Signal(sig)
144	if err != nil {
145		return err
146	}
147
148	return nil
149}
150
151func (p *Process) SuspendWithContext(ctx context.Context) error {
152	return p.SendSignalWithContext(ctx, unix.SIGSTOP)
153}
154
155func (p *Process) ResumeWithContext(ctx context.Context) error {
156	return p.SendSignalWithContext(ctx, unix.SIGCONT)
157}
158
159func (p *Process) TerminateWithContext(ctx context.Context) error {
160	return p.SendSignalWithContext(ctx, unix.SIGTERM)
161}
162
163func (p *Process) KillWithContext(ctx context.Context) error {
164	return p.SendSignalWithContext(ctx, unix.SIGKILL)
165}
166
167func (p *Process) UsernameWithContext(ctx context.Context) (string, error) {
168	uids, err := p.UidsWithContext(ctx)
169	if err != nil {
170		return "", err
171	}
172	if len(uids) > 0 {
173		u, err := user.LookupId(strconv.Itoa(int(uids[0])))
174		if err != nil {
175			return "", err
176		}
177		return u.Username, nil
178	}
179	return "", nil
180}
181