@@ -713,10 +713,19 @@ class NamespaceFS {
713
713
if ( ! delimiter && r . common_prefix ) {
714
714
await process_dir ( r . key ) ;
715
715
} else {
716
- if ( pos < results . length ) {
717
- results . splice ( pos , 0 , r ) ;
718
- } else {
719
- results . push ( r ) ;
716
+ const entry_path = path . join ( this . bucket_path , r . key ) ;
717
+ // If entry is outside of bucket, returns stat of symbolic link
718
+ const use_lstat = ! ( await this . _is_path_in_bucket_boundaries ( fs_context , entry_path ) ) ;
719
+ const stat = await native_fs_utils . stat_if_exists ( fs_context , entry_path ,
720
+ use_lstat , config . NSFS_LIST_IGNORE_ENTRY_ON_EACCES ) ;
721
+ if ( stat ) {
722
+ r . stat = stat ;
723
+ // add the result only if we have the stat information
724
+ if ( pos < results . length ) {
725
+ results . splice ( pos , 0 , r ) ;
726
+ } else {
727
+ results . push ( r ) ;
728
+ }
720
729
}
721
730
if ( results . length > limit ) {
722
731
results . length = limit ;
@@ -756,6 +765,11 @@ class NamespaceFS {
756
765
await insert_entry_to_results_arr ( r ) ;
757
766
} ;
758
767
768
+ // our current mechanism - list the files and skipping inaccessible directory (invisible in the list).
769
+ // We use this check_access in case the directory is not accessible inside a bucket.
770
+ // In a directory if we don’t have access to the directory, we want to skip the directory and its sub directories from the list.
771
+ // We did it outside to avoid undefined values in the cache.
772
+ // Note: It is not the same case as a file without permission.
759
773
if ( ! ( await this . check_access ( fs_context , dir_path ) ) ) return ;
760
774
try {
761
775
if ( list_versions ) {
@@ -877,13 +891,6 @@ class NamespaceFS {
877
891
878
892
const prefix_dir_key = prefix . slice ( 0 , prefix . lastIndexOf ( '/' ) + 1 ) ;
879
893
await process_dir ( prefix_dir_key ) ;
880
- await Promise . all ( results . map ( async r => {
881
- if ( r . common_prefix ) return ;
882
- const entry_path = path . join ( this . bucket_path , r . key ) ;
883
- //If entry is outside of bucket, returns stat of symbolic link
884
- const use_lstat = ! ( await this . _is_path_in_bucket_boundaries ( fs_context , entry_path ) ) ;
885
- r . stat = await nb_native ( ) . fs . stat ( fs_context , entry_path , { use_lstat } ) ;
886
- } ) ) ;
887
894
const res = {
888
895
objects : [ ] ,
889
896
common_prefixes : [ ] ,
@@ -1007,10 +1014,15 @@ class NamespaceFS {
1007
1014
file_path = await this . _find_version_path ( fs_context , params ) ;
1008
1015
await this . _check_path_in_bucket_boundaries ( fs_context , file_path ) ;
1009
1016
1010
- // NOTE: don't move this code after the open
1011
- // this can lead to ENOENT failures due to file not exists when content size is 0
1012
- // if entry is a directory object and its content size = 0 - return empty response
1013
- if ( await this . _is_empty_directory_content ( file_path , fs_context , params ) ) return null ;
1017
+ // NOTE: don't move this code after the open
1018
+ // this can lead to ENOENT failures due to file not exists when content size is 0
1019
+ // if entry is a directory object and its content size = 0 - return empty response
1020
+ if ( await this . _is_empty_directory_content ( file_path , fs_context , params ) ) {
1021
+ res . end ( ) ;
1022
+ // since we don't write anything to the stream wait_finished might not be needed. added just in case there is a delay
1023
+ await stream_utils . wait_finished ( res , { signal : object_sdk . abort_controller . signal } ) ;
1024
+ return null ;
1025
+ }
1014
1026
1015
1027
file = await nb_native ( ) . fs . open (
1016
1028
fs_context ,
@@ -1427,10 +1439,10 @@ class NamespaceFS {
1427
1439
// will retry renaming a file in case of parallel deleting of the destination path
1428
1440
for ( ; ; ) {
1429
1441
try {
1430
- await native_fs_utils . _make_path_dirs ( dest_path , fs_context ) ;
1431
1442
if ( this . _is_versioning_disabled ( ) ||
1432
1443
( this . _is_versioning_enabled ( ) && is_dir_content ) ) {
1433
- // dir_content is not supported in versioning, hence we will treat it like versioning disabled
1444
+ // dir_content is not supported in versioning, hence we will treat it like versioning disabled
1445
+ await native_fs_utils . _make_path_dirs ( dest_path , fs_context ) ;
1434
1446
if ( open_mode === 'wt' ) {
1435
1447
await target_file . linkfileat ( fs_context , dest_path ) ;
1436
1448
} else {
@@ -1478,6 +1490,8 @@ class NamespaceFS {
1478
1490
try {
1479
1491
let new_ver_info ;
1480
1492
let latest_ver_info ;
1493
+ // dir might be deleted by other thread. will recreacte if missing
1494
+ await native_fs_utils . _make_path_dirs ( latest_ver_path , fs_context ) ;
1481
1495
if ( is_gpfs ) {
1482
1496
const latest_ver_info_exist = await native_fs_utils . is_path_exists ( fs_context , latest_ver_path ) ;
1483
1497
gpfs_options = await this . _open_files_gpfs ( fs_context , new_ver_tmp_path , latest_ver_path , upload_file ,
@@ -2558,12 +2572,32 @@ class NamespaceFS {
2558
2572
}
2559
2573
}
2560
2574
2575
+ /**
2576
+ * _delete_path_dirs deletes all the paths in the hierarchy that are empty after a successful delete
2577
+ * if the original file_path to be deleted is a regular object which means file_path is not a directory and it's not a directory object path -
2578
+ * before deletion of the parent directory -
2579
+ * if the parent directory is a directory object (has CONTENT_DIR xattr) - stop the deletion loop
2580
+ * else - delete the directory - if dir is not empty it will stop at the first non empty dir
2581
+ * NOTE - the directory object check is needed because when object size is zero we won't create a .folder file and the dir will be empty
2582
+ * therefore the deletion will succeed although we shouldn't delete the directory object
2583
+ * @param {String } file_path
2584
+ * @param {nb.NativeFSContext } fs_context
2585
+ */
2561
2586
async _delete_path_dirs ( file_path , fs_context ) {
2562
2587
try {
2563
- let dir = path . dirname ( file_path ) ;
2564
- while ( dir !== this . bucket_path ) {
2565
- await nb_native ( ) . fs . rmdir ( fs_context , dir ) ;
2566
- dir = path . dirname ( dir ) ;
2588
+ let dir_path = path . dirname ( file_path ) ;
2589
+ const deleted_file_is_dir = file_path . endsWith ( '/' ) ;
2590
+ const deleted_file_is_dir_object = file_path . endsWith ( config . NSFS_FOLDER_OBJECT_NAME ) ;
2591
+ let should_check_dir_path_is_content_dir = ! deleted_file_is_dir && ! deleted_file_is_dir_object ;
2592
+ while ( dir_path !== this . bucket_path ) {
2593
+ if ( should_check_dir_path_is_content_dir ) {
2594
+ const dir_stat = await nb_native ( ) . fs . stat ( fs_context , dir_path ) ;
2595
+ const file_is_disabled_dir_content = dir_stat . xattr && dir_stat . xattr [ XATTR_DIR_CONTENT ] !== undefined ;
2596
+ if ( file_is_disabled_dir_content ) break ;
2597
+ }
2598
+ await nb_native ( ) . fs . rmdir ( fs_context , dir_path ) ;
2599
+ dir_path = path . dirname ( dir_path ) ;
2600
+ should_check_dir_path_is_content_dir = true ;
2567
2601
}
2568
2602
} catch ( err ) {
2569
2603
if ( err . code !== 'ENOTEMPTY' &&
@@ -2992,10 +3026,18 @@ class NamespaceFS {
2992
3026
return res ;
2993
3027
}
2994
3028
2995
- // delete version_id -
2996
- // 1. get version info, if it's empty - return
2997
- // 2. unlink key
2998
- // 3. if version is latest version - promote second latest -> latest
3029
+ /**
3030
+ * delete version_id does the following -
3031
+ * 1. get version info, if it's empty - return
3032
+ * 2. unlink key
3033
+ * 3. if version is latest version - promote second latest -> latest
3034
+ * 4. if it's the latest version - delete the directory hirerachy of the key if it's empty
3035
+ * if it's a past version - delete .versions/ and the directory hirerachy if it's empty
3036
+ * @param {nb.NativeFSContext } fs_context
3037
+ * @param {String } file_path
3038
+ * @param {Object } params
3039
+ * @returns {Promise<{deleted_delete_marker?: string, version_id?: string}> }
3040
+ */
2999
3041
async _delete_version_id ( fs_context , file_path , params ) {
3000
3042
// TODO optimization - GPFS link overrides, no need to unlink before promoting, but if there is nothing to promote we should unlink
3001
3043
const del_obj_version_info = await this . _delete_single_object_versioned ( fs_context , params . key , params . version_id ) ;
0 commit comments