1#ifndef _GNU_SOURCE
  2//  for vasprintf
  3#   define _GNU_SOURCE
  4#endif
  5
  6#include <stdlib.h>
  7
  8#include <libpussy/mmarray.h>
  9
 10#include "include/pw.h"
 11#include "src/types/status_internal.h"
 12#include "src/types/struct_internal.h"
 13
 14
 15static char* basic_statuses[] = {
 16    [PW_SUCCESS]                        = "SUCCESS",
 17    [PW_STATUS_VA_END]                  = "VA_END",
 18    [PW_ERROR]                          = "ERROR",
 19    [PW_ERROR_ERRNO]                    = "ERRNO",
 20    [PW_ERROR_OOM]                      = "OOM",
 21    [PW_ERROR_NOT_IMPLEMENTED]          = "NOT IMPLEMENTED",
 22    [PW_ERROR_INCOMPATIBLE_TYPE]        = "INCOMPATIBLE_TYPE",
 23    [PW_ERROR_INTERFACE_NOT_DEFINED]    = "INTERFACE_NOT_DEFINED",
 24    [PW_ERROR_EOF]                      = "EOF",
 25    [PW_ERROR_TIMEOUT]                  = "TIMEOUT",
 26    [PW_ERROR_STRING_TOO_LONG]          = "PW_ERROR_STRING_TOO_LONG",
 27    [PW_ERROR_DATA_SIZE_TOO_BIG]        = "DATA_SIZE_TOO_BIG",
 28    [PW_ERROR_INDEX_OUT_OF_RANGE]       = "INDEX_OUT_OF_RANGE",
 29    [PW_ERROR_ITERATION_IN_PROGRESS]    = "ITERATION_IN_PROGRESS",
 30    [PW_ERROR_BAD_NUMBER]               = "BAD_NUMBER",
 31    [PW_ERROR_BAD_DATETIME]             = "BAD_DATETIME",
 32    [PW_ERROR_BAD_TIMESTAMP]            = "BAD_TIMESTAMP",
 33    [PW_ERROR_NUMERIC_OVERFLOW]         = "NUMERIC_OVERFLOW",
 34    [PW_ERROR_INCOMPLETE_UTF8]          = "INCOMPLETE_UTF8",
 35    [PW_ERROR_EXTRACT_FROM_EMPTY_ARRAY] = "EXTRACT_FROM_EMPTY_ARRAY",
 36    [PW_ERROR_DELETE_FROM_EMPTY_ARRAY]  = "DELETE_FROM_EMPTY_ARRAY",
 37    [PW_ERROR_KEY_NOT_FOUND]            = "KEY_NOT_FOUND",
 38    [PW_ERROR_FILE_ALREADY_OPENED]      = "FILE_ALREADY_OPENED",
 39    [PW_ERROR_FD_ALREADY_SET]           = "FD_ALREADY_SET",
 40    [PW_ERROR_CANT_SET_FILENAME]        = "CANT_SET_FILENAME",
 41    [PW_ERROR_FILE_CLOSED]              = "FILE_CLOSED",
 42    [PW_ERROR_NOT_REGULAR_FILE]         = "NOT_REGULAR_FILE",
 43    [PW_ERROR_UNBUFFERED_FILE]          = "UNBUFFERED_FILE",
 44    [PW_ERROR_WRITE]                    = "WRITE",
 45    [PW_ERROR_UNREAD_FAILED]            = "UNREAD_FAILED"
 46};
 47
 48static char** statuses = nullptr;
 49static uint16_t num_statuses = 0;
 50
 51[[ gnu::constructor ]]
 52static void init_statuses()
 53{
 54    if (statuses) {
 55        return;
 56    }
 57    num_statuses = PW_LENGTH(basic_statuses);
 58    statuses = mmarray_allocate(0, num_statuses, sizeof(char*));
 59    for(uint16_t i = 0; i < num_statuses; i++) {
 60        char* status = basic_statuses[i];
 61        if (!status) {
 62            fprintf(stderr, "Status %u is not defined\n", i);
 63            abort();
 64        }
 65        statuses[i] = status;
 66    }
 67}
 68
 69uint16_t pw_define_status(char* status)
 70{
 71    // the order constructor are called is undefined, make sure statuses are initialized
 72    init_statuses();
 73
 74    if (num_statuses == 65535) {
 75        fprintf(stderr, "Cannot define more statuses than %u\n", num_statuses);
 76        return PW_ERROR_OOM;
 77    }
 78    statuses = mmarray_append_item(statuses, &status);
 79    uint16_t status_code = num_statuses++;
 80    return status_code;
 81}
 82
 83char* pw_status_str(uint16_t status_code)
 84{
 85    if (status_code < num_statuses) {
 86        return statuses[status_code];
 87    } else {
 88        static char unknown[] = "(unknown)";
 89        return unknown;
 90    }
 91}
 92
 93void _pw_set_status_location(PwValuePtr status, const char* file_name, unsigned line_number)
 94{
 95    if (status->has_status_data) {
 96        _PwStatusData* status_data = _pw_get_struct_ptr(status, PwTypeId_Status);
 97        status_data->file_name = file_name;
 98        status_data->line_number = line_number;
 99    } else {
100        status->file_name = file_name;
101        status->line_number = line_number;
102    }
103}
104
105void _pw_set_status_desc(PwValuePtr status, const char* fmt, ...)
106{
107    va_list ap;
108    va_start(ap);
109    _pw_set_status_desc_ap(status, fmt, ap);
110    va_end(ap);
111}
112
113void _pw_set_status_desc_ap(PwValuePtr status, const char* fmt, va_list ap)
114{
115    pw_assert_status(status);
116    if (!status->is_error) {
117        // do not set description for PW_SUCCESS
118        // such status must not have any allocated data because it is allowed
119        // to be overwritten without calling pw_destroy on it, similar to null value
120        return;
121    }
122
123    _PwStatusData* status_data;
124
125    if (status->has_status_data) {
126        status_data = _pw_get_struct_ptr(status, PwTypeId_Status);
127        pw_destroy(&status_data->description);
128
129    } else {
130        // allocate structure
131
132        uint8_t* struct_data = _pw_struct_alloc(status->type_id);
133        if (!struct_data) {
134            // ignore error
135            return;
136        }
137        // save fields that will go to the allocated structure
138        const char* file_name   = status->file_name;
139        uint16_t    line_number = status->line_number;
140        int16_t     pw_errno    = status->pw_errno;
141
142        status->has_status_data = 1;
143        status->struct_data = struct_data;
144
145        // set struct_offset for _pw_get_struct_ptr to work properly
146
147        status->struct_offset = _pw_types[status->type_id]->struct_offsets[0];
148
149        // set fields of the allocated structure
150
151        status_data = _pw_get_struct_ptr(status, PwTypeId_Status);
152
153        status_data->file_name   = file_name;
154        status_data->line_number = line_number;
155        status_data->pw_errno    = pw_errno;
156    }
157    char* desc;
158    if (vasprintf(&desc, fmt, ap) == -1) {
159        return;
160    }
161    PwValue s = PW_NULL;
162    if (pw_create_string(&s, desc)) {
163        pw_move(&status_data->description, &s);
164    }
165    free(desc);
166}
167
168void pw_print_status(FILE* fp, PwValuePtr status)
169{
170    // XXX rewrite in error-free way
171
172    PwValue desc = PW_NULL;
173    if (!pw_to_string(status, &desc)) {
174        // XXX
175        pw_dump(fp, &desc);
176    } else {
177        PW_CSTRING(desc_cstr, &desc);
178        fputs(desc_cstr, fp);
179        fputc('\n', fp);
180    }
181}
182
183/****************************************************************
184 * Basic interface methods
185 */
186
187static bool status_create(PwMethod_Basic_create* mthis, PwValuePtr result, PwCtorArgs* ctor_args)
188{
189    // nothing to do here because pw_create2 has already set type_id and zeroed all other fields
190    return true;
191}
192
193static bool status_destroy(PwMethod_Basic_destroy* mthis, PwValuePtr self, _PwCompoundChain* tail)
194{
195    PwValuePtr value_seen = _pw_on_chain(self, tail);
196    if (value_seen) {
197        return true;
198    }
199    if (self->has_status_data) {
200        return pw_super(mthis, self, tail);
201    }
202    return true;
203}
204
205static bool status_clone(PwMethod_Basic_clone* mthis, PwValuePtr self)
206{
207    if (self->has_status_data) {
208        return pw_super(mthis, self);
209    }
210    return true;
211}
212
213static bool status_decref(PwMethod_Basic_decref* mthis, PwValuePtr self)
214{
215    if (self->has_status_data) {
216        return pw_super(mthis, self);
217    }
218    return true;
219}
220
221static bool status_hash(PwMethod_Basic_hash* mthis, PwValuePtr self, PwHashContext* ctx, _PwCompoundChain* tail)
222{
223    _pw_hash_uint64(ctx, self->type_id);
224    _pw_hash_uint64(ctx, self->status_code);
225    if (self->status_code == PW_ERROR_ERRNO) {
226        int pw_errno;
227        if (self->has_status_data) {
228            _PwStatusData* status_data = pw_this_data(self);
229            pw_errno = status_data->pw_errno;
230        } else {
231            pw_errno = self->pw_errno;
232        }
233        _pw_hash_uint64(ctx, pw_errno);
234    }
235    // XXX do not hash description?
236    return true;
237}
238
239static bool status_deepcopy(PwMethod_Basic_deepcopy* mthis, PwValuePtr self, PwValuePtr result, _PwCompoundChain* tail)
240{
241    pw_destroy(result);
242    *result = *self;
243    if (!self->has_status_data) {
244        return true;
245    }
246
247    result->struct_data = _pw_struct_alloc(result->type_id);
248    if (!result->struct_data) {
249        *result = PwNull();  // reset possible garbage in other fields
250        return false;
251    }
252
253    _PwStatusData* src_status_data  = pw_this_data(self);
254    _PwStatusData* dest_status_data = pw_this_data(result);
255
256    dest_status_data->file_name   = src_status_data->file_name;
257    dest_status_data->line_number = src_status_data->line_number;
258    dest_status_data->pw_errno    = src_status_data->pw_errno;
259    if (!pw_deepcopy(&dest_status_data->description, &src_status_data->description)) {
260        pw_destroy(result);
261        return false;
262    }
263    return true;
264}
265
266static bool status_to_string(PwMethod_Basic_to_string* mthis, PwValuePtr self, PwValuePtr result, _PwCompoundChain* tail)
267{
268    if (!self->is_error) {
269        pw_destroy(result);
270        *result = PwString("Success");
271        return true;
272    }
273    char* status_str = pw_status_str(self->status_code);
274    const char* file_name;
275    unsigned line_number;
276    int pw_errno;
277    PwValuePtr description = nullptr;
278    unsigned description_length = 0;
279    uint8_t char_size = 1;
280
281    if (self->has_status_data) {
282        _PwStatusData* status_data = pw_this_data(self);
283        file_name   = status_data->file_name;
284        line_number = status_data->line_number;
285        pw_errno    = status_data->pw_errno;
286        if (pw_is_string(&status_data->description)) {
287            description = &status_data->description;
288            description_length = pw_strlen(description);
289            char_size = description->char_size;
290        }
291    } else {
292        file_name   = self->file_name;
293        line_number = self->line_number;
294        pw_errno    = self->pw_errno;
295    }
296
297    unsigned errno_length = 0;
298    static char errno_fmt[] = "; errno %d: %s";
299    char* errno_str = "";
300    if (self->status_code == PW_ERROR_ERRNO) {
301        errno_str = strerror(pw_errno);
302    }
303    char errno_desc[strlen(errno_fmt) + 16 + strlen(errno_str)];
304    if (self->status_code == PW_ERROR_ERRNO) {
305        errno_length = snprintf(errno_desc, sizeof(errno_desc), errno_fmt, pw_errno, errno_str);
306    } else {
307        errno_desc[0] = 0;
308    }
309
310    static char fmt[] = "%s; %s:%u%s";
311    char desc[sizeof(fmt) + 16 + strlen(status_str) + strlen(file_name) + errno_length];
312    unsigned length = snprintf(desc, sizeof(desc), fmt, status_str, file_name, line_number, errno_desc);
313
314    if (!pw_create_empty_string(length + description_length + 2, char_size, result)) {
315        goto error;
316    }
317    if (!pw_string_append(result, desc, desc + length)) {
318        goto error;
319    }
320    if (description) {
321        if (!pw_string_append(result, "; ", nullptr)) {
322            goto error;
323        }
324        if (!pw_string_append(result, description)) {
325            goto error;
326        }
327    }
328    return true;
329
330error:
331    pw_destroy(result);
332    *result = PwString("(error)");
333    return false;
334}
335
336static bool status_dump(PwMethod_Basic_dump* mthis, PwValuePtr self, FILE* fp, int indent, _PwCompoundChain* tail)
337{
338    if (!pw_super(mthis, self, fp, indent, tail)) {
339        return false;
340    }
341
342    PwValue desc = PW_NULL;
343    if (pw_this_call(to_string, mthis, self, &desc, nullptr)) {
344        // result is always meaningful string, ignore return value
345    }
346    PW_CSTRING(cdesc, &desc);
347    _pw_print_indent(fp, indent);
348    fputs(cdesc, fp);
349    fputc('\n', fp);
350    return true;
351}
352
353[[nodiscard]] static bool status_eq(PwValuePtr self, PwValuePtr other)
354{
355    if (self->status_code == PW_ERROR_ERRNO) {
356        if (other->status_code != PW_ERROR_ERRNO) {
357            return false;
358        }
359        int self_errno, other_errno;
360        if (self->has_status_data) {
361            self_errno = ((_PwStatusData*) _pw_get_struct_ptr(self, PwTypeId_Status))->pw_errno;
362        } else {
363            self_errno = self->pw_errno;
364        }
365        if (other->has_status_data) {
366            other_errno = ((_PwStatusData*) _pw_get_struct_ptr(other, PwTypeId_Status))->pw_errno;
367        } else {
368            other_errno = other->pw_errno;
369        }
370        return self_errno == other_errno;
371    }
372    else {
373        return self->status_code == other->status_code;
374    }
375}
376
377static bool status_equal(PwMethod_Basic_equal* mthis, PwValuePtr self, PwValuePtr other, _PwCompoundChain* tail)
378{
379    // XXX suboptimal, need to unroll, merge with status_eq, and get rid of _pw_get_struct_ptr
380    // but status is to be revised
381    PW_EQUAL_METHOD_IMPL(PwTypeId_Status, status_eq, self, other);
382    return false;
383}
384
385static bool status_is_true(PwMethod_Basic_is_true* mthis, PwValuePtr self, _PwCompoundChain* tail)
386{
387    return self->status_code == PW_SUCCESS;
388}
389
390#define status_iter_children nullptr
391
392PwInterface_Basic _pw_status_basic_interface = {
393#define X(name, ...) .name = { .func = status_##name } __VA_OPT__(,)
394    PW_BASIC_INTERFACE_METHODS
395#undef X
396};