1#include <errno.h>
  2#include <unistd.h>
  3
  4#include "include/pw.h"
  5#include "include/pwlib/file.h"
  6#include "src/pw_interfaces_internal.h"
  7
  8uint16_t PwInterfaceId_File = 0;
  9
 10uint16_t PwTypeId_File = 0;
 11
 12typedef struct {
 13    _PwValue name;
 14    int  fd;              // file descriptor
 15    bool is_external_fd;  // fd is set by `set_fd`
 16    bool own_fd;          // fd is owned, If not owned, it is not closed by `close`
 17    int  error;           // errno, set by `open`
 18
 19} _PwFile;
 20
 21#define get_this_file(value)  _pw_get_this_struct_ptr(_PwFile*, mthis, (value))
 22
 23
 24static inline bool do_file_read(PwValuePtr file, void* buffer, unsigned buffer_size, unsigned* bytes_read)
 25// call Reader::read for File type
 26{
 27    PwInterface_Reader* reader = (PwInterface_Reader*) pw_get_interface(PwTypeId_File, PwInterfaceId_Reader);
 28    return pw_call2(reader, read, file, buffer, buffer_size, bytes_read);
 29}
 30
 31static inline bool do_file_write(PwValuePtr file, void* data, unsigned size, unsigned* bytes_written)
 32// call Writer::write for File type
 33{
 34    PwInterface_Writer* writer = (PwInterface_Writer*) pw_get_interface(PwTypeId_File, PwInterfaceId_Writer);
 35    return pw_call2(writer, write, file, data, size, bytes_written);
 36}
 37
 38/****************************************************************
 39 * Basic interface
 40 */
 41
 42static bool file_create(PwMethod_Basic_create* mthis, PwValuePtr result, PwCtorArgs* ctor_args)
 43{
 44    if (!pw_super(mthis, result, ctor_args)) {
 45        return false;
 46    }
 47
 48    _PwFile* f = get_this_file(result);
 49    f->fd = -1;
 50    f->name = PwNull();
 51    return true;
 52}
 53
 54static bool file_destroy(PwMethod_Basic_destroy* mthis, PwValuePtr self, _PwCompoundChain* tail)
 55{
 56    PwValuePtr value_seen = _pw_on_chain(self, tail);
 57    if (value_seen) {
 58        return true;
 59    }
 60    if (!pw_close(self)) {
 61        fprintf(stderr, "Failed %s\n", __func__);
 62        pw_print_status(stderr, &current_task->status);
 63    }
 64    return pw_super(mthis, self, nullptr);
 65}
 66
 67static bool file_hash(PwMethod_Basic_hash* mthis, PwValuePtr self, PwHashContext* ctx, _PwCompoundChain* tail)
 68{
 69    // it's not a hash of entire file content!
 70
 71    _PwFile* f = get_this_file(self);
 72
 73    _pw_hash_uint64(ctx, self->type_id);
 74
 75    // XXX
 76    _pw_call_hash(&f->name, ctx, nullptr);
 77    _pw_hash_uint64(ctx, f->fd);
 78    _pw_hash_uint64(ctx, f->is_external_fd);
 79    return true;
 80}
 81
 82static bool file_dump(PwMethod_Basic_dump* mthis, PwValuePtr self, FILE* fp, int indent, _PwCompoundChain* tail)
 83{
 84    if (!pw_super(mthis, self, fp, indent, tail)) {
 85        return false;
 86    }
 87
 88    _PwFile* f = get_this_file(self);
 89
 90    _pw_print_indent(fp, indent + 4);
 91    fprintf(fp, "fd: %d", f->fd);
 92    if (f->is_external_fd) {
 93        char* owned = "";
 94        if (f->own_fd) {
 95            owned = ", owned";
 96        }
 97        fprintf(fp, " (external%s)", owned);
 98    }
 99    fputc('\n', fp);
100
101    _pw_print_indent(fp, indent + 4);
102    if (pw_is_string(&f->name)) {
103        PW_CSTRING(file_name, &f->name);
104        fprintf(fp, "File name: %s", file_name);
105    } else {
106        fprintf(fp, "File name: Null");
107    }
108    fputc('\n', fp);
109    return true;
110}
111
112static PwInterface_Basic file_basic_interface = {
113    .create  = { .func = file_create },
114    .destroy = { .func = file_destroy },
115    .hash    = { .func = file_hash },
116    .dump    = { .func = file_dump }
117};
118
119/****************************************************************
120 * File descriptor interface
121 */
122
123static bool file_get_fd(PwMethod_Fd_get_fd* mthis, PwValuePtr self, int* result)
124{
125    *result = get_this_file(self)->fd;
126    return true;
127}
128
129static bool file_set_fd(PwMethod_Fd_set_fd* mthis, PwValuePtr self, int fd, bool move)
130{
131    _PwFile* f = get_this_file(self);
132
133    if (f->fd != -1) {
134        // fd already set
135        pw_set_status(PwStatus(PW_ERROR_FD_ALREADY_SET));
136        return false;
137    }
138    f->fd = fd;
139    f->is_external_fd = true;
140    f->own_fd = move;
141    return true;
142}
143
144static bool file_close(PwMethod_Fd_close* mthis, PwValuePtr self)
145{
146    _PwFile* f = get_this_file(self);
147    bool ret = true;
148    if (f->fd != -1 && f->own_fd) {
149        int result;
150        do {
151            result = close(f->fd);
152        } while (result == -1 && errno == EINTR);
153        ret = result == 0;
154        if (!ret) {
155            f->error = errno;
156            pw_set_status(PwErrno(errno));
157            return false;
158        }
159    }
160    f->fd = -1;
161    f->error = 0;
162    pw_destroy(&f->name);
163    return ret;
164}
165
166static bool file_set_nonblocking(PwMethod_Fd_set_nonblocking* mthis, PwValuePtr self, bool mode)
167{
168    _PwFile* f = get_this_file(self);
169
170    if (f->fd == -1) {
171        pw_set_status(PwStatus(PW_ERROR_FILE_CLOSED));
172        return false;
173    }
174    int flags = fcntl(f->fd, F_GETFL, 0);
175    if (mode) {
176        flags |= O_NONBLOCK;
177    } else {
178        flags &= ~O_NONBLOCK;
179    }
180    if (fcntl(f->fd, F_SETFL, flags) == -1) {
181        pw_set_status(PwErrno(errno));
182        return false;
183    }
184    return true;
185}
186
187static PwInterface_Fd file_fd_interface = {
188#define X(name, ...) .name = { .func = file_##name } __VA_OPT__(,)
189    PW_FD_INTERFACE_METHODS
190#undef X
191};
192
193
194/****************************************************************
195 * File interface
196 */
197
198static bool file_open(PwMethod_File_open* mthis, PwValuePtr self, PwValuePtr file_name, int flags, mode_t mode)
199{
200    _PwFile* f = get_this_file(self);
201
202    if (f->fd != -1) {
203        pw_set_status(PwStatus(PW_ERROR_FILE_ALREADY_OPENED));
204        return false;
205    }
206
207    pw_assert_string(file_name);
208    PW_CSTRING(fname, file_name);
209    do {
210        f->fd = open(fname, flags, mode);
211    } while (f->fd == -1 && errno == EINTR);
212
213    if (f->fd == -1) {
214        f->error = errno;
215        pw_set_status(PwErrno(errno));
216        return false;
217    }
218
219    pw_clone2(file_name, &f->name);
220    f->is_external_fd = false;
221    f->own_fd = true;
222
223    return true;
224}
225
226static bool file_get_name(PwMethod_File_get_name* mthis, PwValuePtr self, PwValuePtr result)
227{
228    _PwFile* f = get_this_file(self);
229    pw_clone2(&f->name, result);
230    return true;
231}
232
233static bool file_set_name(PwMethod_File_set_name* mthis, PwValuePtr self, PwValuePtr file_name)
234{
235    _PwFile* f = get_this_file(self);
236
237    if (f->fd != -1 && !f->is_external_fd) {
238        // not an externally set fd
239        pw_set_status(PwStatus(PW_ERROR_CANT_SET_FILENAME));
240        return false;
241    }
242
243    pw_clone2(file_name, &f->name);
244    return true;
245}
246
247static bool file_seek(PwMethod_File_seek* mthis, PwValuePtr self, off_t offset, int whence, off_t* position)
248{
249    _PwFile* f = get_this_file(self);
250    off_t pos = lseek(f->fd, offset, whence);
251    if (pos == -1) {
252        pw_set_status(PwErrno(errno));
253        return false;
254    }
255    if (position) {
256        *position = pos;
257    }
258    return true;
259}
260
261static bool file_tell(PwMethod_File_tell* mthis, PwValuePtr self, off_t* position)
262{
263    _PwFile* f = get_this_file(self);
264
265    *position = lseek(f->fd, 0, SEEK_CUR);
266    if (*position == -1) {
267        pw_set_status(PwErrno(errno));
268        return false;
269    }
270    return true;
271}
272
273static PwInterface_File file_interface = {
274#define X(name, ...) .name = { .func = file_##name } __VA_OPT__(,)
275    PW_FILE_INTERFACE_METHODS
276#undef X
277};
278
279
280/****************************************************************
281 * Reader interface
282 */
283
284static bool file_read(PwMethod_Reader_read* mthis, PwValuePtr self, void* buffer, unsigned buffer_size, unsigned* bytes_read)
285{
286    _PwFile* f = get_this_file(self);
287
288    ssize_t result;
289    do {
290        result = read(f->fd, buffer, buffer_size);
291    } while (result < 0 && errno == EINTR);
292
293    if (result < 0) {
294        *bytes_read = 0;
295        pw_set_status(PwErrno(errno));
296        return false;
297    }
298    *bytes_read = (unsigned) result;
299    return true;
300}
301
302static PwInterface_Reader file_reader_interface = {
303#define X(name, ...) .name = { .func = file_##name } __VA_OPT__(,)
304    PW_READER_INTERFACE_METHODS
305#undef X
306};
307
308
309/****************************************************************
310 * Writer interface
311 */
312
313static bool file_write(PwMethod_Writer_write* mthis, PwValuePtr self, void* data, unsigned size, unsigned* bytes_written)
314{
315    _PwFile* f = get_this_file(self);
316
317    unsigned written = 0;
318    while (written < size) {
319        ssize_t n;
320        do {
321            n = write(f->fd, ((uint8_t*) data) + written, size - written);
322        } while (n < 0 && errno == EINTR);
323
324        if (n < 0) {
325            if (bytes_written) {
326                *bytes_written = written;
327            }
328            pw_set_status(PwErrno(errno));
329            return false;
330        }
331        written += (unsigned) n;
332    }
333    if (bytes_written) {
334        *bytes_written = written;
335    }
336    return true;
337}
338
339static PwInterface_Writer file_writer_interface = {
340#define X(name, ...) .name = { .func = file_##name } __VA_OPT__(,)
341    PW_WRITER_INTERFACE_METHODS
342#undef X
343};
344
345
346/****************************************************************
347 * Append interface
348 */
349
350static bool file_append_string_data(PwMethod_Append_append_string_data* mthis,
351                                    PwValuePtr self, uint8_t* start_ptr, uint8_t* end_ptr, uint8_t char_size)
352{
353    if (start_ptr >= end_ptr) {
354        return true;
355    }
356    if (char_size < 2) {
357        // write ASCII and UTF-8 directly to file
358        return do_file_write(self, start_ptr, end_ptr - start_ptr, nullptr);
359    }
360
361    // convert wide chars to UTF-8
362
363    uint8_t buffer[1024];
364    unsigned n = 0;
365
366    while (start_ptr < end_ptr) {
367        char32_t codepoint = _pw_get_char(start_ptr, char_size);
368        n += pw_char32_to_utf8(codepoint, (char*) &buffer[n]);
369        if (n > sizeof(buffer) - 4) {
370            if (!do_file_write(self, buffer, n, nullptr)) {
371                return false;
372            }
373            n = 0;
374        }
375    }
376    if (n) {
377        if (!do_file_write(self, buffer, n, nullptr)) {
378            return false;
379        }
380    }
381    return true;
382}
383
384static bool file_append(PwMethod_Append_append* mthis, PwValuePtr self, PwValuePtr value)
385{
386    if (!pw_validate(value, PwTypeId_String)) {
387        return false;
388    }
389
390    uint8_t* end_ptr;
391    uint8_t* start_ptr = _pw_string_start_end(value, &end_ptr);
392    return pw_this_call(append_string_data, mthis, self, start_ptr, end_ptr, value->char_size);
393}
394
395static PwInterface_Append file_append_interface = {
396#define X(name, ...) .name = { .func = file_##name } __VA_OPT__(,)
397    PW_APPEND_INTERFACE_METHODS
398#undef X
399};
400
401/****************************************************************
402 * Initialization
403 */
404
405[[ gnu::constructor ]]
406void _pw_init_file()
407{
408    // interface can be already registered
409    // basically. interfaces can be registered by any type in any order
410    if (PwInterfaceId_File == 0) {
411        _pw_init_interfaces();
412#       define X(name, ...) #name __VA_OPT__(,)
413        PwInterfaceId_File = pw_register_interface("File", PW_FILE_INTERFACE_METHODS, nullptr);
414#       undef X
415    }
416
417    if (PwTypeId_File == 0) {
418
419        _pw_init_types();
420
421        PwTypeId_File = pw_add_type2(
422            "File", _PwFile,
423            PW_PARENTS,
424                PwTypeId_Struct,
425            PW_INTERFACES,
426                PwInterfaceId_Basic,  &file_basic_interface,
427                PwInterfaceId_Fd,     &file_fd_interface,
428                PwInterfaceId_File,   &file_interface,
429                PwInterfaceId_Reader, &file_reader_interface,
430                PwInterfaceId_Writer, &file_writer_interface,
431                PwInterfaceId_Append, &file_append_interface
432        );
433    }
434}