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, &current_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}