summaryrefslogtreecommitdiff
path: root/src/setuptools_scm/win_py31_compat.py
blob: 82a11eb42edae70a110dd58d4cb1837f96a22100 (plain)
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
"""
Backport of os.path.samefile for Python prior to 3.2
on Windows from jaraco.windows 3.8.

DON'T EDIT THIS FILE!

Instead, file tickets and PR's with `jaraco.windows
<https://github.com/jaraco/jaraco.windows>`_ and request
a port to setuptools_scm.
"""

import os
import nt
import posixpath
import ctypes.wintypes
import sys
import __builtin__ as builtins


##
# From jaraco.windows.error

def format_system_message(errno):
	"""
	Call FormatMessage with a system error number to retrieve
	the descriptive error message.
	"""
	# first some flags used by FormatMessageW
	ALLOCATE_BUFFER = 0x100
	FROM_SYSTEM = 0x1000

	# Let FormatMessageW allocate the buffer (we'll free it below)
	# Also, let it know we want a system error message.
	flags = ALLOCATE_BUFFER | FROM_SYSTEM
	source = None
	message_id = errno
	language_id = 0
	result_buffer = ctypes.wintypes.LPWSTR()
	buffer_size = 0
	arguments = None
	bytes = ctypes.windll.kernel32.FormatMessageW(
		flags,
		source,
		message_id,
		language_id,
		ctypes.byref(result_buffer),
		buffer_size,
		arguments,
	)
	# note the following will cause an infinite loop if GetLastError
	#  repeatedly returns an error that cannot be formatted, although
	#  this should not happen.
	handle_nonzero_success(bytes)
	message = result_buffer.value
	ctypes.windll.kernel32.LocalFree(result_buffer)
	return message


class WindowsError(builtins.WindowsError):
	"""
	More info about errors at
	http://msdn.microsoft.com/en-us/library/ms681381(VS.85).aspx
	"""

	def __init__(self, value=None):
		if value is None:
			value = ctypes.windll.kernel32.GetLastError()
		strerror = format_system_message(value)
		if sys.version_info > (3, 3):
			args = 0, strerror, None, value
		else:
			args = value, strerror
		super(WindowsError, self).__init__(*args)

	@property
	def message(self):
		return self.strerror

	@property
	def code(self):
		return self.winerror

	def __str__(self):
		return self.message

	def __repr__(self):
		return '{self.__class__.__name__}({self.winerror})'.format(**vars())


def handle_nonzero_success(result):
	if result == 0:
		raise WindowsError()


##
# From jaraco.windows.api.filesystem

FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
FILE_FLAG_BACKUP_SEMANTICS = 0x2000000
OPEN_EXISTING = 3
FILE_ATTRIBUTE_NORMAL = 0x80
FILE_READ_ATTRIBUTES = 0x80
INVALID_HANDLE_VALUE = ctypes.wintypes.HANDLE(-1).value


class BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
	_fields_ = [
		('file_attributes', ctypes.wintypes.DWORD),
		('creation_time', ctypes.wintypes.FILETIME),
		('last_access_time', ctypes.wintypes.FILETIME),
		('last_write_time', ctypes.wintypes.FILETIME),
		('volume_serial_number', ctypes.wintypes.DWORD),
		('file_size_high', ctypes.wintypes.DWORD),
		('file_size_low', ctypes.wintypes.DWORD),
		('number_of_links', ctypes.wintypes.DWORD),
		('file_index_high', ctypes.wintypes.DWORD),
		('file_index_low', ctypes.wintypes.DWORD),
	]

	@property
	def file_size(self):
		return (self.file_size_high << 32) + self.file_size_low

	@property
	def file_index(self):
		return (self.file_index_high << 32) + self.file_index_low


class SECURITY_ATTRIBUTES(ctypes.Structure):
	_fields_ = (
		('length', ctypes.wintypes.DWORD),
		('p_security_descriptor', ctypes.wintypes.LPVOID),
		('inherit_handle', ctypes.wintypes.BOOLEAN),
	)


LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES)


CreateFile = ctypes.windll.kernel32.CreateFileW
CreateFile.argtypes = (
	ctypes.wintypes.LPWSTR,
	ctypes.wintypes.DWORD,
	ctypes.wintypes.DWORD,
	LPSECURITY_ATTRIBUTES,
	ctypes.wintypes.DWORD,
	ctypes.wintypes.DWORD,
	ctypes.wintypes.HANDLE,
)
CreateFile.restype = ctypes.wintypes.HANDLE

GetFileInformationByHandle = ctypes.windll.kernel32.GetFileInformationByHandle
GetFileInformationByHandle.restype = ctypes.wintypes.BOOL
GetFileInformationByHandle.argtypes = (
	ctypes.wintypes.HANDLE,
	ctypes.POINTER(BY_HANDLE_FILE_INFORMATION),
)


##
# From jaraco.windows.filesystem

def compat_stat(path):
	"""
	Generate stat as found on Python 3.2 and later.
	"""
	stat = os.stat(path)
	info = get_file_info(path)
	# rewrite st_ino, st_dev, and st_nlink based on file info
	return nt.stat_result(
		(stat.st_mode,) +
		(info.file_index, info.volume_serial_number, info.number_of_links) +
		stat[4:]
	)


def samefile(f1, f2):
	"""
	Backport of samefile from Python 3.2 with support for Windows.
	"""
	return posixpath.samestat(compat_stat(f1), compat_stat(f2))


def get_file_info(path):
	# open the file the same way CPython does in posixmodule.c
	desired_access = FILE_READ_ATTRIBUTES
	share_mode = 0
	security_attributes = None
	creation_disposition = OPEN_EXISTING
	flags_and_attributes = (
		FILE_ATTRIBUTE_NORMAL |
		FILE_FLAG_BACKUP_SEMANTICS |
		FILE_FLAG_OPEN_REPARSE_POINT
	)
	template_file = None

	handle = CreateFile(
		path,
		desired_access,
		share_mode,
		security_attributes,
		creation_disposition,
		flags_and_attributes,
		template_file,
	)

	if handle == INVALID_HANDLE_VALUE:
		raise WindowsError()

	info = BY_HANDLE_FILE_INFORMATION()
	res = GetFileInformationByHandle(handle, info)
	handle_nonzero_success(res)

	return info