1 1.1 christos /* Id: dba.c,v 1.10 2017/02/17 14:43:54 schwarze Exp */ 2 1.1 christos /* 3 1.1 christos * Copyright (c) 2016, 2017 Ingo Schwarze <schwarze (at) openbsd.org> 4 1.1 christos * 5 1.1 christos * Permission to use, copy, modify, and distribute this software for any 6 1.1 christos * purpose with or without fee is hereby granted, provided that the above 7 1.1 christos * copyright notice and this permission notice appear in all copies. 8 1.1 christos * 9 1.1 christos * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 1.1 christos * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 1.1 christos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 1.1 christos * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 1.1 christos * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 1.1 christos * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 1.1 christos * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 1.1 christos * 17 1.1 christos * Allocation-based version of the mandoc database, for read-write access. 18 1.1 christos * The interface is defined in "dba.h". 19 1.1 christos */ 20 1.1 christos #include "config.h" 21 1.1 christos 22 1.1 christos #include <sys/types.h> 23 1.1 christos #if HAVE_ENDIAN 24 1.1 christos #include <endian.h> 25 1.1 christos #elif HAVE_SYS_ENDIAN 26 1.1 christos #include <sys/endian.h> 27 1.1 christos #elif HAVE_NTOHL 28 1.1 christos #include <arpa/inet.h> 29 1.1 christos #endif 30 1.1 christos #include <errno.h> 31 1.1 christos #include <stddef.h> 32 1.1 christos #include <stdint.h> 33 1.1 christos #include <stdlib.h> 34 1.1 christos #include <string.h> 35 1.1 christos #include <unistd.h> 36 1.1 christos 37 1.1 christos #include "mandoc_aux.h" 38 1.1 christos #include "mandoc_ohash.h" 39 1.1 christos #include "mansearch.h" 40 1.1 christos #include "dba_write.h" 41 1.1 christos #include "dba_array.h" 42 1.1 christos #include "dba.h" 43 1.1 christos 44 1.1 christos struct macro_entry { 45 1.1 christos struct dba_array *pages; 46 1.1 christos char value[]; 47 1.1 christos }; 48 1.1 christos 49 1.1 christos static void *prepend(const char *, char); 50 1.1 christos static void dba_pages_write(struct dba_array *); 51 1.1 christos static int compare_names(const void *, const void *); 52 1.1 christos static int compare_strings(const void *, const void *); 53 1.1 christos 54 1.1 christos static struct macro_entry 55 1.1 christos *get_macro_entry(struct ohash *, const char *, int32_t); 56 1.1 christos static void dba_macros_write(struct dba_array *); 57 1.1 christos static void dba_macro_write(struct ohash *); 58 1.1 christos static int compare_entries(const void *, const void *); 59 1.1 christos 60 1.1 christos 61 1.1 christos /*** top-level functions **********************************************/ 62 1.1 christos 63 1.1 christos struct dba * 64 1.1 christos dba_new(int32_t npages) 65 1.1 christos { 66 1.1 christos struct dba *dba; 67 1.1 christos struct ohash *macro; 68 1.1 christos int32_t im; 69 1.1 christos 70 1.1 christos dba = mandoc_malloc(sizeof(*dba)); 71 1.1 christos dba->pages = dba_array_new(npages, DBA_GROW); 72 1.1 christos dba->macros = dba_array_new(MACRO_MAX, 0); 73 1.1 christos for (im = 0; im < MACRO_MAX; im++) { 74 1.1 christos macro = mandoc_malloc(sizeof(*macro)); 75 1.1 christos mandoc_ohash_init(macro, 4, 76 1.1 christos offsetof(struct macro_entry, value)); 77 1.1 christos dba_array_set(dba->macros, im, macro); 78 1.1 christos } 79 1.1 christos return dba; 80 1.1 christos } 81 1.1 christos 82 1.1 christos void 83 1.1 christos dba_free(struct dba *dba) 84 1.1 christos { 85 1.1 christos struct dba_array *page; 86 1.1 christos struct ohash *macro; 87 1.1 christos struct macro_entry *entry; 88 1.1 christos unsigned int slot; 89 1.1 christos 90 1.1 christos dba_array_FOREACH(dba->macros, macro) { 91 1.1 christos for (entry = ohash_first(macro, &slot); entry != NULL; 92 1.1 christos entry = ohash_next(macro, &slot)) { 93 1.1 christos dba_array_free(entry->pages); 94 1.1 christos free(entry); 95 1.1 christos } 96 1.1 christos ohash_delete(macro); 97 1.1 christos free(macro); 98 1.1 christos } 99 1.1 christos dba_array_free(dba->macros); 100 1.1 christos 101 1.1 christos dba_array_undel(dba->pages); 102 1.1 christos dba_array_FOREACH(dba->pages, page) { 103 1.1 christos dba_array_free(dba_array_get(page, DBP_NAME)); 104 1.1 christos dba_array_free(dba_array_get(page, DBP_SECT)); 105 1.1 christos dba_array_free(dba_array_get(page, DBP_ARCH)); 106 1.1 christos free(dba_array_get(page, DBP_DESC)); 107 1.1 christos dba_array_free(dba_array_get(page, DBP_FILE)); 108 1.1 christos dba_array_free(page); 109 1.1 christos } 110 1.1 christos dba_array_free(dba->pages); 111 1.1 christos 112 1.1 christos free(dba); 113 1.1 christos } 114 1.1 christos 115 1.1 christos /* 116 1.1 christos * Write the complete mandoc database to disk; the format is: 117 1.1 christos * - One integer each for magic and version. 118 1.1 christos * - One pointer each to the macros table and to the final magic. 119 1.1 christos * - The pages table. 120 1.1 christos * - The macros table. 121 1.1 christos * - And at the very end, the magic integer again. 122 1.1 christos */ 123 1.1 christos int 124 1.1 christos dba_write(const char *fname, struct dba *dba) 125 1.1 christos { 126 1.1 christos int save_errno; 127 1.1 christos int32_t pos_end, pos_macros, pos_macros_ptr; 128 1.1 christos 129 1.1 christos if (dba_open(fname) == -1) 130 1.1 christos return -1; 131 1.1 christos dba_int_write(MANDOCDB_MAGIC); 132 1.1 christos dba_int_write(MANDOCDB_VERSION); 133 1.1 christos pos_macros_ptr = dba_skip(1, 2); 134 1.1 christos dba_pages_write(dba->pages); 135 1.1 christos pos_macros = dba_tell(); 136 1.1 christos dba_macros_write(dba->macros); 137 1.1 christos pos_end = dba_tell(); 138 1.1 christos dba_int_write(MANDOCDB_MAGIC); 139 1.1 christos dba_seek(pos_macros_ptr); 140 1.1 christos dba_int_write(pos_macros); 141 1.1 christos dba_int_write(pos_end); 142 1.1 christos if (dba_close() == -1) { 143 1.1 christos save_errno = errno; 144 1.1 christos unlink(fname); 145 1.1 christos errno = save_errno; 146 1.1 christos return -1; 147 1.1 christos } 148 1.1 christos return 0; 149 1.1 christos } 150 1.1 christos 151 1.1 christos 152 1.1 christos /*** functions for handling pages *************************************/ 153 1.1 christos 154 1.1 christos /* 155 1.1 christos * Create a new page and append it to the pages table. 156 1.1 christos */ 157 1.1 christos struct dba_array * 158 1.1 christos dba_page_new(struct dba_array *pages, const char *arch, 159 1.1 christos const char *desc, const char *file, enum form form) 160 1.1 christos { 161 1.1 christos struct dba_array *page, *entry; 162 1.1 christos 163 1.1 christos page = dba_array_new(DBP_MAX, 0); 164 1.1 christos entry = dba_array_new(1, DBA_STR | DBA_GROW); 165 1.1 christos dba_array_add(page, entry); 166 1.1 christos entry = dba_array_new(1, DBA_STR | DBA_GROW); 167 1.1 christos dba_array_add(page, entry); 168 1.1 christos if (arch != NULL && *arch != '\0') { 169 1.1 christos entry = dba_array_new(1, DBA_STR | DBA_GROW); 170 1.2 christos dba_array_add(entry, __UNCONST(arch)); 171 1.1 christos } else 172 1.1 christos entry = NULL; 173 1.1 christos dba_array_add(page, entry); 174 1.1 christos dba_array_add(page, mandoc_strdup(desc)); 175 1.1 christos entry = dba_array_new(1, DBA_STR | DBA_GROW); 176 1.1 christos dba_array_add(entry, prepend(file, form)); 177 1.1 christos dba_array_add(page, entry); 178 1.1 christos dba_array_add(pages, page); 179 1.1 christos return page; 180 1.1 christos } 181 1.1 christos 182 1.1 christos /* 183 1.1 christos * Add a section, architecture, or file name to an existing page. 184 1.1 christos * Passing the NULL pointer for the architecture makes the page MI. 185 1.1 christos * In that case, any earlier or later architectures are ignored. 186 1.1 christos */ 187 1.1 christos void 188 1.1 christos dba_page_add(struct dba_array *page, int32_t ie, const char *str) 189 1.1 christos { 190 1.1 christos struct dba_array *entries; 191 1.1 christos char *entry; 192 1.1 christos 193 1.1 christos entries = dba_array_get(page, ie); 194 1.1 christos if (ie == DBP_ARCH) { 195 1.1 christos if (entries == NULL) 196 1.1 christos return; 197 1.1 christos if (str == NULL || *str == '\0') { 198 1.1 christos dba_array_free(entries); 199 1.1 christos dba_array_set(page, DBP_ARCH, NULL); 200 1.1 christos return; 201 1.1 christos } 202 1.1 christos } 203 1.1 christos if (*str == '\0') 204 1.1 christos return; 205 1.1 christos dba_array_FOREACH(entries, entry) { 206 1.1 christos if (ie == DBP_FILE && *entry < ' ') 207 1.1 christos entry++; 208 1.1 christos if (strcmp(entry, str) == 0) 209 1.1 christos return; 210 1.1 christos } 211 1.2 christos dba_array_add(entries, __UNCONST(str)); 212 1.1 christos } 213 1.1 christos 214 1.1 christos /* 215 1.1 christos * Add an additional name to an existing page. 216 1.1 christos */ 217 1.1 christos void 218 1.1 christos dba_page_alias(struct dba_array *page, const char *name, uint64_t mask) 219 1.1 christos { 220 1.1 christos struct dba_array *entries; 221 1.1 christos char *entry; 222 1.1 christos char maskbyte; 223 1.1 christos 224 1.1 christos if (*name == '\0') 225 1.1 christos return; 226 1.1 christos maskbyte = mask & NAME_MASK; 227 1.1 christos entries = dba_array_get(page, DBP_NAME); 228 1.1 christos dba_array_FOREACH(entries, entry) { 229 1.1 christos if (strcmp(entry + 1, name) == 0) { 230 1.1 christos *entry |= maskbyte; 231 1.1 christos return; 232 1.1 christos } 233 1.1 christos } 234 1.1 christos dba_array_add(entries, prepend(name, maskbyte)); 235 1.1 christos } 236 1.1 christos 237 1.1 christos /* 238 1.1 christos * Return a pointer to a temporary copy of instr with inbyte prepended. 239 1.1 christos */ 240 1.1 christos static void * 241 1.1 christos prepend(const char *instr, char inbyte) 242 1.1 christos { 243 1.1 christos static char *outstr = NULL; 244 1.1 christos static size_t outlen = 0; 245 1.1 christos size_t newlen; 246 1.1 christos 247 1.1 christos newlen = strlen(instr) + 1; 248 1.1 christos if (newlen > outlen) { 249 1.1 christos outstr = mandoc_realloc(outstr, newlen + 1); 250 1.1 christos outlen = newlen; 251 1.1 christos } 252 1.1 christos *outstr = inbyte; 253 1.1 christos memcpy(outstr + 1, instr, newlen); 254 1.1 christos return outstr; 255 1.1 christos } 256 1.1 christos 257 1.1 christos /* 258 1.1 christos * Write the pages table to disk; the format is: 259 1.1 christos * - One integer containing the number of pages. 260 1.1 christos * - For each page, five pointers to the names, sections, 261 1.1 christos * architectures, description, and file names of the page. 262 1.1 christos * MI pages write 0 instead of the architecture pointer. 263 1.1 christos * - One list each for names, sections, architectures, descriptions and 264 1.1 christos * file names. The description for each page ends with a NUL byte. 265 1.1 christos * For all the other lists, each string ends with a NUL byte, 266 1.1 christos * and the last string for a page ends with two NUL bytes. 267 1.1 christos * - To assure alignment of following integers, 268 1.1 christos * the end is padded with NUL bytes up to a multiple of four bytes. 269 1.1 christos */ 270 1.1 christos static void 271 1.1 christos dba_pages_write(struct dba_array *pages) 272 1.1 christos { 273 1.1 christos struct dba_array *page, *entry; 274 1.1 christos int32_t pos_pages, pos_end; 275 1.1 christos 276 1.1 christos pos_pages = dba_array_writelen(pages, 5); 277 1.1 christos dba_array_FOREACH(pages, page) { 278 1.1 christos dba_array_setpos(page, DBP_NAME, dba_tell()); 279 1.1 christos entry = dba_array_get(page, DBP_NAME); 280 1.1 christos dba_array_sort(entry, compare_names); 281 1.1 christos dba_array_writelst(entry); 282 1.1 christos } 283 1.1 christos dba_array_FOREACH(pages, page) { 284 1.1 christos dba_array_setpos(page, DBP_SECT, dba_tell()); 285 1.1 christos entry = dba_array_get(page, DBP_SECT); 286 1.1 christos dba_array_sort(entry, compare_strings); 287 1.1 christos dba_array_writelst(entry); 288 1.1 christos } 289 1.1 christos dba_array_FOREACH(pages, page) { 290 1.1 christos if ((entry = dba_array_get(page, DBP_ARCH)) != NULL) { 291 1.1 christos dba_array_setpos(page, DBP_ARCH, dba_tell()); 292 1.1 christos dba_array_sort(entry, compare_strings); 293 1.1 christos dba_array_writelst(entry); 294 1.1 christos } else 295 1.1 christos dba_array_setpos(page, DBP_ARCH, 0); 296 1.1 christos } 297 1.1 christos dba_array_FOREACH(pages, page) { 298 1.1 christos dba_array_setpos(page, DBP_DESC, dba_tell()); 299 1.1 christos dba_str_write(dba_array_get(page, DBP_DESC)); 300 1.1 christos } 301 1.1 christos dba_array_FOREACH(pages, page) { 302 1.1 christos dba_array_setpos(page, DBP_FILE, dba_tell()); 303 1.1 christos dba_array_writelst(dba_array_get(page, DBP_FILE)); 304 1.1 christos } 305 1.1 christos pos_end = dba_align(); 306 1.1 christos dba_seek(pos_pages); 307 1.1 christos dba_array_FOREACH(pages, page) 308 1.1 christos dba_array_writepos(page); 309 1.1 christos dba_seek(pos_end); 310 1.1 christos } 311 1.1 christos 312 1.1 christos static int 313 1.1 christos compare_names(const void *vp1, const void *vp2) 314 1.1 christos { 315 1.1 christos const char *cp1, *cp2; 316 1.1 christos int diff; 317 1.1 christos 318 1.1 christos cp1 = *(const char * const *)vp1; 319 1.1 christos cp2 = *(const char * const *)vp2; 320 1.1 christos return (diff = *cp2 - *cp1) ? diff : 321 1.1 christos strcasecmp(cp1 + 1, cp2 + 1); 322 1.1 christos } 323 1.1 christos 324 1.1 christos static int 325 1.1 christos compare_strings(const void *vp1, const void *vp2) 326 1.1 christos { 327 1.1 christos const char *cp1, *cp2; 328 1.1 christos 329 1.1 christos cp1 = *(const char * const *)vp1; 330 1.1 christos cp2 = *(const char * const *)vp2; 331 1.1 christos return strcmp(cp1, cp2); 332 1.1 christos } 333 1.1 christos 334 1.1 christos /*** functions for handling macros ************************************/ 335 1.1 christos 336 1.1 christos /* 337 1.1 christos * In the hash table for a single macro, look up an entry by 338 1.1 christos * the macro value or add an empty one if it doesn't exist yet. 339 1.1 christos */ 340 1.1 christos static struct macro_entry * 341 1.1 christos get_macro_entry(struct ohash *macro, const char *value, int32_t np) 342 1.1 christos { 343 1.1 christos struct macro_entry *entry; 344 1.1 christos size_t len; 345 1.1 christos unsigned int slot; 346 1.1 christos 347 1.1 christos slot = ohash_qlookup(macro, value); 348 1.1 christos if ((entry = ohash_find(macro, slot)) == NULL) { 349 1.1 christos len = strlen(value) + 1; 350 1.1 christos entry = mandoc_malloc(sizeof(*entry) + len); 351 1.1 christos memcpy(&entry->value, value, len); 352 1.1 christos entry->pages = dba_array_new(np, DBA_GROW); 353 1.1 christos ohash_insert(macro, slot, entry); 354 1.1 christos } 355 1.1 christos return entry; 356 1.1 christos } 357 1.1 christos 358 1.1 christos /* 359 1.1 christos * In addition to get_macro_entry(), add multiple page references, 360 1.1 christos * converting them from the on-disk format (byte offsets in the file) 361 1.1 christos * to page pointers in memory. 362 1.1 christos */ 363 1.1 christos void 364 1.1 christos dba_macro_new(struct dba *dba, int32_t im, const char *value, 365 1.1 christos const int32_t *pp) 366 1.1 christos { 367 1.1 christos struct macro_entry *entry; 368 1.1 christos const int32_t *ip; 369 1.1 christos int32_t np; 370 1.1 christos 371 1.1 christos np = 0; 372 1.1 christos for (ip = pp; *ip; ip++) 373 1.1 christos np++; 374 1.1 christos 375 1.1 christos entry = get_macro_entry(dba_array_get(dba->macros, im), value, np); 376 1.1 christos for (ip = pp; *ip; ip++) 377 1.1 christos dba_array_add(entry->pages, dba_array_get(dba->pages, 378 1.1 christos be32toh(*ip) / 5 / sizeof(*ip) - 1)); 379 1.1 christos } 380 1.1 christos 381 1.1 christos /* 382 1.1 christos * In addition to get_macro_entry(), add one page reference, 383 1.1 christos * directly taking the in-memory page pointer as an argument. 384 1.1 christos */ 385 1.1 christos void 386 1.1 christos dba_macro_add(struct dba_array *macros, int32_t im, const char *value, 387 1.1 christos struct dba_array *page) 388 1.1 christos { 389 1.1 christos struct macro_entry *entry; 390 1.1 christos 391 1.1 christos if (*value == '\0') 392 1.1 christos return; 393 1.1 christos entry = get_macro_entry(dba_array_get(macros, im), value, 1); 394 1.1 christos dba_array_add(entry->pages, page); 395 1.1 christos } 396 1.1 christos 397 1.1 christos /* 398 1.1 christos * Write the macros table to disk; the format is: 399 1.1 christos * - The number of macro tables (actually, MACRO_MAX). 400 1.1 christos * - That number of pointers to the individual macro tables. 401 1.1 christos * - The individual macro tables. 402 1.1 christos */ 403 1.1 christos static void 404 1.1 christos dba_macros_write(struct dba_array *macros) 405 1.1 christos { 406 1.1 christos struct ohash *macro; 407 1.1 christos int32_t im, pos_macros, pos_end; 408 1.1 christos 409 1.1 christos pos_macros = dba_array_writelen(macros, 1); 410 1.1 christos im = 0; 411 1.1 christos dba_array_FOREACH(macros, macro) { 412 1.1 christos dba_array_setpos(macros, im++, dba_tell()); 413 1.1 christos dba_macro_write(macro); 414 1.1 christos } 415 1.1 christos pos_end = dba_tell(); 416 1.1 christos dba_seek(pos_macros); 417 1.1 christos dba_array_writepos(macros); 418 1.1 christos dba_seek(pos_end); 419 1.1 christos } 420 1.1 christos 421 1.1 christos /* 422 1.1 christos * Write one individual macro table to disk; the format is: 423 1.1 christos * - The number of entries in the table. 424 1.1 christos * - For each entry, two pointers, the first one to the value 425 1.1 christos * and the second one to the list of pages. 426 1.1 christos * - A list of values, each ending in a NUL byte. 427 1.1 christos * - To assure alignment of following integers, 428 1.1 christos * padding with NUL bytes up to a multiple of four bytes. 429 1.1 christos * - A list of pointers to pages, each list ending in a 0 integer. 430 1.1 christos */ 431 1.1 christos static void 432 1.1 christos dba_macro_write(struct ohash *macro) 433 1.1 christos { 434 1.1 christos struct macro_entry **entries, *entry; 435 1.1 christos struct dba_array *page; 436 1.1 christos int32_t *kpos, *dpos; 437 1.1 christos unsigned int ie, ne, slot; 438 1.1 christos int use; 439 1.1 christos int32_t addr, pos_macro, pos_end; 440 1.1 christos 441 1.1 christos /* Temporary storage for filtering and sorting. */ 442 1.1 christos 443 1.1 christos ne = ohash_entries(macro); 444 1.1 christos entries = mandoc_reallocarray(NULL, ne, sizeof(*entries)); 445 1.1 christos kpos = mandoc_reallocarray(NULL, ne, sizeof(*kpos)); 446 1.1 christos dpos = mandoc_reallocarray(NULL, ne, sizeof(*dpos)); 447 1.1 christos 448 1.1 christos /* Build a list of non-empty entries and sort it. */ 449 1.1 christos 450 1.1 christos ne = 0; 451 1.1 christos for (entry = ohash_first(macro, &slot); entry != NULL; 452 1.1 christos entry = ohash_next(macro, &slot)) { 453 1.1 christos use = 0; 454 1.1 christos dba_array_FOREACH(entry->pages, page) 455 1.1 christos if (dba_array_getpos(page)) 456 1.1 christos use = 1; 457 1.1 christos if (use) 458 1.1 christos entries[ne++] = entry; 459 1.1 christos } 460 1.1 christos qsort(entries, ne, sizeof(*entries), compare_entries); 461 1.1 christos 462 1.1 christos /* Number of entries, and space for the pointer pairs. */ 463 1.1 christos 464 1.1 christos dba_int_write(ne); 465 1.1 christos pos_macro = dba_skip(2, ne); 466 1.1 christos 467 1.1 christos /* String table. */ 468 1.1 christos 469 1.1 christos for (ie = 0; ie < ne; ie++) { 470 1.1 christos kpos[ie] = dba_tell(); 471 1.1 christos dba_str_write(entries[ie]->value); 472 1.1 christos } 473 1.1 christos dba_align(); 474 1.1 christos 475 1.1 christos /* Pages table. */ 476 1.1 christos 477 1.1 christos for (ie = 0; ie < ne; ie++) { 478 1.1 christos dpos[ie] = dba_tell(); 479 1.1 christos dba_array_FOREACH(entries[ie]->pages, page) 480 1.1 christos if ((addr = dba_array_getpos(page))) 481 1.1 christos dba_int_write(addr); 482 1.1 christos dba_int_write(0); 483 1.1 christos } 484 1.1 christos pos_end = dba_tell(); 485 1.1 christos 486 1.1 christos /* Fill in the pointer pairs. */ 487 1.1 christos 488 1.1 christos dba_seek(pos_macro); 489 1.1 christos for (ie = 0; ie < ne; ie++) { 490 1.1 christos dba_int_write(kpos[ie]); 491 1.1 christos dba_int_write(dpos[ie]); 492 1.1 christos } 493 1.1 christos dba_seek(pos_end); 494 1.1 christos 495 1.1 christos free(entries); 496 1.1 christos free(kpos); 497 1.1 christos free(dpos); 498 1.1 christos } 499 1.1 christos 500 1.1 christos static int 501 1.1 christos compare_entries(const void *vp1, const void *vp2) 502 1.1 christos { 503 1.1 christos const struct macro_entry *ep1, *ep2; 504 1.1 christos 505 1.1 christos ep1 = *(const struct macro_entry * const *)vp1; 506 1.1 christos ep2 = *(const struct macro_entry * const *)vp2; 507 1.1 christos return strcmp(ep1->value, ep2->value); 508 1.1 christos } 509