1#include "include/pw.h"
2#include "include/pwlib/file.h"
3
4#include <libpussy/alignment.h>
5
6
7uint16_t PwInterfaceId_BufferedFile = 0;
8
9uint16_t PwTypeId_BufferedFile = 0;
10
11typedef struct {
12 uint8_t* read_buffer;
13 unsigned read_buffer_size; // size of read_buffer
14 unsigned read_data_size; // size of data in read_buffer
15 unsigned read_position; // current position in read_buffer
16
17 uint8_t* write_buffer;
18 unsigned write_buffer_size; // size of write_buffer
19 unsigned write_position; // current position in write_buffer, also it's the size of data
20
21 // line reader data
22 char8_t partial_utf8[8]; // UTF-8 sequence may span adjacent reads, the buffer size is for surrogate pair
23 unsigned partial_utf8_len;
24 _PwValue pushback; // for unread_line
25
26 // line reader iterator data
27 bool iterating; // indicates that iteration is in progress
28 unsigned line_number;
29
30} _PwBufferedFile;
31
32#define get_this_bfile(value) _pw_get_this_struct_ptr(_PwBufferedFile*, mthis, (value))
33
34
35static void stop_read_lines(_PwBufferedFile* f)
36{
37 f->iterating = false;
38 pw_destroy(&f->pushback);
39}
40
41static inline bool do_file_read(PwValuePtr file, void* buffer, unsigned buffer_size, unsigned* bytes_read)
42// call Reader::read for File type
43{
44 PwInterface_Reader* reader = (PwInterface_Reader*) pw_get_interface(PwTypeId_File, PwInterfaceId_Reader);
45 return pw_call2(reader, read, file, buffer, buffer_size, bytes_read);
46}
47
48static inline bool do_file_write(PwValuePtr file, void* data, unsigned size, unsigned* bytes_written)
49// call Writer::write for File type
50{
51 PwInterface_Writer* writer = (PwInterface_Writer*) pw_get_interface(PwTypeId_File, PwInterfaceId_Writer);
52 return pw_call2(writer, write, file, data, size, bytes_written);
53}
54
55/****************************************************************
56 * Basic interface
57 */
58
59static bool bfile_create(PwMethod_Basic_create* mthis, PwValuePtr result, PwCtorArgs* ctor_args)
60{
61 if (!pw_super(mthis, result, ctor_args)) {
62 return false;
63 }
64
65 PwBufferedFileCtorArgs* args = pw_get_ctor_args(PwTypeId_BufferedFile, ctor_args);;
66
67 _PwBufferedFile* f = get_this_bfile(result);
68 f->read_buffer_size = align_unsigned_to_page(args->read_bufsize);
69 f->write_buffer_size = align_unsigned_to_page(args->write_bufsize);
70
71 if (f->read_buffer_size) {
72 f->read_buffer = allocate(f->read_buffer_size, false);
73 if (!f->read_buffer) {
74 pw_set_status(PwStatus(PW_ERROR_OOM));
75 if (!pw_super_call(destroy, mthis, result, nullptr)) { /* no op */ }
76 return false;
77 }
78 }
79 if (f->write_buffer_size) {
80 f->write_buffer = allocate(f->write_buffer_size, false);
81 if (!f->write_buffer) {
82 release((void**) &f->read_buffer, f->read_buffer_size);
83 pw_set_status(PwStatus(PW_ERROR_OOM));
84 if (!pw_super_call(destroy, mthis, result, nullptr)) { /* no op */ }
85 return false;
86 }
87 }
88 f->pushback = PwNull();
89 return true;
90}
91
92static bool bfile_destroy(PwMethod_Basic_destroy* mthis, PwValuePtr self, _PwCompoundChain* tail)
93{
94 PwValuePtr value_seen = _pw_on_chain(self, tail);
95 if (value_seen) {
96 return true;
97 }
98
99 // XXX close() may block asynchronous execution, need to use a worker thread and/or a garbage pool
100 if (!pw_close(self)) {
101 fprintf(stderr, "Failed %s\n", __func__);
102 pw_print_status(stderr, ¤t_task->status);
103 }
104 _PwBufferedFile* f = get_this_bfile(self);
105 if (f->read_buffer) {
106 release((void**) &f->read_buffer, f->read_buffer_size);
107 }
108 if (f->write_buffer) {
109 release((void**) &f->write_buffer, f->write_buffer_size);
110 }
111 pw_destroy(&f->pushback);
112 return pw_super(mthis, self, nullptr);
113}
114
115static bool bfile_dump(PwMethod_Basic_dump* mthis, PwValuePtr self, FILE* fp, int indent, _PwCompoundChain* tail)
116{
117 if (!pw_super(mthis, self, fp, indent, tail)) {
118 return false;
119 }
120
121 _PwBufferedFile* f = get_this_bfile(self);
122
123 _pw_print_indent(fp, indent + 4);
124 fprintf(fp, "read_buffer: %p", f->read_buffer);
125 if (f->read_buffer) {
126 fprintf(fp, " %u bytes", f->read_buffer_size);
127 }
128 fprintf(fp, " write_buffer: %p", f->write_buffer);
129 if (f->write_buffer) {
130 fprintf(fp, " %u bytes", f->write_buffer_size);
131 }
132 fputc('\n', fp);
133 return true;
134}
135
136static PwInterface_Basic bfile_basic_interface = {
137 .create = { .func = bfile_create },
138 .destroy = { .func = bfile_destroy },
139 .dump = { .func = bfile_dump }
140};
141
142/****************************************************************
143 * BufferedFile interface
144 */
145
146[[nodiscard]] static bool flush(_PwBufferedFile* f, PwValuePtr self)
147{
148 if (f->write_position == 0) {
149 // nothing to write
150 return true;
151 }
152
153 if (f->iterating) {
154 pw_set_status(PwStatus(PW_ERROR_ITERATION_IN_PROGRESS));
155 return false;
156 }
157
158 unsigned bytes_written;
159 if (pw_write(self, f->write_buffer, f->write_position, &bytes_written)) {
160 // success, all data is written
161 f->write_position = 0;
162 return true;
163 }
164 f->write_position -= bytes_written;
165 if (f->write_position) {
166 // move unwritten data to the beginning of `data`
167 memmove(f->write_buffer, f->write_buffer + bytes_written, f->write_position);
168 }
169 return false;
170}
171
172static bool bfile_flush(PwMethod_BufferedFile_flush* mthis, PwValuePtr self)
173{
174 _PwBufferedFile* f = get_this_bfile(self);
175 return flush(f, self);
176}
177
178static PwInterface_BufferedFile bfile_buffered_file_interface = {
179#define X(name, ...) .name = { .func = bfile_##name } __VA_OPT__(,)
180 PW_BUFFERED_FILE_INTERFACE_METHODS
181#undef X
182};
183
184
185/****************************************************************
186 * Fd interface for BufferedFile
187 */
188
189static void reset_bfile_data(_PwBufferedFile* f)
190{
191 f->read_data_size = 0;
192 f->read_position = 0;
193 f->write_position = 0;
194 f->partial_utf8_len = 0;
195 pw_destroy(&f->pushback);
196}
197
198static bool bfile_close(PwMethod_Fd_close* mthis, PwValuePtr self)
199{
200 _PwBufferedFile* f = get_this_bfile(self);
201 stop_read_lines(f);
202 bool flush_result = flush(f, self);
203 reset_bfile_data(f);
204 return pw_super(mthis, self) && flush_result; // XXX if both flush and close are failed, flush status is lost
205}
206
207static bool bfile_set_fd(PwMethod_Fd_set_fd* mthis, PwValuePtr self, int fd, bool move)
208{
209 _PwBufferedFile* f = get_this_bfile(self);
210
211 if (f->iterating) {
212 pw_set_status(PwStatus(PW_ERROR_ITERATION_IN_PROGRESS));
213 return false;
214 }
215 reset_bfile_data(f);
216 return pw_super(mthis, self, fd, move);
217}
218
219static PwInterface_Fd bfile_fd_interface = {
220 .close = { .func = bfile_close },
221 .set_fd = { .func = bfile_set_fd }
222};
223
224/****************************************************************
225 * File interface for BufferedFile
226 */
227
228static bool bfile_set_name(PwMethod_File_set_name* mthis, PwValuePtr self, PwValuePtr file_name)
229{
230 _PwBufferedFile* f = get_this_bfile(self);
231
232 if (f->iterating) {
233 pw_set_status(PwStatus(PW_ERROR_ITERATION_IN_PROGRESS));
234 return false;
235 }
236 reset_bfile_data(f);
237 return pw_super(mthis, self, file_name);
238}
239
240static bool bfile_seek(PwMethod_File_seek* mthis, PwValuePtr self, off_t offset, int whence, off_t* position)
241{
242 _PwBufferedFile* f = get_this_bfile(self);
243
244 if (f->iterating) {
245 pw_set_status(PwStatus(PW_ERROR_ITERATION_IN_PROGRESS));
246 return false;
247 }
248 // reset read buffer
249 f->read_data_size = 0;
250 f->read_position = 0;
251 // flush write buffer
252 if (!flush(f, self)) {
253 return false;
254 }
255 // seek
256 return pw_super(mthis, self, offset, whence, position);
257}
258
259static PwInterface_File bfile_file_interface = {
260 .set_name = { .func = bfile_set_name },
261 .seek = { .func = bfile_seek }
262};
263
264
265/****************************************************************
266 * Reader interface for BufferedFile
267 */
268
269static bool bfile_read(PwMethod_Reader_read* mthis, PwValuePtr self, void* buffer, unsigned buffer_size, unsigned* bytes_read)
270{
271 _PwBufferedFile* f = get_this_bfile(self);
272
273 if (f->iterating) {
274 *bytes_read = 0;
275 pw_set_status(PwStatus(PW_ERROR_ITERATION_IN_PROGRESS));
276 return false;
277 }
278
279 if (f->read_buffer_size == 0) {
280 // return directly from file
281 return pw_super(mthis, self, buffer, buffer_size, bytes_read);
282 }
283
284 if (f->read_position == f->read_data_size) {
285
286 // no data in the read_buffer, read next portion
287 f->read_position = 0;
288
289 if (!pw_super(mthis, self, f->read_buffer, f->read_buffer_size, &f->read_data_size)) {
290 return false;
291 }
292 if (f->read_data_size == 0) {
293 pw_set_status(PwStatus(PW_ERROR_EOF));
294 return false;
295 }
296 }
297 unsigned avail = f->read_data_size - f->read_position;
298 unsigned size = (avail < buffer_size)? avail : buffer_size;
299 memcpy(buffer, f->read_buffer + f->read_position, size);
300 f->read_position += size;
301 *bytes_read = size;
302 return true;
303}
304
305static PwInterface_Reader bfile_reader_interface = {
306#define X(name, ...) .name = { .func = bfile_##name } __VA_OPT__(,)
307 PW_READER_INTERFACE_METHODS
308#undef X
309};
310
311
312/****************************************************************
313 * Writer interface for BufferedFile
314 */
315
316static bool bfile_write(PwMethod_Writer_write* mthis, PwValuePtr self, void* data, unsigned size, unsigned* bytes_written)
317{
318 _PwBufferedFile* f = get_this_bfile(self);
319
320 *bytes_written = 0;
321
322 if (size == 0) {
323 return true;
324 }
325
326 if (f->iterating) {
327 pw_set_status(PwStatus(PW_ERROR_ITERATION_IN_PROGRESS));
328 return false;
329 }
330
331 if (f->write_buffer_size == 0) {
332 // write directly to file
333 return pw_super(mthis, self, data, size, bytes_written);
334 }
335
336 unsigned remaining_capacity = f->write_buffer_size - f->write_position;
337
338 if (remaining_capacity) {
339 // fill the write_buffer
340 unsigned n = (size < remaining_capacity)? size : remaining_capacity;
341 memcpy(f->write_buffer + f->write_position, data, n);
342 f->write_position += n;
343 *bytes_written += n;
344 if (f->write_position < f->write_buffer_size) {
345 // write_buffer is not full yet
346 return true;
347 }
348 size -= n;
349 data = ((uint8_t*) data) + n;
350 }
351 // write_buffer is full, flush it
352 if (!flush(f, self)) {
353 return false;
354 }
355 // write directly to file
356 while (size >= f->write_buffer_size) {{
357 unsigned n;
358 bool ret = pw_super(mthis, self, data, f->write_buffer_size, &n);
359 *bytes_written += n;
360 if (!ret) {
361 return false;
362 }
363 size -= n;
364 data = ((uint8_t*) data) + n;
365 }}
366
367 if (size) {
368 // move remaining data to the write_buffer
369 memcpy(f->write_buffer, data, size);
370 *bytes_written += size;
371 f->write_position = size;
372 }
373 return true;
374}
375
376static PwInterface_Writer bfile_writer_interface = {
377#define X(name, ...) .name = { .func = bfile_##name } __VA_OPT__(,)
378 PW_WRITER_INTERFACE_METHODS
379#undef X
380};
381
382
383/****************************************************************
384 * LineReader interface methods for buffered file
385 */
386
387static bool bfile_start(PwMethod_LineReader_start* mthis, PwValuePtr self)
388{
389 _PwBufferedFile* f = get_this_bfile(self);
390
391 if (f->read_buffer_size == 0) {
392 pw_set_status(PwStatus(PW_ERROR_UNBUFFERED_FILE));
393 return false;
394 }
395
396 f->partial_utf8_len = 0;
397 f->line_number = 0;
398 pw_destroy(&f->pushback);
399 f->iterating = true;
400 return true;
401}
402
403static bool bfile_read_line_inplace(PwMethod_LineReader_read_line_inplace* mthis, PwValuePtr self, PwValuePtr line)
404{
405 _PwBufferedFile* f = get_this_bfile(self);
406
407 if (f->read_buffer_size == 0) {
408 pw_set_status(PwStatus(PW_ERROR_UNBUFFERED_FILE));
409 return false;
410 }
411 if (!pw_string_truncate(line, 0)) {
412 return false;
413 }
414 if (pw_is_string(&f->pushback)) {
415 if (!pw_string_append(line, &f->pushback)) {
416 return false;
417 }
418 pw_destroy(&f->pushback);
419 f->line_number++;
420 return true;
421 }
422 do {
423 if (f->read_position == f->read_data_size) {
424
425 // reached end of data scanning for line break
426
427 f->read_position = 0;
428
429 // read next chunk of file
430 if (!do_file_read(self, f->read_buffer, f->read_buffer_size, &f->read_data_size)) {
431 return false;
432 }
433 if (f->read_data_size == 0) {
434 pw_set_status(PwStatus(PW_ERROR_EOF));
435 return false;
436 }
437
438 if (f->partial_utf8_len) {
439 // process partial UTF-8 sequence
440 while (f->partial_utf8_len < sizeof(f->partial_utf8)) {
441
442 if (f->read_position == f->read_data_size) {
443 // premature end of file
444 // XXX warn?
445 pw_set_status(PwStatus(PW_ERROR_EOF));
446 return false;
447 }
448
449 char8_t c = f->read_buffer[f->read_position];
450 if (c < 0x80 || ((c & 0xC0) != 0x80)) {
451 // malformed UTF-8 sequence
452 break;
453 }
454 f->read_position++;
455 f->partial_utf8[f->partial_utf8_len++] = c;
456
457 char8_t* ptr = f->partial_utf8;
458 unsigned bytes_remaining = f->partial_utf8_len;
459 char32_t chr;
460 if (_pw_decode_utf8_buffer(&ptr, &bytes_remaining, &chr)) {
461 if (chr != 0xFFFFFFFF) {
462 if (!pw_string_append(line, chr)) {
463 return false;
464 }
465 }
466 break;
467 }
468 }
469 f->partial_utf8_len = 0;
470 }
471 }
472
473 char8_t* ptr = f->read_buffer + f->read_position;
474 unsigned bytes_remaining = f->read_data_size - f->read_position;
475 while (bytes_remaining) {
476 char32_t chr;
477 if (!_pw_decode_utf8_buffer(&ptr, &bytes_remaining, &chr)) {
478 break;
479 }
480 if (chr != 0xFFFFFFFF) {
481 if (!pw_string_append(line, chr)) {
482 return false;
483 }
484 if (chr == '\n') {
485 f->read_position = f->read_data_size - bytes_remaining;
486 f->line_number++;
487 return true;
488 }
489 }
490 }
491 // move unprocessed data to partial_utf8
492 while (bytes_remaining--) {
493 f->partial_utf8[f->partial_utf8_len++] = *ptr++;
494 }
495 if (f->read_data_size < f->read_buffer_size) {
496 // reached end of file
497 f->read_position = 0;
498 f->read_data_size = 0;
499 f->line_number++;
500 return true;
501 }
502
503 // go read next chunk
504 f->read_position = f->read_data_size;
505
506 } while(true);
507}
508
509static bool bfile_read_line(PwMethod_LineReader_read_line* mthis, PwValuePtr self, PwValuePtr result)
510{
511 PwValue line = PW_STRING("");
512 if (!pw_this_call(read_line_inplace, mthis, self, &line)) {
513 return false;
514 }
515 pw_move(&line, result);
516 return true;
517}
518
519static bool bfile_unread_line(PwMethod_LineReader_unread_line* mthis, PwValuePtr self, PwValuePtr line)
520{
521 _PwBufferedFile* f = get_this_bfile(self);
522
523 if (pw_is_null(&f->pushback)) {
524 __pw_clone(line, &f->pushback); // puchback is already Null, so use __pw_clone here
525 f->line_number--;
526 return true;
527 } else {
528 return false;
529 }
530}
531
532static bool bfile_get_line_number(PwMethod_LineReader_get_line_number* mthis, PwValuePtr self, unsigned* result)
533{
534 *result = get_this_bfile(self)->line_number;
535 return true;
536}
537
538static bool bfile_stop(PwMethod_LineReader_stop* mthis,PwValuePtr self)
539{
540 _PwBufferedFile* f = get_this_bfile(self);
541 stop_read_lines(f);
542 return true;
543}
544
545static PwInterface_LineReader bfile_line_reader_interface = {
546#define X(name, ...) .name = { .func = bfile_##name } __VA_OPT__(,)
547 PW_LINE_READER_INTERFACE_METHODS
548#undef X
549};
550
551
552/****************************************************************
553 * Append interface methods
554 */
555
556static bool bfile_append_string_data(PwMethod_Append_append_string_data* mthis,
557 PwValuePtr self, uint8_t* start_ptr, uint8_t* end_ptr, uint8_t char_size)
558{
559 if (start_ptr >= end_ptr) {
560 return true;
561 }
562
563 _PwBufferedFile* f = get_this_bfile(self);
564
565 if (char_size < 2) {
566 // write ASCII and UTF-8 directly to file
567 return do_file_write(self, start_ptr, end_ptr - start_ptr, nullptr);
568 }
569
570 // convert wide chars to UTF-8
571
572 if (f->write_buffer_size == 0) {
573 // invoke unbuffered version
574 return pw_super(mthis, self, start_ptr, end_ptr, char_size);
575 }
576
577 while (start_ptr < end_ptr) {
578 unsigned remaining = f->write_buffer_size - f->write_position;
579 if (remaining < 4) {
580 if (!pw_flush(self)) {
581 return false;
582 }
583 }
584 char32_t codepoint = _pw_get_char(start_ptr, char_size);
585 start_ptr += char_size;
586 f->write_position += pw_char32_to_utf8(codepoint, (char*) &f->write_buffer[f->write_position]);
587 }
588 return true;
589}
590
591static bool bfile_append(PwMethod_Append_append* mthis, PwValuePtr self, PwValuePtr value)
592{
593 if (!pw_validate(value, PwTypeId_String)) {
594 return false;
595 }
596
597 uint8_t* end_ptr;
598 uint8_t* start_ptr = _pw_string_start_end(value, &end_ptr);
599 return pw_this_call(append_string_data, mthis, self, start_ptr, end_ptr, value->char_size);
600}
601
602static PwInterface_Append bfile_append_interface = {
603#define X(name, ...) .name = { .func = bfile_##name } __VA_OPT__(,)
604 PW_APPEND_INTERFACE_METHODS
605#undef X
606};
607
608/****************************************************************
609 * Initialization
610 */
611
612extern void _pw_init_file();
613
614[[ gnu::constructor ]]
615void _pw_init_buffered_file()
616{
617 _pw_init_file();
618
619 // interface can be already registered
620
621 if (PwInterfaceId_BufferedFile == 0) {
622# define X(name, ...) #name __VA_OPT__(,)
623 PwInterfaceId_BufferedFile = pw_register_interface("BufferedFile", PW_BUFFERED_FILE_INTERFACE_METHODS, nullptr);
624# undef X
625 }
626
627 if (PwTypeId_BufferedFile == 0) {
628
629 PwTypeId_BufferedFile = pw_add_type2(
630 "BufferedFile", _PwBufferedFile,
631 PW_PARENTS,
632 PwTypeId_File,
633 PW_INTERFACES,
634 PwInterfaceId_Basic, &bfile_basic_interface,
635 PwInterfaceId_BufferedFile, &bfile_buffered_file_interface,
636 PwInterfaceId_File, &bfile_file_interface,
637 PwInterfaceId_Fd, &bfile_fd_interface,
638 PwInterfaceId_Reader, &bfile_reader_interface,
639 PwInterfaceId_Writer, &bfile_writer_interface,
640 PwInterfaceId_LineReader, &bfile_line_reader_interface,
641 PwInterfaceId_Append, &bfile_append_interface
642 );
643 }
644}