root/src/libsir/src/sirfilecache.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. _sir_addfile
  2. _sir_updatefile
  3. _sir_remfile
  4. _sirfile_create
  5. _sirfile_open
  6. _sirfile_close
  7. _sirfile_write
  8. _sirfile_writeheader
  9. _sirfile_needsroll
  10. _sirfile_roll
  11. _sirfile_rollifneeded
  12. _sirfile_archive
  13. _sirfile_splitpath
  14. _sirfile_destroy
  15. _sirfile_validate
  16. _sirfile_update
  17. _sir_fcache_add
  18. _sir_fcache_update
  19. _sir_fcache_rem
  20. _sir_fcache_shift
  21. _sir_fcache_pred_path
  22. _sir_fcache_pred_id
  23. _sir_fcache_find
  24. _sir_fcache_destroy
  25. _sir_fcache_dispatch
  26. _sir_fflush

   1 /*
   2  * sirfilecache.c
   3  *
   4  * Version: 2.2.5
   5  *
   6  * -----------------------------------------------------------------------------
   7  *
   8  * SPDX-License-Identifier: MIT
   9  *
  10  * Copyright (c) 2018-2024 Ryan M. Lederman <lederman@gmail.com>
  11  * Copyright (c) 2018-2024 Jeffrey H. Johnson <trnsz@pobox.com>
  12  *
  13  * Permission is hereby granted, free of charge, to any person obtaining a copy of
  14  * this software and associated documentation files (the "Software"), to deal in
  15  * the Software without restriction, including without limitation the rights to
  16  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  17  * the Software, and to permit persons to whom the Software is furnished to do so,
  18  * subject to the following conditions:
  19  *
  20  * The above copyright notice and this permission notice shall be included in all
  21  * copies or substantial portions of the Software.
  22  *
  23  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  24  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  25  * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  26  * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  27  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  28  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29  *
  30  * -----------------------------------------------------------------------------
  31  */
  32 
  33 #include "sir/filecache.h"
  34 #include "sir/filesystem.h"
  35 #include "sir/internal.h"
  36 #include "sir/defaults.h"
  37 
  38 sirfileid _sir_addfile(const char* path, sir_levels levels, sir_options opts) {
     /* [previous][next][first][last][top][bottom][index][help] */
  39     (void)_sir_seterror(_SIR_E_NOERROR);
  40 
  41     if (!_sir_sanity())
  42         return 0U;
  43 
  44     _SIR_LOCK_SECTION(sirfcache, sfc, SIRMI_FILECACHE, 0U);
  45 
  46     _sir_defaultlevels(&levels, sir_file_def_lvls);
  47     _sir_defaultopts(&opts, sir_file_def_opts);
  48 
  49     sirfileid retval = _sir_fcache_add(sfc, path, levels, opts);
  50     _SIR_UNLOCK_SECTION(SIRMI_FILECACHE);
  51 
  52     return retval;
  53 }
  54 
  55 bool _sir_updatefile(sirfileid id, const sir_update_config_data* data) {
     /* [previous][next][first][last][top][bottom][index][help] */
  56     (void)_sir_seterror(_SIR_E_NOERROR);
  57 
  58     if (!_sir_sanity() || !_sir_validfileid(id) || !_sir_validupdatedata(data))
  59         return false;
  60 
  61     _SIR_LOCK_SECTION(const sirfcache, sfc, SIRMI_FILECACHE, false);
  62     bool retval = _sir_fcache_update(sfc, id, data);
  63     _SIR_UNLOCK_SECTION(SIRMI_FILECACHE);
  64 
  65     return retval;
  66 }
  67 
  68 bool _sir_remfile(sirfileid id) {
     /* [previous][next][first][last][top][bottom][index][help] */
  69     (void)_sir_seterror(_SIR_E_NOERROR);
  70 
  71     if (!_sir_sanity() || !_sir_validfileid(id))
  72         return false;
  73 
  74     _SIR_LOCK_SECTION(sirfcache, sfc, SIRMI_FILECACHE, false);
  75     bool retval = _sir_fcache_rem(sfc, id);
  76     _SIR_UNLOCK_SECTION(SIRMI_FILECACHE);
  77 
  78     return retval;
  79 }
  80 
  81 sirfile* _sirfile_create(const char* path, sir_levels levels, sir_options opts) {
     /* [previous][next][first][last][top][bottom][index][help] */
  82     if (!_sir_validstr(path) || !_sir_validlevels(levels) || !_sir_validopts(opts))
  83         return NULL;
  84 
  85     sirfile* sf = (sirfile*)calloc(1, sizeof(sirfile));
  86     if (!sf) {
  87         (void)_sir_handleerr(errno);
  88         return NULL;
  89     }
  90 
  91     sf->path = strndup(path, strnlen(path, SIR_MAXPATH));
  92     if (!sf->path) {
  93         (void)_sir_handleerr(errno);
  94         _sir_safefree(&sf);
  95         return NULL;
  96     }
  97 
  98     sf->levels = levels;
  99     sf->opts   = opts;
 100 
 101     if (!_sirfile_open(sf) || !_sirfile_validate(sf)) {
 102         _sirfile_destroy(&sf);
 103         return NULL;
 104     }
 105 
 106     return sf;
 107 }
 108 
 109 bool _sirfile_open(sirfile* sf) {
     /* [previous][next][first][last][top][bottom][index][help] */
 110     bool retval = _sir_validptr(sf) && _sir_validstr(sf->path);
 111 
 112     if (retval) {
 113         FILE* f  = NULL;
 114         retval = _sir_openfile(&f, sf->path, SIR_FOPENMODE, SIR_PATH_REL_TO_CWD);
 115         if (retval && NULL != f) {
 116             _sirfile_close(sf);
 117 
 118             sf->f  = f;
 119             sf->id = FNV32_1a((const uint8_t*)sf->path, strnlen(sf->path, SIR_MAXPATH));
 120         }
 121     }
 122 
 123     return retval;
 124 }
 125 
 126 void _sirfile_close(sirfile* sf) {
     /* [previous][next][first][last][top][bottom][index][help] */
 127     if (_sir_validptrnofail(sf))
 128         _sir_safefclose(&sf->f);
 129 }
 130 
 131 bool _sirfile_write(sirfile* sf, const char* output) {
     /* [previous][next][first][last][top][bottom][index][help] */
 132     bool retval = _sirfile_validate(sf) && _sir_validstr(output);
 133 
 134     if (retval) {
 135         if (sf->writes_since_size_chk++ > SIR_FILE_CHK_SIZE_WRITES) {
 136             sf->writes_since_size_chk = 0;
 137             _sirfile_rollifneeded(sf);
 138         }
 139 
 140         size_t writeLen = strnlen(output, SIR_MAXOUTPUT);
 141         size_t wrote    = fwrite(output, sizeof(char), writeLen, sf->f);
 142 
 143         SIR_ASSERT(wrote == writeLen);
 144 
 145         if (wrote < writeLen) {
 146             (void)_sir_handleerr(errno);
 147             clearerr(sf->f);
 148         }
 149 
 150         retval = wrote == writeLen;
 151     }
 152 
 153     return retval;
 154 }
 155 
 156 bool _sirfile_writeheader(sirfile* sf, const char* msg) {
     /* [previous][next][first][last][top][bottom][index][help] */
 157     bool retval = _sirfile_validate(sf) && _sir_validstr(msg);
 158 
 159     if (retval) {
 160         time_t now = -1;
 161         (void)time(&now);
 162 
 163         char timestamp[SIR_MAXTIME] = {0};
 164         bool fmttime = _sir_formattime(now, timestamp, SIR_FHTIMEFORMAT);
 165         if (!fmttime)
 166             return false;
 167 
 168         char header[SIR_MAXFHEADER] = {0};
 169         (void)snprintf(header, SIR_MAXFHEADER, SIR_FHFORMAT, msg, timestamp);
 170 
 171         retval = _sirfile_write(sf, header);
 172     }
 173 
 174     return retval;
 175 }
 176 
 177 bool _sirfile_needsroll(sirfile* sf) {
     /* [previous][next][first][last][top][bottom][index][help] */
 178     bool retval = _sirfile_validate(sf);
 179 
 180     if (retval) {
 181         struct stat st = {0};
 182         int getstat    = fstat(fileno(sf->f), &st);
 183 
 184         if (0 != getstat) { /* if fstat fails, try stat on the path. */
 185             getstat = stat(sf->path, &st);
 186             if (0 != getstat)
 187                 return _sir_handleerr(errno);
 188         }
 189 
 190         retval = st.st_size + BUFSIZ >= SIR_FROLLSIZE ||
 191             SIR_FROLLSIZE - (st.st_size + BUFSIZ) <= BUFSIZ;
 192     }
 193 
 194     return retval;
 195 }
 196 
 197 bool _sirfile_roll(sirfile* sf, char** newpath) {
     /* [previous][next][first][last][top][bottom][index][help] */
 198     if (!_sirfile_validate(sf) || !_sir_validptrptr(newpath))
 199         return false;
 200 
 201     bool retval = false;
 202     char* name = NULL;
 203     char* ext  = NULL;
 204 
 205     bool split = _sirfile_splitpath(sf, &name, &ext);
 206     SIR_ASSERT(split);
 207 
 208     if (split) {
 209         time_t now = -1;
 210 
 211         (void)time(&now);
 212         SIR_ASSERT(-1 != now);
 213 
 214         if (-1 != now) {
 215             char timestamp[SIR_MAXTIME] = {0};
 216             bool fmttime = _sir_formattime(now, timestamp, SIR_FNAMETIMEFORMAT);
 217             SIR_ASSERT(fmttime);
 218 
 219             if (fmttime) {
 220                 *newpath = (char*)calloc(SIR_MAXPATH, sizeof(char));
 221 
 222                 if (_sir_validptr(*newpath)) {
 223                     char seqbuf[7] = {0};
 224                     bool exists = false;
 225                     bool resolved = false;
 226                     uint16_t sequence = 0U;
 227 
 228                     do {
 229                         (void)snprintf(*newpath, SIR_MAXPATH, SIR_FNAMEFORMAT, name,
 230                             timestamp, (sequence > 0U ? seqbuf : ""),
 231                             _sir_validstrnofail(ext) ? ext : "");
 232 
 233                         /* if less than one second has elapsed since the last roll
 234                          * operation, then we'll overwrite the last rolled log file,
 235                          * and that = data loss. make sure the target path does not
 236                          * already exist. */
 237                         if (!_sir_pathexists(*newpath, &exists, SIR_PATH_REL_TO_CWD)) {
 238                             /* failed to determine if the file already exists; it is better
 239                              * to continue logging to the same file than to possibly overwrite
 240                              * another (if it failed this time, it will again, so there's no
 241                              * way to definitively choose a good new path). */
 242                             break;
 243                         } else if (exists) {
 244                             /* the file already exists; add a number to the file name
 245                              * until one that does not exist is found. */
 246                             _sir_selflog("path: '%s' already exists; incrementing sequence", *newpath); //-V576
 247                             sequence++;
 248                         } else {
 249                             _sir_selflog("found good path: '%s'", *newpath);
 250                             resolved = true;
 251                             break;
 252                         }
 253 
 254                         if (sequence > 0)
 255                             (void)snprintf(seqbuf, 7, SIR_FNAMESEQFORMAT, sequence);
 256 
 257                     } while (sequence <= 999U);
 258 
 259                     if (!resolved)
 260                         _sir_selflog("error: unable to determine suitable path for '%s';"
 261                                         " not rolling!", sf->path);
 262 
 263                     retval = resolved && _sirfile_archive(sf, *newpath);
 264                 }
 265             }
 266         }
 267     }
 268 
 269     _sir_safefree(&name);
 270     _sir_safefree(&ext);
 271 
 272     return retval;
 273 }
 274 
 275 void _sirfile_rollifneeded(sirfile* sf) {
     /* [previous][next][first][last][top][bottom][index][help] */
 276     if (_sirfile_validate(sf) && _sirfile_needsroll(sf)) {
 277         bool rolled   = false;
 278         char* newpath = NULL;
 279 
 280         _sir_selflog("file (path: '%s', id: %"PRIx32") reached ~%d bytes in size;"
 281             " rolling...", sf->path, sf->id, SIR_FROLLSIZE);
 282 
 283         _sir_fflush(sf->f);
 284 
 285         if (_sirfile_roll(sf, &newpath)) {
 286             char header[SIR_MAXFHEADER] = {0};
 287             (void)snprintf(header, SIR_MAXFHEADER, SIR_FHROLLED, newpath);
 288             rolled = _sirfile_writeheader(sf, header);
 289         }
 290 
 291         _sir_safefree(&newpath);
 292         if (!rolled) /* write anyway; don't want to lose data. */
 293             _sir_selflog("error: failed to roll file (path: '%s', id: %"PRIx32")!",
 294                 sf->path, sf->id);
 295     }
 296 }
 297 
 298 bool _sirfile_archive(sirfile* sf, const char* newpath) {
     /* [previous][next][first][last][top][bottom][index][help] */
 299     bool retval = _sirfile_validate(sf) && _sir_validstr(newpath);
 300 
 301     if (retval) {
 302 #if defined(__WIN__)
 303         /* apparently, need to close the old file first on windows. */
 304         _sirfile_close(sf);
 305 #endif
 306         if (0 != rename(sf->path, newpath)) {
 307             retval = _sir_handleerr(errno);
 308         } else {
 309             retval = _sirfile_open(sf);
 310             if (retval)
 311                 _sir_selflog("archived '%s' " SIR_R_ARROW " '%s'", sf->path, newpath);
 312         }
 313     }
 314 
 315     return retval;
 316 }
 317 
 318 bool _sirfile_splitpath(const sirfile* sf, char** name, char** ext) {
     /* [previous][next][first][last][top][bottom][index][help] */
 319     if (_sir_validptrptr(name))
 320         *name = NULL;
 321     if (_sir_validptrptr(ext))
 322         *ext = NULL;
 323 
 324     bool retval = _sirfile_validate(sf) && _sir_validptrptr(name) && _sir_validptrptr(ext);
 325 
 326     if (retval) {
 327         char* tmp = strndup(sf->path, strnlen(sf->path, SIR_MAXPATH));
 328         if (!tmp)
 329             return _sir_handleerr(errno);
 330 
 331         const char* fullstop = strrchr(tmp, '.');
 332         if (fullstop && fullstop != tmp) {
 333             uintptr_t namesize = fullstop - tmp;
 334             SIR_ASSERT(namesize < SIR_MAXPATH);
 335 
 336             tmp[namesize] = '\0';
 337             *name = (char*)calloc(namesize + 1, sizeof(char));
 338             if (!*name) {
 339                 _sir_safefree(&tmp);
 340                 return _sir_handleerr(errno);
 341             }
 342 
 343             (void)_sir_strncpy(*name, namesize + 1, tmp, namesize);
 344             *ext = strndup(sf->path + namesize, strnlen(sf->path + namesize, SIR_MAXPATH));
 345         } else {
 346             fullstop = NULL;
 347             *name = strndup(sf->path, strnlen(sf->path, SIR_MAXPATH));
 348         }
 349 
 350         _sir_safefree(&tmp);
 351         retval = _sir_validstrnofail(*name) && (!fullstop || _sir_validstrnofail(*ext));
 352     }
 353 
 354     return retval;
 355 }
 356 
 357 void _sirfile_destroy(sirfile** sf) {
     /* [previous][next][first][last][top][bottom][index][help] */
 358     if (sf && *sf) {
 359         _sirfile_close(*sf);
 360         _sir_safefree(&(*sf)->path);
 361         _sir_safefree(sf);
 362     }
 363 }
 364 
 365 bool _sirfile_validate(const sirfile* sf) {
     /* [previous][next][first][last][top][bottom][index][help] */
 366     return _sir_validptrnofail(sf) && _sir_validptrnofail(sf->f) &&
 367            _sir_validstrnofail(sf->path) && _sir_validfileid(sf->id);
 368 }
 369 
 370 bool _sirfile_update(sirfile* sf, const sir_update_config_data* data) {
     /* [previous][next][first][last][top][bottom][index][help] */
 371     bool retval = _sirfile_validate(sf);
 372 
 373     if (retval) {
 374         bool updated = false;
 375         if (_sir_bittest(data->fields, SIRU_LEVELS)) {
 376             if (sf->levels != *data->levels) {
 377                 _sir_selflog("updating file (id: %"PRIx32") levels from %04"PRIx16
 378                             " to %04"PRIx16, sf->id, sf->levels, *data->levels);
 379                 sf->levels = *data->levels;
 380             } else {
 381                 _sir_selflog("skipped superfluous update of file (id: %"PRIx32")"
 382                             " levels: %04"PRIx16, sf->id, sf->levels);
 383             }
 384 
 385             updated = true;
 386         }
 387 
 388         if (_sir_bittest(data->fields, SIRU_OPTIONS)) {
 389             if (sf->opts != *data->opts) {
 390                 _sir_selflog("updating file (id: %"PRIx32") options from %08"PRIx32
 391                             " to %08"PRIx32, sf->id, sf->opts, *data->opts);
 392                 sf->opts = *data->opts;
 393             } else {
 394                 _sir_selflog("skipped superfluous update of file (id: %"PRIx32")"
 395                             " options: %08"PRIx32, sf->id, sf->opts);
 396             }
 397 
 398             updated = true;
 399         }
 400 
 401         retval = updated;
 402     }
 403 
 404     return retval;
 405 }
 406 
 407 sirfileid _sir_fcache_add(sirfcache* sfc, const char* path, sir_levels levels,
     /* [previous][next][first][last][top][bottom][index][help] */
 408     sir_options opts) {
 409     if (!_sir_validptr(sfc) || !_sir_validstr(path) || !_sir_validlevels(levels) ||
 410         !_sir_validopts(opts))
 411         return 0U;
 412 
 413     if (sfc->count >= SIR_MAXFILES)
 414         return _sir_seterror(_SIR_E_NOROOM);
 415 
 416     const sirfile* existing = _sir_fcache_find(sfc, (const void*)path, _sir_fcache_pred_path);
 417     if (NULL != existing) {
 418         _sir_selflog("error: already have file (path: '%s', id: %"PRIx32")",
 419             path, existing->id);
 420         return _sir_seterror(_SIR_E_DUPITEM);
 421     }
 422 
 423     sirfile* sf = _sirfile_create(path, levels, opts);
 424     if (_sirfile_validate(sf)) {
 425         _sir_selflog("adding file (path: '%s', id: %"PRIx32"); count = %zu", //-V522
 426             sf->path, sf->id, sfc->count + 1);
 427 
 428         sfc->files[sfc->count++] = sf;
 429 
 430         if (!_sir_bittest(sf->opts, SIRO_NOHDR) && !_sirfile_writeheader(sf, SIR_FHBEGIN))
 431             _sir_selflog("warning: failed to write file header (path: '%s', id: %"PRIx32")",
 432                 sf->path, sf->id);
 433 
 434         return sf->id;
 435     }
 436 
 437     _sir_safefree(&sf);
 438 
 439     return 0U;
 440 }
 441 
 442 bool _sir_fcache_update(const sirfcache* sfc, sirfileid id, const sir_update_config_data* data) {
     /* [previous][next][first][last][top][bottom][index][help] */
 443     bool retval = _sir_validptr(sfc) && _sir_validfileid(id) && _sir_validupdatedata(data);
 444 
 445     if (retval) {
 446         sirfile* found = _sir_fcache_find(sfc, (const void*)&id, _sir_fcache_pred_id);
 447         retval = found ? _sirfile_update(found, data) : _sir_seterror(_SIR_E_NOITEM);
 448     }
 449 
 450     return retval;
 451 }
 452 
 453 bool _sir_fcache_rem(sirfcache* sfc, sirfileid id) {
     /* [previous][next][first][last][top][bottom][index][help] */
 454     bool retval = _sir_validptr(sfc) && _sir_validfileid(id);
 455 
 456     if (retval) {
 457         bool found = false;
 458         for (size_t n = 0; n < sfc->count; n++) {
 459             SIR_ASSERT(_sirfile_validate(sfc->files[n]));
 460 
 461             if (sfc->files[n]->id == id) {
 462                 _sir_selflog("removing file (path: '%s', id: %"PRIx32"); count = %zu",
 463                     sfc->files[n]->path, sfc->files[n]->id, sfc->count - 1);
 464 
 465                 _sirfile_destroy(&sfc->files[n]);
 466                 _sir_fcache_shift(sfc, n);
 467 
 468                 sfc->count--;
 469                 found = true;
 470                 break;
 471             }
 472         }
 473 
 474         if (!found)
 475             retval = _sir_seterror(_SIR_E_NOITEM);
 476     }
 477 
 478     return retval;
 479 }
 480 
 481 void _sir_fcache_shift(sirfcache* sfc, size_t idx) {
     /* [previous][next][first][last][top][bottom][index][help] */
 482     if (_sir_validptr(sfc) && sfc->count <= SIR_MAXFILES) {
 483         for (size_t n = idx; n < sfc->count - 1; n++) {
 484             sfc->files[n] = sfc->files[n + 1];
 485             sfc->files[n + 1] = NULL;
 486         }
 487     }
 488 }
 489 
 490 bool _sir_fcache_pred_path(const void* match, const sirfile* iter) {
     /* [previous][next][first][last][top][bottom][index][help] */
 491     const char* path = (const char*)match;
 492     bool equal       = false;
 493 
 494     _sir_selflog("comparing '%s' == '%s'", path, iter->path);
 495 
 496 #if !defined(__WIN__)
 497     /* if we're able to stat both files then we can use that information for the
 498      * comparison. otherwise, fall back on comparing the canonical path strings
 499      * returned by realpath. */
 500     char resolved1[SIR_MAXPATH] = {0};
 501     char resolved2[SIR_MAXPATH] = {0};
 502     struct stat st1             = {0};
 503     struct stat st2             = {0};
 504 
 505     const char* real1 = realpath(path, resolved1);
 506     const char* real2 = realpath(iter->path, resolved2);
 507 
 508     SIR_UNUSED(real1);
 509     SIR_UNUSED(real2);
 510 
 511     if ((!stat(resolved1, &st1) && !stat(resolved2, &st2)) ||
 512         (!stat(path, &st1) && !stat(iter->path, &st2))) {
 513         equal = st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino;
 514         _sir_selflog("returning %d for '%s' == '%s'", equal, path, iter->path);
 515     } else {
 516         _sir_selflog("falling back to canonical path string compare");
 517         equal = 0 == strncmp(resolved1, resolved2, SIR_MAXPATH);
 518         _sir_selflog("returning %d for '%s' == '%s'", equal, resolved1, resolved2);
 519     }
 520 
 521     return equal;
 522 #else /* __WIN__ */
 523     /* open both files (only if they already exist) and compare their
 524      * filesystem info. failing that, fall back on conversion to canonical path
 525      * and string comparison. */
 526     DWORD sh_flags   = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
 527     DWORD open_type  = OPEN_EXISTING;
 528     DWORD attr_flags = FILE_ATTRIBUTE_NORMAL;
 529     BY_HANDLE_FILE_INFORMATION fi1 = {0};
 530     BY_HANDLE_FILE_INFORMATION fi2 = {0};
 531 
 532     HANDLE h1 = CreateFileA(path, 0, sh_flags, NULL, open_type, attr_flags, NULL);
 533     HANDLE h2 = CreateFileA(iter->path,0, sh_flags, NULL, open_type, attr_flags, NULL);
 534 
 535     if (INVALID_HANDLE_VALUE != h1 && INVALID_HANDLE_VALUE != h2 &&
 536         GetFileInformationByHandle(h1, &fi1) && GetFileInformationByHandle(h2, &fi2)) {
 537         equal = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber &&
 538                 fi1.nFileIndexLow        == fi2.nFileIndexLow        &&
 539                 fi1.nFileIndexHigh       == fi2.nFileIndexHigh;
 540         _sir_selflog("returning %d for '%s' == '%s'", equal, path, iter->path);
 541     } else {
 542         _sir_selflog("falling back to canonical path string compare");
 543         char resolved1[SIR_MAXPATH] = {0}, resolved2[SIR_MAXPATH] = {0};
 544         if (GetFullPathNameA(path, SIR_MAXPATH, resolved1, NULL) &&
 545             GetFullPathNameA(iter->path, SIR_MAXPATH, resolved2, NULL))
 546             equal = 0 == StrCmpIA(resolved1, resolved2);
 547         _sir_selflog("returning %d for '%s' == '%s'", equal, resolved1, resolved2);
 548     }
 549 
 550     if (INVALID_HANDLE_VALUE != h1)
 551         CloseHandle(h1);
 552 
 553     if (INVALID_HANDLE_VALUE != h2)
 554         CloseHandle(h2);
 555 
 556     return equal;
 557 #endif
 558 }
 559 
 560 bool _sir_fcache_pred_id(const void* match, const sirfile* iter) {
     /* [previous][next][first][last][top][bottom][index][help] */
 561     const sirfileid* id = (const sirfileid*)match;
 562     return iter->id == *id;
 563 }
 564 
 565 sirfile* _sir_fcache_find(const sirfcache* sfc, const void* match, sir_fcache_pred pred) {
     /* [previous][next][first][last][top][bottom][index][help] */
 566     bool valid = _sir_validptr(sfc) && _sir_validptr(match) && _sir_validfnptr(pred);
 567 
 568     if (valid) {
 569         for (size_t n = 0; n < sfc->count; n++) {
 570             if (pred(match, sfc->files[n]))
 571                 return sfc->files[n];
 572         }
 573     }
 574 
 575     return NULL;
 576 }
 577 
 578 bool _sir_fcache_destroy(sirfcache* sfc) {
     /* [previous][next][first][last][top][bottom][index][help] */
 579     bool retval = _sir_validptr(sfc);
 580 
 581     if (retval) {
 582         while (sfc->count > 0) {
 583             size_t idx = sfc->count - 1;
 584             SIR_ASSERT(_sirfile_validate(sfc->files[idx]));
 585             _sirfile_destroy(&sfc->files[idx]);
 586             sfc->files[idx] = NULL;
 587             sfc->count--;
 588         }
 589 
 590         (void)_sir_explicit_memset(sfc, 0, sizeof(sirfcache));
 591     }
 592 
 593     return retval;
 594 }
 595 
 596 bool _sir_fcache_dispatch(const sirfcache* sfc, sir_level level, sirbuf* buf,
     /* [previous][next][first][last][top][bottom][index][help] */
 597     size_t* dispatched, size_t* wanted) {
 598     bool retval = _sir_validptr(sfc) && _sir_validlevel(level) &&
 599                   _sir_validptr(buf) && _sir_validptr(dispatched) &&
 600                   _sir_validptr(wanted);
 601 
 602     if (retval) {
 603         const char* wrote    = NULL;
 604         sir_options lastopts = 0U;
 605 
 606         *dispatched = 0;
 607         *wanted     = 0;
 608 
 609         for (size_t n = 0; n < sfc->count; n++) {
 610             SIR_ASSERT(_sirfile_validate(sfc->files[n]));
 611 
 612             if (!_sir_bittest(sfc->files[n]->levels, level)) {
 613                 _sir_selflog("level %04"PRIx16" not set in level mask (%04"PRIx16
 614                             ") for file (path: '%s', id: %"PRIx32"); skipping",
 615                             level, sfc->files[n]->levels, sfc->files[n]->path,
 616                             sfc->files[n]->id);
 617                 continue;
 618             }
 619 
 620             (*wanted)++;
 621 
 622             if (!wrote || sfc->files[n]->opts != lastopts) {
 623                 wrote = _sir_format(false, sfc->files[n]->opts, buf);
 624                 SIR_ASSERT(wrote);
 625                 lastopts = sfc->files[n]->opts;
 626             }
 627 
 628             if (wrote && _sirfile_write(sfc->files[n], wrote)) {
 629                 (*dispatched)++;
 630             } else {
 631                 _sir_selflog("error: write to file (path: '%s', id: %"PRIx32") failed!",
 632                     sfc->files[n]->path, sfc->files[n]->id);
 633             }
 634         }
 635 
 636         retval = (*dispatched == *wanted);
 637     }
 638 
 639     return retval;
 640 }
 641 
 642 void _sir_fflush(FILE* f) {
     /* [previous][next][first][last][top][bottom][index][help] */
 643     if (_sir_validptr(f) && 0 != fflush(f))
 644         (void)_sir_handleerr(errno);
 645 }

/* [previous][next][first][last][top][bottom][index][help] */