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