1 /*-------------------------------------------------------------------------
4 * Replacements for <sys/stat.h> functions using GetFileInformationByHandle
6 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
11 * src/port/win32stat.c
13 *-------------------------------------------------------------------------
17 #include "port/win32ntdll.h"
22 * Convert a FILETIME struct into a 64 bit time_t.
25 filetime_to_time(const FILETIME
*ft
)
27 ULARGE_INTEGER unified_ft
= {0};
28 static const uint64 EpochShift
= UINT64CONST(116444736000000000);
30 unified_ft
.LowPart
= ft
->dwLowDateTime
;
31 unified_ft
.HighPart
= ft
->dwHighDateTime
;
33 if (unified_ft
.QuadPart
< EpochShift
)
36 unified_ft
.QuadPart
-= EpochShift
;
37 unified_ft
.QuadPart
/= 10 * 1000 * 1000;
39 return unified_ft
.QuadPart
;
43 * Convert WIN32 file attributes to a Unix-style mode.
45 * Only owner permissions are set.
48 fileattr_to_unixmode(int attr
)
50 unsigned short uxmode
= 0;
52 uxmode
|= (unsigned short) ((attr
& FILE_ATTRIBUTE_DIRECTORY
) ?
53 (_S_IFDIR
) : (_S_IFREG
));
55 uxmode
|= (unsigned short) ((attr
& FILE_ATTRIBUTE_READONLY
) ?
56 (_S_IREAD
) : (_S_IREAD
| _S_IWRITE
));
58 /* there is no need to simulate _S_IEXEC using CMD's PATHEXT extensions */
65 * Convert WIN32 file information (from a HANDLE) to a struct stat.
68 fileinfo_to_stat(HANDLE hFile
, struct stat
*buf
)
70 BY_HANDLE_FILE_INFORMATION fiData
;
72 memset(buf
, 0, sizeof(*buf
));
75 * GetFileInformationByHandle minimum supported version: Windows XP and
76 * Windows Server 2003, so it exists everywhere we care about.
78 if (!GetFileInformationByHandle(hFile
, &fiData
))
80 _dosmaperr(GetLastError());
84 if (fiData
.ftLastWriteTime
.dwLowDateTime
||
85 fiData
.ftLastWriteTime
.dwHighDateTime
)
86 buf
->st_mtime
= filetime_to_time(&fiData
.ftLastWriteTime
);
88 if (fiData
.ftLastAccessTime
.dwLowDateTime
||
89 fiData
.ftLastAccessTime
.dwHighDateTime
)
90 buf
->st_atime
= filetime_to_time(&fiData
.ftLastAccessTime
);
92 buf
->st_atime
= buf
->st_mtime
;
94 if (fiData
.ftCreationTime
.dwLowDateTime
||
95 fiData
.ftCreationTime
.dwHighDateTime
)
96 buf
->st_ctime
= filetime_to_time(&fiData
.ftCreationTime
);
98 buf
->st_ctime
= buf
->st_mtime
;
100 buf
->st_mode
= fileattr_to_unixmode(fiData
.dwFileAttributes
);
101 buf
->st_nlink
= fiData
.nNumberOfLinks
;
103 buf
->st_size
= ((((uint64
) fiData
.nFileSizeHigh
) << 32) |
104 fiData
.nFileSizeLow
);
110 * Windows implementation of lstat().
113 _pglstat64(const char *name
, struct stat
*buf
)
116 * Our open wrapper will report STATUS_DELETE_PENDING as ENOENT. We
117 * request FILE_FLAG_BACKUP_SEMANTICS so that we can open directories too,
118 * for limited purposes. We use the private handle-based version, so we
119 * don't risk running out of fds.
124 hFile
= pgwin32_open_handle(name
, O_RDONLY
, true);
125 if (hFile
== INVALID_HANDLE_VALUE
)
130 * If it's a junction point pointing to a non-existent path, we'll
131 * have ENOENT here (because pgwin32_open_handle does not use
132 * FILE_FLAG_OPEN_REPARSE_POINT). In that case, we'll try again
133 * with readlink() below, which will distinguish true ENOENT from
136 memset(buf
, 0, sizeof(*buf
));
143 ret
= fileinfo_to_stat(hFile
, buf
);
146 * Junction points appear as directories to fileinfo_to_stat(), so we'll
147 * need to do a bit more work to distinguish them.
149 if ((ret
== 0 && S_ISDIR(buf
->st_mode
)) || hFile
== INVALID_HANDLE_VALUE
)
151 char next
[MAXPGPATH
];
155 * POSIX says we need to put the length of the target path into
156 * st_size. Use readlink() to get it, or learn that this is not a
159 size
= readlink(name
, next
, sizeof(next
));
162 if (errno
== EACCES
&&
163 pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING
)
165 /* Unlinked underneath us. */
169 else if (errno
== EINVAL
)
171 /* It's not a junction point, nothing to do. */
175 /* Some other failure. */
181 /* It's a junction point, so report it as a symlink. */
182 buf
->st_mode
&= ~S_IFDIR
;
183 buf
->st_mode
|= S_IFLNK
;
189 if (hFile
!= INVALID_HANDLE_VALUE
)
195 * Windows implementation of stat().
198 _pgstat64(const char *name
, struct stat
*buf
)
202 char curr
[MAXPGPATH
];
204 ret
= _pglstat64(name
, buf
);
206 strlcpy(curr
, name
, MAXPGPATH
);
208 /* Do we need to follow a symlink (junction point)? */
209 while (ret
== 0 && S_ISLNK(buf
->st_mode
))
211 char next
[MAXPGPATH
];
221 * _pglstat64() already called readlink() once to be able to fill in
222 * st_size, and now we need to do it again to get the path to follow.
223 * That could be optimized, but stat() on symlinks is probably rare
224 * and this way is simple.
226 size
= readlink(curr
, next
, sizeof(next
));
229 if (errno
== EACCES
&&
230 pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING
)
232 /* Unlinked underneath us. */
237 if (size
>= sizeof(next
))
239 errno
= ENAMETOOLONG
;
244 ret
= _pglstat64(next
, buf
);
252 * Windows implementation of fstat().
255 _pgfstat64(int fileno
, struct stat
*buf
)
257 HANDLE hFile
= (HANDLE
) _get_osfhandle(fileno
);
258 DWORD fileType
= FILE_TYPE_UNKNOWN
;
259 unsigned short st_mode
;
267 fileType
= pgwin32_get_file_type(hFile
);
273 /* The specified file is a disk file */
275 return fileinfo_to_stat(hFile
, buf
);
278 * The specified file is a socket, a named pipe, or an anonymous
284 /* The specified file is a character file */
288 /* Unused flag and unknown file type */
289 case FILE_TYPE_REMOTE
:
290 case FILE_TYPE_UNKNOWN
:
296 memset(buf
, 0, sizeof(*buf
));
297 buf
->st_mode
= st_mode
;
298 buf
->st_dev
= fileno
;
299 buf
->st_rdev
= fileno
;