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, ¤t_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}