1 /*-------------------------------------------------------------------------
2  *
3  * win32stat.c
4  *	  Replacements for <sys/stat.h> functions using GetFileInformationByHandle
5  *
6  * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *	  src/port/win32stat.c
12  *
13  *-------------------------------------------------------------------------
14  */
15 
16 #ifdef WIN32
17 
18 #include "c.h"
19 #include <windows.h>
20 
21 /*
22  * In order to support MinGW and MSVC2013 we use NtQueryInformationFile as an
23  * alternative for GetFileInformationByHandleEx. It is loaded from the ntdll
24  * library.
25  */
26 #if _WIN32_WINNT < 0x0600
27 #include <winternl.h>
28 
29 #if !defined(__MINGW32__) && !defined(__MINGW64__)
30 /* MinGW includes this in <winternl.h>, but it is missing in MSVC */
31 typedef struct _FILE_STANDARD_INFORMATION
32 {
33 	LARGE_INTEGER AllocationSize;
34 	LARGE_INTEGER EndOfFile;
35 	ULONG		NumberOfLinks;
36 	BOOLEAN		DeletePending;
37 	BOOLEAN		Directory;
38 } FILE_STANDARD_INFORMATION;
39 #define FileStandardInformation 5
40 #endif							/* !defined(__MINGW32__) &&
41 								 * !defined(__MINGW64__) */
42 
43 typedef NTSTATUS (NTAPI * PFN_NTQUERYINFORMATIONFILE)
44 			(IN HANDLE FileHandle,
45 			 OUT PIO_STATUS_BLOCK IoStatusBlock,
46 			 OUT PVOID FileInformation,
47 			 IN ULONG Length,
48 			 IN FILE_INFORMATION_CLASS FileInformationClass);
49 
50 static PFN_NTQUERYINFORMATIONFILE _NtQueryInformationFile = NULL;
51 
52 static HMODULE ntdll = NULL;
53 
54 /*
55  * Load DLL file just once regardless of how many functions we load/call in it.
56  */
57 static void
LoadNtdll(void)58 LoadNtdll(void)
59 {
60 	if (ntdll != NULL)
61 		return;
62 	ntdll = LoadLibraryEx("ntdll.dll", NULL, 0);
63 }
64 
65 #endif							/* _WIN32_WINNT < 0x0600 */
66 
67 
68 /*
69  * Convert a FILETIME struct into a 64 bit time_t.
70  */
71 static __time64_t
filetime_to_time(const FILETIME * ft)72 filetime_to_time(const FILETIME *ft)
73 {
74 	ULARGE_INTEGER unified_ft = {0};
75 	static const uint64 EpochShift = UINT64CONST(116444736000000000);
76 
77 	unified_ft.LowPart = ft->dwLowDateTime;
78 	unified_ft.HighPart = ft->dwHighDateTime;
79 
80 	if (unified_ft.QuadPart < EpochShift)
81 		return -1;
82 
83 	unified_ft.QuadPart -= EpochShift;
84 	unified_ft.QuadPart /= 10 * 1000 * 1000;
85 
86 	return unified_ft.QuadPart;
87 }
88 
89 /*
90  * Convert WIN32 file attributes to a Unix-style mode.
91  *
92  * Only owner permissions are set.
93  */
94 static unsigned short
fileattr_to_unixmode(int attr)95 fileattr_to_unixmode(int attr)
96 {
97 	unsigned short uxmode = 0;
98 
99 	uxmode |= (unsigned short) ((attr & FILE_ATTRIBUTE_DIRECTORY) ?
100 								(_S_IFDIR) : (_S_IFREG));
101 
102 	uxmode |= (unsigned short) ((attr & FILE_ATTRIBUTE_READONLY) ?
103 								(_S_IREAD) : (_S_IREAD | _S_IWRITE));
104 
105 	/* there is no need to simulate _S_IEXEC using CMD's PATHEXT extensions */
106 	uxmode |= _S_IEXEC;
107 
108 	return uxmode;
109 }
110 
111 /*
112  * Convert WIN32 file information (from a HANDLE) to a struct stat.
113  */
114 static int
fileinfo_to_stat(HANDLE hFile,struct stat * buf)115 fileinfo_to_stat(HANDLE hFile, struct stat *buf)
116 {
117 	BY_HANDLE_FILE_INFORMATION fiData;
118 
119 	memset(buf, 0, sizeof(*buf));
120 
121 	/*
122 	 * GetFileInformationByHandle minimum supported version: Windows XP and
123 	 * Windows Server 2003, so it exists everywhere we care about.
124 	 */
125 	if (!GetFileInformationByHandle(hFile, &fiData))
126 	{
127 		_dosmaperr(GetLastError());
128 		return -1;
129 	}
130 
131 	if (fiData.ftLastWriteTime.dwLowDateTime ||
132 		fiData.ftLastWriteTime.dwHighDateTime)
133 		buf->st_mtime = filetime_to_time(&fiData.ftLastWriteTime);
134 
135 	if (fiData.ftLastAccessTime.dwLowDateTime ||
136 		fiData.ftLastAccessTime.dwHighDateTime)
137 		buf->st_atime = filetime_to_time(&fiData.ftLastAccessTime);
138 	else
139 		buf->st_atime = buf->st_mtime;
140 
141 	if (fiData.ftCreationTime.dwLowDateTime ||
142 		fiData.ftCreationTime.dwHighDateTime)
143 		buf->st_ctime = filetime_to_time(&fiData.ftCreationTime);
144 	else
145 		buf->st_ctime = buf->st_mtime;
146 
147 	buf->st_mode = fileattr_to_unixmode(fiData.dwFileAttributes);
148 	buf->st_nlink = fiData.nNumberOfLinks;
149 
150 	buf->st_size = ((((uint64) fiData.nFileSizeHigh) << 32) |
151 					fiData.nFileSizeLow);
152 
153 	return 0;
154 }
155 
156 /*
157  * Windows implementation of stat().
158  *
159  * This currently also implements lstat(), though perhaps that should change.
160  */
161 int
_pgstat64(const char * name,struct stat * buf)162 _pgstat64(const char *name, struct stat *buf)
163 {
164 	/*
165 	 * We must use a handle so lstat() returns the information of the target
166 	 * file.  To have a reliable test for ERROR_DELETE_PENDING, we use
167 	 * NtQueryInformationFile from Windows 2000 or
168 	 * GetFileInformationByHandleEx from Server 2008 / Vista.
169 	 */
170 	SECURITY_ATTRIBUTES sa;
171 	HANDLE		hFile;
172 	int			ret;
173 #if _WIN32_WINNT < 0x0600
174 	IO_STATUS_BLOCK ioStatus;
175 	FILE_STANDARD_INFORMATION standardInfo;
176 #else
177 	FILE_STANDARD_INFO standardInfo;
178 #endif
179 
180 	if (name == NULL || buf == NULL)
181 	{
182 		errno = EINVAL;
183 		return -1;
184 	}
185 
186 	/* fast not-exists check */
187 	if (GetFileAttributes(name) == INVALID_FILE_ATTRIBUTES)
188 	{
189 		_dosmaperr(GetLastError());
190 		return -1;
191 	}
192 
193 	/* get a file handle as lightweight as we can */
194 	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
195 	sa.bInheritHandle = TRUE;
196 	sa.lpSecurityDescriptor = NULL;
197 	hFile = CreateFile(name,
198 					   GENERIC_READ,
199 					   (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
200 					   &sa,
201 					   OPEN_EXISTING,
202 					   (FILE_FLAG_NO_BUFFERING | FILE_FLAG_BACKUP_SEMANTICS |
203 						FILE_FLAG_OVERLAPPED),
204 					   NULL);
205 	if (hFile == INVALID_HANDLE_VALUE)
206 	{
207 		DWORD		err = GetLastError();
208 
209 		CloseHandle(hFile);
210 		_dosmaperr(err);
211 		return -1;
212 	}
213 
214 	memset(&standardInfo, 0, sizeof(standardInfo));
215 
216 #if _WIN32_WINNT < 0x0600
217 	if (_NtQueryInformationFile == NULL)
218 	{
219 		/* First time through: load ntdll.dll and find NtQueryInformationFile */
220 		LoadNtdll();
221 		if (ntdll == NULL)
222 		{
223 			DWORD		err = GetLastError();
224 
225 			CloseHandle(hFile);
226 			_dosmaperr(err);
227 			return -1;
228 		}
229 
230 		_NtQueryInformationFile = (PFN_NTQUERYINFORMATIONFILE) (pg_funcptr_t)
231 			GetProcAddress(ntdll, "NtQueryInformationFile");
232 		if (_NtQueryInformationFile == NULL)
233 		{
234 			DWORD		err = GetLastError();
235 
236 			CloseHandle(hFile);
237 			_dosmaperr(err);
238 			return -1;
239 		}
240 	}
241 
242 	if (!NT_SUCCESS(_NtQueryInformationFile(hFile, &ioStatus, &standardInfo,
243 											sizeof(standardInfo),
244 											FileStandardInformation)))
245 	{
246 		DWORD		err = GetLastError();
247 
248 		CloseHandle(hFile);
249 		_dosmaperr(err);
250 		return -1;
251 	}
252 #else
253 	if (!GetFileInformationByHandleEx(hFile, FileStandardInfo, &standardInfo,
254 									  sizeof(standardInfo)))
255 	{
256 		DWORD		err = GetLastError();
257 
258 		CloseHandle(hFile);
259 		_dosmaperr(err);
260 		return -1;
261 	}
262 #endif							/* _WIN32_WINNT < 0x0600 */
263 
264 	if (standardInfo.DeletePending)
265 	{
266 		/*
267 		 * File has been deleted, but is not gone from the filesystem yet.
268 		 * This can happen when some process with FILE_SHARE_DELETE has it
269 		 * open, and it will be fully removed once that handle is closed.
270 		 * Meanwhile, we can't open it, so indicate that the file just doesn't
271 		 * exist.
272 		 */
273 		CloseHandle(hFile);
274 		errno = ENOENT;
275 		return -1;
276 	}
277 
278 	/* At last we can invoke fileinfo_to_stat */
279 	ret = fileinfo_to_stat(hFile, buf);
280 
281 	CloseHandle(hFile);
282 	return ret;
283 }
284 
285 /*
286  * Windows implementation of fstat().
287  */
288 int
_pgfstat64(int fileno,struct stat * buf)289 _pgfstat64(int fileno, struct stat *buf)
290 {
291 	HANDLE		hFile = (HANDLE) _get_osfhandle(fileno);
292 
293 	if (hFile == INVALID_HANDLE_VALUE || buf == NULL)
294 	{
295 		errno = EINVAL;
296 		return -1;
297 	}
298 
299 	/*
300 	 * Since we already have a file handle there is no need to check for
301 	 * ERROR_DELETE_PENDING.
302 	 */
303 
304 	return fileinfo_to_stat(hFile, buf);
305 }
306 
307 #endif							/* WIN32 */
308