Ruby 4.1.0dev (2026-03-31 revision 34c94b31ab0be79792a48e42e3a6611f300a0dc2)
source.c
1#include "prism/internal/source.h"
2
3#include "prism/internal/allocator.h"
4#include "prism/internal/buffer.h"
5
6#include <stdlib.h>
7#include <string.h>
8
9/* The following headers are necessary to read files using demand paging. */
10#ifdef _WIN32
11#include <windows.h>
12#elif defined(_POSIX_MAPPED_FILES)
13#include <fcntl.h>
14#include <sys/mman.h>
15#include <sys/stat.h>
16#elif defined(PRISM_HAS_FILESYSTEM)
17#include <fcntl.h>
18#include <sys/stat.h>
19#endif
20
21static const uint8_t empty_source[] = "";
22
26static pm_source_t *
27pm_source_alloc(const uint8_t *source, size_t length, pm_source_type_t type) {
28 pm_source_t *result = xmalloc(sizeof(pm_source_t));
29 if (result == NULL) abort();
30
31 *result = (struct pm_source_t) {
32 .source = source,
33 .length = length,
34 .type = type
35 };
36
37 return result;
38}
39
44pm_source_constant_new(const uint8_t *data, size_t length) {
45 return pm_source_alloc(data, length, PM_SOURCE_CONSTANT);
46}
47
52pm_source_shared_new(const uint8_t *data, size_t length) {
53 return pm_source_alloc(data, length, PM_SOURCE_SHARED);
54}
55
60pm_source_owned_new(uint8_t *data, size_t length) {
61 return pm_source_alloc(data, length, PM_SOURCE_OWNED);
62}
63
64#ifdef _WIN32
69typedef struct {
71 WCHAR *path;
72
74 size_t path_size;
75
77 HANDLE file;
78} pm_source_file_handle_t;
79
84pm_source_file_handle_open(pm_source_file_handle_t *handle, const char *filepath) {
85 int length = MultiByteToWideChar(CP_UTF8, 0, filepath, -1, NULL, 0);
86 if (length == 0) return PM_SOURCE_INIT_ERROR_GENERIC;
87
88 handle->path_size = sizeof(WCHAR) * ((size_t) length);
89 handle->path = xmalloc(handle->path_size);
90 if ((handle->path == NULL) || (MultiByteToWideChar(CP_UTF8, 0, filepath, -1, handle->path, length) == 0)) {
91 xfree_sized(handle->path, handle->path_size);
93 }
94
95 handle->file = CreateFileW(handle->path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
96 if (handle->file == INVALID_HANDLE_VALUE) {
98
99 if (GetLastError() == ERROR_ACCESS_DENIED) {
100 DWORD attributes = GetFileAttributesW(handle->path);
101 if ((attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
103 }
104 }
105
106 xfree_sized(handle->path, handle->path_size);
107 return result;
108 }
109
111}
112
116static void
117pm_source_file_handle_close(pm_source_file_handle_t *handle) {
118 xfree_sized(handle->path, handle->path_size);
119 CloseHandle(handle->file);
120}
121#endif
122
127pm_source_mapped_new(const char *filepath, int open_flags, pm_source_init_result_t *result) {
128#ifdef _WIN32
129 (void) open_flags;
130
131 /* Open the file for reading. */
132 pm_source_file_handle_t handle;
133 *result = pm_source_file_handle_open(&handle, filepath);
134 if (*result != PM_SOURCE_INIT_SUCCESS) return NULL;
135
136 /* Get the file size. */
137 DWORD file_size = GetFileSize(handle.file, NULL);
138 if (file_size == INVALID_FILE_SIZE) {
139 pm_source_file_handle_close(&handle);
141 return NULL;
142 }
143
144 /* If the file is empty, then return a constant source. */
145 if (file_size == 0) {
146 pm_source_file_handle_close(&handle);
147 *result = PM_SOURCE_INIT_SUCCESS;
148 return pm_source_alloc(empty_source, 0, PM_SOURCE_CONSTANT);
149 }
150
151 /* Create a mapping of the file. */
152 HANDLE mapping = CreateFileMapping(handle.file, NULL, PAGE_READONLY, 0, 0, NULL);
153 if (mapping == NULL) {
154 pm_source_file_handle_close(&handle);
156 return NULL;
157 }
158
159 /* Map the file into memory. */
160 uint8_t *source = (uint8_t *) MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
161 CloseHandle(mapping);
162 pm_source_file_handle_close(&handle);
163
164 if (source == NULL) {
166 return NULL;
167 }
168
169 *result = PM_SOURCE_INIT_SUCCESS;
170 return pm_source_alloc(source, (size_t) file_size, PM_SOURCE_MAPPED);
171#elif defined(_POSIX_MAPPED_FILES)
172 /* Open the file for reading. */
173 int fd = open(filepath, O_RDONLY | open_flags);
174 if (fd == -1) {
176 return NULL;
177 }
178
179 /* Stat the file to get the file size. */
180 struct stat sb;
181 if (fstat(fd, &sb) == -1) {
182 close(fd);
184 return NULL;
185 }
186
187 /* Ensure it is a file and not a directory. */
188 if (S_ISDIR(sb.st_mode)) {
189 close(fd);
191 return NULL;
192 }
193
194 /*
195 * For non-regular files (pipes, character devices), return a specific
196 * error so the caller can handle reading through their own I/O layer.
197 */
198 if (!S_ISREG(sb.st_mode)) {
199 close(fd);
201 return NULL;
202 }
203
204 /* mmap the file descriptor to virtually get the contents. */
205 size_t size = (size_t) sb.st_size;
206
207 if (size == 0) {
208 close(fd);
209 *result = PM_SOURCE_INIT_SUCCESS;
210 return pm_source_alloc(empty_source, 0, PM_SOURCE_CONSTANT);
211 }
212
213 uint8_t *source = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
214 if (source == MAP_FAILED) {
215 close(fd);
217 return NULL;
218 }
219
220 close(fd);
221 *result = PM_SOURCE_INIT_SUCCESS;
222 return pm_source_alloc(source, size, PM_SOURCE_MAPPED);
223#else
224 (void) open_flags;
225 return pm_source_file_new(filepath, result);
226#endif
227}
228
233pm_source_file_new(const char *filepath, pm_source_init_result_t *result) {
234#ifdef _WIN32
235 /* Open the file for reading. */
236 pm_source_file_handle_t handle;
237 *result = pm_source_file_handle_open(&handle, filepath);
238 if (*result != PM_SOURCE_INIT_SUCCESS) return NULL;
239
240 /* Get the file size. */
241 const DWORD file_size = GetFileSize(handle.file, NULL);
242 if (file_size == INVALID_FILE_SIZE) {
243 pm_source_file_handle_close(&handle);
245 return NULL;
246 }
247
248 /* If the file is empty, return a constant source. */
249 if (file_size == 0) {
250 pm_source_file_handle_close(&handle);
251 *result = PM_SOURCE_INIT_SUCCESS;
252 return pm_source_alloc(empty_source, 0, PM_SOURCE_CONSTANT);
253 }
254
255 /* Create a buffer to read the file into. */
256 uint8_t *source = xmalloc(file_size);
257 if (source == NULL) {
258 pm_source_file_handle_close(&handle);
260 return NULL;
261 }
262
263 /* Read the contents of the file. */
264 DWORD bytes_read;
265 if (!ReadFile(handle.file, source, file_size, &bytes_read, NULL)) {
266 xfree_sized(source, file_size);
267 pm_source_file_handle_close(&handle);
269 return NULL;
270 }
271
272 /* Check the number of bytes read. */
273 if (bytes_read != file_size) {
274 xfree_sized(source, file_size);
275 pm_source_file_handle_close(&handle);
277 return NULL;
278 }
279
280 pm_source_file_handle_close(&handle);
281 *result = PM_SOURCE_INIT_SUCCESS;
282 return pm_source_alloc(source, (size_t) file_size, PM_SOURCE_OWNED);
283#elif defined(PRISM_HAS_FILESYSTEM)
284 /* Open the file for reading. */
285 int fd = open(filepath, O_RDONLY);
286 if (fd == -1) {
288 return NULL;
289 }
290
291 /* Stat the file to get the file size. */
292 struct stat sb;
293 if (fstat(fd, &sb) == -1) {
294 close(fd);
296 return NULL;
297 }
298
299 /* Ensure it is a file and not a directory. */
300 if (S_ISDIR(sb.st_mode)) {
301 close(fd);
303 return NULL;
304 }
305
306 /* Check the size to see if it's empty. */
307 size_t size = (size_t) sb.st_size;
308 if (size == 0) {
309 close(fd);
310 *result = PM_SOURCE_INIT_SUCCESS;
311 return pm_source_alloc(empty_source, 0, PM_SOURCE_CONSTANT);
312 }
313
314 const size_t length = (size_t) size;
315 uint8_t *source = xmalloc(length);
316 if (source == NULL) {
317 close(fd);
319 return NULL;
320 }
321
322 ssize_t bytes_read = read(fd, source, length);
323 close(fd);
324
325 if (bytes_read == -1 || (size_t) bytes_read != length) {
326 xfree_sized(source, length);
328 return NULL;
329 }
330
331 *result = PM_SOURCE_INIT_SUCCESS;
332 return pm_source_alloc(source, length, PM_SOURCE_OWNED);
333#else
334 (void) filepath;
336 perror("pm_source_file_new is not implemented for this platform");
337 return NULL;
338#endif
339}
340
347pm_source_stream_new(void *stream, pm_source_stream_fgets_t *fgets, pm_source_stream_feof_t *feof) {
348 pm_source_t *source = pm_source_alloc(NULL, 0, PM_SOURCE_STREAM);
349 source->stream.buffer = pm_buffer_new();
350 source->stream.stream = stream;
351 source->stream.fgets = fgets;
352 source->stream.feof = feof;
353 source->stream.eof = false;
354
355 return source;
356}
357
364bool
365pm_source_stream_read(pm_source_t *source) {
366 pm_buffer_t *buffer = source->stream.buffer;
367
368#define LINE_SIZE 4096
369 char line[LINE_SIZE];
370
371 while (memset(line, '\n', LINE_SIZE), source->stream.fgets(line, LINE_SIZE, source->stream.stream) != NULL) {
372 size_t length = LINE_SIZE;
373 while (length > 0 && line[length - 1] == '\n') length--;
374
375 if (length == LINE_SIZE) {
376 /*
377 * If we read a line that is the maximum size and it doesn't end
378 * with a newline, then we'll just append it to the buffer and
379 * continue reading.
380 */
381 length--;
382 pm_buffer_append_string(buffer, line, length);
383 continue;
384 }
385
386 /* Append the line to the buffer. */
387 length--;
388 pm_buffer_append_string(buffer, line, length);
389
390 /*
391 * Check if the line matches the __END__ marker. If it does, then stop
392 * reading and return false. In most circumstances, this means we should
393 * stop reading from the stream so that the DATA constant can pick it
394 * up.
395 */
396 switch (length) {
397 case 7:
398 if (strncmp(line, "__END__", 7) == 0) {
399 source->source = (const uint8_t *) pm_buffer_value(buffer);
400 source->length = pm_buffer_length(buffer);
401 return false;
402 }
403 break;
404 case 8:
405 if (strncmp(line, "__END__\n", 8) == 0) {
406 source->source = (const uint8_t *) pm_buffer_value(buffer);
407 source->length = pm_buffer_length(buffer);
408 return false;
409 }
410 break;
411 case 9:
412 if (strncmp(line, "__END__\r\n", 9) == 0) {
413 source->source = (const uint8_t *) pm_buffer_value(buffer);
414 source->length = pm_buffer_length(buffer);
415 return false;
416 }
417 break;
418 }
419
420 /*
421 * All data should be read via gets. If the string returned by gets
422 * _doesn't_ end with a newline, then we assume we hit EOF condition.
423 */
424 if (source->stream.feof(source->stream.stream)) {
425 break;
426 }
427 }
428
429#undef LINE_SIZE
430
431 source->stream.eof = true;
432 source->source = (const uint8_t *) pm_buffer_value(buffer);
433 source->length = pm_buffer_length(buffer);
434 return true;
435}
436
440bool
441pm_source_stream_eof(const pm_source_t *source) {
442 return source->stream.eof;
443}
444
448void
449pm_source_free(pm_source_t *source) {
450 switch (source->type) {
451 case PM_SOURCE_CONSTANT:
452 case PM_SOURCE_SHARED:
453 /* No cleanup needed for the data. */
454 break;
455 case PM_SOURCE_OWNED:
456 xfree_sized((void *) source->source, source->length);
457 break;
458 case PM_SOURCE_MAPPED:
459#if defined(_WIN32)
460 if (source->length > 0) {
461 UnmapViewOfFile((void *) source->source);
462 }
463#elif defined(_POSIX_MAPPED_FILES)
464 if (source->length > 0) {
465 munmap((void *) source->source, source->length);
466 }
467#endif
468 break;
469 case PM_SOURCE_STREAM:
470 pm_buffer_free(source->stream.buffer);
471 break;
472 }
473
474 xfree_sized(source, sizeof(pm_source_t));
475}
476
480size_t
481pm_source_length(const pm_source_t *source) {
482 return source->length;
483}
484
488const uint8_t *
489pm_source_source(const pm_source_t *source) {
490 return source->source;
491}
#define xmalloc
Old name of ruby_xmalloc.
Definition xmalloc.h:53
VALUE type(ANYARGS)
ANYARGS-ed function type.
int() pm_source_stream_feof_t(void *stream)
This function is used to check whether a stream is at EOF.
Definition source.h:34
char *() pm_source_stream_fgets_t(char *string, int size, void *stream)
This function is used to retrieve a line of input from a stream.
Definition source.h:28
pm_source_init_result_t
Represents the result of initializing a source from a file.
Definition source.h:39
@ PM_SOURCE_INIT_ERROR_GENERIC
Indicates a generic error from a source init function, where the type of error should be read from er...
Definition source.h:47
@ PM_SOURCE_INIT_SUCCESS
Indicates that the source was successfully initialized.
Definition source.h:41
@ PM_SOURCE_INIT_ERROR_NON_REGULAR
Indicates that the file is not a regular file (e.g.
Definition source.h:58
@ PM_SOURCE_INIT_ERROR_DIRECTORY
Indicates that the file that was attempted to be opened was a directory.
Definition source.h:52