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

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

86 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 enum 

8import glob 

9import os 

10import signal 

11import time 

12 

13from ._common import MACOS 

14from ._common import TimeoutExpired 

15from ._common import memoize 

16from ._common import sdiskusage 

17from ._common import usage_percent 

18 

19 

20if MACOS: 

21 from . import _psutil_osx 

22 

23 

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

25 

26 

27def pid_exists(pid): 

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

29 if pid == 0: 

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

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

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

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

34 # a process with id 0. 

35 return True 

36 try: 

37 os.kill(pid, 0) 

38 except ProcessLookupError: 

39 return False 

40 except PermissionError: 

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

42 return True 

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

44 # (EINVAL, EPERM, ESRCH) 

45 else: 

46 return True 

47 

48 

49Negsignal = enum.IntEnum( 

50 'Negsignal', {x.name: -x.value for x in signal.Signals} 

51) 

52 

53 

54def negsig_to_enum(num): 

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

56 try: 

57 return Negsignal(num) 

58 except ValueError: 

59 return num 

60 

61 

62def wait_pid( 

63 pid, 

64 timeout=None, 

65 proc_name=None, 

66 _waitpid=os.waitpid, 

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

68 _min=min, 

69 _sleep=time.sleep, 

70 _pid_exists=pid_exists, 

71): 

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

73 

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

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

76 passed to *exit(). 

77 

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

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

80 

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

82 wait until the process disappears and return None. 

83 

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

85 

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

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

88 """ 

89 if pid <= 0: 

90 # see "man waitpid" 

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

92 raise ValueError(msg) 

93 interval = 0.0001 

94 flags = 0 

95 if timeout is not None: 

96 flags |= os.WNOHANG 

97 stop_at = _timer() + timeout 

98 

99 def sleep(interval): 

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

101 if timeout is not None: 

102 if _timer() >= stop_at: 

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

104 _sleep(interval) 

105 return _min(interval * 2, 0.04) 

106 

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

108 while True: 

109 try: 

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

111 except InterruptedError: 

112 interval = sleep(interval) 

113 except ChildProcessError: 

114 # This has two meanings: 

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

116 # we keep polling until it's gone 

117 # - PID never existed in the first place 

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

119 # can't determine its exit status code. 

120 while _pid_exists(pid): 

121 interval = sleep(interval) 

122 return None 

123 else: 

124 if retpid == 0: 

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

126 interval = sleep(interval) 

127 continue 

128 

129 if os.WIFEXITED(status): 

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

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

132 # positive integer passed to *exit(). 

133 return os.WEXITSTATUS(status) 

134 elif os.WIFSIGNALED(status): 

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

136 # of that signal. 

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

138 # elif os.WIFSTOPPED(status): 

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

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

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

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

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

144 # # ignore SIGTERM. 

145 # interval = sleep(interval) 

146 # continue 

147 # elif os.WIFCONTINUED(status): 

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

149 # # with WCONTINUED flag. 

150 # interval = sleep(interval) 

151 # continue 

152 else: 

153 # Should never happen. 

154 msg = f"unknown process exit status {status!r}" 

155 raise ValueError(msg) 

156 

157 

158def disk_usage(path): 

159 """Return disk usage associated with path. 

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

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

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

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

164 """ 

165 st = os.statvfs(path) 

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

167 # at system level). 

168 total = st.f_blocks * st.f_frsize 

169 # Remaining free space usable by root. 

170 avail_to_root = st.f_bfree * st.f_frsize 

171 # Remaining free space usable by user. 

172 avail_to_user = st.f_bavail * st.f_frsize 

173 # Total space being used in general. 

174 used = total - avail_to_root 

175 if MACOS: 

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

177 used = _psutil_osx.disk_usage_used(path, used) 

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

179 # for the user). 

180 total_user = used + avail_to_user 

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

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

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

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

185 

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

187 # reserved blocks that we are currently not considering: 

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

189 return sdiskusage( 

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

191 ) 

192 

193 

194@memoize 

195def get_terminal_map(): 

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

197 Used by Process.terminal(). 

198 """ 

199 ret = {} 

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

201 for name in ls: 

202 assert name not in ret, name 

203 try: 

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

205 except FileNotFoundError: 

206 pass 

207 return ret