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

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