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