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