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