summaryrefslogtreecommitdiff
path: root/firmware/common
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2017-01-08 17:14:10 -0500
committerMichael Sevakis <jethead71@rockbox.org>2017-01-17 14:35:36 -0500
commita931c76b3a46d1884e985a3bfc82b947521dab97 (patch)
tree1141cf9a9a8c123bde3d76d147ee1e910c7c6045 /firmware/common
parent0056ea8a256af0e2daaf451af43c00708a31d4df (diff)
downloadrockbox-a931c76b3a46d1884e985a3bfc82b947521dab97.tar.gz
rockbox-a931c76b3a46d1884e985a3bfc82b947521dab97.zip
Do some debug and preparatory work for ramcache and playlist
The file system rework introduced incompatibility between dircache and the tagcache ramcache and playlist dircache path caching. This update makes changes to filesystem code to reintegrate all that. It also fixes a couple bugs that were found when vetting all the code. The filestream cache was being reset without regard to the stream even if it was shared in write mode (made work of .playlist_control). Better handling of unmounting gives files a better go at force-closing them without risk to disk integrity. Did some miscellaneous pedantic changes. Improved efficiency of testing a file's existence (a little) since the path parser will be shared between file code and parsing for the sake of finding dircache references, not duplicated as before. This commit doesn't reenable said items just for the sake of keeping changes separate and related. Plan for the next is to enable dircache again for the playlists (easy peasy) and reenable tagcache ramcache but *without* the dircache path caching because it's rather substantial to change in itself. The ramcache will still function without dircache. Change-Id: I7e2a9910b866251fa8333e1275f72fcfc8425d2d
Diffstat (limited to 'firmware/common')
-rw-r--r--firmware/common/dir.c2
-rw-r--r--firmware/common/dircache.c351
-rw-r--r--firmware/common/file.c57
-rw-r--r--firmware/common/file_internal.c48
-rw-r--r--firmware/common/fileobj_mgr.c126
5 files changed, 312 insertions, 272 deletions
diff --git a/firmware/common/dir.c b/firmware/common/dir.c
index da798c71d5..59f7bd747a 100644
--- a/firmware/common/dir.c
+++ b/firmware/common/dir.c
@@ -56,7 +56,7 @@ static struct dirstr_desc * get_dirstr(DIR *dirp)
56 { 56 {
57 errnum = EFAULT; 57 errnum = EFAULT;
58 } 58 }
59 else if (dir->stream.flags == FV_NONEXIST) 59 else if (dir->stream.flags & FD_NONEXIST)
60 { 60 {
61 DEBUGF("dir #%d: nonexistant device\n", (int)(dir - open_streams)); 61 DEBUGF("dir #%d: nonexistant device\n", (int)(dir - open_streams));
62 errnum = ENXIO; 62 errnum = ENXIO;
diff --git a/firmware/common/dircache.c b/firmware/common/dircache.c
index b93ee73fc6..a3538ff96f 100644
--- a/firmware/common/dircache.c
+++ b/firmware/common/dircache.c
@@ -41,9 +41,7 @@
41#include "audio.h" 41#include "audio.h"
42#include "rbpaths.h" 42#include "rbpaths.h"
43#include "linked_list.h" 43#include "linked_list.h"
44#ifdef HAVE_EEPROM_SETTINGS
45#include "crc32.h" 44#include "crc32.h"
46#endif
47 45
48/** 46/**
49 * Cache memory layout: 47 * Cache memory layout:
@@ -1457,7 +1455,7 @@ int dircache_readdir_dirent(struct filestr_base *stream,
1457 unsigned int direntry = scanp->fatscan.entry; 1455 unsigned int direntry = scanp->fatscan.entry;
1458 while (1) 1456 while (1)
1459 { 1457 {
1460 if (idx == 0 || direntry == FAT_RW_VAL) /* rewound? */ 1458 if (idx == 0 || direntry == FAT_DIRSCAN_RW_VAL) /* rewound? */
1461 { 1459 {
1462 idx = diridx <= 0 ? dcvolp->root_down : get_entry(diridx)->down; 1460 idx = diridx <= 0 ? dcvolp->root_down : get_entry(diridx)->down;
1463 break; 1461 break;
@@ -1487,7 +1485,7 @@ int dircache_readdir_dirent(struct filestr_base *stream,
1487 return 0; /* end of dir */ 1485 return 0; /* end of dir */
1488 } 1486 }
1489 1487
1490 if (ce->direntry > direntry || direntry == FAT_RW_VAL) 1488 if (ce->direntry > direntry || direntry == FAT_DIRSCAN_RW_VAL)
1491 break; /* cache reader is caught up to FS scan */ 1489 break; /* cache reader is caught up to FS scan */
1492 1490
1493 idx = ce->next; 1491 idx = ce->next;
@@ -1549,7 +1547,12 @@ int dircache_readdir_internal(struct filestr_base *stream,
1549 1547
1550 /* is parent cached? if not, readthrough because nothing is here yet */ 1548 /* is parent cached? if not, readthrough because nothing is here yet */
1551 if (!dirinfop->dcfile.serialnum) 1549 if (!dirinfop->dcfile.serialnum)
1550 {
1551 if (stream->flags & FF_CACHEONLY)
1552 goto read_eod;
1553
1552 return uncached_readdir_internal(stream, infop, fatent); 1554 return uncached_readdir_internal(stream, infop, fatent);
1555 }
1553 1556
1554 int diridx = dirinfop->dcfile.idx; 1557 int diridx = dirinfop->dcfile.idx;
1555 unsigned int frontier = diridx < 0 ? 1558 unsigned int frontier = diridx < 0 ?
@@ -1562,7 +1565,8 @@ int dircache_readdir_internal(struct filestr_base *stream,
1562 idx = get_entry(idx)->next; 1565 idx = get_entry(idx)->next;
1563 1566
1564 struct dircache_entry *ce = get_entry(idx); 1567 struct dircache_entry *ce = get_entry(idx);
1565 if (frontier != FRONTIER_SETTLED) 1568
1569 if (frontier != FRONTIER_SETTLED && !(stream->flags & FF_CACHEONLY))
1566 { 1570 {
1567 /* the directory being read is reported to be incompletely cached; 1571 /* the directory being read is reported to be incompletely cached;
1568 readthrough and if the entry exists, return it with its binding 1572 readthrough and if the entry exists, return it with its binding
@@ -1577,9 +1581,7 @@ int dircache_readdir_internal(struct filestr_base *stream,
1577 else if (!ce) 1581 else if (!ce)
1578 { 1582 {
1579 /* end of dir */ 1583 /* end of dir */
1580 fat_empty_fat_direntry(fatent); 1584 goto read_eod;
1581 infop->fatfile.e.entries = 0;
1582 return 0;
1583 } 1585 }
1584 1586
1585 /* FS entry information that we maintain */ 1587 /* FS entry information that we maintain */
@@ -1612,6 +1614,11 @@ int dircache_readdir_internal(struct filestr_base *stream,
1612 } 1614 }
1613 1615
1614 return rc; 1616 return rc;
1617
1618read_eod:
1619 fat_empty_fat_direntry(fatent);
1620 infop->fatfile.e.entries = 0;
1621 return 0;
1615} 1622}
1616 1623
1617/** 1624/**
@@ -2451,30 +2458,41 @@ void dircache_fileop_sync(struct file_base_binding *bindp,
2451 2458
2452/** Dircache paths and files **/ 2459/** Dircache paths and files **/
2453 2460
2454#ifdef DIRCACHE_DUMPSTER 2461/**
2455/* helper for dircache_get_path() */ 2462 * helper for returning a path and serial hash represented by an index
2456static ssize_t get_path_sub(int idx, char *buf, size_t size) 2463 */
2464struct get_path_sub_data
2465{
2466 char *buf;
2467 size_t size;
2468 dc_serial_t serialhash;
2469};
2470
2471static ssize_t get_path_sub(int idx, struct get_path_sub_data *data)
2457{ 2472{
2458 if (idx == 0) 2473 if (idx == 0)
2459 return -2; /* entry is an orphan split from any root */ 2474 return -1; /* entry is an orphan split from any root */
2460 2475
2461 ssize_t offset; 2476 ssize_t len;
2462 char *cename; 2477 char *cename;
2463 2478
2464 if (idx > 0) 2479 if (idx > 0)
2465 { 2480 {
2466 /* go all the way up then move back down from the root */
2467 struct dircache_entry *ce = get_entry(idx); 2481 struct dircache_entry *ce = get_entry(idx);
2468 offset = get_path_sub(ce->up, buf, size) - 1; 2482
2469 if (offset < 0) 2483 data->serialhash = dc_hash_serialnum(ce->serialnum, data->serialhash);
2470 return -3; 2484
2485 /* go all the way up then move back down from the root */
2486 len = get_path_sub(ce->up, data) - 1;
2487 if (len < 0)
2488 return -2;
2471 2489
2472 cename = alloca(MAX_NAME + 1); 2490 cename = alloca(MAX_NAME + 1);
2473 entry_name_copy(cename, ce); 2491 entry_name_copy(cename, ce);
2474 } 2492 }
2475 else /* idx < 0 */ 2493 else /* idx < 0 */
2476 { 2494 {
2477 offset = 0; 2495 len = 0;
2478 cename = ""; 2496 cename = "";
2479 2497
2480 #ifdef HAVE_MULTIVOLUME 2498 #ifdef HAVE_MULTIVOLUME
@@ -2486,14 +2504,14 @@ static ssize_t get_path_sub(int idx, char *buf, size_t size)
2486 get_volume_name(volume, cename); 2504 get_volume_name(volume, cename);
2487 } 2505 }
2488 #endif /* HAVE_MULTIVOLUME */ 2506 #endif /* HAVE_MULTIVOLUME */
2507
2508 data->serialhash = dc_hash_serialnum(get_idx_dcvolp(idx)->serialnum,
2509 data->serialhash);
2489 } 2510 }
2490 2511
2491 return offset + path_append(buf + offset, PA_SEP_HARD, cename, 2512 return len + path_append(data->buf + len, PA_SEP_HARD, cename,
2492 size > (size_t)offset ? size - offset : 0); 2513 data->size > (size_t)len ? data->size - len : 0);
2493} 2514}
2494#endif /* DIRCACHE_DUMPSTER */
2495
2496#if 0
2497 2515
2498/** 2516/**
2499 * retrieve and validate the file's entry/binding serial number 2517 * retrieve and validate the file's entry/binding serial number
@@ -2523,201 +2541,173 @@ static dc_serial_t get_file_serialnum(const struct dircache_file *dcfilep)
2523} 2541}
2524 2542
2525/** 2543/**
2544 * Obtain the hash of the serial numbers of the canonical path, index to root
2545 */
2546static dc_serial_t get_file_serialhash(const struct dircache_file *dcfilep)
2547{
2548 int idx = dcfilep->idx;
2549
2550 dc_serial_t h = DC_SERHASH_START;
2551
2552 while (idx > 0)
2553 {
2554 struct dircache_entry *ce = get_entry(idx);
2555 h = dc_hash_serialnum(ce->serialnum, h);
2556 idx = ce->up;
2557 }
2558
2559 h = dc_hash_serialnum(get_idx_dcvolp(idx)->serialnum, h);
2560
2561 return h;
2562}
2563
2564/**
2565 * Initialize the fileref
2566 */
2567void dircache_fileref_init(struct dircache_fileref *dcfrefp)
2568{
2569 dircache_dcfile_init(&dcfrefp->dcfile);
2570 dcfrefp->serialhash = DC_SERHASH_START;
2571}
2572
2573/**
2526 * usermode function to construct a full absolute path from dircache into the 2574 * usermode function to construct a full absolute path from dircache into the
2527 * given buffer given the dircache file info 2575 * given buffer given the dircache file info
2528 * 2576 *
2529 * returns: 2577 * returns:
2530 * success - the length of the string, not including the trailing null 2578 * success - the length of the string, not including the trailing null or the
2579 * buffer length required if the buffer is too small (return is >=
2580 * size)
2531 * failure - a negative value 2581 * failure - a negative value
2532 * 2582 *
2533 * successful return value is as strlcpy()
2534 *
2535 * errors: 2583 * errors:
2536 * ENOENT - the file or directory does not exist 2584 * ENOENT - No such file or directory
2537 */ 2585 */
2538ssize_t dircache_get_path(const struct dircache_file *dcfilep, char *buf, 2586ssize_t dircache_get_fileref_path(const struct dircache_fileref *dcfrefp, char *buf,
2539 size_t size) 2587 size_t size)
2540{ 2588{
2589 ssize_t rc;
2590
2541 /* if missing buffer space, still return what's needed a la strlcpy */ 2591 /* if missing buffer space, still return what's needed a la strlcpy */
2542 if (!buf) 2592 if (!buf)
2543 size = 0; 2593 size = 0;
2544 else if (size) 2594 else if (size)
2545 *buf = '\0'; 2595 *buf = '\0';
2546 2596
2547 ssize_t len = -1;
2548
2549 dircache_lock(); 2597 dircache_lock();
2550 2598
2551 /* first and foremost, there must be a cache and the serial number must 2599 /* first and foremost, there must be a cache and the serial number must
2552 check out */ 2600 check out */
2553 if (dircache_runinfo.handle && get_file_serialnum(dcfilep)) 2601 if (!dircache_runinfo.handle)
2554 len = get_path_sub(dcfilep->idx, buf, size); 2602 FILE_ERROR(ENOENT, -1);
2555
2556 if (len < 0)
2557 errno = ENOENT;
2558
2559 dircache_unlock();
2560 return len;
2561}
2562 2603
2563/** 2604 if (get_file_serialnum(&dcfrefp->dcfile) == 0)
2564 * searches the sublist starting at 'idx' for the named component 2605 FILE_ERROR(ENOENT, -2);
2565 */
2566 2606
2567/* helper for get_file_sub() */ 2607 struct get_path_sub_data data =
2568static struct dircache_entry *
2569get_file_sub_scan(int idx, const char *name, size_t length, int *idxp)
2570{
2571 struct dircache_entry *ce = get_entry(idx);
2572 if (ce)
2573 { 2608 {
2574 char entname[MAX_NAME+1]; 2609 .buf = buf,
2575 name = strmemdupa(name, length); 2610 .size = size,
2576 2611 .serialhash = DC_SERHASH_START,
2577 do 2612 };
2578 {
2579 entry_name_copy(entname, ce);
2580 if (!strcasecmp(entname, name))
2581 {
2582 *idxp = idx;
2583 break;
2584 }
2585
2586 idx = ce->next;
2587 }
2588 while ((ce = get_entry(idx)));
2589 }
2590
2591 return ce;
2592}
2593
2594/**
2595 * searches for the subcomponent of *pathp
2596 */
2597
2598/* helper for dircache_get_file() */
2599static int get_file_sub(const char **pathp, int *downp, int *idxp)
2600{
2601 int rc;
2602 const char *name;
2603 rc = parse_path_component(pathp, &name, false);
2604 if (rc <= 0)
2605 return rc;
2606 else if (rc >= MAX_PATH)
2607 return ENAMETOOLONG; /* that's just unpossible, man */
2608 2613
2609 struct dircache_entry *ce = get_file_sub_scan(*downp, name, rc, idxp); 2614 rc = get_path_sub(dcfrefp->dcfile.idx, &data);
2615 if (rc < 0)
2616 FILE_ERROR(ENOENT, rc * 10 - 3);
2610 2617
2611 if (!ce) 2618 if (data.serialhash != dcfrefp->serialhash)
2612 rc = RC_NOT_FOUND; /* not there; tellibry solly */ 2619 FILE_ERROR(ENOENT, -4);
2613 else if (!*pathp)
2614 rc = RC_PATH_ENDED; /* done */
2615 else if (!(ce->attr & ATTR_DIRECTORY))
2616 rc = ENOTDIR; /* a parent component must be a directory */
2617 else
2618 while ((rc = get_file_sub(pathp, &ce->down, idxp)) == RC_CONTINUE);
2619 2620
2620 switch (rc) 2621file_error:
2621 { 2622 dircache_unlock();
2622 case RC_GO_UP: /* hit ".."; drop to previous level */ 2623 return rc;
2623 return RC_CONTINUE;
2624 case RC_PATH_ENDED: /* success! */
2625 return RC_FOUND;
2626 default: /* component not found or error */
2627 return rc;
2628 }
2629} 2624}
2630 2625
2631/** 2626/**
2632 * usermode function to return dircache file info for the given path 2627 * Test a path to various levels of rigor and optionally return dircache file
2628 * info for the given path
2633 * 2629 *
2634 * returns: 2630 * returns:
2635 * success: the volume number that is specified for the file 2631 * success: 0
2636 * failure: a negative value 2632 * failure: a negative value
2637 * 2633 *
2638 * errors: 2634 * errors (including but not limited to):
2639 * ENOENT - the file or directory does not exist or path is empty 2635 * EFAULT - Bad address
2640 * ENAMETOOLONG - a component of the path is too long 2636 * EINVAL - Invalid argument
2641 * ENOTDIR - a component of the path is not a directory 2637 * ENAMETOOLONG - File or path name too long
2638 * ENOENT - No such file or directory
2639 * ENOTDIR - Not a directory
2640 * ENXIO - No such device or address
2642 */ 2641 */
2643int dircache_get_file(const char *path, struct dircache_file *dcfilep) 2642int dircache_search(unsigned int flags, struct dircache_fileref *dcfrefp, const char *path)
2644{ 2643{
2645 if (!path_is_absolute(path) || !dcfilep) 2644 int rc;
2646 { 2645
2647 errno = ENOENT; 2646 if (!(flags & (DCS_FILEREF | DCS_CACHED_PATH)))
2648 return -1; 2647 FILE_ERROR_RETURN(EINVAL, -1); /* search nothing? */
2649 }
2650 2648
2651 dircache_lock(); 2649 dircache_lock();
2652 2650
2653 if (!dircache_runinfo.handle) 2651 if (!dircache_runinfo.handle)
2652 FILE_ERROR(ENOENT, -2);
2653
2654 if (flags & DCS_FILEREF)
2654 { 2655 {
2655 dircache_unlock(); 2656 if (!dcfrefp)
2656 errno = ENOENT; 2657 FILE_ERROR(EFAULT, -3);
2657 return -2;
2658 }
2659 2658
2660 int volume = 0; 2659 if (get_file_serialnum(&dcfrefp->dcfile) != 0)
2661 int idx = 0; 2660 {
2662 dc_serial_t serialnum = 0; 2661 if (!(flags & _DCS_VERIFY_FLAG))
2663 struct dircache_volume *dcvolp = NULL; 2662 goto file_success; /* no robust verification wanted */
2664 struct dircache_entry *ce = NULL; 2663
2664 if (get_file_serialhash(&dcfrefp->dcfile) == dcfrefp->serialhash)
2665 goto file_success; /* reference is most likely still valid */
2666 }
2665 2667
2666 int rc = RC_GO_UP; 2668 if (!(flags & DCS_CACHED_PATH))
2669 FILE_ERROR(ENOENT, -4); /* no path search wanted */
2670 }
2667 2671
2668 while (rc == RC_CONTINUE || rc == RC_GO_UP) 2672 if (flags & DCS_CACHED_PATH)
2669 { 2673 {
2670 #ifdef HAVE_MULTIVOLUME 2674 const bool update = flags & DCS_UPDATE_FILEREF;
2671 if (rc == RC_GO_UP) 2675 struct path_component_info *compinfop = NULL;
2676
2677 if (update)
2672 { 2678 {
2673 volume = path_strip_volume(path, &path, false); 2679 if (!dcfrefp)
2674 if (!CHECK_VOL(volume)) 2680 FILE_ERROR(EFAULT, -5);
2675 {
2676 rc = ENXIO;
2677 break;
2678 }
2679 }
2680 #endif /* HAVE_MULTIVOLUME */
2681 2681
2682 dcvolp = DCVOL(volume); 2682 compinfop = alloca(sizeof (*compinfop));
2683 }
2683 2684
2684 int *downp = &dcvolp->root_down; 2685 struct filestr_base stream;
2685 if (*downp <= 0) 2686 rc = open_stream_internal(path, FF_ANYTYPE | FF_PROBE | FF_SELFINFO |
2687 ((flags & _DCS_STORAGE_FLAG) ? 0 : FF_CACHEONLY),
2688 &stream, compinfop);
2689 if (rc <= 0)
2686 { 2690 {
2687 rc = ENXIO; 2691 if (update)
2688 break; 2692 dircache_fileref_init(dcfrefp);
2693
2694 FILE_ERROR(rc ? ERRNO : ENOENT, rc * 10 - 6);
2689 } 2695 }
2690 2696
2691 rc = get_file_sub(&path, downp, &idx); 2697 if (update)
2692 } 2698 {
2693 2699 dcfrefp->dcfile = compinfop->info.dcfile;
2694 switch (rc) 2700 dcfrefp->serialhash = get_file_serialhash(&compinfop->info.dcfile);
2695 { 2701 }
2696 case RC_FOUND: /* hit: component found */
2697 serialnum = ce->serialnum;
2698 rc = volume;
2699 break;
2700 case RC_PATH_ENDED: /* hit: it's a root (volume or system) */
2701 idx = -volume - 1;
2702 serialnum = dcvolp->serialnum;
2703 rc = volume;
2704 break;
2705 case RC_NOT_FOUND: /* miss */
2706 rc = ENOENT;
2707 default:
2708 idx = 0;
2709 errno = rc;
2710 rc = -3;
2711 break;
2712 } 2702 }
2713 2703
2714 dcfilep->idx = idx; 2704file_success:
2715 dcfilep->serialnum = serialnum; 2705 rc = 0;
2716 2706
2707file_error:
2717 dircache_unlock(); 2708 dircache_unlock();
2718 return rc; 2709 return rc;
2719} 2710}
2720#endif /* 0 */
2721 2711
2722 2712
2723/** Debug screen/info stuff **/ 2713/** Debug screen/info stuff **/
@@ -2737,13 +2727,12 @@ void dircache_get_info(struct dircache_info *info)
2737 if (!info) 2727 if (!info)
2738 return; 2728 return;
2739 2729
2740 memset(info, 0, sizeof (*info));
2741
2742 dircache_lock(); 2730 dircache_lock();
2743 2731
2744 enum dircache_status status = DIRCACHE_IDLE; 2732 enum dircache_status status = DIRCACHE_IDLE;
2733 info->build_ticks = 0;
2745 2734
2746 for (unsigned int volume = 0; volume < NUM_VOLUMES; volume++) 2735 FOR_EACH_VOLUME(-1, volume)
2747 { 2736 {
2748 struct dircache_volume *dcvolp = DCVOL(volume); 2737 struct dircache_volume *dcvolp = DCVOL(volume);
2749 enum dircache_status volstatus = dcvolp->status; 2738 enum dircache_status volstatus = dcvolp->status;
@@ -2781,11 +2770,18 @@ void dircache_get_info(struct dircache_info *info)
2781 /* report usage only if there is something ready or being built */ 2770 /* report usage only if there is something ready or being built */
2782 if (status != DIRCACHE_IDLE) 2771 if (status != DIRCACHE_IDLE)
2783 { 2772 {
2784 info->reserve_used = reserve_buf_used();
2785 info->size = dircache.size; 2773 info->size = dircache.size;
2786 info->sizeused = dircache.sizeused; 2774 info->sizeused = dircache.sizeused;
2775 info->reserve_used = reserve_buf_used();
2787 info->entry_count = dircache.numentries; 2776 info->entry_count = dircache.numentries;
2788 } 2777 }
2778 else
2779 {
2780 info->size = 0;
2781 info->sizeused = 0;
2782 info->reserve_used = 0;
2783 info->entry_count = 0;
2784 }
2789 2785
2790 dircache_unlock(); 2786 dircache_unlock();
2791} 2787}
@@ -2817,7 +2813,7 @@ void dircache_dump(void)
2817 dircache_runinfo.bufsize + 1); 2813 dircache_runinfo.bufsize + 1);
2818 2814
2819 /* CSV */ 2815 /* CSV */
2820 fdprintf(fdcsv, "\"Index\",\"Serialnum\"," 2816 fdprintf(fdcsv, "\"Index\",\"Serialnum\",\"Serialhash\","
2821 "\"Path\",\"Frontier\"," 2817 "\"Path\",\"Frontier\","
2822 "\"Attribute\",\"File Size\"," 2818 "\"Attribute\",\"File Size\","
2823 "\"Mod Date\",\"Mod Time\"\n"); 2819 "\"Mod Date\",\"Mod Time\"\n");
@@ -2833,11 +2829,12 @@ void dircache_dump(void)
2833 get_volume_name(volume, name); 2829 get_volume_name(volume, name);
2834 #endif 2830 #endif
2835 fdprintf(fdcsv, 2831 fdprintf(fdcsv,
2836 "%d,%lu," 2832 "%d," DC_SERIAL_FMT "," DC_SERIAL_FMT ","
2837 "\"%c" IF_MV("%s") "\",%u," 2833 "\"%c" IF_MV("%s") "\",%u,"
2838 "0x%08X,0," 2834 "0x%08X,0,"
2839 "\"\",\"\"\n", 2835 "\"\",\"\"\n",
2840 -volume-1, dcvolp->serialnum, 2836 -volume-1, dcvolp->serialnum,
2837 dc_hash_serialnum(dcvolp->serialnum, DC_SERHASH_START),
2841 PATH_SEPCH, IF_MV(name,) dcvolp->frontier, 2838 PATH_SEPCH, IF_MV(name,) dcvolp->frontier,
2842 ATTR_DIRECTORY | ATTR_VOLUME); 2839 ATTR_DIRECTORY | ATTR_VOLUME);
2843 } 2840 }
@@ -2855,15 +2852,23 @@ void dircache_dump(void)
2855 char buf[DC_MAX_NAME + 2]; 2852 char buf[DC_MAX_NAME + 2];
2856 *buf = '\0'; 2853 *buf = '\0';
2857 int idx = get_index(ce); 2854 int idx = get_index(ce);
2858 get_path_sub(idx, buf, sizeof (buf)); 2855
2856 struct get_path_sub_data data =
2857 {
2858 .buf = buf,
2859 .size = sizeof (buf),
2860 .serialhash = DC_SERHASH_START,
2861 };
2862
2863 get_path_sub(idx, &data);
2859 2864
2860 fdprintf(fdcsv, 2865 fdprintf(fdcsv,
2861 "%d,%lu," 2866 "%d," DC_SERIAL_FMT "," DC_SERIAL_FMT ","
2862 "\"%s\",%u," 2867 "\"%s\",%u,"
2863 "0x%08X,%lu," 2868 "0x%08X,%lu,"
2864 "%04d/%02d/%02d," 2869 "%04d/%02d/%02d,"
2865 "%02d:%02d:%02d\n", 2870 "%02d:%02d:%02d\n",
2866 idx, ce->serialnum, 2871 idx, ce->serialnum, data.serialhash,
2867 buf, ce->frontier, 2872 buf, ce->frontier,
2868 ce->attr, (ce->attr & ATTR_DIRECTORY) ? 2873 ce->attr, (ce->attr & ATTR_DIRECTORY) ?
2869 0ul : (unsigned long)ce->filesize, 2874 0ul : (unsigned long)ce->filesize,
diff --git a/firmware/common/file.c b/firmware/common/file.c
index 6444918f1b..1f93824dc8 100644
--- a/firmware/common/file.c
+++ b/firmware/common/file.c
@@ -54,7 +54,7 @@ static struct filestr_desc * get_filestr(int fildes)
54 return file; 54 return file;
55 55
56 DEBUGF("fildes %d: bad file number\n", fildes); 56 DEBUGF("fildes %d: bad file number\n", fildes);
57 errno = (file && file->stream.flags == FV_NONEXIST) ? ENXIO : EBADF; 57 errno = (file && (file->stream.flags & FD_NONEXIST)) ? ENXIO : EBADF;
58 return NULL; 58 return NULL;
59} 59}
60 60
@@ -187,24 +187,28 @@ file_error:
187 return rc; 187 return rc;
188} 188}
189 189
190/* callback for each file stream to make sure all data is in sync with new 190/* Handle syncing all file's streams to the truncation */
191 size */ 191static void handle_truncate(struct filestr_desc * const file, file_size_t size)
192void ftruncate_internal_callback(struct filestr_base *stream,
193 struct filestr_base *s)
194{ 192{
195 struct filestr_desc *file = (struct filestr_desc *)s; 193 unsigned long filesectors = filesize_sectors(size);
196 file_size_t size = *file->sizep;
197
198 /* caches with data beyond new extents are invalid */
199 unsigned long sector = file->stream.cachep->sector;
200 if (sector != INVALID_SECNUM && sector >= filesize_sectors(size))
201 filestr_discard_cache(&file->stream);
202 194
203 /* keep all positions within bounds */ 195 struct filestr_base *s = NULL;
204 if (file->offset > size) 196 while ((s = fileobj_get_next_stream(&file->stream, s)))
205 file->offset = size; 197 {
206 198 /* caches with data beyond new extents are invalid */
207 (void)stream; 199 unsigned long sector = s->cachep->sector;
200 if (sector != INVALID_SECNUM && sector >= filesectors)
201 filestr_discard_cache(s);
202
203 /* files outside bounds must be rewound */
204 if (fat_query_sectornum(&s->fatstr) > filesectors)
205 fat_seek_to_stream(&s->fatstr, &file->stream.fatstr);
206
207 /* clip file offset too if needed */
208 struct filestr_desc *f = (struct filestr_desc *)s;
209 if (f->offset > size)
210 f->offset = size;
211 }
208} 212}
209 213
210/* truncate the file to the specified length */ 214/* truncate the file to the specified length */
@@ -246,13 +250,17 @@ static int ftruncate_internal(struct filestr_desc *file, file_size_t size,
246 rc2 = fat_truncate(&file->stream.fatstr); 250 rc2 = fat_truncate(&file->stream.fatstr);
247 if (rc2 < 0) 251 if (rc2 < 0)
248 FILE_ERROR(EIO, rc2 * 10 - 3); 252 FILE_ERROR(EIO, rc2 * 10 - 3);
253
254 /* never needs to be done this way again since any data beyond the
255 cached size is now gone */
256 fileobj_change_flags(&file->stream, 0, FO_TRUNC);
249 } 257 }
250 /* else just change the cached file size */ 258 /* else just change the cached file size */
251 259
252 if (truncsize < cursize) 260 if (truncsize < cursize)
253 { 261 {
254 *file->sizep = truncsize; 262 *file->sizep = truncsize;
255 fileop_ontruncate_internal(&file->stream); 263 handle_truncate(file, truncsize);
256 } 264 }
257 265
258 /* if truncation was partially successful, it effectively destroyed 266 /* if truncation was partially successful, it effectively destroyed
@@ -299,10 +307,6 @@ static int fsync_internal(struct filestr_desc *file)
299 int rc2 = ftruncate_internal(file, size, rc == 0); 307 int rc2 = ftruncate_internal(file, size, rc == 0);
300 if (rc2 < 0) 308 if (rc2 < 0)
301 FILE_ERROR(ERRNO, rc2 * 10 - 2); 309 FILE_ERROR(ERRNO, rc2 * 10 - 2);
302
303 /* never needs to be done this way again since any data beyond the
304 cached size is now gone */
305 fileobj_change_flags(&file->stream, 0, FO_TRUNC);
306 } 310 }
307 311
308file_error:; 312file_error:;
@@ -327,8 +331,7 @@ static int close_internal(struct filestr_desc *file)
327 /* call only when holding WRITER lock (updates directory entries) */ 331 /* call only when holding WRITER lock (updates directory entries) */
328 int rc; 332 int rc;
329 333
330 if ((file->stream.flags & FD_WRITE) && 334 if ((file->stream.flags & (FD_WRITE|FD_NONEXIST)) == FD_WRITE)
331 !(fileobj_get_flags(&file->stream) & FO_REMOVED))
332 { 335 {
333 rc = fsync_internal(file); 336 rc = fsync_internal(file);
334 if (rc < 0) 337 if (rc < 0)
@@ -786,6 +789,12 @@ int open_noiso_internal(const char *path, int oflag)
786 return open_internal_locked(path, oflag, FF_ANYTYPE | FF_NOISO); 789 return open_internal_locked(path, oflag, FF_ANYTYPE | FF_NOISO);
787} 790}
788 791
792void force_close_writer_internal(struct filestr_base *stream)
793{
794 /* only we do writers so we know this is our guy */
795 close_internal((struct filestr_desc *)stream);
796}
797
789 798
790/** POSIX **/ 799/** POSIX **/
791 800
diff --git a/firmware/common/file_internal.c b/firmware/common/file_internal.c
index f9c6bfb570..aa0edb7ebb 100644
--- a/firmware/common/file_internal.c
+++ b/firmware/common/file_internal.c
@@ -292,7 +292,7 @@ struct pathwalk_component
292 struct pathwalk_component *nextp; /* parent if in use else next free */ 292 struct pathwalk_component *nextp; /* parent if in use else next free */
293}; 293};
294 294
295#define WALK_RC_NOT_FOUND 0 /* successfully not found */ 295#define WALK_RC_NOT_FOUND 0 /* successfully not found (aid for file creation) */
296#define WALK_RC_FOUND 1 /* found and opened */ 296#define WALK_RC_FOUND 1 /* found and opened */
297#define WALK_RC_FOUND_ROOT 2 /* found and opened sys/volume root */ 297#define WALK_RC_FOUND_ROOT 2 /* found and opened sys/volume root */
298#define WALK_RC_CONT_AT_ROOT 3 /* continue at root level */ 298#define WALK_RC_CONT_AT_ROOT 3 /* continue at root level */
@@ -351,7 +351,10 @@ static int fill_path_compinfo(struct pathwalk *walkp,
351 compinfo->length = compp->length; 351 compinfo->length = compp->length;
352 compinfo->attr = compp->attr; 352 compinfo->attr = compp->attr;
353 compinfo->filesize = walkp->filesize; 353 compinfo->filesize = walkp->filesize;
354 compinfo->parentinfo = (compp->nextp ?: compp)->info; 354 if (!(walkp->callflags & FF_SELFINFO))
355 compinfo->parentinfo = (compp->nextp ?: compp)->info;
356 else
357 compinfo->info = compp->info;
355 } 358 }
356 359
357 return rc; 360 return rc;
@@ -393,7 +396,10 @@ static int walk_open_info(struct pathwalk *walkp,
393 else 396 else
394 callflags &= ~FO_DIRECTORY; 397 callflags &= ~FO_DIRECTORY;
395 398
396 fileop_onopen_internal(stream, &compp->info, callflags); 399 /* make open official if not simply probing for presence */
400 if (!(callflags & FF_PROBE))
401 fileop_onopen_internal(stream, &compp->info, callflags);
402
397 return compp->nextp ? WALK_RC_FOUND : WALK_RC_FOUND_ROOT; 403 return compp->nextp ? WALK_RC_FOUND : WALK_RC_FOUND_ROOT;
398} 404}
399 405
@@ -511,7 +517,8 @@ walk_path(struct pathwalk *walkp, struct pathwalk_component *compp,
511 { 517 {
512 /* is ".." */ 518 /* is ".." */
513 struct pathwalk_component *parentp = compp->nextp; 519 struct pathwalk_component *parentp = compp->nextp;
514 if (!parentp) 520
521 if (!parentp IF_MV( || parentp->attr == ATTR_SYSTEM_ROOT ))
515 return WALK_RC_CONT_AT_ROOT; 522 return WALK_RC_CONT_AT_ROOT;
516 523
517 compp->nextp = freep; 524 compp->nextp = freep;
@@ -567,6 +574,9 @@ int open_stream_internal(const char *path, unsigned int callflags,
567 if (!compinfo) 574 if (!compinfo)
568 callflags &= ~FF_CHECKPREFIX; 575 callflags &= ~FF_CHECKPREFIX;
569 576
577 /* This lets it be passed quietly to directory scanning */
578 stream->flags = callflags & FF_MASK;
579
570 struct pathwalk walk; 580 struct pathwalk walk;
571 walk.path = path; 581 walk.path = path;
572 walk.callflags = callflags; 582 walk.callflags = callflags;
@@ -575,7 +585,7 @@ int open_stream_internal(const char *path, unsigned int callflags,
575 585
576 struct pathwalk_component *rootp = pathwalk_comp_alloc(NULL); 586 struct pathwalk_component *rootp = pathwalk_comp_alloc(NULL);
577 rootp->nextp = NULL; 587 rootp->nextp = NULL;
578 rootp->attr = ATTR_DIRECTORY; 588 rootp->attr = ATTR_SYSTEM_ROOT;
579 589
580#ifdef HAVE_MULTIVOLUME 590#ifdef HAVE_MULTIVOLUME
581 int volume = 0, rootrc = WALK_RC_FOUND; 591 int volume = 0, rootrc = WALK_RC_FOUND;
@@ -584,7 +594,7 @@ int open_stream_internal(const char *path, unsigned int callflags,
584 while (1) 594 while (1)
585 { 595 {
586 const char *pathptr = walk.path; 596 const char *pathptr = walk.path;
587 597
588 #ifdef HAVE_MULTIVOLUME 598 #ifdef HAVE_MULTIVOLUME
589 /* this seamlessly integrates secondary filesystems into the 599 /* this seamlessly integrates secondary filesystems into the
590 root namespace (e.g. "/<0>/../../<1>/../foo/." :<=> "/foo") */ 600 root namespace (e.g. "/<0>/../../<1>/../foo/." :<=> "/foo") */
@@ -596,8 +606,19 @@ int open_stream_internal(const char *path, unsigned int callflags,
596 FILE_ERROR(ENXIO, -2); 606 FILE_ERROR(ENXIO, -2);
597 } 607 }
598 608
599 /* the root of this subpath is the system root? */ 609 if (p == pathptr)
600 rootrc = p == pathptr ? WALK_RC_FOUND_ROOT : WALK_RC_FOUND; 610 {
611 /* the root of this subpath is the system root */
612 rootp->attr = ATTR_SYSTEM_ROOT;
613 rootrc = WALK_RC_FOUND_ROOT;
614 }
615 else
616 {
617 /* this subpath specifies a mount point */
618 rootp->attr = ATTR_MOUNT_POINT;
619 rootrc = WALK_RC_FOUND;
620 }
621
601 walk.path = p; 622 walk.path = p;
602 #endif /* HAVE_MULTIVOLUME */ 623 #endif /* HAVE_MULTIVOLUME */
603 624
@@ -633,11 +654,9 @@ int open_stream_internal(const char *path, unsigned int callflags,
633 default: /* utter, abject failure :`( */ 654 default: /* utter, abject failure :`( */
634 DEBUGF("Open failed: rc=%d, errno=%d\n", rc, errno); 655 DEBUGF("Open failed: rc=%d, errno=%d\n", rc, errno);
635 filestr_base_destroy(stream); 656 filestr_base_destroy(stream);
636 FILE_ERROR(-rc, -2); 657 FILE_ERROR(-rc, -3);
637 } 658 }
638 659
639 file_cache_reset(stream->cachep);
640
641file_error: 660file_error:
642 return rc; 661 return rc;
643} 662}
@@ -757,9 +776,10 @@ int test_stream_exists_internal(const char *path, unsigned int callflags)
757{ 776{
758 /* only FF_* flags should be in callflags */ 777 /* only FF_* flags should be in callflags */
759 struct filestr_base stream; 778 struct filestr_base stream;
760 int rc = open_stream_internal(path, callflags, &stream, NULL); 779 int rc = open_stream_internal(path, callflags | FF_PROBE, &stream, NULL);
761 if (rc > 0) 780
762 close_stream_internal(&stream); 781 if (rc >= 0)
782 filestr_base_destroy(&stream);
763 783
764 return rc; 784 return rc;
765} 785}
diff --git a/firmware/common/fileobj_mgr.c b/firmware/common/fileobj_mgr.c
index 8e7831d36c..e34a460e10 100644
--- a/firmware/common/fileobj_mgr.c
+++ b/firmware/common/fileobj_mgr.c
@@ -187,7 +187,8 @@ void fileobj_fileop_open(struct filestr_base *stream,
187 ll_insert_last(&fobp->list, &stream->node); 187 ll_insert_last(&fobp->list, &stream->node);
188 188
189 /* initiate the new stream into the enclave */ 189 /* initiate the new stream into the enclave */
190 stream->flags = FDO_BUSY | (callflags & (FD_WRITE|FD_WRONLY|FD_APPEND)); 190 stream->flags = FDO_BUSY |
191 (callflags & (FF_MASK|FD_WRITE|FD_WRONLY|FD_APPEND));
191 stream->infop = &fobp->bind.info; 192 stream->infop = &fobp->bind.info;
192 stream->fatstr.fatfilep = &fobp->bind.info.fatfile; 193 stream->fatstr.fatfilep = &fobp->bind.info.fatfile;
193 stream->bindp = &fobp->bind; 194 stream->bindp = &fobp->bind;
@@ -202,14 +203,6 @@ void fileobj_fileop_open(struct filestr_base *stream,
202 fobp->writers = 0; 203 fobp->writers = 0;
203 fobp->size = 0; 204 fobp->size = 0;
204 205
205 if (callflags & FD_WRITE)
206 {
207 /* first one is a writer */
208 fobp->writers = 1;
209 file_cache_init(&fobp->cache);
210 filestr_assign_cache(stream, &fobp->cache);
211 }
212
213 fileobj_bind_file(&fobp->bind); 206 fileobj_bind_file(&fobp->bind);
214 } 207 }
215 else 208 else
@@ -225,32 +218,33 @@ void fileobj_fileop_open(struct filestr_base *stream,
225 DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n", 218 DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n",
226 __func__, stream, callflags); 219 __func__, stream, callflags);
227 } 220 }
221 }
228 222
229 if (fobp->writers) 223 if ((callflags & FD_WRITE) && ++fobp->writers == 1)
230 { 224 {
231 /* already writers present */ 225 /* first writer */
232 fobp->writers++; 226 file_cache_init(&fobp->cache);
233 filestr_assign_cache(stream, &fobp->cache); 227 FOR_EACH_STREAM(FIRST, fobp, s)
234 } 228 filestr_assign_cache(s, &fobp->cache);
235 else if (callflags & FD_WRITE) 229 }
236 { 230 else if (fobp->writers)
237 /* first writer */ 231 {
238 fobp->writers = 1; 232 /* already writers present */
239 file_cache_init(&fobp->cache); 233 filestr_assign_cache(stream, &fobp->cache);
240 FOR_EACH_STREAM(FIRST, fobp, s) 234 }
241 filestr_assign_cache(s, &fobp->cache); 235 else
242 } 236 {
243 /* else another reader */ 237 /* another reader and no writers present */
238 file_cache_reset(stream->cachep);
244 } 239 }
245} 240}
246 241
247/* close the stream and free associated resources */ 242/* close the stream and free associated resources */
248void fileobj_fileop_close(struct filestr_base *stream) 243void fileobj_fileop_close(struct filestr_base *stream)
249{ 244{
250 switch (stream->flags) 245 if (!(stream->flags & FDO_BUSY))
251 { 246 {
252 case 0: /* not added to manager */ 247 /* not added to manager or forced-closed by unmounting */
253 case FV_NONEXIST: /* forced-closed by unmounting */
254 filestr_base_destroy(stream); 248 filestr_base_destroy(stream);
255 return; 249 return;
256 } 250 }
@@ -260,30 +254,30 @@ void fileobj_fileop_close(struct filestr_base *stream)
260 254
261 ll_remove(&fobp->list, &stream->node); 255 ll_remove(&fobp->list, &stream->node);
262 256
263 if ((foflags & FO_SINGLE) || fobp->writers == 0) 257 if (foflags & FO_SINGLE)
264 { 258 {
265 if (foflags & FO_SINGLE) 259 /* last stream for file; close everything */
266 { 260 fileobj_unbind_file(&fobp->bind);
267 /* last stream for file; close everything */
268 fileobj_unbind_file(&fobp->bind);
269 261
270 if (fobp->writers) 262 if (fobp->writers)
271 file_cache_free(&fobp->cache); 263 file_cache_free(&fobp->cache);
272 264
273 binding_add_to_free_list(fobp); 265 binding_add_to_free_list(fobp);
274 }
275 } 266 }
276 else if ((stream->flags & FD_WRITE) && --fobp->writers == 0) 267 else
277 { 268 {
278 /* only readers remain; switch back to stream-local caching */ 269 if ((stream->flags & FD_WRITE) && --fobp->writers == 0)
279 FOR_EACH_STREAM(FIRST, fobp, s) 270 {
280 filestr_copy_cache(s, &fobp->cache); 271 /* only readers remain; switch back to stream-local caching */
272 FOR_EACH_STREAM(FIRST, fobp, s)
273 filestr_copy_cache(s, &fobp->cache);
281 274
282 file_cache_free(&fobp->cache); 275 file_cache_free(&fobp->cache);
283 } 276 }
284 277
285 if (!(foflags & FO_SINGLE) && fobp->list.head == fobp->list.tail) 278 if (fobp->list.head == fobp->list.tail)
286 fobp->flags |= FO_SINGLE; /* only one open stream remaining */ 279 fobp->flags |= FO_SINGLE; /* only one open stream remaining */
280 }
287 281
288 filestr_base_destroy(stream); 282 filestr_base_destroy(stream);
289} 283}
@@ -320,15 +314,10 @@ void fileobj_fileop_rename(struct filestr_base *stream,
320/* informs manager than directory entries have been updated */ 314/* informs manager than directory entries have been updated */
321void fileobj_fileop_sync(struct filestr_base *stream) 315void fileobj_fileop_sync(struct filestr_base *stream)
322{ 316{
323 fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1); 317 if (((struct fileobj_binding *)stream->bindp)->flags & FO_REMOVED)
324} 318 return; /* no dir to sync */
325 319
326/* inform manager that file has been truncated */ 320 fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1);
327void fileobj_fileop_truncate(struct filestr_base *stream)
328{
329 /* let caller update internal info */
330 FOR_EACH_STREAM(FIRST, (struct fileobj_binding *)stream->bindp, s)
331 ftruncate_internal_callback(stream, s);
332} 321}
333 322
334/* query for the pointer to the size storage for the file object */ 323/* query for the pointer to the size storage for the file object */
@@ -340,6 +329,16 @@ file_size_t * fileobj_get_sizep(const struct filestr_base *stream)
340 return &((struct fileobj_binding *)stream->bindp)->size; 329 return &((struct fileobj_binding *)stream->bindp)->size;
341} 330}
342 331
332/* iterate the list of streams for this stream's file */
333struct filestr_base * fileobj_get_next_stream(const struct filestr_base *stream,
334 const struct filestr_base *s)
335{
336 if (!stream->bindp)
337 return NULL;
338
339 return s ? STREAM_NEXT(s) : STREAM_FIRST((struct fileobj_binding *)stream->bindp);
340}
341
343/* query manager bitflags for the file object */ 342/* query manager bitflags for the file object */
344unsigned int fileobj_get_flags(const struct filestr_base *stream) 343unsigned int fileobj_get_flags(const struct filestr_base *stream)
345{ 344{
@@ -349,20 +348,21 @@ unsigned int fileobj_get_flags(const struct filestr_base *stream)
349 return ((struct fileobj_binding *)stream->bindp)->flags; 348 return ((struct fileobj_binding *)stream->bindp)->flags;
350} 349}
351 350
352/* change manager bitflags for the file object */ 351/* change manager bitflags for the file object (permitted only) */
353void fileobj_change_flags(struct filestr_base *stream, 352void fileobj_change_flags(struct filestr_base *stream,
354 unsigned int flags, unsigned int mask) 353 unsigned int flags, unsigned int mask)
355{ 354{
356 struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp; 355 struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp;
357 if (fobp) 356 if (!fobp)
358 fobp->flags = (fobp->flags & ~mask) | (flags & mask); 357 return;
358
359 mask &= FDO_CHG_MASK;
360 fobp->flags = (fobp->flags & ~mask) | (flags & mask);
359} 361}
360 362
361/* mark all open streams on a device as "nonexistant" */ 363/* mark all open streams on a device as "nonexistant" */
362void fileobj_mgr_unmount(IF_MV_NONVOID(int volume)) 364void fileobj_mgr_unmount(IF_MV_NONVOID(int volume))
363{ 365{
364 /* right now, there is nothing else to be freed when marking a descriptor
365 as "nonexistant" but a callback could be added if that changes */
366 FOR_EACH_VOLUME(volume, v) 366 FOR_EACH_VOLUME(volume, v)
367 { 367 {
368 struct fileobj_binding *fobp; 368 struct fileobj_binding *fobp;
@@ -371,11 +371,17 @@ void fileobj_mgr_unmount(IF_MV_NONVOID(int volume))
371 struct filestr_base *s; 371 struct filestr_base *s;
372 while ((s = STREAM_FIRST(fobp))) 372 while ((s = STREAM_FIRST(fobp)))
373 { 373 {
374 /* last ditch effort to preserve FS integrity; we could still
375 be alive (soft unmount, maybe); we get informed early */
376 fileop_onunmount_internal(s);
377
378 if (STREAM_FIRST(fobp) == s)
379 fileop_onclose_internal(s); /* above didn't close it */
380
374 /* keep it "busy" to avoid races; any valid file/directory 381 /* keep it "busy" to avoid races; any valid file/directory
375 descriptor returned by an open call should always be 382 descriptor returned by an open call should always be
376 closed by whomever opened it (of course!) */ 383 closed by whoever opened it (of course!) */
377 fileop_onclose_internal(s); 384 s->flags = (s->flags & ~FDO_BUSY) | FD_NONEXIST;
378 s->flags = FV_NONEXIST;
379 } 385 }
380 } 386 }
381 } 387 }