@@ -294,6 +294,10 @@ function to_fs_xattr(xattr) {
294
294
return _ . mapKeys ( xattr , ( val , key ) => XATTR_USER_PREFIX + key ) ;
295
295
}
296
296
297
+ function filter_fs_xattr ( xattr ) {
298
+ return _ . pickBy ( xattr , ( val , key ) => key ?. startsWith ( XATTR_NOOBAA_INTERNAL_PREFIX ) ) ;
299
+ }
300
+
297
301
/**
298
302
* get_random_delay returns a random delay number between base + min and max
299
303
* @param {number } base
@@ -1199,7 +1203,7 @@ class NamespaceFS {
1199
1203
try {
1200
1204
await this . _check_path_in_bucket_boundaries ( fs_context , file_path ) ;
1201
1205
1202
- if ( this . empty_dir_content_flow ( file_path , params ) ) {
1206
+ if ( this . should_use_empty_content_dir_optimization ( ) && this . empty_dir_content_flow ( file_path , params ) ) {
1203
1207
const content_dir_info = await this . _create_empty_dir_content ( fs_context , params , file_path ) ;
1204
1208
return content_dir_info ;
1205
1209
}
@@ -1337,7 +1341,8 @@ class NamespaceFS {
1337
1341
const part_upload = file_path === upload_path ;
1338
1342
const same_inode = params . copy_source && copy_res === COPY_STATUS_ENUM . SAME_INODE ;
1339
1343
const should_replace_xattr = params . copy_source ? copy_res === COPY_STATUS_ENUM . FALLBACK : true ;
1340
- const is_dir_content = this . _is_directory_content ( file_path , params . key ) ;
1344
+ const is_dir_content_unoptimized_flow = this . _is_directory_content ( file_path , params . key ) &&
1345
+ this . should_use_empty_content_dir_optimization ( ) ;
1341
1346
1342
1347
const stat = await target_file . stat ( fs_context ) ;
1343
1348
this . _verify_encryption ( params . encryption , this . _get_encryption_info ( stat ) ) ;
@@ -1380,18 +1385,21 @@ class NamespaceFS {
1380
1385
} ) ;
1381
1386
}
1382
1387
}
1383
- if ( fs_xattr && ! is_dir_content && should_replace_xattr ) await target_file . replacexattr ( fs_context , fs_xattr ) ;
1388
+ if ( fs_xattr && ! is_dir_content_unoptimized_flow && should_replace_xattr ) {
1389
+ await target_file . replacexattr ( fs_context , fs_xattr ) ;
1390
+ }
1384
1391
// fsync
1385
1392
if ( config . NSFS_TRIGGER_FSYNC ) await target_file . fsync ( fs_context ) ;
1386
1393
dbg . log1 ( 'NamespaceFS._finish_upload:' , open_mode , file_path , upload_path , fs_xattr ) ;
1387
1394
1388
1395
if ( ! same_inode && ! part_upload ) {
1389
- await this . _move_to_dest ( fs_context , upload_path , file_path , target_file , open_mode , params . key , is_dir_content ) ;
1396
+ await this . _move_to_dest ( fs_context , upload_path , file_path , target_file , open_mode , params . key ,
1397
+ is_dir_content_unoptimized_flow ) ;
1390
1398
}
1391
1399
1392
1400
// when object is a dir, xattr are set on the folder itself and the content is in .folder file
1393
1401
// we still should put the xattr if copy is link/same inode because we put the xattr on the directory
1394
- if ( is_dir_content ) {
1402
+ if ( is_dir_content_unoptimized_flow ) {
1395
1403
await this . _assign_dir_content_to_xattr ( fs_context , fs_xattr , { ...params , size : stat . size } , copy_xattr ) ;
1396
1404
}
1397
1405
stat . xattr = { ...stat . xattr , ...fs_xattr } ;
@@ -1419,16 +1427,14 @@ class NamespaceFS {
1419
1427
}
1420
1428
1421
1429
// move to dest GPFS (wt) / POSIX (w / undefined) - non part upload
1422
- async _move_to_dest ( fs_context , source_path , dest_path , target_file , open_mode , key , is_dir_content ) {
1423
- dbg . log2 ( '_move_to_dest' , fs_context , source_path , dest_path , target_file , open_mode , key , is_dir_content ) ;
1430
+ async _move_to_dest ( fs_context , source_path , dest_path , target_file , open_mode , key , is_dir_content_unoptimized_flow ) {
1431
+ dbg . log2 ( '_move_to_dest' , fs_context , source_path , dest_path , target_file , open_mode , key , is_dir_content_unoptimized_flow ) ;
1424
1432
let retries = config . NSFS_RENAME_RETRIES ;
1425
1433
// will retry renaming a file in case of parallel deleting of the destination path
1426
1434
for ( ; ; ) {
1427
1435
try {
1428
1436
await native_fs_utils . _make_path_dirs ( dest_path , fs_context ) ;
1429
- if ( this . _is_versioning_disabled ( ) ||
1430
- ( this . _is_versioning_enabled ( ) && is_dir_content ) ) {
1431
- // dir_content is not supported in versioning, hence we will treat it like versioning disabled
1437
+ if ( this . _is_versioning_disabled ( ) || is_dir_content_unoptimized_flow ) {
1432
1438
if ( open_mode === 'wt' ) {
1433
1439
await target_file . linkfileat ( fs_context , dest_path ) ;
1434
1440
} else {
@@ -1471,6 +1477,7 @@ class NamespaceFS {
1471
1477
dbg . log1 ( 'Namespace_fs._move_to_dest_version:' , new_ver_tmp_path , latest_ver_path , upload_file ) ;
1472
1478
let gpfs_options ;
1473
1479
const is_gpfs = native_fs_utils . _is_gpfs ( fs_context ) ;
1480
+ const is_dir_content = this . _is_directory_content ( latest_ver_path , key ) ;
1474
1481
let retries = config . NSFS_RENAME_RETRIES ;
1475
1482
for ( ; ; ) {
1476
1483
try {
@@ -1490,7 +1497,7 @@ class NamespaceFS {
1490
1497
latest_ver_info = await this . _get_version_info ( fs_context , latest_ver_path ) ;
1491
1498
}
1492
1499
const bucket_tmp_dir_path = this . get_bucket_tmpdir_full_path ( ) ;
1493
- const versioned_path = latest_ver_info && this . _get_version_path ( key , latest_ver_info . version_id_str ) ;
1500
+ const versioned_path = latest_ver_info && this . _get_version_path ( key , latest_ver_info . version_id_str , is_dir_content ) ;
1494
1501
dbg . log1 ( 'Namespace_fs._move_to_dest_version:' , latest_ver_info , new_ver_info , gpfs_options ) ;
1495
1502
1496
1503
if ( this . _is_versioning_suspended ( ) ) {
@@ -1513,6 +1520,9 @@ class NamespaceFS {
1513
1520
await native_fs_utils . safe_move ( fs_context , latest_ver_path , versioned_path , latest_ver_info ,
1514
1521
gpfs_options ?. move_to_versions , bucket_tmp_dir_path ) ;
1515
1522
}
1523
+ if ( is_dir_content ) {
1524
+ await this . _handle_latest_disabled_dir_content_xattr ( fs_context , key , versioned_path , latest_ver_path ) ;
1525
+ }
1516
1526
try {
1517
1527
// move new version to latest_ver_path (key path)
1518
1528
await native_fs_utils . safe_move ( fs_context , new_ver_tmp_path , latest_ver_path , new_ver_info ,
@@ -1536,6 +1546,37 @@ class NamespaceFS {
1536
1546
}
1537
1547
}
1538
1548
1549
+ /** handle xattr of content dir when moving from disabled to enabled mode.
1550
+ * in the disabled version of content dir, the xattr is on the directory itself. so need to move it seperatly from the obejct
1551
+ * in case of enabled mode we need to move the xattr to the new object
1552
+ * both for suspended and enabled mode we need to clear the user xattr from the directory
1553
+ * @param {nb.NativeFSContext } fs_context
1554
+ * @param {string } key
1555
+ * @param {string } latest_ver_path
1556
+ * @param {string } versioned_path
1557
+ */
1558
+ async _handle_latest_disabled_dir_content_xattr ( fs_context , key , versioned_path , latest_ver_path ) {
1559
+ const latest_version_dir_path = path . dirname ( latest_ver_path ) ;
1560
+ const directory_stat = await native_fs_utils . stat_ignore_enoent ( fs_context , latest_version_dir_path ) ;
1561
+ const is_disabled_dir_content = directory_stat && directory_stat . xattr && directory_stat . xattr [ XATTR_DIR_CONTENT ] ;
1562
+ if ( is_disabled_dir_content ) {
1563
+ if ( this . _is_versioning_enabled ( ) ) {
1564
+ dbg . log1 ( 'NamespaceFS._move_to_dest_version latest object is a directory object with attributes on the directory. move the xattr to the new .version file' ) ;
1565
+ if ( versioned_path ) {
1566
+ await this . set_fs_xattr_op ( fs_context , versioned_path , filter_fs_xattr ( directory_stat . xattr ) , undefined ) ;
1567
+ } else {
1568
+ //if no versioned_path, then we have empty content dir. need to create new .folder file
1569
+ //this scenario happens only after moving from disabled to enabled mode or after upgrade. version-id is always null
1570
+ versioned_path = this . _get_version_path ( key , NULL_VERSION_ID , true ) ;
1571
+ await native_fs_utils . _make_path_dirs ( versioned_path , fs_context ) ;
1572
+ //in case of empty directory object .folder of the latest doesn't exist. use 'w' to create it if its missing
1573
+ await this . set_fs_xattr_op ( fs_context , versioned_path , filter_fs_xattr ( directory_stat . xattr ) , undefined , "w" ) ;
1574
+ }
1575
+ }
1576
+ await this . _clear_user_xattr ( fs_context , latest_version_dir_path , XATTR_USER_PREFIX ) ;
1577
+ }
1578
+ }
1579
+
1539
1580
// Comparing both device and inode number (st_dev and st_ino returned by stat)
1540
1581
// will tell you whether two different file names refer to the same thing.
1541
1582
// If so, we will return the etag and encryption info of the file_path
@@ -2284,10 +2325,10 @@ class NamespaceFS {
2284
2325
* @param {* } clear - the xattr prefix to be cleared
2285
2326
* @returns {Promise<void> }
2286
2327
*/
2287
- async set_fs_xattr_op ( fs_context , file_path , set , clear ) {
2328
+ async set_fs_xattr_op ( fs_context , file_path , set , clear , mode = config . NSFS_OPEN_READ_MODE ) {
2288
2329
let file ;
2289
2330
try {
2290
- file = await nb_native ( ) . fs . open ( fs_context , file_path , config . NSFS_OPEN_READ_MODE ,
2331
+ file = await nb_native ( ) . fs . open ( fs_context , file_path , mode ,
2291
2332
native_fs_utils . get_umasked_mode ( config . BASE_MODE_FILE ) ) ;
2292
2333
await file . replacexattr ( fs_context , set , clear ) ;
2293
2334
await file . close ( fs_context ) ;
@@ -2673,6 +2714,10 @@ class NamespaceFS {
2673
2714
const is_dir_content = this . _is_directory_content ( file_path , params . key ) ;
2674
2715
return is_dir_content && params . size === 0 ;
2675
2716
}
2717
+
2718
+ should_use_empty_content_dir_optimization ( ) {
2719
+ return this . _is_versioning_disabled ( ) || ! config . NSFS_CONTENT_DIRECTORY_VERSIONING_ENABLED ;
2720
+ }
2676
2721
/**
2677
2722
* returns if should force md5 calculation for the bucket/account.
2678
2723
* first check if defined for bucket / account, if not use global default
@@ -2730,9 +2775,12 @@ class NamespaceFS {
2730
2775
}
2731
2776
2732
2777
// returns version path of the form bucket_path/dir/.versions/{key}_{version_id}
2733
- _get_version_path ( key , version_id ) {
2734
- const key_version = path . basename ( key ) + ( version_id ? '_' + version_id : '' ) ;
2735
- return path . normalize ( path . join ( this . bucket_path , path . dirname ( key ) , HIDDEN_VERSIONS_PATH , key_version ) ) ;
2778
+ // in case of directory content, the path is bucket_path/dir/{key}/.versions/.folder_{version_id}
2779
+ _get_version_path ( key , version_id , is_dir_content = false ) {
2780
+ const key_name = is_dir_content ? config . NSFS_FOLDER_OBJECT_NAME : path . basename ( key ) ;
2781
+ const dir_name = is_dir_content ? key : path . dirname ( key ) ;
2782
+ const key_version = key_name + ( version_id ? '_' + version_id : '' ) ;
2783
+ return path . normalize ( path . join ( this . bucket_path , dir_name , HIDDEN_VERSIONS_PATH , key_version ) ) ;
2736
2784
}
2737
2785
2738
2786
// this function returns the following version information -
0 commit comments