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