/ 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 }