-
Notifications
You must be signed in to change notification settings - Fork 184
Expand file tree
/
Copy pathsource.c
More file actions
518 lines (450 loc) · 15.4 KB
/
Copy pathsource.c
File metadata and controls
518 lines (450 loc) · 15.4 KB
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
#include "prism/internal/source.h"
#include "prism/internal/allocator.h"
#include "prism/internal/buffer.h"
#include <stdlib.h>
#include <string.h>
/* The following headers are necessary to read files using demand paging. */
#ifdef _WIN32
#include <windows.h>
#elif defined(_POSIX_MAPPED_FILES)
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#elif defined(PRISM_HAS_FILESYSTEM)
#include <fcntl.h>
#include <sys/stat.h>
#endif
static const uint8_t empty_source[] = "";
/**
* Allocate and initialize a pm_source_t with the given fields.
*/
static pm_source_t *
pm_source_alloc(const uint8_t *source, size_t length, pm_source_type_t type) {
pm_source_t *result = xmalloc(sizeof(pm_source_t));
if (result == NULL) abort();
*result = (struct pm_source_t) {
.source = source,
.length = length,
.type = type
};
return result;
}
/**
* Create a new source that wraps existing constant memory.
*/
pm_source_t *
pm_source_constant_new(const uint8_t *data, size_t length) {
return pm_source_alloc(data, length, PM_SOURCE_CONSTANT);
}
/**
* Create a new source that wraps existing shared memory.
*/
pm_source_t *
pm_source_shared_new(const uint8_t *data, size_t length) {
return pm_source_alloc(data, length, PM_SOURCE_SHARED);
}
/**
* Create a new source that owns its memory.
*/
pm_source_t *
pm_source_owned_new(uint8_t *data, size_t length) {
return pm_source_alloc(data, length, PM_SOURCE_OWNED);
}
#ifdef _WIN32
/**
* Represents a file handle on Windows, where the path will need to be freed
* when the file is closed.
*/
typedef struct {
/** The path to the file, which will become allocated memory. */
WCHAR *path;
/** The size of the allocated path in bytes. */
size_t path_size;
/** The handle to the file, which will start as uninitialized memory. */
HANDLE file;
} pm_source_file_handle_t;
/**
* Open the file indicated by the filepath parameter for reading on Windows.
*/
static pm_source_init_result_t
pm_source_file_handle_open(pm_source_file_handle_t *handle, const char *filepath) {
int length = MultiByteToWideChar(CP_UTF8, 0, filepath, -1, NULL, 0);
if (length == 0) return PM_SOURCE_INIT_ERROR_GENERIC;
handle->path_size = sizeof(WCHAR) * ((size_t) length);
handle->path = xmalloc(handle->path_size);
if ((handle->path == NULL) || (MultiByteToWideChar(CP_UTF8, 0, filepath, -1, handle->path, length) == 0)) {
xfree_sized(handle->path, handle->path_size);
return PM_SOURCE_INIT_ERROR_GENERIC;
}
handle->file = CreateFileW(handle->path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (handle->file == INVALID_HANDLE_VALUE) {
pm_source_init_result_t result = PM_SOURCE_INIT_ERROR_GENERIC;
if (GetLastError() == ERROR_ACCESS_DENIED) {
DWORD attributes = GetFileAttributesW(handle->path);
if ((attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
result = PM_SOURCE_INIT_ERROR_DIRECTORY;
}
}
xfree_sized(handle->path, handle->path_size);
return result;
}
return PM_SOURCE_INIT_SUCCESS;
}
/**
* Close the file handle and free the path.
*/
static void
pm_source_file_handle_close(pm_source_file_handle_t *handle) {
xfree_sized(handle->path, handle->path_size);
CloseHandle(handle->file);
}
#endif
/**
* Create a new source by memory-mapping a file.
*/
pm_source_t *
pm_source_mapped_new(const char *filepath, int open_flags, pm_source_init_result_t *result) {
#ifdef _WIN32
(void) open_flags;
/* Open the file for reading. */
pm_source_file_handle_t handle;
*result = pm_source_file_handle_open(&handle, filepath);
if (*result != PM_SOURCE_INIT_SUCCESS) return NULL;
/* Get the file size. */
DWORD file_size = GetFileSize(handle.file, NULL);
if (file_size == INVALID_FILE_SIZE) {
pm_source_file_handle_close(&handle);
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
/* If the file is empty, then return a constant source. */
if (file_size == 0) {
pm_source_file_handle_close(&handle);
*result = PM_SOURCE_INIT_SUCCESS;
return pm_source_alloc(empty_source, 0, PM_SOURCE_CONSTANT);
}
/* Create a mapping of the file. */
HANDLE mapping = CreateFileMapping(handle.file, NULL, PAGE_READONLY, 0, 0, NULL);
if (mapping == NULL) {
pm_source_file_handle_close(&handle);
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
/* Map the file into memory. */
uint8_t *source = (uint8_t *) MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
CloseHandle(mapping);
pm_source_file_handle_close(&handle);
if (source == NULL) {
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
*result = PM_SOURCE_INIT_SUCCESS;
return pm_source_alloc(source, (size_t) file_size, PM_SOURCE_MAPPED);
#elif defined(_POSIX_MAPPED_FILES)
/* Open the file for reading. */
int fd = open(filepath, O_RDONLY | open_flags);
if (fd == -1) {
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
/* Stat the file to get the file size. */
struct stat sb;
if (fstat(fd, &sb) == -1) {
close(fd);
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
/* Ensure it is a file and not a directory. */
if (S_ISDIR(sb.st_mode)) {
close(fd);
*result = PM_SOURCE_INIT_ERROR_DIRECTORY;
return NULL;
}
/*
* For non-regular files (pipes, character devices), return a specific
* error so the caller can handle reading through their own I/O layer.
*/
if (!S_ISREG(sb.st_mode)) {
close(fd);
*result = PM_SOURCE_INIT_ERROR_NON_REGULAR;
return NULL;
}
/* mmap the file descriptor to virtually get the contents. */
size_t size = (size_t) sb.st_size;
if (size == 0) {
close(fd);
*result = PM_SOURCE_INIT_SUCCESS;
return pm_source_alloc(empty_source, 0, PM_SOURCE_CONSTANT);
}
uint8_t *source = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (source == MAP_FAILED) {
close(fd);
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
close(fd);
*result = PM_SOURCE_INIT_SUCCESS;
return pm_source_alloc(source, size, PM_SOURCE_MAPPED);
#else
(void) open_flags;
return pm_source_file_new(filepath, result);
#endif
}
/**
* Create a new source by reading a file into a heap-allocated buffer.
*/
pm_source_t *
pm_source_file_new(const char *filepath, pm_source_init_result_t *result) {
#ifdef _WIN32
/* Open the file for reading. */
pm_source_file_handle_t handle;
*result = pm_source_file_handle_open(&handle, filepath);
if (*result != PM_SOURCE_INIT_SUCCESS) return NULL;
/* Get the file size. */
const DWORD file_size = GetFileSize(handle.file, NULL);
if (file_size == INVALID_FILE_SIZE) {
pm_source_file_handle_close(&handle);
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
/* If the file is empty, return a constant source. */
if (file_size == 0) {
pm_source_file_handle_close(&handle);
*result = PM_SOURCE_INIT_SUCCESS;
return pm_source_alloc(empty_source, 0, PM_SOURCE_CONSTANT);
}
/* Create a buffer to read the file into. */
uint8_t *source = xmalloc(file_size);
if (source == NULL) {
pm_source_file_handle_close(&handle);
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
/* Read the contents of the file. */
DWORD bytes_read;
if (!ReadFile(handle.file, source, file_size, &bytes_read, NULL)) {
xfree_sized(source, file_size);
pm_source_file_handle_close(&handle);
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
/* Check the number of bytes read. */
if (bytes_read != file_size) {
xfree_sized(source, file_size);
pm_source_file_handle_close(&handle);
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
pm_source_file_handle_close(&handle);
*result = PM_SOURCE_INIT_SUCCESS;
return pm_source_alloc(source, (size_t) file_size, PM_SOURCE_OWNED);
#elif defined(PRISM_HAS_FILESYSTEM)
/* Open the file for reading. */
int fd = open(filepath, O_RDONLY);
if (fd == -1) {
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
/* Stat the file to get the file size. */
struct stat sb;
if (fstat(fd, &sb) == -1) {
close(fd);
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
/* Ensure it is a file and not a directory. */
if (S_ISDIR(sb.st_mode)) {
close(fd);
*result = PM_SOURCE_INIT_ERROR_DIRECTORY;
return NULL;
}
/* Check the size to see if it's empty. */
size_t size = (size_t) sb.st_size;
if (size == 0) {
close(fd);
*result = PM_SOURCE_INIT_SUCCESS;
return pm_source_alloc(empty_source, 0, PM_SOURCE_CONSTANT);
}
const size_t length = (size_t) size;
uint8_t *source = xmalloc(length);
if (source == NULL) {
close(fd);
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
ssize_t bytes_read = read(fd, source, length);
close(fd);
if (bytes_read == -1 || (size_t) bytes_read != length) {
xfree_sized(source, length);
*result = PM_SOURCE_INIT_ERROR_GENERIC;
return NULL;
}
*result = PM_SOURCE_INIT_SUCCESS;
return pm_source_alloc(source, length, PM_SOURCE_OWNED);
#else
(void) filepath;
*result = PM_SOURCE_INIT_ERROR_GENERIC;
perror("pm_source_file_new is not implemented for this platform");
return NULL;
#endif
}
/**
* Create a new source by reading from a stream. This allocates the source
* but does not read from the stream yet. Use pm_source_stream_read to read
* data.
*/
pm_source_t *
pm_source_stream_new(void *stream, pm_source_stream_fgets_t *fgets, pm_source_stream_feof_t *feof) {
pm_source_t *source = pm_source_alloc(NULL, 0, PM_SOURCE_STREAM);
source->stream.buffer = pm_buffer_new();
source->stream.stream = stream;
source->stream.fgets = fgets;
source->stream.feof = feof;
source->stream.eof = false;
return source;
}
/**
* Read from the stream into the source's internal buffer until __END__ is
* encountered or EOF is reached. Updates the source pointer and length.
*
* Returns true if EOF was reached, false if __END__ was encountered.
*/
bool
pm_source_stream_read(pm_source_t *source) {
pm_buffer_t *buffer = source->stream.buffer;
#define LINE_SIZE 4096
char line[LINE_SIZE];
/*
* A chunk read from the stream may legitimately contain embedded NUL bytes,
* so the NUL terminator written by fgets cannot be located with strlen.
* Instead we prefill the buffer with a non-NUL sentinel before each read;
* the terminator is then the only NUL at or after the data, so it can be
* found by scanning from the end. The value only needs to differ from '\0'.
*/
#define LINE_FILL 0xff
while (memset(line, LINE_FILL, LINE_SIZE), source->stream.fgets(line, LINE_SIZE, source->stream.stream) != NULL) {
/*
* Locate the NUL terminator written by fgets (the last NUL in the
* buffer) to recover the length of the chunk, then drop the terminator.
*/
size_t length = LINE_SIZE;
while (length > 0 && line[length - 1] != '\0') length--;
if (length > 0) length--;
/*
* An empty chunk means the stream returned an empty string instead of
* signaling EOF (a well-behaved stream never does this). We can't make
* progress on it, so stop reading rather than spinning forever.
*/
if (length == 0) {
break;
}
/* Append the chunk to the buffer. */
pm_buffer_append_string(buffer, line, length);
bool newline = (line[length - 1] == '\n');
/*
* A chunk with no trailing newline is either a line longer than the
* read buffer (keep reading the rest of it) or the final line at EOF.
* This is the only place the stream's EOF state changes what we do
* next, so it is the only place we ask the stream whether it has hit
* EOF. A newline-terminated line needs no such check: if it happens to
* be the last line, the next fgets returns NULL and ends the loop.
*/
if (!newline && !source->stream.feof(source->stream.stream)) {
continue;
}
/*
* Check if the line matches the __END__ marker. If it does, then stop
* reading and return false. In most circumstances, this means we should
* stop reading from the stream so that the DATA constant can pick it
* up.
*/
switch (length) {
case 7:
if (strncmp(line, "__END__", 7) == 0) {
source->source = (const uint8_t *) pm_buffer_value(buffer);
source->length = pm_buffer_length(buffer);
return false;
}
break;
case 8:
if (strncmp(line, "__END__\n", 8) == 0) {
source->source = (const uint8_t *) pm_buffer_value(buffer);
source->length = pm_buffer_length(buffer);
return false;
}
break;
case 9:
if (strncmp(line, "__END__\r\n", 9) == 0) {
source->source = (const uint8_t *) pm_buffer_value(buffer);
source->length = pm_buffer_length(buffer);
return false;
}
break;
}
/*
* A chunk that reached here without a trailing newline is the final
* line at EOF (the check above would have continued otherwise), so
* there is nothing more to read.
*/
if (!newline) {
break;
}
}
#undef LINE_FILL
#undef LINE_SIZE
source->stream.eof = true;
source->source = (const uint8_t *) pm_buffer_value(buffer);
source->length = pm_buffer_length(buffer);
return true;
}
/**
* Returns whether the stream source has reached EOF.
*/
bool
pm_source_stream_eof(const pm_source_t *source) {
return source->stream.eof;
}
/**
* Free the given source and any memory it owns.
*/
void
pm_source_free(pm_source_t *source) {
switch (source->type) {
case PM_SOURCE_CONSTANT:
case PM_SOURCE_SHARED:
/* No cleanup needed for the data. */
break;
case PM_SOURCE_OWNED:
xfree_sized((void *) source->source, source->length);
break;
case PM_SOURCE_MAPPED:
#if defined(_WIN32)
if (source->length > 0) {
UnmapViewOfFile((void *) source->source);
}
#elif defined(_POSIX_MAPPED_FILES)
if (source->length > 0) {
munmap((void *) source->source, source->length);
}
#endif
break;
case PM_SOURCE_STREAM:
pm_buffer_free(source->stream.buffer);
break;
}
xfree_sized(source, sizeof(pm_source_t));
}
/**
* Returns the length of the source data in bytes.
*/
size_t
pm_source_length(const pm_source_t *source) {
return source->length;
}
/**
* Returns a pointer to the source data.
*/
const uint8_t *
pm_source_source(const pm_source_t *source) {
return source->source;
}