Fix a compiler warning in initStringInfo().
[pgsql.git] / src / port / win32stat.c
blobd9bad97b113b8698be57f529a1e40a47f86f659c
1 /*-------------------------------------------------------------------------
3 * win32stat.c
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
10 * IDENTIFICATION
11 * src/port/win32stat.c
13 *-------------------------------------------------------------------------
16 #include "c.h"
17 #include "port/win32ntdll.h"
19 #include <windows.h>
22 * Convert a FILETIME struct into a 64 bit time_t.
24 static __time64_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)
34 return -1;
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.
47 static unsigned short
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 */
59 uxmode |= _S_IEXEC;
61 return uxmode;
65 * Convert WIN32 file information (from a HANDLE) to a struct stat.
67 static int
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());
81 return -1;
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);
91 else
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);
97 else
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);
106 return 0;
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.
121 HANDLE hFile;
122 int ret;
124 hFile = pgwin32_open_handle(name, O_RDONLY, true);
125 if (hFile == INVALID_HANDLE_VALUE)
127 if (errno == ENOENT)
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
134 * pseudo-symlink.
136 memset(buf, 0, sizeof(*buf));
137 ret = 0;
139 else
140 return -1;
142 else
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];
152 ssize_t size;
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
157 * junction point.
159 size = readlink(name, next, sizeof(next));
160 if (size < 0)
162 if (errno == EACCES &&
163 pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING)
165 /* Unlinked underneath us. */
166 errno = ENOENT;
167 ret = -1;
169 else if (errno == EINVAL)
171 /* It's not a junction point, nothing to do. */
173 else
175 /* Some other failure. */
176 ret = -1;
179 else
181 /* It's a junction point, so report it as a symlink. */
182 buf->st_mode &= ~S_IFDIR;
183 buf->st_mode |= S_IFLNK;
184 buf->st_size = size;
185 ret = 0;
189 if (hFile != INVALID_HANDLE_VALUE)
190 CloseHandle(hFile);
191 return ret;
195 * Windows implementation of stat().
198 _pgstat64(const char *name, struct stat *buf)
200 int loops = 0;
201 int ret;
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];
212 ssize_t size;
214 if (++loops > 8)
216 errno = ELOOP;
217 return -1;
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));
227 if (size < 0)
229 if (errno == EACCES &&
230 pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING)
232 /* Unlinked underneath us. */
233 errno = ENOENT;
235 return -1;
237 if (size >= sizeof(next))
239 errno = ENAMETOOLONG;
240 return -1;
242 next[size] = 0;
244 ret = _pglstat64(next, buf);
245 strcpy(curr, next);
248 return ret;
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;
261 if (buf == NULL)
263 errno = EINVAL;
264 return -1;
267 fileType = pgwin32_get_file_type(hFile);
268 if (errno != 0)
269 return -1;
271 switch (fileType)
273 /* The specified file is a disk file */
274 case FILE_TYPE_DISK:
275 return fileinfo_to_stat(hFile, buf);
278 * The specified file is a socket, a named pipe, or an anonymous
279 * pipe.
281 case FILE_TYPE_PIPE:
282 st_mode = _S_IFIFO;
283 break;
284 /* The specified file is a character file */
285 case FILE_TYPE_CHAR:
286 st_mode = _S_IFCHR;
287 break;
288 /* Unused flag and unknown file type */
289 case FILE_TYPE_REMOTE:
290 case FILE_TYPE_UNKNOWN:
291 default:
292 errno = EINVAL;
293 return -1;
296 memset(buf, 0, sizeof(*buf));
297 buf->st_mode = st_mode;
298 buf->st_dev = fileno;
299 buf->st_rdev = fileno;
300 buf->st_nlink = 1;
301 return 0;