A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 764 lines 23 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2014 by Michael Sevakis 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 2 15 * of the License, or (at your option) any later version. 16 * 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 * KIND, either express or implied. 19 * 20 ****************************************************************************/ 21#include "config.h" 22#include <errno.h> 23#include "system.h" 24#include "debug.h" 25#include "panic.h" 26#include "pathfuncs.h" 27#include "disk_cache.h" 28#include "fileobj_mgr.h" 29#include "rb_namespace.h" 30#include "string-extra.h" 31#include "rbunicode.h" 32 33/* Define LOGF_ENABLE to enable logf output in this file */ 34//#define LOGF_ENABLE 35#ifdef LOGF_ENABLE 36#include "logf.h" 37#undef DEBUGF 38#define DEBUGF logf 39#endif 40 41/** Internal common filesystem service functions **/ 42 43/* for internal functions' scanning use to save quite a bit of stack space - 44 access must be serialized by the writer lock */ 45#if defined(IAUDIO_M5) || CONFIG_CPU == IMX233 46/* otherwise, out of IRAM */ 47struct fat_direntry dir_fatent; 48#else 49struct fat_direntry dir_fatent IBSS_ATTR; 50#endif 51 52struct mrsw_lock file_internal_mrsw SHAREDBSS_ATTR; 53 54 55/** File stream sector caching **/ 56 57/* initialize a new cache structure */ 58void file_cache_init(struct filestr_cache *cachep) 59{ 60 cachep->buffer = NULL; 61 cachep->sector = INVALID_SECNUM; 62 cachep->flags = 0; 63} 64 65/* discard and mark the cache buffer as unused */ 66void file_cache_reset(struct filestr_cache *cachep) 67{ 68 cachep->sector = INVALID_SECNUM; 69 cachep->flags = 0; 70} 71 72/* allocate resources attached to the cache */ 73void file_cache_alloc(struct filestr_cache *cachep) 74{ 75 /* if this fails, it is a bug; check for leaks and that the cache has 76 enough buffers for the worst case */ 77 if (!cachep->buffer && !(cachep->buffer = dc_get_buffer())) 78 panicf("file_cache_alloc - OOM"); 79} 80 81/* free resources attached to the cache */ 82void file_cache_free(struct filestr_cache *cachep) 83{ 84 if (cachep) 85 { 86 if(cachep->buffer) 87 { 88 dc_release_buffer(cachep->buffer); 89 cachep->buffer = NULL; 90 } 91 file_cache_reset(cachep); 92 } 93} 94 95 96/** Stream base APIs **/ 97 98static inline void filestr_clear(struct filestr_base *stream, unsigned int flags) 99{ 100 stream->flags = flags; 101 stream->bindp = NULL; 102#if 0 103 stream->mtx = NULL; 104#endif 105} 106 107/* actually late-allocate the assigned cache */ 108void filestr_alloc_cache(struct filestr_base *stream) 109{ 110 file_cache_alloc(stream->cachep); 111} 112 113/* free the stream's cache buffer if it's its own */ 114void filestr_free_cache(struct filestr_base *stream) 115{ 116 if (stream->cachep == &stream->cache) 117 file_cache_free(stream->cachep); 118} 119 120/* assign a cache to the stream */ 121void filestr_assign_cache(struct filestr_base *stream, 122 struct filestr_cache *cachep) 123{ 124 if (cachep) 125 { 126 filestr_free_cache(stream); 127 stream->cachep = cachep; 128 } 129 else /* assign own cache */ 130 { 131 file_cache_reset(&stream->cache); 132 stream->cachep = &stream->cache; 133 } 134} 135 136/* duplicate a cache into a stream's local cache */ 137void filestr_copy_cache(struct filestr_base *stream, 138 struct filestr_cache *cachep) 139{ 140 stream->cachep = &stream->cache; 141 stream->cache.sector = cachep->sector; 142 stream->cache.flags = cachep->flags; 143 144 if (cachep->buffer) 145 { 146 file_cache_alloc(&stream->cache); 147 memcpy(stream->cache.buffer, cachep->buffer, DC_CACHE_BUFSIZE); 148 } 149 else 150 { 151 file_cache_free(&stream->cache); 152 } 153} 154 155/* discard cache contents and invalidate it */ 156void filestr_discard_cache(struct filestr_base *stream) 157{ 158 file_cache_reset(stream->cachep); 159} 160 161/* Initialize the base descriptor */ 162void filestr_base_init(struct filestr_base *stream) 163{ 164 filestr_clear(stream, FD_VALID); 165 file_cache_init(&stream->cache); 166 stream->cachep = &stream->cache; 167} 168 169/* free base descriptor resources */ 170void filestr_base_destroy(struct filestr_base *stream) 171{ 172 filestr_clear(stream, 0); 173 filestr_free_cache(stream); 174} 175 176 177/** Internal directory service functions **/ 178 179/* read the next directory entry and return its FS info */ 180int uncached_readdir_internal(struct filestr_base *stream, 181 struct file_base_info *infop, 182 struct fat_direntry *fatent) 183{ 184 return fat_readdir(&stream->fatstr, &infop->fatfile.e, 185 filestr_get_cache(stream), fatent); 186} 187 188/* rewind the FS directory to the beginning */ 189void uncached_rewinddir_internal(struct file_base_info *infop) 190{ 191 fat_rewinddir(&infop->fatfile.e); 192} 193 194/* check if the directory is empty (ie. only "." and/or ".." entries 195 exist at most) */ 196int test_dir_empty_internal(struct filestr_base *stream) 197{ 198 int rc; 199 200 struct file_base_info info; 201 fat_rewind(&stream->fatstr); 202 rewinddir_internal(&info); 203 204 while ((rc = readdir_internal(stream, &info, &dir_fatent)) > 0) 205 { 206 /* no OEM decoding is recessary for this simple check */ 207 if (!is_dotdir_name(dir_fatent.name)) 208 { 209 DEBUGF("Directory not empty\n"); 210 FILE_ERROR_RETURN(ENOTEMPTY, -1); 211 } 212 } 213 214 if (rc < 0) 215 { 216 DEBUGF("I/O error checking directory: %d\n", rc); 217 FILE_ERROR_RETURN(EIO, rc * 10 - 2); 218 } 219 220 return 0; 221} 222 223/* iso decode the name to UTF-8 */ 224void iso_decode_d_name(char *d_name) 225{ 226#ifdef HAVE_FILESYSTEM_CODEPAGE 227 if (is_dotdir_name(d_name)) 228 return; 229 230 char shortname[13]; 231 /* this only gets called in the case of DOS (8.3) filenames */ 232 size_t len = strlcpy(shortname, d_name, sizeof (shortname)); 233 /* This MUST be the default codepage thus not something that could be 234 loaded on call */ 235 iso_decode(shortname, d_name, -1, len + 1); 236#else 237 (void)d_name; 238#endif 239} 240 241#ifdef HAVE_DIRCACHE 242/* nullify all the fields of the struct dirent */ 243void empty_dirent(struct DIRENT *entry) 244{ 245 entry->d_name[0] = '\0'; 246 entry->info.attr = 0; 247 entry->info.size = 0; 248 entry->info.wrtdate = 0; 249 entry->info.wrttime = 0; 250} 251 252/* fill the native dirinfo from the static dir_fatent */ 253void fill_dirinfo_native(struct dirinfo_native *dinp) 254{ 255 struct fat_direntry *fatent = get_dir_fatent(); 256 dinp->attr = fatent->attr; 257 dinp->size = fatent->filesize; 258 dinp->wrtdate = fatent->wrtdate; 259 dinp->wrttime = fatent->wrttime; 260} 261#endif /* HAVE_DIRCACHE */ 262 263int uncached_readdir_dirent(struct filestr_base *stream, 264 struct dirscan_info *scanp, 265 struct DIRENT *entry) 266{ 267 struct fat_direntry fatent; 268 int rc = fat_readdir(&stream->fatstr, &scanp->fatscan, 269 filestr_get_cache(stream), &fatent); 270 271 /* FAT driver clears the struct fat_dirent if nothing is returned */ 272 strcpy(entry->d_name, fatent.name); 273 entry->info.attr = fatent.attr; 274 entry->info.size = fatent.filesize; 275 entry->info.wrtdate = fatent.wrtdate; 276 entry->info.wrttime = fatent.wrttime; 277 278 return rc; 279} 280 281/* rewind the FS directory pointer */ 282void uncached_rewinddir_dirent(struct dirscan_info *scanp) 283{ 284 fat_rewinddir(&scanp->fatscan); 285} 286 287 288/** open_stream_internal() helpers and types **/ 289 290struct pathwalk 291{ 292 const char *path; /* current location in input path */ 293 unsigned int callflags; /* callflags parameter */ 294 struct path_component_info *compinfo; /* compinfo parameter */ 295 file_size_t filesize; /* size of the file */ 296}; 297 298struct pathwalk_component 299{ 300 struct file_base_info info; /* basic file information */ 301 const char *name; /* component name location in path */ 302 uint16_t length; /* length of name of component */ 303 uint16_t attr; /* attributes of this component */ 304 struct pathwalk_component *nextp; /* parent if in use else next free */ 305}; 306 307#define WALK_RC_NOT_FOUND 0 /* successfully not found (aid for file creation) */ 308#define WALK_RC_FOUND 1 /* found and opened */ 309#define WALK_RC_FOUND_ROOT 2 /* found and opened sys root */ 310#define WALK_RC_CONT_AT_ROOT 3 /* continue at root level */ 311 312/* return another struct pathwalk_component from the pool, or NULL if the 313 pool is completely used */ 314static void * pathwalk_comp_alloc_(struct pathwalk_component *parentp) 315{ 316 /* static pool that goes to a depth of STATIC_COMP_NUM before allocating 317 elements from the stack */ 318 static struct pathwalk_component aux_pathwalk[STATIC_PATHCOMP_NUM]; 319 struct pathwalk_component *compp = NULL; 320 321 if (!parentp) 322 compp = &aux_pathwalk[0]; /* root */ 323 else if (PTR_IN_ARRAY(aux_pathwalk, parentp, STATIC_PATHCOMP_NUM-1)) 324 compp = parentp + 1; 325 326 return compp; 327} 328 329/* allocates components from the pool or stack depending upon the depth */ 330#define pathwalk_comp_alloc(parentp) \ 331 ({ \ 332 void *__c = pathwalk_comp_alloc_(parentp); \ 333 if (!__builtin_constant_p(parentp) && !__c) \ 334 __c = alloca(sizeof (struct pathwalk_component)); \ 335 (struct pathwalk_component *)__c; \ 336 }) 337 338/* fill in the details of the struct path_component_info for caller */ 339static int fill_path_compinfo(struct pathwalk *walkp, 340 struct pathwalk_component *compp, 341 int rc) 342{ 343 if (rc == -ENOENT) 344 { 345 /* this component wasn't found; see if more of them exist or path 346 has trailing separators; if it does, this component should be 347 interpreted as a directory even if it doesn't exist and it's the 348 final one; also, this has to be the last part or it's an error*/ 349 const char *p = GOBBLE_PATH_SEPCH(walkp->path); 350 if (!*p) 351 { 352 if (p > walkp->path) 353 compp->attr |= ATTR_DIRECTORY; 354 355 rc = WALK_RC_NOT_FOUND; /* successfully not found */ 356 } 357 } 358 359 if (rc >= 0) 360 { 361 struct path_component_info *compinfo = walkp->compinfo; 362 compinfo->name = compp->name; 363 compinfo->length = compp->length; 364 compinfo->attr = compp->attr; 365 compinfo->filesize = walkp->filesize; 366 if (walkp->callflags & FF_INFO) 367 compinfo->info = compp->info; 368 if (walkp->callflags & FF_PARENTINFO) 369 compinfo->parentinfo = (compp->nextp ?: compp)->info; 370 } 371 372 return rc; 373} 374 375/* open the final stream itself, if found */ 376static int walk_open_info(struct pathwalk *walkp, 377 struct pathwalk_component *compp, 378 int rc, 379 struct filestr_base *stream) 380{ 381 /* this may make adjustments to things; do it first */ 382 if (walkp->compinfo) 383 rc = fill_path_compinfo(walkp, compp, rc); 384 385 if (rc < 0 || rc == WALK_RC_NOT_FOUND) 386 return rc; 387 388 unsigned int callflags = walkp->callflags; 389 bool isdir = compp->attr & ATTR_DIRECTORY; 390 391 /* type must match what is called for */ 392 switch (callflags & FF_TYPEMASK) 393 { 394 case FF_FILE: 395 if (!isdir) break; 396 DEBUGF("File is a directory\n"); 397 return -EISDIR; 398 case FF_DIR: 399 if (isdir) break; 400 DEBUGF("File is not a directory\n"); 401 return -ENOTDIR; 402 /* FF_ANYTYPE: basically, ignore FF_FILE/FF_DIR */ 403 } 404 405 /* FO_DIRECTORY must match type */ 406 if (isdir) 407 callflags |= FO_DIRECTORY; 408 else 409 callflags &= ~FO_DIRECTORY; 410 411 /* make open official if not simply probing for presence - must do it here 412 or compp->info on stack will get destroyed before it was copied */ 413 if (!(callflags & (FF_PROBE|FF_NOFS))) 414 fileop_onopen_internal(stream, &compp->info, callflags); 415 return compp->attr == ATTR_SYSTEM_ROOT ? WALK_RC_FOUND_ROOT : WALK_RC_FOUND; 416} 417 418/* check the component against the prefix test info */ 419static void walk_check_prefix(struct pathwalk *walkp, 420 struct pathwalk_component *compp) 421{ 422 if (compp->attr & ATTR_PREFIX) 423 return; 424 425 if (!fat_file_is_same(&compp->info.fatfile, 426 &walkp->compinfo->prefixp->fatfile)) 427 return; 428 429 compp->attr |= ATTR_PREFIX; 430} 431 432/* opens the component named by 'comp' in the directory 'parent' */ 433static NO_INLINE int open_path_component(struct pathwalk *walkp, 434 struct pathwalk_component *compp, 435 struct filestr_base *stream) 436{ 437 int rc; 438 439 /* create a null-terminated copy of the component name */ 440 char *compname = strmemdupa(compp->name, compp->length); 441 442 unsigned int callflags = walkp->callflags; 443 struct pathwalk_component *parentp = compp->nextp; 444 445 /* children inherit the prefix coloring from the parent */ 446 compp->attr = parentp->attr & ATTR_PREFIX; 447 448 /* most of the next would be abstracted elsewhere if doing other 449 filesystems */ 450 451 /* scan parent for name; stream is converted to this parent */ 452 file_cache_reset(stream->cachep); 453 stream->infop = &parentp->info; 454 fat_filestr_init(&stream->fatstr, &parentp->info.fatfile); 455 rewinddir_internal(&compp->info); 456 457 while ((rc = readdir_internal(stream, &compp->info, &dir_fatent)) > 0) 458 { 459 if (rc > 1 && !(callflags & FF_NOISO)) 460 iso_decode_d_name(dir_fatent.name); 461 462 if (!strcasecmp(compname, dir_fatent.name)) 463 break; 464 } 465 466 if (rc == 0) 467 { 468 DEBUGF("File/directory not found\n"); 469 return -ENOENT; 470 } 471 else if (rc < 0) 472 { 473 DEBUGF("I/O error reading directory %d\n", rc); 474 return -EIO; 475 } 476 477 rc = fat_open(stream->fatstr.fatfilep, dir_fatent.firstcluster, 478 &compp->info.fatfile); 479 if (rc < 0) 480 { 481 DEBUGF("I/O error opening file/directory %s (%d)\n", 482 compname, rc); 483 return -EIO; 484 } 485 486 walkp->filesize = dir_fatent.filesize; 487 compp->attr |= dir_fatent.attr; 488 489 if (callflags & FF_CHECKPREFIX) 490 walk_check_prefix(walkp, compp); 491 492 return WALK_RC_FOUND; 493} 494 495/* parse a path component, open it and process the next */ 496static NO_INLINE int 497walk_path(struct pathwalk *walkp, struct pathwalk_component *compp, 498 struct filestr_base *stream) 499{ 500 int rc = WALK_RC_FOUND; 501 502 if (walkp->callflags & FF_CHECKPREFIX) 503 walk_check_prefix(walkp, compp); 504 505 /* alloca is used in a loop, but we reuse any blocks previously allocated 506 if we went up then back down; if the path takes us back to the root, then 507 everything is cleaned automatically */ 508 struct pathwalk_component *freep = NULL; 509 510 const char *name; 511 ssize_t len; 512 513 while ((len = parse_path_component(&walkp->path, &name))) 514 { 515 /* whatever is to be a parent must be a directory */ 516 if (!(compp->attr & ATTR_DIRECTORY)) 517 return -ENOTDIR; 518 519 if (len > MAX_COMPNAME) 520 return -ENAMETOOLONG; 521 522 /* no filesystem is mounted here */ 523 if (walkp->callflags & FF_NOFS) 524 return -ENOENT; 525 526 /* check for "." and ".." */ 527 if (name[0] == '.') 528 { 529 if (len == 1) 530 continue; /* is "." */ 531 532 if (len == 2 && name[1] == '.') 533 { 534 /* is ".." */ 535 struct pathwalk_component *parentp = compp->nextp; 536 537 if (!parentp IF_MV( || parentp->attr == ATTR_SYSTEM_ROOT )) 538 return WALK_RC_CONT_AT_ROOT; 539 540 compp->nextp = freep; 541 freep = compp; 542 compp = parentp; 543 continue; 544 } 545 } 546 547 struct pathwalk_component *newp = freep; 548 if (!newp) 549 newp = pathwalk_comp_alloc(compp); 550 else 551 freep = freep->nextp; 552 553 newp->nextp = compp; 554 compp = newp; 555 556 compp->name = name; 557 compp->length = len; 558 559 rc = open_path_component(walkp, compp, stream); 560 if (rc < 0) 561 break; /* return info below */ 562 } 563 564 return walk_open_info(walkp, compp, rc, stream); 565} 566 567/* open a stream given a path to the resource */ 568int open_stream_internal(const char *path, unsigned int callflags, 569 struct filestr_base *stream, 570 struct path_component_info *compinfo) 571{ 572 DEBUGF("%s(path=\"%s\",flg=%X,str=%p,compinfo=%p)\n", __func__, 573 path, callflags, stream, compinfo); 574 int rc; 575 576 filestr_base_init(stream); 577 578 if (!path_is_absolute(path)) 579 { 580 /* while this supports relative components, there is currently no 581 current working directory concept at this level by which to 582 fully qualify the path (though that would not be excessively 583 difficult to add) */ 584 DEBUGF("\"%s\" is not an absolute path\n" 585 "Only absolute paths currently supported.\n", path); 586 FILE_ERROR(path ? ENOENT : EFAULT, -1); 587 } 588 589 /* if !compinfo then these cannot be returned anyway */ 590 if (!compinfo) 591 callflags &= ~(FF_INFO | FF_PARENTINFO | FF_CHECKPREFIX); 592 593 /* This lets it be passed quietly to directory scanning */ 594 stream->flags |= callflags & FF_MASK; 595 596 struct pathwalk walk; 597 walk.path = path; 598 walk.callflags = callflags; 599 walk.compinfo = compinfo; 600 walk.filesize = 0; 601 602 struct pathwalk_component *rootp = pathwalk_comp_alloc(NULL); 603 rootp->nextp = NULL; 604 605 while (1) 606 { 607 rc = ns_parse_root(walk.path, &rootp->name, &rootp->length); 608 if (rc < 0) 609 break; 610 611 rc = ns_open_root(IF_MV(rc,) &walk.callflags, &rootp->info, &rootp->attr); 612 if (rc < 0) 613 break; 614 615 walk.path = rootp->name + rootp->length; 616 617 rc = walk_path(&walk, rootp, stream); 618 if (rc != WALK_RC_CONT_AT_ROOT) 619 break; 620 } 621 622 if (rc >= 0) 623 { 624 /* FF_PROBE leaves nothing for caller to clean up */ 625 if (walk.callflags & FF_PROBE) 626 filestr_base_destroy(stream); 627 } 628 else 629 { 630 /* utter, abject failure :`( */ 631 DEBUGF("Open failed: rc=%d, errno=%d\n", rc, errno); 632 filestr_base_destroy(stream); 633 FILE_ERROR(-rc, -1); 634 } 635 636file_error: 637 return rc; 638} 639 640/* close the stream referenced by 'stream' */ 641int close_stream_internal(struct filestr_base *stream) 642{ 643 int rc; 644 unsigned int foflags = fileobj_get_flags(stream); 645 646 if ((foflags & (FO_SINGLE|FO_REMOVED)) == (FO_SINGLE|FO_REMOVED)) 647 { 648 /* nothing is referencing it so now remove the file's data */ 649 rc = fat_remove(&stream->infop->fatfile, FAT_RM_DATA); 650 if (rc < 0) 651 { 652 DEBUGF("I/O error removing file data: %d\n", rc); 653 FILE_ERROR(EIO, rc * 10 - 1); 654 } 655 } 656 657 rc = 0; 658file_error: 659 /* close no matter what */ 660 fileop_onclose_internal(stream); 661 return rc; 662} 663 664/* create a new stream in the parent directory */ 665int create_stream_internal(struct file_base_info *parentinfop, 666 const char *basename, size_t length, 667 unsigned int attr, unsigned int callflags, 668 struct filestr_base *stream) 669{ 670 /* assumes an attempt was made beforehand to open *stream with 671 open_stream_internal() which returned zero (successfully not found), 672 so does not initialize it here */ 673 const char * const name = strmemdupa(basename, length); 674 DEBUGF("Creating \"%s\"\n", name); 675 676 struct file_base_info info; 677 int rc = fat_create_file(&parentinfop->fatfile, name, attr, 678 &info.fatfile, get_dir_fatent_dircache()); 679 if (rc < 0) 680 { 681 DEBUGF("Create failed: %d\n", rc); 682 FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 1); 683 } 684 685 /* dir_fatent is implicit arg */ 686 fileop_oncreate_internal(stream, &info, callflags, parentinfop, name); 687 rc = 0; 688file_error: 689 return rc; 690} 691 692/* removes files and directories - back-end to remove() and rmdir() */ 693int remove_stream_internal(const char *path, struct filestr_base *stream, 694 unsigned int callflags) 695{ 696 /* Only FF_* flags should be in callflags */ 697 int rc; 698 699 struct filestr_base opened_stream; 700 if (!stream) 701 stream = &opened_stream; 702 703 if (stream == &opened_stream) 704 { 705 /* no stream provided so open local one */ 706 rc = open_stream_internal(path, callflags, stream, NULL); 707 if (rc < 0) 708 { 709 DEBUGF("Failed opening path: %d\n", rc); 710 FILE_ERROR(ERRNO, rc * 10 - 1); 711 } 712 } 713 /* else ignore the 'path' argument */ 714 715 if (callflags & FF_DIR) 716 { 717 /* directory to be removed must be empty */ 718 rc = test_dir_empty_internal(stream); 719 if (rc < 0) 720 FILE_ERROR(ERRNO, rc * 10 - 2); 721 } 722 723 /* save old info since fat_remove() will destroy the dir info */ 724 struct file_base_info oldinfo = *stream->infop; 725 rc = fat_remove(&stream->infop->fatfile, FAT_RM_DIRENTRIES); 726 if (rc < 0) 727 { 728 DEBUGF("I/O error removing dir entries: %d\n", rc); 729 FILE_ERROR(EIO, rc * 10 - 3); 730 } 731 732 fileop_onremove_internal(stream, &oldinfo); 733 734 rc = 0; 735file_error: 736 if (stream == &opened_stream) 737 { 738 /* will do removal of data below if this is the only reference */ 739 int rc2 = close_stream_internal(stream); 740 if (rc2 < 0 && rc >= 0) 741 { 742 rc = rc2 * 10 - 4; 743 DEBUGF("Success but failed closing stream: %d\n", rc); 744 } 745 } 746 747 return rc; 748} 749 750/* test file/directory existence with constraints */ 751int test_stream_exists_internal(const char *path, unsigned int callflags) 752{ 753 /* only FF_* flags should be in callflags */ 754 struct filestr_base stream; 755 return open_stream_internal(path, callflags | FF_PROBE, &stream, NULL); 756} 757 758/* one-time init at startup */ 759void filesystem_init(void) 760{ 761 mrsw_init(&file_internal_mrsw); 762 dc_init(); 763 fileobj_mgr_init(); 764}