1#include <errno.h>
  2
  3#include "include/pw.h"
  4#include "include/pwlib/file.h"
  5#include "include/pwlib/path.h"
  6#include "src/pw_interfaces_internal.h"
  7
  8uint16_t PwInterfaceId_File = 0;
  9
 10uint16_t PwTypeId_File = 0;
 11
 12PW_STRUCT(_PwFile) {
 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
 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*/
 29
 30static inline bool do_file_write(PwValuePtr file, void* data, unsigned size, unsigned* bytes_written)
 31// call Writer::write for File type
 32{
 33    PwInterface_Writer* writer = (PwInterface_Writer*) pw_get_interface(PwTypeId_File, PwInterfaceId_Writer);
 34    return pw_call2(writer, write, file, data, size, bytes_written);
 35}
 36
 37/****************************************************************
 38 * Basic interface
 39 */
 40
 41static bool file_create(PwMethod_Basic_create* mthis, PwValuePtr result, PwCtorArgs* ctor_args)
 42{
 43    if (!pw_super(mthis, result, ctor_args)) {
 44        return false;
 45    }
 46
 47    _PwFile* f = pw_this_data(result);
 48    f->fd = -1;
 49    f->name = PwNull();
 50    return true;
 51}
 52
 53static bool file_destroy(PwMethod_Basic_destroy* mthis, PwValuePtr self, _PwCompoundChain* tail)
 54{
 55    PwValuePtr value_seen = _pw_on_chain(self, tail);
 56    if (value_seen) {
 57        return true;
 58    }
 59    if (!pw_call(Fd, close, self)) {
 60        fprintf(stderr, "Failed %s\n", __func__);
 61        pw_dump(stderr, &current_task->status);
 62    }
 63    return pw_super(mthis, self, tail);
 64}
 65
 66static bool file_hash(PwMethod_Basic_hash* mthis, PwValuePtr self, PwHashContext* ctx, _PwCompoundChain* tail)
 67{
 68    // it's not a hash of entire file content!
 69
 70    _PwFile* f = pw_this_data(self);
 71
 72    _pw_hash_uint64(ctx, self->type_id);
 73
 74    // XXX
 75    _pw_call_hash(&f->name, ctx, nullptr);
 76    _pw_hash_uint64(ctx, f->fd);
 77    _pw_hash_uint64(ctx, f->is_external_fd);
 78    return true;
 79}
 80
 81static bool file_dump(PwMethod_Basic_dump* mthis, PwValuePtr self, FILE* fp, int indent, _PwCompoundChain* tail)
 82{
 83    if (!pw_super(mthis, self, fp, indent, tail)) {
 84        return false;
 85    }
 86
 87    _PwFile* f = pw_this_data(self);
 88
 89    _pw_print_indent(fp, indent + 4);
 90    fprintf(fp, "fd: %d", f->fd);
 91    if (f->is_external_fd) {
 92        char* owned = "";
 93        if (f->own_fd) {
 94            owned = ", owned";
 95        }
 96        fprintf(fp, " (external%s)", owned);
 97    }
 98    fputc('\n', fp);
 99
100    _pw_print_indent(fp, indent + 4);
101    if (pw_is_string(&f->name)) {
102        PW_CSTRING(file_name, &f->name);
103        fprintf(fp, "File name: %s", file_name);
104    } else {
105        fprintf(fp, "File name: Null");
106    }
107    fputc('\n', fp);
108    return true;
109}
110
111static PwInterface_Basic file_basic_interface = {
112    .create  = { .func = file_create },
113    .destroy = { .func = file_destroy },
114    .hash    = { .func = file_hash },
115    .dump    = { .func = file_dump }
116};
117
118/****************************************************************
119 * File descriptor interface
120 */
121
122static bool file_get_fd(PwMethod_Fd_get_fd* mthis, PwValuePtr self, int* result)
123{
124    _PwFile* f = pw_this_data(self);
125    *result = f->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 = pw_this_data(self);
132
133    if (f->fd != -1) {
134        // fd already set
135        pw_set_status(PwStatus(PweFdAlreadySet));
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 = pw_this_data(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 = pw_this_data(self);
169
170    if (f->fd == -1) {
171        pw_set_status(PwStatus(PweFileClosed));
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 = pw_this_data(self);
201
202    if (f->fd != -1) {
203        pw_set_status(PwStatus(PweFileAlreadyOpened));
204        return false;
205    }
206    PwValue file_name_str = PW_NULL;
207    if (!pw_path_as_string(file_name, &file_name_str)) {
208        return false;
209    }
210    PW_CSTRING(fname, &file_name_str);
211    do {
212        f->fd = open(fname, flags, mode);
213    } while (f->fd == -1 && errno == EINTR);
214
215    if (f->fd == -1) {
216        f->error = errno;
217        pw_exception(PwErrno(errno), "Cannot open: %s", fname);
218        return false;
219    }
220
221    pw_move(&f->name, &file_name_str);
222    f->is_external_fd = false;
223    f->own_fd = true;
224
225    return true;
226}
227
228static bool file_get_name(PwMethod_File_get_name* mthis, PwValuePtr self, PwValuePtr result)
229{
230    _PwFile* f = pw_this_data(self);
231    pw_clone2(result, &f->name);
232    return true;
233}
234
235static bool file_set_name(PwMethod_File_set_name* mthis, PwValuePtr self, PwValuePtr file_name)
236{
237    _PwFile* f = pw_this_data(self);
238
239    if (f->fd != -1 && !f->is_external_fd) {
240        // not an externally set fd
241        pw_set_status(PwStatus(PweCantSetFilename));
242        return false;
243    }
244
245    pw_clone2(&f->name, file_name);
246    return true;
247}
248
249static bool file_seek(PwMethod_File_seek* mthis, PwValuePtr self, off_t offset, int whence, off_t* position)
250{
251    _PwFile* f = pw_this_data(self);
252    off_t pos = lseek(f->fd, offset, whence);
253    if (pos == -1) {
254        pw_set_status(PwErrno(errno));
255        return false;
256    }
257    if (position) {
258        *position = pos;
259    }
260    return true;
261}
262
263static bool file_tell(PwMethod_File_tell* mthis, PwValuePtr self, off_t* position)
264{
265    _PwFile* f = pw_this_data(self);
266
267    *position = lseek(f->fd, 0, SEEK_CUR);
268    if (*position == -1) {
269        pw_set_status(PwErrno(errno));
270        return false;
271    }
272    return true;
273}
274
275static PwInterface_File file_interface = {
276#define X(name, ...) .name = { .func = file_##name } __VA_OPT__(,)
277    PW_FILE_INTERFACE_METHODS
278#undef X
279};
280
281
282/****************************************************************
283 * Reader interface
284 */
285
286static bool file_read(PwMethod_Reader_read* mthis, PwValuePtr self, void* buffer, unsigned buffer_size, unsigned* bytes_read)
287{
288    _PwFile* f = pw_this_data(self);
289
290    ssize_t result;
291    do {
292        result = read(f->fd, buffer, buffer_size);
293    } while (result < 0 && errno == EINTR);
294
295    if (result < 0) {
296        *bytes_read = 0;
297        PW_CSTRING(fname, &f->name);
298        pw_exception(PwErrno(errno), "read failed: %s", fname);
299        return false;
300    }
301    *bytes_read = (unsigned) result;
302    return true;
303}
304
305static PwInterface_Reader file_reader_interface = {
306#define X(name, ...) .name = { .func = file_##name } __VA_OPT__(,)
307    PW_READER_INTERFACE_METHODS
308#undef X
309};
310
311
312/****************************************************************
313 * Writer interface
314 */
315
316static bool file_write(PwMethod_Writer_write* mthis, PwValuePtr self, void* data, unsigned size, unsigned* bytes_written)
317{
318    _PwFile* f = pw_this_data(self);
319
320    unsigned written = 0;
321    while (written < size) {
322        ssize_t n;
323        do {
324            n = write(f->fd, ((uint8_t*) data) + written, size - written);
325        } while (n < 0 && errno == EINTR);
326
327        if (n < 0) {
328            if (bytes_written) {
329                *bytes_written = written;
330            }
331            pw_set_status(PwErrno(errno));
332            return false;
333        }
334        written += (unsigned) n;
335    }
336    if (bytes_written) {
337        *bytes_written = written;
338    }
339    return true;
340}
341
342static PwInterface_Writer file_writer_interface = {
343#define X(name, ...) .name = { .func = file_##name } __VA_OPT__(,)
344    PW_WRITER_INTERFACE_METHODS
345#undef X
346};
347
348
349/****************************************************************
350 * Append interface
351 */
352
353static bool file_append_string_data(PwMethod_Append_append_string_data* mthis,
354                                    PwValuePtr self, uint8_t* start_ptr, uint8_t* end_ptr, uint8_t char_size)
355{
356    if (start_ptr >= end_ptr) {
357        return true;
358    }
359    if (char_size < 2) {
360        // write ASCII and UTF-8 directly to file
361        return do_file_write(self, start_ptr, end_ptr - start_ptr, nullptr);
362    }
363
364    // convert wide chars to UTF-8
365
366    uint8_t buffer[1024];
367    unsigned n = 0;
368
369    while (start_ptr < end_ptr) {
370        char32_t codepoint = _pw_get_char(start_ptr, char_size);
371        n += pw_char32_to_utf8(codepoint, (char*) &buffer[n]);
372        if (n > sizeof(buffer) - 4) {
373            if (!do_file_write(self, buffer, n, nullptr)) {
374                return false;
375            }
376            n = 0;
377        }
378    }
379    if (n) {
380        if (!do_file_write(self, buffer, n, nullptr)) {
381            return false;
382        }
383    }
384    return true;
385}
386
387static bool file_append(PwMethod_Append_append* mthis, PwValuePtr self, PwValuePtr value)
388{
389    pw_assert(pw_is_string(value));
390
391    uint8_t* end_ptr;
392    uint8_t* start_ptr = _pw_string_start_end(value, &end_ptr);
393    return pw_this_call(append_string_data, mthis, self, start_ptr, end_ptr, value->str_params.char_size);
394}
395
396static PwInterface_Append file_append_interface = {
397#define X(name, ...) .name = { .func = file_##name } __VA_OPT__(,)
398    PW_APPEND_INTERFACE_METHODS
399#undef X
400};
401
402/****************************************************************
403 * Initialization
404 */
405
406[[gnu::constructor]]
407void _pw_init_file()
408{
409    // interface can be already registered
410    // basically. interfaces can be registered by any type in any order
411    if (PwInterfaceId_File) {
412        return;
413    }
414
415    _pw_init_types();
416
417#   define X(name, ...) #name __VA_OPT__(,)
418    PwInterfaceId_File = pw_register_interface("File", PW_FILE_INTERFACE_METHODS, nullptr);
419#   undef X
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}