Fossil SCM

fossil-scm / src / winfile.c
Blame History Raw 540 lines
1
/*
2
** Copyright (c) 2006 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file implements several non-trivial file handling wrapper functions
19
** on Windows using the Win32 API.
20
*/
21
#include "config.h"
22
#ifdef _WIN32
23
/* This code is for win32 only */
24
#include <sys/stat.h>
25
#include <windows.h>
26
#include "winfile.h"
27
28
#ifndef LABEL_SECURITY_INFORMATION
29
# define LABEL_SECURITY_INFORMATION (0x00000010L)
30
#endif
31
32
/*
33
** Fill stat buf with information received from stat() or lstat().
34
** lstat() is called on Unix if eFType is RepoFile and the allow-symlinks
35
** setting is on. But as windows does not support symbolic links, the
36
** eFType parameter is ignored here.
37
*/
38
int win32_stat(const wchar_t *zFilename, struct fossilStat *buf, int eFType){
39
WIN32_FILE_ATTRIBUTE_DATA attr;
40
int rc = GetFileAttributesExW(zFilename, GetFileExInfoStandard, &attr);
41
if( rc ){
42
ULARGE_INTEGER ull;
43
ull.LowPart = attr.ftLastWriteTime.dwLowDateTime;
44
ull.HighPart = attr.ftLastWriteTime.dwHighDateTime;
45
buf->st_mode = (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?
46
S_IFDIR : S_IFREG;
47
buf->st_size = (((i64)attr.nFileSizeHigh)<<32) | attr.nFileSizeLow;
48
buf->st_mtime = ull.QuadPart / 10000000ULL - 11644473600ULL;
49
}
50
return !rc;
51
}
52
53
/*
54
** Wrapper around the access() system call. This code was copied from Tcl
55
** 8.6 and then modified.
56
*/
57
int win32_access(const wchar_t *zFilename, int flags){
58
int rc = 0;
59
PSECURITY_DESCRIPTOR pSd = NULL;
60
unsigned long size = 0;
61
PSID pSid = NULL;
62
BOOL sidDefaulted;
63
BOOL impersonated = FALSE;
64
SID_IDENTIFIER_AUTHORITY unmapped = {{0, 0, 0, 0, 0, 22}};
65
GENERIC_MAPPING genMap;
66
HANDLE hToken = NULL;
67
DWORD desiredAccess = 0, grantedAccess = 0;
68
BOOL accessYesNo = FALSE;
69
PPRIVILEGE_SET pPrivSet = NULL;
70
DWORD privSetSize = 0;
71
DWORD attr = GetFileAttributesW(zFilename);
72
73
if( attr==INVALID_FILE_ATTRIBUTES ){
74
/*
75
* File might not exist.
76
*/
77
78
if( GetLastError()!=ERROR_SHARING_VIOLATION ){
79
rc = -1; goto done;
80
}
81
}
82
83
if( flags==F_OK ){
84
/*
85
* File exists, nothing else to check.
86
*/
87
88
goto done;
89
}
90
91
if( (flags & W_OK)
92
&& (attr & FILE_ATTRIBUTE_READONLY)
93
&& !(attr & FILE_ATTRIBUTE_DIRECTORY) ){
94
/*
95
* The attributes say the file is not writable. If the file is a
96
* regular file (i.e., not a directory), then the file is not
97
* writable, full stop. For directories, the read-only bit is
98
* (mostly) ignored by Windows, so we can't ascertain anything about
99
* directory access from the attrib data.
100
*/
101
102
rc = -1; goto done;
103
}
104
105
/*
106
* It looks as if the permissions are ok, but if we are on NT, 2000 or XP,
107
* we have a more complex permissions structure so we try to check that.
108
* The code below is remarkably complex for such a simple thing as finding
109
* what permissions the OS has set for a file.
110
*/
111
112
/*
113
* First find out how big the buffer needs to be.
114
*/
115
116
GetFileSecurityW(zFilename,
117
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
118
DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
119
0, 0, &size);
120
121
/*
122
* Should have failed with ERROR_INSUFFICIENT_BUFFER
123
*/
124
125
if( GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){
126
/*
127
* Most likely case is ERROR_ACCESS_DENIED, which we will convert to
128
* EACCES - just what we want!
129
*/
130
131
rc = -1; goto done;
132
}
133
134
/*
135
* Now size contains the size of buffer needed.
136
*/
137
138
pSd = (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(), 0, size);
139
140
if( pSd==NULL ){
141
rc = -1; goto done;
142
}
143
144
/*
145
* Call GetFileSecurity() for real.
146
*/
147
148
if( !GetFileSecurityW(zFilename,
149
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
150
DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
151
pSd, size, &size) ){
152
/*
153
* Error getting owner SD
154
*/
155
156
rc = -1; goto done;
157
}
158
159
/*
160
* As of Samba 3.0.23 (10-Jul-2006), unmapped users and groups are
161
* assigned to SID domains S-1-22-1 and S-1-22-2, where "22" is the
162
* top-level authority. If the file owner and group is unmapped then
163
* the ACL access check below will only test against world access,
164
* which is likely to be more restrictive than the actual access
165
* restrictions. Since the ACL tests are more likely wrong than
166
* right, skip them. Moreover, the unix owner access permissions are
167
* usually mapped to the Windows attributes, so if the user is the
168
* file owner then the attrib checks above are correct (as far as they
169
* go).
170
*/
171
172
if( !GetSecurityDescriptorOwner(pSd, &pSid, &sidDefaulted) ||
173
memcmp(GetSidIdentifierAuthority(pSid), &unmapped,
174
sizeof(SID_IDENTIFIER_AUTHORITY))==0 ){
175
goto done; /* Attrib tests say access allowed. */
176
}
177
178
/*
179
* Perform security impersonation of the user and open the resulting
180
* thread token.
181
*/
182
183
if( !ImpersonateSelf(SecurityImpersonation) ){
184
/*
185
* Unable to perform security impersonation.
186
*/
187
188
rc = -1; goto done;
189
}
190
impersonated = TRUE;
191
192
if( !OpenThreadToken(GetCurrentThread(),
193
TOKEN_DUPLICATE | TOKEN_QUERY, FALSE, &hToken) ){
194
/*
195
* Unable to get current thread's token.
196
*/
197
198
rc = -1; goto done;
199
}
200
201
/*
202
* Setup desiredAccess according to the access priveleges we are
203
* checking.
204
*/
205
206
if( flags & R_OK ){
207
desiredAccess |= FILE_GENERIC_READ;
208
}
209
if( flags & W_OK){
210
desiredAccess |= FILE_GENERIC_WRITE;
211
}
212
213
memset(&genMap, 0, sizeof(GENERIC_MAPPING));
214
genMap.GenericRead = FILE_GENERIC_READ;
215
genMap.GenericWrite = FILE_GENERIC_WRITE;
216
genMap.GenericExecute = FILE_GENERIC_EXECUTE;
217
genMap.GenericAll = FILE_ALL_ACCESS;
218
219
AccessCheck(pSd, hToken, desiredAccess, &genMap, 0,
220
&privSetSize, &grantedAccess, &accessYesNo);
221
/*
222
* Should have failed with ERROR_INSUFFICIENT_BUFFER
223
*/
224
225
if( GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){
226
rc = -1; goto done;
227
}
228
pPrivSet = (PPRIVILEGE_SET)HeapAlloc(GetProcessHeap(), 0, privSetSize);
229
230
if( pPrivSet==NULL ){
231
rc = -1; goto done;
232
}
233
234
/*
235
* Perform access check using the token.
236
*/
237
238
if( !AccessCheck(pSd, hToken, desiredAccess, &genMap, pPrivSet,
239
&privSetSize, &grantedAccess, &accessYesNo) ){
240
/*
241
* Unable to perform access check.
242
*/
243
244
rc = -1; goto done;
245
}
246
if( !accessYesNo ){
247
rc = -1;
248
}
249
250
done:
251
252
if( hToken != NULL ){
253
CloseHandle(hToken);
254
}
255
if( impersonated ){
256
RevertToSelf();
257
impersonated = FALSE;
258
}
259
if( pPrivSet!=NULL ){
260
HeapFree(GetProcessHeap(), 0, pPrivSet);
261
}
262
if( pSd!=NULL ){
263
HeapFree(GetProcessHeap(), 0, pSd);
264
}
265
return rc;
266
}
267
268
/*
269
** Wrapper around the chdir() system call.
270
*/
271
int win32_chdir(const wchar_t *zChDir, int bChroot){
272
int rc = (int)!SetCurrentDirectoryW(zChDir);
273
return rc;
274
}
275
276
/*
277
** Get the current working directory.
278
**
279
** On windows, the name is converted from unicode to UTF8 and all '\\'
280
** characters are converted to '/'.
281
*/
282
void win32_getcwd(char *zBuf, int nBuf){
283
int i;
284
char *zUtf8;
285
wchar_t *zWide = fossil_malloc( sizeof(wchar_t)*nBuf );
286
if( GetCurrentDirectoryW(nBuf, zWide)==0 ){
287
fossil_fatal("cannot find current working directory.");
288
}
289
zUtf8 = fossil_path_to_utf8(zWide);
290
fossil_free(zWide);
291
for(i=0; zUtf8[i]; i++) if( zUtf8[i]=='\\' ) zUtf8[i] = '/';
292
strncpy(zBuf, zUtf8, nBuf);
293
fossil_path_free(zUtf8);
294
}
295
296
/* Perform case-insensitive comparison of two UTF-16 file names. Try to load the
297
** CompareStringOrdinal() function on Windows Vista and newer, and resort to the
298
** RtlEqualUnicodeString() function on Windows XP.
299
** The dance to invoke RtlEqualUnicodeString() is necessary because lstrcmpiW()
300
** performs linguistic comparison, while the former performs binary comparison.
301
** As an example, matching "ß" (U+00DF Latin Small Letter Sharp S) with "ss" is
302
** undesirable in file name comparison, so lstrcmpiW() is only invoked in cases
303
** that are technically impossible and contradicting all known laws of physics.
304
*/
305
int win32_filenames_equal_nocase(
306
const wchar_t *fn1,
307
const wchar_t *fn2
308
){
309
/* ---- Data types used by dynamically loaded API functions. -------------- */
310
typedef struct { /* UNICODE_STRING from <ntdef.h> */
311
USHORT Length;
312
USHORT MaximumLength;
313
PWSTR Buffer;
314
} MY_UNICODE_STRING;
315
/* ---- Prototypes for dynamically loaded API functions. ------------------ */
316
typedef int (WINAPI *FNCOMPARESTRINGORDINAL)(LPCWCH,int,LPCWCH,int,BOOL);
317
typedef VOID (NTAPI *FNRTLINITUNICODESTRING)(MY_UNICODE_STRING*,PCWSTR);
318
typedef BOOLEAN (NTAPI *FNRTLEQUALUNICODESTRING)
319
(MY_UNICODE_STRING*,MY_UNICODE_STRING*,BOOLEAN);
320
/* ------------------------------------------------------------------------ */
321
static FNCOMPARESTRINGORDINAL fnCompareStringOrdinal;
322
static FNRTLINITUNICODESTRING fnRtlInitUnicodeString;
323
static FNRTLEQUALUNICODESTRING fnRtlEqualUnicodeString;
324
static int loaded_CompareStringOrdinal;
325
static int loaded_RtlUnicodeStringAPIs;
326
if( !loaded_CompareStringOrdinal ){
327
fnCompareStringOrdinal = (FNCOMPARESTRINGORDINAL)
328
GetProcAddress(GetModuleHandleA("kernel32"),"CompareStringOrdinal");
329
loaded_CompareStringOrdinal = 1;
330
}
331
if( fnCompareStringOrdinal ){
332
return fnCompareStringOrdinal(fn1,-1,fn2,-1,1)-2==0;
333
}
334
if( !loaded_RtlUnicodeStringAPIs ){
335
fnRtlInitUnicodeString = (FNRTLINITUNICODESTRING)
336
GetProcAddress(GetModuleHandleA("ntdll"),"RtlInitUnicodeString");
337
fnRtlEqualUnicodeString = (FNRTLEQUALUNICODESTRING)
338
GetProcAddress(GetModuleHandleA("ntdll"),"RtlEqualUnicodeString");
339
loaded_RtlUnicodeStringAPIs = 1;
340
}
341
if( fnRtlInitUnicodeString && fnRtlEqualUnicodeString ){
342
MY_UNICODE_STRING u1, u2;
343
fnRtlInitUnicodeString(&u1,fn1);
344
fnRtlInitUnicodeString(&u2,fn2);
345
return (BOOLEAN/*unsigned char*/)fnRtlEqualUnicodeString(&u1,&u2,1);
346
}
347
/* In what kind of strange parallel universe are we? */
348
return lstrcmpiW(fn1,fn2)==0;
349
}
350
351
/* Helper macros to deal with directory separators. */
352
#define IS_DIRSEP(s,i) ( s[i]=='/' || s[i]=='\\' )
353
#define NEXT_DIRSEP(s,i) while( s[i] && s[i]!='/' && s[i]!='\\' ){i++;}
354
355
/* The Win32 version of file_case_preferred_name() from file.c, which is able to
356
** find case-preserved file names containing non-ASCII characters. The result is
357
** allocated by fossil_malloc() and *should* be free'd by the caller. While this
358
** function usually gets canonicalized paths, it is able to handle any input and
359
** figure out more cases than the original:
360
**
361
** fossil test-case-filename C:/ .//..\WINDOWS\/.//.\SYSTEM32\.\NOTEPAD.EXE
362
** → Original: .//..\WINDOWS\/.//.\SYSTEM32\.\NOTEPAD.EXE
363
** → Modified: .//..\Windows\/.//.\System32\.\notepad.exe
364
**
365
** md ÄÖÜ
366
** fossil test-case-filename ./\ .\äöü\/[empty]\\/
367
** → Original: ./äöü\/[empty]\\/
368
** → Modified: .\ÄÖÜ\/[empty]\\/
369
**
370
** The function preserves slashes and backslashes: only single file or directory
371
** components without directory separators ("basenames") are converted to UTF-16
372
** using fossil_utf8_to_path(), so bypassing its slash ↔ backslash translations.
373
** Note that the original function doesn't preserve all slashes and backslashes,
374
** for example in the second example above.
375
**
376
** NOTE: As of Windows 10, version 1803, case sensitivity may be enabled on a
377
** per-directory basis, as returned by NtQueryInformationFile() with the file
378
** information class FILE_CASE_SENSITIVE_INFORMATION. So this function may be
379
** changed to act like fossil_strdup() for files located in such directories.
380
*/
381
char *win32_file_case_preferred_name(
382
const char *zBase,
383
const char *zPath
384
){
385
int cchBase;
386
int cchPath;
387
int cchBuf;
388
int cchRes;
389
char *zBuf;
390
char *zRes;
391
int ncUsed;
392
int i, j;
393
if( filenames_are_case_sensitive() ){
394
return fossil_strdup(zPath);
395
}
396
cchBase = strlen(zBase);
397
cchPath = strlen(zPath);
398
cchBuf = cchBase + cchPath + 2; /* + NULL + optional directory slash */
399
cchRes = cchPath + 1; /* + NULL */
400
zBuf = fossil_malloc(cchBuf);
401
zRes = fossil_malloc(cchRes);
402
ncUsed = 0;
403
memcpy(zBuf,zBase,cchBase);
404
if( !IS_DIRSEP(zBuf,cchBase-1) ){
405
zBuf[cchBase++]=L'/';
406
}
407
memcpy(zBuf+cchBase,zPath,cchPath+1);
408
i = j = cchBase;
409
while( 1 ){
410
WIN32_FIND_DATAW fd;
411
HANDLE hFind;
412
wchar_t *wzBuf;
413
char *zCompBuf = 0;
414
char *zComp = &zBuf[i];
415
int cchComp;
416
char chSep;
417
int fDone;
418
if( IS_DIRSEP(zBuf,i) ){
419
if( ncUsed+2>cchRes ){ /* Directory slash + NULL*/
420
cchRes += 32; /* Overprovisioning. */
421
zRes = fossil_realloc(zRes,cchRes);
422
}
423
zRes[ncUsed++] = zBuf[i];
424
i = j = i+1;
425
continue;
426
}
427
NEXT_DIRSEP(zBuf,j);
428
fDone = zBuf[j]==0;
429
chSep = zBuf[j];
430
zBuf[j] = 0; /* Truncate working buffer. */
431
wzBuf = fossil_utf8_to_path(zBuf,0);
432
hFind = FindFirstFileW(wzBuf,&fd);
433
if( hFind!=INVALID_HANDLE_VALUE ){
434
wchar_t *wzComp = fossil_utf8_to_path(zComp,0);
435
FindClose(hFind);
436
/* Test fd.cFileName, not fd.cAlternateFileName (classic 8.3 format). */
437
if( win32_filenames_equal_nocase(wzComp,fd.cFileName) ){
438
zCompBuf = fossil_path_to_utf8(fd.cFileName);
439
zComp = zCompBuf;
440
}
441
fossil_path_free(wzComp);
442
}
443
fossil_path_free(wzBuf);
444
cchComp = strlen(zComp);
445
if( ncUsed+cchComp+1>cchRes ){ /* Current component + NULL */
446
cchRes += cchComp + 32; /* Overprovisioning. */
447
zRes = fossil_realloc(zRes,cchRes);
448
}
449
memcpy(zRes+ncUsed,zComp,cchComp);
450
ncUsed += cchComp;
451
if( zCompBuf ){
452
fossil_path_free(zCompBuf);
453
}
454
if( fDone ){
455
zRes[ncUsed] = 0;
456
break;
457
}
458
zBuf[j] = chSep; /* Undo working buffer truncation. */
459
i = j;
460
}
461
fossil_free(zBuf);
462
return zRes;
463
}
464
465
/* Return the unique identifier (UID) for a file, made up of the file identifier
466
** (equal to "inode" for Unix-style file systems) plus the volume serial number.
467
** Call the GetFileInformationByHandleEx() function on Windows Vista, and resort
468
** to the GetFileInformationByHandle() function on Windows XP. The result string
469
** is allocated by mprintf(), or NULL on failure.
470
*/
471
char *win32_file_id(
472
const char *zFileName
473
){
474
/* ---- Data types used by dynamically loaded API functions. -------------- */
475
typedef struct { /* FILE_ID_INFO from <winbase.h> */
476
ULONGLONG VolumeSerialNumber;
477
BYTE FileId[16];
478
} MY_FILE_ID_INFO;
479
/* ---- Prototypes for dynamically loaded API functions. ------------------ */
480
typedef int (WINAPI *FNGETFILEINFORMATIONBYHANDLEEX)
481
(HANDLE,int/*enum*/,MY_FILE_ID_INFO*,DWORD);
482
/* ------------------------------------------------------------------------ */
483
static FNGETFILEINFORMATIONBYHANDLEEX fnGetFileInformationByHandleEx;
484
static int loaded_fnGetFileInformationByHandleEx;
485
wchar_t *wzFileName = fossil_utf8_to_path(zFileName,0);
486
HANDLE hFile;
487
char *zFileId = 0;
488
hFile = CreateFileW(
489
wzFileName,
490
0,
491
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
492
NULL,
493
OPEN_EXISTING,
494
FILE_FLAG_BACKUP_SEMANTICS,
495
NULL);
496
if( hFile!=INVALID_HANDLE_VALUE ){
497
BY_HANDLE_FILE_INFORMATION fi;
498
MY_FILE_ID_INFO fi2;
499
if( !loaded_fnGetFileInformationByHandleEx ){
500
fnGetFileInformationByHandleEx = (FNGETFILEINFORMATIONBYHANDLEEX)
501
GetProcAddress(
502
GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx");
503
loaded_fnGetFileInformationByHandleEx = 1;
504
}
505
if( fnGetFileInformationByHandleEx ){
506
if( fnGetFileInformationByHandleEx(
507
hFile,/*FileIdInfo*/0x12,&fi2,sizeof(fi2)) ){
508
zFileId = mprintf(
509
"%016llx/"
510
"%02x%02x%02x%02x%02x%02x%02x%02x"
511
"%02x%02x%02x%02x%02x%02x%02x%02x",
512
fi2.VolumeSerialNumber,
513
fi2.FileId[15], fi2.FileId[14],
514
fi2.FileId[13], fi2.FileId[12],
515
fi2.FileId[11], fi2.FileId[10],
516
fi2.FileId[9], fi2.FileId[8],
517
fi2.FileId[7], fi2.FileId[6],
518
fi2.FileId[5], fi2.FileId[4],
519
fi2.FileId[3], fi2.FileId[2],
520
fi2.FileId[1], fi2.FileId[0]);
521
}
522
}
523
if( zFileId==0 ){
524
if( GetFileInformationByHandle(hFile,&fi) ){
525
ULARGE_INTEGER FileId = {{
526
/*.LowPart = */ fi.nFileIndexLow,
527
/*.HighPart = */ fi.nFileIndexHigh
528
}};
529
zFileId = mprintf(
530
"%08x/%016llx",
531
fi.dwVolumeSerialNumber,(u64)FileId.QuadPart);
532
}
533
}
534
CloseHandle(hFile);
535
}
536
fossil_path_free(wzFileName);
537
return zFileId;
538
}
539
#endif /* _WIN32 -- This code is for win32 only */
540

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button