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