Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/psutil/_psposix.py: 62%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

95 statements  

1# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. 

2# Use of this source code is governed by a BSD-style license that can be 

3# found in the LICENSE file. 

4 

5"""Routines common to all posix systems.""" 

6 

7import glob 

8import os 

9import signal 

10import sys 

11import time 

12 

13from ._common import MACOS 

14from ._common import TimeoutExpired 

15from ._common import memoize 

16from ._common import sdiskusage 

17from ._common import usage_percent 

18from ._compat import PY3 

19from ._compat import ChildProcessError 

20from ._compat import FileNotFoundError 

21from ._compat import InterruptedError 

22from ._compat import PermissionError 

23from ._compat import ProcessLookupError 

24from ._compat import unicode 

25 

26 

27if MACOS: 

28 from . import _psutil_osx 

29 

30 

31if PY3: 

32 import enum 

33else: 

34 enum = None 

35 

36 

37__all__ = ['pid_exists', 'wait_pid', 'disk_usage', 'get_terminal_map'] 

38 

39 

40def pid_exists(pid): 

41 """Check whether pid exists in the current process table.""" 

42 if pid == 0: 

43 # According to "man 2 kill" PID 0 has a special meaning: 

44 # it refers to <<every process in the process group of the 

45 # calling process>> so we don't want to go any further. 

46 # If we get here it means this UNIX platform *does* have 

47 # a process with id 0. 

48 return True 

49 try: 

50 os.kill(pid, 0) 

51 except ProcessLookupError: 

52 return False 

53 except PermissionError: 

54 # EPERM clearly means there's a process to deny access to 

55 return True 

56 # According to "man 2 kill" possible error values are 

57 # (EINVAL, EPERM, ESRCH) 

58 else: 

59 return True 

60 

61 

62# Python 3.5 signals enum (contributed by me ^^): 

63# https://bugs.python.org/issue21076 

64if enum is not None and hasattr(signal, "Signals"): 

65 Negsignal = enum.IntEnum( 

66 'Negsignal', dict([(x.name, -x.value) for x in signal.Signals]) 

67 ) 

68 

69 def negsig_to_enum(num): 

70 """Convert a negative signal value to an enum.""" 

71 try: 

72 return Negsignal(num) 

73 except ValueError: 

74 return num 

75 

76else: # pragma: no cover 

77 

78 def negsig_to_enum(num): 

79 return num 

80 

81 

82def wait_pid( 

83 pid, 

84 timeout=None, 

85 proc_name=None, 

86 _waitpid=os.waitpid, 

87 _timer=getattr(time, 'monotonic', time.time), # noqa: B008 

88 _min=min, 

89 _sleep=time.sleep, 

90 _pid_exists=pid_exists, 

91): 

92 """Wait for a process PID to terminate. 

93 

94 If the process terminated normally by calling exit(3) or _exit(2), 

95 or by returning from main(), the return value is the positive integer 

96 passed to *exit(). 

97 

98 If it was terminated by a signal it returns the negated value of the 

99 signal which caused the termination (e.g. -SIGTERM). 

100 

101 If PID is not a children of os.getpid() (current process) just 

102 wait until the process disappears and return None. 

103 

104 If PID does not exist at all return None immediately. 

105 

106 If *timeout* != None and process is still alive raise TimeoutExpired. 

107 timeout=0 is also possible (either return immediately or raise). 

108 """ 

109 if pid <= 0: 

110 # see "man waitpid" 

111 msg = "can't wait for PID 0" 

112 raise ValueError(msg) 

113 interval = 0.0001 

114 flags = 0 

115 if timeout is not None: 

116 flags |= os.WNOHANG 

117 stop_at = _timer() + timeout 

118 

119 def sleep(interval): 

120 # Sleep for some time and return a new increased interval. 

121 if timeout is not None: 

122 if _timer() >= stop_at: 

123 raise TimeoutExpired(timeout, pid=pid, name=proc_name) 

124 _sleep(interval) 

125 return _min(interval * 2, 0.04) 

126 

127 # See: https://linux.die.net/man/2/waitpid 

128 while True: 

129 try: 

130 retpid, status = os.waitpid(pid, flags) 

131 except InterruptedError: 

132 interval = sleep(interval) 

133 except ChildProcessError: 

134 # This has two meanings: 

