/ status.c
status.c
  1  /*
  2   * SPDX-FileCopyrightText: 2024 Le’
  3   *
  4   * SPDX-License-Identifier: LGPL-3.0-or-later
  5   */
  6  
  7  #include <stdio.h>
  8  #include <stdlib.h>
  9  #include <unistd.h>
 10  #include <stdarg.h>
 11  #include <string.h>
 12  #include <leutils/status.h>
 13  #include "hashmap.h"
 14  
 15  /*
 16   * Reference implementation of stpecpy and strtcpy, taken from string_copying(7)
 17   */
 18  
 19  #include <errno.h>
 20  
 21  /* This code is in the public domain. */
 22  
 23  #ifndef HAVE_mempcpy
 24  static void *
 25  mempcpy(void *restrict dest, const void *restrict src, size_t n)
 26  {
 27    return (unsigned char *)memcpy(dest, src, n) + n;
 28  }
 29  #endif
 30  
 31  #ifndef HAVE_strtcpy
 32  static ssize_t
 33  strtcpy(char *restrict dst, const char *restrict src, size_t dsize)
 34  {
 35    _Bool    trunc;
 36    size_t  dlen, slen;
 37  
 38    if (dsize == 0) {
 39      errno = ENOBUFS;
 40      return -1;
 41    }
 42  
 43    slen = strnlen(src, dsize);
 44    trunc = (slen == dsize);
 45    dlen = slen - trunc;
 46  
 47    stpcpy(mempcpy(dst, src, dlen), "");
 48    if (trunc)
 49      errno = E2BIG;
 50    return trunc ? -1 : slen;
 51  }
 52  #endif
 53  
 54  #ifndef HAVE_stpecpy
 55  static char *
 56  stpecpy(char *dst, const char *end, const char *restrict src)
 57  {
 58    size_t  dlen;
 59  
 60    if (dst == NULL)
 61      return NULL;
 62  
 63    dlen = strtcpy(dst, src, end - dst);
 64    return (dlen == -1) ? NULL : dst + dlen;
 65  }
 66  #endif
 67  
 68  
 69  struct LE_status_reason_st {
 70    LE_STATUS sts_message;
 71    const char *identity;
 72    const char *message;
 73  };
 74  
 75  static uint64_t hash_reason(const void *item, uint64_t seed0, uint64_t seed1)
 76  {
 77    const struct LE_status_reason_st *reason = item;
 78    return hashmap_xxhash3(&reason->sts_message, sizeof(reason->sts_message),
 79                           seed0, seed1);
 80  }
 81  
 82  static int cmp_reason(const void *a, const void *b, void *udata)
 83  {
 84    const struct LE_status_reason_st *reason_a = a;
 85    const struct LE_status_reason_st *reason_b = b;
 86    return reason_a->sts_message - reason_b->sts_message;
 87  }
 88  
 89  struct LE_status_unit_st {
 90    LE_STATUS sts_unit;
 91    const char *name;
 92    struct hashmap *reasons; /* elements are struct LE_status_reason_st */
 93  };
 94  
 95  static uint64_t hash_unit(const void *item, uint64_t seed0, uint64_t seed1)
 96  {
 97    const struct LE_status_unit_st *unit = item;
 98    return hashmap_xxhash3(&unit->sts_unit, sizeof(unit->sts_unit),
 99                           seed0, seed1);
100  }
101  
102  static int cmp_unit(const void *a, const void *b, void *udata)
103  {
104    const struct LE_status_unit_st *unit_a = a;
105    const struct LE_status_unit_st *unit_b = b;
106    return unit_a->sts_unit - unit_b->sts_unit;
107  }
108  
109  static void free_unit(void *item)
110  {
111    const struct LE_status_unit_st *unit = item;
112    hashmap_free(unit->reasons);
113  }
114  
115  struct LE_messages_st {
116    struct LE_status_handler_st *handlers;
117    size_t handlers_num;
118    size_t handlers_cap;
119    struct hashmap *units;   /* elements are struct LE_status_unit_st */
120  };
121  
122  LE_STATUS LE_new_status_messages(LE_messages_t **mdata)
123  {
124    if (mdata == NULL || *mdata != NULL)
125      return LE_STS_INVALID_PARAMETER;
126  
127    *mdata = malloc(sizeof(**mdata));
128    if (*mdata == NULL)
129      return LE_STS_ALLOC_FAILURE;
130  
131    memset(*mdata, 0, sizeof(**mdata));
132    (*mdata)->units = hashmap_new(sizeof(struct LE_status_unit_st), 0, 0, 0,
133                                  hash_unit, cmp_unit, free_unit, NULL);
134    if ((*mdata)->units == NULL) {
135      free(*mdata);
136      *mdata = NULL;
137      return LE_STS_ALLOC_FAILURE;
138    }
139  
140    /*
141     * Populate with Unit 0 status messages
142     */
143    LE_STATUS sts;
144  
145    !LE_status_is_OK(sts = LE_register_status_unit(*mdata, 0, NULL))
146      || !LE_status_is_OK(sts = LE_register_status_message(*mdata, LE_STS_ALLOC_FAILURE,
147                                                           "ALLOCFAIL", "Allocation failure"))
148      || !LE_status_is_OK(sts = LE_register_status_message(*mdata, LE_STS_NOT_IMPLEMENTED,
149                                                           "NOIMPL", "Not implemented"))
150      || !LE_status_is_OK(sts = LE_register_status_message(*mdata, LE_STS_NOT_SUPPORTED,
151                                                           "NOSUPPORT", "Not supported"))
152      || !LE_status_is_OK(sts = LE_register_status_message(*mdata, LE_STS_INVALID_PARAMETER,
153                                                           "INVPAR", "Invalid parameter"));
154    return sts;
155  }
156  
157  LE_STATUS LE_free_status_messages(LE_messages_t **mdata)
158  {
159    if (mdata == NULL || *mdata == NULL)
160      return LE_STS_INVALID_PARAMETER;
161  
162    free((*mdata)->handlers);
163    hashmap_free((*mdata)->units);
164    free(*mdata);
165    *mdata = NULL;
166  
167    return LE_STS_SUCCESS;
168  }
169  
170  LE_STATUS LE_register_status_unit(LE_messages_t *mdata, LE_STATUS sts,
171                                    const char *name)
172  {
173    if (mdata == NULL)
174      return LE_STS_INVALID_PARAMETER;
175  
176    struct LE_status_unit_st tmpl = { sts & LE_M_STATUS_UNIT, name, NULL };
177    const struct LE_status_unit_st *unit = hashmap_get(mdata->units, &tmpl);
178  
179    if (unit != NULL) {
180      if (strcmp(unit->name, name) != 0)
181        return LE_STS_INVALID_PARAMETER;
182    } else {
183      tmpl.reasons = hashmap_new(sizeof(struct LE_status_reason_st), 0, 0, 0,
184                                 hash_reason, cmp_reason, NULL, NULL);
185      if (tmpl.reasons == NULL)
186        return LE_STS_ALLOC_FAILURE;
187      hashmap_set(mdata->units, &tmpl);
188      if (hashmap_oom(mdata->units)) {
189        hashmap_free(tmpl.reasons);
190        return LE_STS_ALLOC_FAILURE;
191      }
192    }
193  
194    return LE_STS_SUCCESS;
195  }
196  
197  LE_STATUS LE_register_status_message(LE_messages_t *mdata, LE_STATUS sts,
198                                       const char *identity, const char *message)
199  {
200    if (mdata == NULL || mdata->units == NULL)
201      return LE_STS_INVALID_PARAMETER;
202  
203    struct LE_status_unit_st unit_tmpl = { sts & LE_M_STATUS_UNIT };
204    const struct LE_status_unit_st *unit = hashmap_get(mdata->units, &unit_tmpl);
205  
206    if (unit == NULL)
207      return LE_STS_INVALID_PARAMETER;
208  
209    struct LE_status_reason_st reason_tmpl = { sts & LE_M_STATUS_REASON, identity, message };
210    const struct LE_status_reason_st *reason = hashmap_get(unit->reasons, &reason_tmpl);
211  
212    if (reason != NULL) {
213      if (strcmp(reason->message, message) != 0)
214        return LE_STS_INVALID_PARAMETER;
215    } else {
216      hashmap_set(unit->reasons, &reason_tmpl);
217      if (hashmap_oom(unit->reasons))
218        return LE_STS_ALLOC_FAILURE;
219    }
220  
221    return LE_STS_SUCCESS;
222  }
223  
224  LE_STATUS LE_get_status_text(const LE_messages_t *mdata, LE_STATUS sts,
225                               char *buf, size_t *buflen, size_t bufsize)
226  {
227    struct LE_status_unit_st unit_tmpl = { sts & LE_M_STATUS_UNIT, "NOUNIT", NULL };
228    const struct LE_status_unit_st *unit = hashmap_get(mdata->units, &unit_tmpl);
229  
230    if (unit == NULL)
231      unit = &unit_tmpl;
232  
233    char default_message_text[128];
234    struct LE_status_reason_st reason_tmpl = { sts & LE_M_STATUS_REASON, "NOMSG", default_message_text };
235    const struct LE_status_reason_st *reason = NULL;
236    if (unit->reasons != NULL)
237      reason = hashmap_get(unit->reasons, &reason_tmpl);
238    if (reason == NULL) {
239      sprintf(default_message_text, "Status=0x%08X", sts);
240      reason = &reason_tmpl;
241    }
242  
243    static const char severity_chars[] = { 'W', 'S', 'E', 'I', 'F' };
244    static const char *severity_prefixes[] = {
245      "warning",
246      "note",
247      "error",
248      "note",
249      "error"
250    };
251    uint8_t severity = LE_status_severity(sts);
252    const char severity_char
253      = severity < sizeof(severity_chars) ? severity_chars[severity] : '?';
254    const char *severity_prefix
255      = severity < sizeof(severity_prefixes) ? severity_prefixes[severity] : "???";
256  
257    *buflen = snprintf(buf, bufsize, "%s:%s-%c-%s, %s",
258                       severity_prefix,
259                       unit->name, severity_char, reason->identity,
260                       reason->message);
261    return LE_STS_SUCCESS;
262  }
263  
264  LE_STATUS LE_print_status(const LE_messages_t *mdata,
265                            size_t message_vector_count,
266                            LE_message_vector_t *message_vector,
267                            _Bool (*action)(const char *message))
268  {
269    if (mdata == NULL)
270      return LE_STS_INVALID_PARAMETER;
271  
272    char *message = malloc(65536);
273    char *message_end = message + 65536;
274    if (message == NULL)
275      return LE_STS_ALLOC_FAILURE;
276  
277    for (size_t i = 0; i < message_vector_count; i++) {
278      char *message_ptr = message;
279      size_t bytes = 0;
280      if (i > 0)
281        *message_ptr++ = '-';
282      LE_get_status_text(mdata, message_vector[i].sts,
283                         message_ptr, &bytes, message_end - message_ptr);
284      message_ptr += bytes;    
285      for (size_t j = 0; j < message_vector[i].additional_text_count; j++) {
286        message_ptr = stpecpy(message_ptr, message_end, "  ");
287        message_ptr = stpecpy(message_ptr, message_end,
288                              message_vector[i].additional_text[j]);
289      }
290      if (message_ptr)
291        /* Truncation happened */
292        message_ptr = message_end - 1;
293  
294      if (action(message))
295        write(2, message, message_ptr - message);
296    }
297  
298    free(message);
299    return LE_STS_SUCCESS;
300  }