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 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
"""File utilities
"""
# pylint:disable=unspecified-encoding
import errno
import os
import sys
from typing import Optional, Union
__all__ = ['write_atomic', 'signal_pidfile']
def write_atomic_unix(fn: str, data: Union[bytes, str], bakext: Optional[str] = None, mode: str = 'b') -> None:
"""Write file with rename."""
if mode not in ['', 'b', 't']:
raise ValueError("unsupported fopen mode")
if mode == "b" and not isinstance(data, bytes):
data = data.encode('utf8')
mode = 'w' + mode
# write new data to tmp file
fn2 = fn + '.new'
if "b" in mode:
with open(fn2, mode) as f:
f.write(data)
else:
with open(fn2, mode, encoding="utf8") as f:
f.write(data)
# link old data to bak file
if bakext:
if bakext.find('/') >= 0:
raise ValueError("invalid bakext")
fnb = fn + bakext
try:
os.unlink(fnb)
except OSError as e:
if e.errno != errno.ENOENT:
raise
try:
os.link(fn, fnb)
except OSError as e:
if e.errno != errno.ENOENT:
raise
# win32 does not like replace
if sys.platform == 'win32':
try:
os.remove(fn)
except BaseException:
pass
# atomically replace file
os.rename(fn2, fn)
def signal_pidfile(pidfile: str, sig: int) -> bool:
"""Send a signal to process whose ID is located in pidfile.
Read only first line of pidfile to support multiline
pidfiles like postmaster.pid.
Returns True is successful, False if pidfile does not exist
or process itself is dead. Any other errors will passed
as exceptions."""
ln = ''
try:
with open(pidfile, 'r', encoding="utf8") as f:
ln = f.readline().strip()
pid = int(ln)
if sig == 0 and sys.platform == 'win32':
return win32_detect_pid(pid)
os.kill(pid, sig)
return True
except (IOError, OSError) as ex:
if ex.errno not in (errno.ESRCH, errno.ENOENT):
raise
except ValueError:
# this leaves slight race when someone is just creating the file,
# but more common case is old empty file.
if not ln:
return False
raise ValueError('Corrupt pidfile: %s' % pidfile) from None
return False
def win32_detect_pid(pid: int) -> bool:
"""Process detection for win32."""
# avoid pywin32 dependecy, use ctypes instead
import ctypes
# win32 constants
PROCESS_QUERY_INFORMATION = 1024
STILL_ACTIVE = 259
ERROR_INVALID_PARAMETER = 87
ERROR_ACCESS_DENIED = 5
# Load kernel32.dll
k = ctypes.windll.kernel32 # type: ignore
OpenProcess = k.OpenProcess
OpenProcess.restype = ctypes.c_void_p
# query pid exit code
h = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid)
if h is None:
err = k.GetLastError()
if err == ERROR_INVALID_PARAMETER:
return False
if err == ERROR_ACCESS_DENIED:
return True
raise OSError(errno.EFAULT, "Unknown win32error: " + str(err))
code = ctypes.c_int()
k.GetExitCodeProcess(h, ctypes.byref(code))
k.CloseHandle(h)
return code.value == STILL_ACTIVE
def win32_write_atomic(fn: str, data: Union[bytes, str], bakext: Optional[str] = None, mode: str = 'b') -> None:
"""Write file with rename for win32."""
if mode not in ['', 'b', 't']:
raise ValueError("unsupported fopen mode")
if mode == "b" and not isinstance(data, bytes):
data = data.encode('utf8')
mode = "w" + mode
# write new data to tmp file
fn2 = fn + '.new'
if "b" in mode:
with open(fn2, mode) as f:
f.write(data)
else:
with open(fn2, mode, encoding="utf8") as f:
f.write(data)
# move old data to bak file
if bakext:
if bakext.find('/') >= 0:
raise ValueError("invalid bakext")
fnb = fn + bakext
try:
os.remove(fnb)
except OSError as e:
if e.errno != errno.ENOENT:
raise
try:
os.rename(fn, fnb)
except OSError as e:
if e.errno != errno.ENOENT:
raise
else:
try:
os.remove(fn)
except BaseException:
pass
# replace file
os.rename(fn2, fn)
if sys.platform == 'win32':
write_atomic = win32_write_atomic
else:
write_atomic = write_atomic_unix
|