-
Notifications
You must be signed in to change notification settings - Fork 71
/
sqWin32FilePrims.c
557 lines (478 loc) · 16.1 KB
/
sqWin32FilePrims.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
/****************************************************************************
* PROJECT: Squeak port for Win32 (NT / Win95)
* FILE: sqWin32FilePrims.c
* CONTENT: File functions
*
* AUTHOR: Andreas Raab (ar)
* ADDRESS: University of Magdeburg, Germany
* EMAIL: raab@isg.cs.uni-magdeburg.de
*
* NOTES:
* 1) This is a bare windows implementation *not* using any stdio stuff.
* It can be used instead of the standard sqFilePrims.c file on systems
* not having standard io libraries (e.g. WinCE)
* 2) For using this you'll need to define WIN32_FILE_SUPPORT globally
* (e.g., in your compiler's project settings)
*
* UPDATES:
* 1) Support for long path names added by using UNC prefix in that case
* (Marcel Taeumel, Hasso Plattner Institute, Postdam, Germany)
* 2) Try to remove the read-only attribute from a file before calling
* DeleteFile. DeleteFile cannot delete read-only files (see comment
* in sqFileDeleteNameSize).
* (Max Leske)
*
*****************************************************************************/
#include <windows.h>
#include <malloc.h>
#include "sq.h"
#include "FilePlugin.h"
#include "sqWin32File.h"
#include "pharovm/debug.h"
#include "sqaio.h"
extern struct VirtualMachine *interpreterProxy;
#ifdef WIN32_FILE_SUPPORT
#define true 1
#define false 0
#define FILE_HANDLE(f) ((HANDLE) (f)->file)
#define FAIL() { return interpreterProxy->primitiveFail(); }
/***
The state of a file is kept in the following structure,
which is stored directly in a Squeak bytes object.
NOTE: The Squeak side is responsible for creating an
object with enough room to store sizeof(SQFile) bytes.
The session ID is used to detect stale file objects--
files that were still open when an image was written.
The file pointer of such files is meaningless.
typedef struct {
int sessionID; (* ikp: must be first *)
void *file;
char writable;
char lastOp; (* 0 = uncommitted, 1 = read, 2 = write *)
char lastChar;
char isStdioStream;
} SQFile;
***/
/**********************************************************************/
#include "sqWin32HandleTable.h"
static HandleTable *win32Files = NULL;
/**********************************************************************/
/*** Variables ***/
int thisSession = 0;
/* answers if the file name in question has a case-sensitive duplicate */
int hasCaseSensitiveDuplicate(WCHAR *path);
typedef union {
LARGE_INTEGER li;
squeakFileOffsetType offset;
} win32FileOffset;
sqInt sqFileThisSession(void) {
return thisSession;
}
sqInt sqFileAtEnd(SQFile *f) {
win32FileOffset ofs;
/* Return true if the file's read/write head is at the end of the file. */
if (!sqFileValid(f))
FAIL();
/* If a stdio handle then assume not at end. */
if (f->isStdioStream)
return 0;
ofs.offset = 0;
ofs.li.LowPart = SetFilePointer(FILE_HANDLE(f), 0, &ofs.li.HighPart, FILE_CURRENT);
return ofs.offset >= sqFileSize(f);
}
sqInt sqFileClose(SQFile *f) {
/* Close the given file. */
if (!sqFileValid(f))
FAIL();
if(!CloseHandle(FILE_HANDLE(f)))
FAIL();
RemoveHandleFromTable(win32Files, FILE_HANDLE(f));
f->file = NULL;
f->sessionID = 0;
f->writable = false;
return 1;
}
sqInt sqFileDeleteNameSize(char* fileNameIndex, sqInt fileNameSize) {
WCHAR *win32Path = NULL;
/* convert the file name into a null-terminated C string */
ALLOC_WIN32_PATH(win32Path, fileNameIndex, fileNameSize);
if(hasCaseSensitiveDuplicate(win32Path))
FAIL();
/* DeleteFile will not delete a file with the read-only attribute set
(e.g. -r--r--r--, see https://msdn.microsoft.com/en-us/library/windows/desktop/aa363915(v=vs.85).aspx).
To ensure that this works the same way as on *nix platforms we need to
remove the read-only attribute (which might fail if the current user
doesn't own the file).
Also note that DeleteFile cannot *effectively* delete a file as long as
there are other processes that hold open handles to that file. The function
will still report success since the file is *marked* for deletion (no new
handles can be opened. See the URL mentioned above for reference).
This will lead to problems during a recursive delete operation since now
the parent directory wont be empty. */
SetFileAttributesW(win32Path, FILE_ATTRIBUTE_NORMAL);
if(!DeleteFileW(win32Path))
FAIL();
return 1;
}
squeakFileOffsetType sqFileGetPosition(SQFile *f) {
win32FileOffset ofs;
/* Return the current position of the file's read/write head. */
if (!sqFileValid(f))
FAIL();
ofs.offset = 0;
ofs.li.LowPart = SetFilePointer(FILE_HANDLE(f), 0, &ofs.li.HighPart, FILE_CURRENT);
return ofs.offset;
}
sqInt sqFileInit(void) {
/* Create a session ID that is unlikely to be repeated.
Zero is never used for a valid session number.
Should be called once at startup time.
*/
#if VM_PROXY_MINOR > 6
thisSession = interpreterProxy->getThisSessionID();
#else
thisSession = GetTickCount();
if (thisSession == 0) thisSession = 1; /* don't use 0 */
#endif
win32Files = (HandleTable*) calloc(1, sizeof(HandleTable));
return 1;
}
sqInt sqFileShutdown(void) {
return 1;
}
sqInt sqFileOpen(SQFile *f, char* fileNameIndex, sqInt fileNameSize, sqInt writeFlag) {
/* Opens the given file using the supplied sqFile structure
to record its state. Fails with no side effects if f is
already open. Files are always opened in binary mode;
Squeak must take care of any line-end character mapping.
*/
HANDLE h;
WCHAR *win32Path = NULL;
/* convert the file name into a null-terminated C string */
ALLOC_WIN32_PATH(win32Path, fileNameIndex, fileNameSize);
if(hasCaseSensitiveDuplicate(win32Path)) {
f->sessionID = 0;
FAIL();
}
h = CreateFileW(win32Path,
writeFlag ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_READ,
writeFlag ? FILE_SHARE_READ : (FILE_SHARE_READ | FILE_SHARE_WRITE),
NULL, /* No security descriptor */
writeFlag ? OPEN_ALWAYS : OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL /* No template */);
if(h == INVALID_HANDLE_VALUE) {
f->sessionID = 0;
FAIL();
} else {
win32FileOffset ofs;
f->sessionID = thisSession;
f->file = (HANDLE)h;
AddHandleToTable(win32Files, h);
/* compute and cache file size */
ofs.offset = 0;
ofs.li.LowPart = SetFilePointer(h, 0, &ofs.li.HighPart, FILE_END);
SetFilePointer(h, 0, NULL, FILE_BEGIN);
f->writable = writeFlag ? true : false;
}
return 1;
}
sqInt sqFileOpenNew(SQFile *f, char* fileNameIndex, sqInt fileNameSize, sqInt* exists) {
HANDLE h;
WCHAR *win32Path = NULL;
*exists = false;
/* convert the file name into a null-terminated C string */
ALLOC_WIN32_PATH(win32Path, fileNameIndex, fileNameSize);
/* test for case duplicates using hasCaseSensitiveDuplicate(), even though
CreateFileW() with CREATE_NEW should fail if any exist, so if
hasCaseSensitiveDuplicate() treats some paths as duplicates that
CreateFileW() doesn't, sqFileOpenNew() will fail like sqFileOpen() would
*/
if(hasCaseSensitiveDuplicate(win32Path)) {
f->sessionID = 0;
FAIL();
}
h = CreateFileW(win32Path,
(GENERIC_READ | GENERIC_WRITE),
FILE_SHARE_READ,
NULL, /* No security descriptor */
CREATE_NEW, /* Only create and open if it doesn't exist */
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL /* No template */);
if(h == INVALID_HANDLE_VALUE) {
f->sessionID = 0;
if (GetLastError() == ERROR_FILE_EXISTS)
*exists = true;
FAIL();
} else {
f->sessionID = thisSession;
f->file = (HANDLE)h;
f->writable = true;
AddHandleToTable(win32Files, h);
}
return 1;
}
sqInt
sqConnectToFileDescriptor(SQFile *sqFile, int fd, sqInt writeFlag)
{
/*
* Open the file with the supplied file descriptor in binary mode.
*
* writeFlag determines whether the file is read-only or writable
* and must be compatible with the existing access.
* sqFile is populated with the file information.
* Smalltalk is reponsible for handling character encoding and
* line ends.
*/
HANDLE file = _fdopen(fd, writeFlag ? "wb" : "rb");
if (!file)
return interpreterProxy->success(false);
return sqConnectToFile(sqFile, file, writeFlag);
}
sqInt
sqConnectToFile(SQFile *sqFile, void *file, sqInt writeFlag)
{
/*
* Populate the supplied SQFile structure with the supplied FILE.
*
* writeFlag indicates whether the file is read-only or writable
* and must be compatible with the existing access.
*/
sqFile->file = (HANDLE)file;
AddHandleToTable(win32Files, file);
/* setSize(sqFile, 0); */
sqFile->sessionID = thisSession;
sqFile->lastOp = 0; /* Unused on Win32 */
sqFile->writable = writeFlag;
return 1;
}
void
sqFileStdioHandlesIntoFile_WithHandle_IsWritable(SQFile * file, HANDLE handle, int isWritable) {
file->sessionID = thisSession;
file->file = handle;
file->writable = isWritable;
file->lastOp = 0; /* unused on win32 */
file->isStdioStream = isFileHandleATTY(handle);
AddHandleToTable(win32Files, handle);
}
/*
* Fill-in files with handles for stdin, stdout and seterr as available and
* answer a bit-mask of the availability:
*
* <0 - Error. The value will be returned to the image using primitiveFailForOSError().
* 0 - no stdio available
* 1 - stdin available
* 2 - stdout available
* 4 - stderr available
*/
sqInt
sqFileStdioHandlesInto(SQFile files[3])
{
HANDLE handle;
int status = 0;
handle = GetStdHandle(STD_INPUT_HANDLE);
if (handle != 0) {
sqFileStdioHandlesIntoFile_WithHandle_IsWritable(&files[0], handle, false);
status += 1; }
handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (handle != 0) {
sqFileStdioHandlesIntoFile_WithHandle_IsWritable(&files[1], handle, true);
status += 2; }
handle = GetStdHandle(STD_ERROR_HANDLE);
if (handle != 0) {
sqFileStdioHandlesIntoFile_WithHandle_IsWritable(&files[2], handle, true);
status += 4; }
return status;
}
/*
* Allow to test if the standard input/output files are from a console or not
* Return values:
* -1 - Error
* 0 - no console (windows only)
* 1 - normal terminal (unix terminal / windows console)
* 2 - pipe
* 3 - file
* 4 - cygwin terminal (windows only)
*/
sqInt sqFileDescriptorType(int fdNum) {
return fileHandleType(_get_osfhandle(fdNum));
}
/*
* Check if the following operation of readConsole will Block
* Returns 1 if it blocks, 0 if not.
*
* If the console is set with mode ENABLE_LINE_INPUT this function
* returns true if there is a VK_RETURN key down in the buffer of events.
* If the console is not set to ENABLE_LINE_INPUT, if the console has an event, this function returns true.
*
* On any error the function returns TRUE, as it it nos clear if the ReadConsole will not block.
*/
int checkIfReadConsoleWillBlock(HANDLE consoleHandle){
INPUT_RECORD *events = NULL;
DWORD numberOfEvents;
DWORD mode;
//Try to get the number of events in the queue. On error, I return as it the ReadConsole will block.
if(GetNumberOfConsoleInputEvents(consoleHandle, &numberOfEvents) == 0){
return true;
}
if(numberOfEvents == 0)
return true;
if(GetConsoleMode(consoleHandle, &mode) == 0){
return true;
}
/*
* Having a single event in ENABLE_LINE_INPUT mode, we return that it will not block
*/
if(numberOfEvents > 0 && ((mode & ENABLE_LINE_INPUT) == 0)) {
return false;
}
events = malloc(sizeof(INPUT_RECORD) * numberOfEvents);
if(PeekConsoleInput(consoleHandle, events, numberOfEvents, &numberOfEvents) == 0){
free(events);
return true;
};
/*
* Check for the keyDown of VK_RETURN, as this is used by ReadConsole in ENABLE_LINE_INPUT mode
*/
int i = 0;
for(i = 0; i < numberOfEvents; i++){
if( events[i].EventType == KEY_EVENT
&& events[i].Event.KeyEvent.bKeyDown
&& events[i].Event.KeyEvent.wVirtualKeyCode == VK_RETURN){
free(events);
return false;
}
}
free(events);
return true;
}
size_t sqFileReadIntoAt(SQFile *f, size_t count, char* byteArrayIndex, size_t startIndex) {
/* Read count bytes from the given file into byteArray starting at
startIndex. byteArray is the address of the first byte of a
Squeak bytes object (e.g. String or ByteArray). startIndex
is a zero-based index; that is a startIndex of 0 starts writing
at the first byte of byteArray.
*/
DWORD dwReallyRead;
if (!sqFileValid(f))
FAIL();
if (f->isStdioStream){
if(checkIfReadConsoleWillBlock(FILE_HANDLE(f))){
return 0;
}
ReadConsole(FILE_HANDLE(f), (LPVOID) (byteArrayIndex+startIndex), count,
&dwReallyRead, NULL);
} else {
if (GetFileType(FILE_HANDLE(f)) == FILE_TYPE_PIPE ){
DWORD maxDataAvailable;
DWORD toRead;
PeekNamedPipe(FILE_HANDLE(f),
NULL,
0,
NULL,
&maxDataAvailable,
NULL);
if(maxDataAvailable == 0)
return 0;
toRead = count < maxDataAvailable ? count : maxDataAvailable;
ReadFile(FILE_HANDLE(f), (LPVOID) (byteArrayIndex+startIndex), toRead,
&dwReallyRead, NULL);
} else {
ReadFile(FILE_HANDLE(f), (LPVOID) (byteArrayIndex+startIndex), count,
&dwReallyRead, NULL);
}
}
return dwReallyRead;
}
sqInt sqFileRenameOldSizeNewSize(char* oldNameIndex, sqInt oldNameSize, char* newNameIndex, sqInt newNameSize)
{
WCHAR *oldPath = NULL;
WCHAR *newPath = NULL;
/* convert the file names into a null-terminated C string */
ALLOC_WIN32_PATH(oldPath, oldNameIndex, oldNameSize);
ALLOC_WIN32_PATH(newPath, newNameIndex, newNameSize);
if(hasCaseSensitiveDuplicate(oldPath))
FAIL();
if(!MoveFileW(oldPath, newPath))
FAIL();
return 1;
}
sqInt sqFileSetPosition(SQFile *f, squeakFileOffsetType position)
{
win32FileOffset ofs;
ofs.offset = position;
/* Set the file's read/write head to the given position. */
if (!sqFileValid(f))
FAIL();
SetFilePointer(FILE_HANDLE(f), ofs.li.LowPart, &ofs.li.HighPart, FILE_BEGIN);
return 1;
}
squeakFileOffsetType sqFileSize(SQFile *f) {
/* Return the length of the given file. */
win32FileOffset ofs;
if (!sqFileValid(f))
FAIL();
ofs.offset = 0;
ofs.li.LowPart = GetFileSize(FILE_HANDLE(f), &ofs.li.HighPart);
return ofs.offset;
}
sqInt sqFileFlush(SQFile *f) {
if (!sqFileValid(f))
FAIL();
/* note: ignores the return value in case of read-only access */
FlushFileBuffers(FILE_HANDLE(f));
return 1;
}
sqInt sqFileSync(SQFile *f) {
/*
* sqFileFlush uses FlushFileBuffers which is equivalent to fsync on windows
* as long as WriteFile is used directly and no other buffering is done.
*/
return sqFileFlush(f);
}
sqInt sqFileTruncate(SQFile *f, squeakFileOffsetType offset) {
win32FileOffset ofs;
ofs.offset = offset;
if (!sqFileValid(f))
FAIL();
SetFilePointer(FILE_HANDLE(f), ofs.li.LowPart, (PLONG)&ofs.li.HighPart, FILE_BEGIN);
if(!SetEndOfFile(FILE_HANDLE(f))) return 0;
return 1;
}
sqInt sqFileValid(SQFile *f) {
if(NULL == f) return false;
if(f->sessionID != thisSession) return false;
if(!IsHandleInTable(win32Files, FILE_HANDLE(f))) {
logWarn("WARNING: Manufactured file handle detected!\n");
return false;
}
return true;
}
size_t sqFileWriteFromAt(SQFile *f, size_t count, char* byteArrayIndex, size_t startIndex) {
/* Write count bytes to the given writable file starting at startIndex
in the given byteArray. (See comment in sqFileReadIntoAt for interpretation
of byteArray and startIndex).
*/
DWORD dwReallyWritten;
win32FileOffset ofs;
if (!(sqFileValid(f) && f->writable))
FAIL();
if (f->isStdioStream)
WriteConsole(FILE_HANDLE(f), (LPVOID) (byteArrayIndex + startIndex), count, &dwReallyWritten, NULL);
else
WriteFile(FILE_HANDLE(f), (LPVOID) (byteArrayIndex + startIndex), count, &dwReallyWritten, NULL);
if (dwReallyWritten != count)
FAIL();
return dwReallyWritten;
}
EXPORT(void) aioEnableExternalHandler(int fd, HANDLE handle, void *clientData, aioHandler handlerFn, int mask);
EXPORT(void)
handleWaitOnStream(int fd, void *clientData, int flag){
interpreterProxy->signalSemaphoreWithIndex((sqInt)clientData);
aioDisable(fd);
}
EXPORT(sqInt)
waitForDataonSemaphoreIndex(SQFile *file, sqInt semaphoreIndex){
aioEnableExternalHandler((int)FILE_HANDLE(file), FILE_HANDLE(file), (void*)semaphoreIndex, handleWaitOnStream, AIO_R);
}
#endif /* WIN32_FILE_SUPPORT */