135 # - PID is not a child of os.getpid() in which case 

136 # we keep polling until it's gone 

137 # - PID never existed in the first place 

138 # In both cases we'll eventually return None as we 

139 # can't determine its exit status code. 

140 while _pid_exists(pid): 

141 interval = sleep(interval) 

142 return 

143 else: 

144 if retpid == 0: 

145 # WNOHANG flag was used and PID is still running. 

146 interval = sleep(interval) 

147 continue 

148 

149 if os.WIFEXITED(status): 

150 # Process terminated normally by calling exit(3) or _exit(2), 

151 # or by returning from main(). The return value is the 

152 # positive integer passed to *exit(). 

153 return os.WEXITSTATUS(status) 

154 elif os.WIFSIGNALED(status): 

155 # Process exited due to a signal. Return the negative value 

156 # of that signal. 

157 return negsig_to_enum(-os.WTERMSIG(status)) 

158 # elif os.WIFSTOPPED(status): 

159 # # Process was stopped via SIGSTOP or is being traced, and 

160 # # waitpid() was called with WUNTRACED flag. PID is still 

161 # # alive. From now on waitpid() will keep returning (0, 0) 

162 # # until the process state doesn't change. 

163 # # It may make sense to catch/enable this since stopped PIDs 

164 # # ignore SIGTERM. 

165 # interval = sleep(interval) 

166 # continue 

167 # elif os.WIFCONTINUED(status): 

168 # # Process was resumed via SIGCONT and waitpid() was called 

169 # # with WCONTINUED flag. 

170 # interval = sleep(interval) 

171 # continue 

172 else: 

173 # Should never happen. 

174 raise ValueError("unknown process exit status %r" % status) 

175 

176 

177def disk_usage(path): 

178 """Return disk usage associated with path. 

179 Note: UNIX usually reserves 5% disk space which is not accessible 

180 by user. In this function "total" and "used" values reflect the 

181 total and used disk space whereas "free" and "percent" represent 

182 the "free" and "used percent" user disk space. 

183 """ 

184 if PY3: 

185 st = os.statvfs(path) 

186 else: # pragma: no cover 

187 # os.statvfs() does not support unicode on Python 2: 

188 # - https://github.com/giampaolo/psutil/issues/416 

189 # - http://bugs.python.org/issue18695 

190 try: 

191 st = os.statvfs(path) 

192 except UnicodeEncodeError: 

193 if isinstance(path, unicode): 

194 try: 

195 path = path.encode(sys.getfilesystemencoding()) 

196 except UnicodeEncodeError: 

197 pass 

198 st = os.statvfs(path) 

199 else: 

200 raise 

201 

202 # Total space which is only available to root (unless changed 

203 # at system level). 

204 total = st.f_blocks * st.f_frsize 

205 # Remaining free space usable by root. 

206 avail_to_root = st.f_bfree * st.f_frsize 

207 # Remaining free space usable by user. 

208 avail_to_user = st.f_bavail * st.f_frsize 

209 # Total space being used in general. 

210 used = total - avail_to_root 

211 if MACOS: 

212 # see: https://github.com/giampaolo/psutil/pull/2152 

213 used = _psutil_osx.disk_usage_used(path, used) 

214 # Total space which is available to user (same as 'total' but 

215 # for the user). 

216 total_user = used + avail_to_user 

217 # User usage percent compared to the total amount of space 

218 # the user can use. This number would be higher if compared 

219 # to root's because the user has less space (usually -5%). 

220 usage_percent_user = usage_percent(used, total_user, round_=1) 

221 

222 # NB: the percentage is -5% than what shown by df due to 

223 # reserved blocks that we are currently not considering: 

224 # https://github.com/giampaolo/psutil/issues/829#issuecomment-223750462 

225 return sdiskusage( 

226 total=total, used=used, free=avail_to_user, percent=usage_percent_user 

227 ) 

228 

229 

230@memoize 

231def get_terminal_map(): 

232 """Get a map of device-id -> path as a dict. 

233 Used by Process.terminal(). 

234 """ 

235 ret = {} 

236 ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*') 

237 for name in ls: 

238 assert name not in ret, name 

239 try: 

240 ret[os.stat(name).st_rdev] = name 

241 except FileNotFoundError: 

242 pass 

243 return ret