1 /*@ Implementation of code.h: (unavoidable) basics.
2  *@ TODO Log: domain should be configurable
3  *@ TODO Assert: the C++ lib has per-thread assertion states, s_nolog to
4  *@ TODO    suppress log, test_state(), test_and_clear_state(): for unit tests!
5  *@ TODO su_program: if set, the PID should be logged, too!
6  *
7  * Copyright (c) 2019 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
8  * SPDX-License-Identifier: ISC
9  *
10  * Permission to use, copy, modify, and/or distribute this software for any
11  * purpose with or without fee is hereby granted, provided that the above
12  * copyright notice and this permission notice appear in all copies.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21  */
22 #undef su_FILE
23 #define su_FILE su_core_code
24 #define su_SOURCE
25 #define su_SOURCE_CORE_CODE
26 #define su_MASTER
27 
28 #include "su/code.h"
29 
30 #include <errno.h> /* XXX Grrrr */
31 #include <stdarg.h>
32 #include <stdio.h> /* TODO Get rid */
33 #include <stdlib.h>
34 #include <unistd.h> /* TODO POSIX module! */
35 
36 #include "su/icodec.h"
37 
38 /*#include "su/code.h"*/
39 #include "su/code-in.h"
40 
41 #if defined su_HAVE_DEBUG || defined su_HAVE_DEVEL
42 struct a_core_nyd_info{
43    char const *cni_file;
44    char const *cni_fun;
45    u32 cni_chirp_line;
46    u32 cni_level;
47 };
48 #endif
49 
50 static char const a_core_lvlnames[][8] = {
51    FIELD_INITI(su_LOG_EMERG) "emerg",
52    FIELD_INITI(su_LOG_ALERT) "alert",
53    FIELD_INITI(su_LOG_CRIT) "crit",
54    FIELD_INITI(su_LOG_ERR) "error",
55    FIELD_INITI(su_LOG_WARN) "warning",
56    FIELD_INITI(su_LOG_NOTICE) "notice",
57    FIELD_INITI(su_LOG_INFO) "info",
58    FIELD_INITI(su_LOG_DEBUG) "debug"
59 };
60 /* You can deduce the value from the offset */
61 CTAV(su_LOG_EMERG == 0);
62 CTAV(su_LOG_DEBUG == 7);
63 
64 union su__bom_union const su__bom_little = {{'\xFF', '\xFE'}};
65 union su__bom_union const su__bom_big = {{'\xFE', '\xFF'}};
66 
67 #if DVLOR(1, 0)
68 MT( static uz a_core_glock_recno[su__GLOCK_MAX +1]; )
69 
70 static u32 a_core_nyd_curr, a_core_nyd_level;
71 static boole a_core_nyd_skip;
72 static struct a_core_nyd_info a_core_nyd_infos[su_NYD_ENTRIES];
73 #endif
74 
75 uz su__state;
76 
77 char const su_empty[] = "";
78 char const su_reproducible_build[] = "reproducible_build";
79 u16 const su_bom = su_BOM;
80 
81 char const *su_program;
82 
83 /* TODO Eventually all the I/O is SU based, then this will vanish!
84  * TODO We need some _USECASE_ hook to store readily prepared lines then.
85  * TODO Also, our log does not yet prepend "su_progam: " to each output line,
86  * TODO because of all that (port FmtEncCtx, use rounds!!) */
87 su_SINLINE void a_evlog(BITENUM_IS(u32,su_log_level) lvl, char const *fmt,
88       va_list ap);
89 
90 /* */
91 #if DVLOR(1, 0)
92 static void a_core_nyd_printone(void (*ptf)(up cookie, char const *buf,
93       uz blen), up cookie, struct a_core_nyd_info const *cnip);
94 #endif
95 
96 su_SINLINE void
a_evlog(BITENUM_IS (u32,su_log_level)lvl,char const * fmt,va_list ap)97 a_evlog(BITENUM_IS(u32,su_log_level) lvl, char const *fmt, va_list ap){
98 #ifdef su_USECASE_MX
99 # ifndef mx_HAVE_AMALGAMATION
100    /*extern*/ void n_err(char const *, ...);
101    /*extern*/ void n_verr(char const *, va_list);
102 # endif
103 #endif
104    char buf[su_IENC_BUFFER_SIZE];
105    char const *cp, *xfmt;
106    u32 f;
107 
108    f = lvl & ~su__LOG_MASK;
109    lvl &= su__LOG_MASK;
110 
111    if(!(f & su_LOG_F_CORE)){
112 #ifdef su_USECASE_MX
113       if(lvl != su_LOG_EMERG)
114          goto jnostd;
115 #endif
116    }
117 
118    /* TODO ensure each line has the prefix; use FormatEncodeCtx */
119    if(su_program != NIL){
120       if(su_state_has(su_STATE_LOG_SHOW_PID)){
121          cp = su_ienc_u32(buf, getpid(), 10);
122          xfmt = "%s[%s]: ";
123       }else{
124          cp = su_empty;
125          xfmt = "%s: ";
126       }
127       fprintf(stderr, xfmt, su_program, cp);
128    }
129 
130    if(su_state_has(su_STATE_LOG_SHOW_LEVEL))
131       fprintf(stderr, "[%s] ",
132          a_core_lvlnames[lvl]);
133 
134    vfprintf(stderr, fmt, ap);
135 
136 #ifdef su_USECASE_MX
137    goto jnomx;
138 jnostd:
139    n_verr(fmt, ap);
140 jnomx:
141 #endif
142 
143    if(lvl == su_LOG_EMERG)
144       abort(); /* TODO configurable */
145 }
146 
147 #if DVLOR(1, 0)
148 static void
a_core_nyd_printone(void (* ptf)(up cookie,char const * buf,uz blen),up cookie,struct a_core_nyd_info const * cnip)149 a_core_nyd_printone(void (*ptf)(up cookie, char const *buf, uz blen),
150       up cookie, struct a_core_nyd_info const *cnip){
151    char buf[80 +1], c;
152    union {int i; uz z;} u;
153    char const *sep, *cp;
154 
155    /* Ensure actual file name can be seen, unless multibyte comes into play */
156    sep = su_empty;
157    cp = cnip->cni_file;
158    for(u.z = 0; (c = cp[u.z]) != '\0'; ++u.z)
159       if(S(uc,c) & 0x80){
160          u.z = 0;
161          break;
162       }
163    if(u.z > 40){
164       cp += -(38 - u.z);
165       sep = "..";
166    }
167 
168    u.i = snprintf(buf, sizeof(buf) - 1,
169          "%c[%2" PRIu32 "] %.25s (%s%.40s:%" PRIu32 ")\n",
170          "=><"[(cnip->cni_chirp_line >> 29) & 0x3], cnip->cni_level,
171          cnip->cni_fun, sep, cp, (cnip->cni_chirp_line & 0x1FFFFFFFu));
172    if(u.i > 0){
173       u.z = u.i;
174       if(u.z >= sizeof(buf) -1){
175          buf[sizeof(buf) - 2] = '\n';
176          buf[sizeof(buf) - 1] = '\0';
177          u.z = sizeof(buf) -1; /* (Skip \0) */
178       }
179       (*ptf)(cookie, buf, u.z);
180    }
181 }
182 #endif /* DVLOR(1, 0) */
183 
184 #ifdef su_HAVE_MT
185 void
su__glock(enum su__glock_type gt)186 su__glock(enum su__glock_type gt){
187    NYD2_IN;
188 
189    switch(gt){
190    case su__GLOCK_STATE: /* XXX spinlock */
191       break;
192    case su__GLOCK_LOG: /* XXX mutex */
193       break;
194    }
195 
196 # if DVLOR(1, 0)
197    ASSERT(a_core_glock_recno[gt] != UZ_MAX);
198    ++a_core_glock_recno[gt];
199 # endif
200    NYD2_OU;
201 }
202 
203 void
su__gunlock(enum su__glock_type gt)204 su__gunlock(enum su__glock_type gt){
205    NYD2_IN;
206 
207    switch(gt){
208    case su__GLOCK_STATE: /* XXX spinlock */
209       break;
210    case su__GLOCK_LOG: /* XXX mutex */
211       break;
212    }
213 
214 # if DVLOR(1, 0)
215    ASSERT(a_core_glock_recno[gt] > 0);
216    --a_core_glock_recno[gt];
217 # endif
218    NYD2_OU;
219 }
220 #endif /* su_HAVE_MT */
221 
222 s32
su_state_err(enum su_state_err_type err,uz state,char const * msg_or_nil)223 su_state_err(enum su_state_err_type err, uz state, char const *msg_or_nil){
224    static char const intro_nomem[] = N_("Out of memory: %s\n"),
225       intro_overflow[] = N_("Datatype overflow: %s\n");
226 
227    enum su_log_level lvl;
228    char const *introp;
229    s32 eno;
230    u32 xerr;
231    NYD2_IN;
232 
233    if(msg_or_nil == NIL)
234       msg_or_nil = N_("(no error information)");
235    state &= su_STATE_ERR_MASK;
236 
237    xerr = err;
238    switch(xerr &= su_STATE_ERR_TYPE_MASK){
239    default:
240       ASSERT(0);
241       /* FALLTHRU */
242    case su_STATE_ERR_NOMEM:
243       eno = su_ERR_NOMEM;
244       introp = intro_nomem;
245       break;
246    case su_STATE_ERR_OVERFLOW:
247       eno = su_ERR_OVERFLOW;
248       introp = intro_overflow;
249       break;
250    }
251 
252    lvl = su_LOG_EMERG;
253    if(state & su_STATE_ERR_NOPASS)
254       goto jdolog;
255    if(state & su_STATE_ERR_PASS)
256       lvl = su_LOG_DEBUG;
257    else if((state & xerr) || su_state_has(xerr))
258       lvl = su_LOG_ALERT;
259 
260    if(su_log_would_write(lvl))
261 jdolog:
262       su_log_write(lvl, V_(introp), V_(msg_or_nil));
263 
264    if(!(state & su_STATE_ERR_NOERRNO))
265       su_err_set_no(eno);
266    NYD2_OU;
267    return eno;
268 }
269 
270 s32
su_err_no(void)271 su_err_no(void){
272    s32 rv;
273    rv = errno; /* TODO a_core_eno */
274    return rv;
275 }
276 
277 s32
su_err_set_no(s32 eno)278 su_err_set_no(s32 eno){
279    errno = eno; /* TODO a_core_eno; */
280    return eno;
281 }
282 
283 s32
su_err_no_via_errno(void)284 su_err_no_via_errno(void){
285    s32 rv;
286    rv = /*TODO a_core_eno =*/errno;
287    return rv;
288 }
289 
290 void
su_log_write(BITENUM_IS (u32,su_log_level)lvl,char const * fmt,...)291 su_log_write(BITENUM_IS(u32,su_log_level) lvl, char const *fmt, ...){
292    va_list va;
293    NYD_IN;
294 
295    if(su_log_would_write(lvl)){
296       va_start(va, fmt);
297       a_evlog(lvl, fmt, va);
298       va_end(va);
299    }
300    NYD_OU;
301 }
302 
303 void
su_log_vwrite(BITENUM_IS (u32,su_log_level)lvl,char const * fmt,void * vp)304 su_log_vwrite(BITENUM_IS(u32,su_log_level) lvl, char const *fmt, void *vp){
305    NYD_IN;
306 
307    if(su_log_would_write(lvl))
308       a_evlog(lvl, fmt, *S(va_list*,vp));
309    NYD_OU;
310 }
311 
312 void
su_assert(char const * expr,char const * file,u32 line,char const * fun,boole crash)313 su_assert(char const *expr, char const *file, u32 line, char const *fun,
314       boole crash){
315    su_log_write((crash ? su_LOG_EMERG : su_LOG_ALERT),
316       "SU assert failed: %.60s\n"
317       "  File %.60s, line %" PRIu32 "\n"
318       "  Function %.142s\n",
319       expr, file, line, fun);
320 }
321 
322 #if DVLOR(1, 0)
323 void
su_nyd_set_disabled(boole disabled)324 su_nyd_set_disabled(boole disabled){
325    a_core_nyd_skip = (disabled != FAL0);
326 }
327 
328 void
su_nyd_reset_level(u32 nlvl)329 su_nyd_reset_level(u32 nlvl){
330    if(nlvl < a_core_nyd_level)
331       a_core_nyd_level = nlvl;
332 }
333 
334 void
su_nyd_chirp(u8 act,char const * file,u32 line,char const * fun)335 su_nyd_chirp(u8 act, char const *file, u32 line, char const *fun){
336    if(!a_core_nyd_skip){
337       struct a_core_nyd_info *cnip;
338 
339       cnip = &a_core_nyd_infos[0];
340 
341       if(a_core_nyd_curr != su_NELEM(a_core_nyd_infos))
342          cnip += a_core_nyd_curr++;
343       else
344          a_core_nyd_curr = 1;
345       cnip->cni_file = file;
346       cnip->cni_fun = fun;
347       cnip->cni_chirp_line = (S(u32,act & 0x3) << 29) | (line & 0x1FFFFFFFu);
348       cnip->cni_level = ((act == 0) ? a_core_nyd_level /* TODO spinlock */
349             : (act == 1) ? ++a_core_nyd_level : a_core_nyd_level--);
350    }
351 }
352 
353 void
su_nyd_dump(void (* ptf)(up cookie,char const * buf,uz blen),up cookie)354 su_nyd_dump(void (*ptf)(up cookie, char const *buf, uz blen), up cookie){
355    uz i;
356    struct a_core_nyd_info const *cnip;
357 
358    a_core_nyd_skip = TRU1;
359    if(a_core_nyd_infos[su_NELEM(a_core_nyd_infos) - 1].cni_file != NULL)
360       for(i = a_core_nyd_curr, cnip = &a_core_nyd_infos[i];
361             i < su_NELEM(a_core_nyd_infos); ++i)
362          a_core_nyd_printone(ptf, cookie, cnip++);
363    for(i = 0, cnip = a_core_nyd_infos; i < a_core_nyd_curr; ++i)
364       a_core_nyd_printone(ptf, cookie, cnip++);
365 }
366 #endif /* DVLOR(1, 0) */
367 
368 #include "su/code-ou.h"
369 /* s-it-mode */
370