@@ -4541,11 +4541,15 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
45414541 if (node == NULL )
45424542 goto error ;
45434543
4544+ /* If NOREPLACE flag is given, check if we should throw an error now.
4545+ If not, just remove the flag as it might cause problems with replacing whiteouts later. */
45444546 if (flags & RENAME_NOREPLACE && destnode && !destnode -> whiteout )
45454547 {
45464548 errno = EEXIST ;
45474549 goto error ;
45484550 }
4551+ else
4552+ flags = flags & ~RENAME_NOREPLACE ;
45494553
45504554 if (destnode )
45514555 {
@@ -4600,17 +4604,6 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
46004604 }
46014605 }
46024606
4603- /* If the destnode is a whiteout, first attempt to EXCHANGE the source and the destination,
4604- so that with one operation we get both the rename and the whiteout created. */
4605- if (destnode_is_whiteout )
4606- {
4607- ret = direct_renameat2 (srcfd , name , destfd , newname , flags |RENAME_EXCHANGE );
4608- if (ret == 0 )
4609- goto done ;
4610-
4611- /* If it fails for any reason, fallback to the more articulated method. */
4612- }
4613-
46144607 /* If the node is a directory we must ensure there is no whiteout at the
46154608 destination, otherwise the renameat2 will fail. Create a .wh.$NAME style
46164609 whiteout file until the renameat2 is completed. */
@@ -4622,39 +4615,51 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
46224615 unlinkat (destfd , newname , 0 );
46234616 }
46244617
4625- /* Try to create the whiteout atomically, if it fails do the
4626- rename+mknod separately. */
4627- if (! can_mknod )
4618+ bool src_needs_whiteout = ( node -> last_layer != get_upper_layer ( lo ));
4619+
4620+ if (src_needs_whiteout )
46284621 {
4629- ret = -1 ;
4630- errno = EPERM ;
4622+ /* Trying to atomically both rename and create the whiteout.
4623+ If destination is a whiteout, we can EXCHANGE source and destination and reuse the old whiteout.
4624+ If not, we can try to atomically create one with the WHITEOUT flag. */
4625+ if (destnode_is_whiteout )
4626+ ret = direct_renameat2 (srcfd , name , destfd , newname , flags |RENAME_EXCHANGE );
4627+ else
4628+ {
4629+ if (! can_mknod )
4630+ {
4631+ ret = -1 ;
4632+ errno = EPERM ;
4633+ }
4634+ else
4635+ ret = direct_renameat2 (srcfd , name , destfd , newname , flags |RENAME_WHITEOUT );
4636+ }
4637+
4638+ /* If atomic whiteout creation failed, fall back to separate rename and whiteout creation. */
4639+ if (ret < 0 )
4640+ {
4641+ ret = direct_renameat2 (srcfd , name , destfd , newname , flags );
4642+ if (ret < 0 )
4643+ goto error ;
4644+
4645+ ret = create_whiteout (lo , pnode , name , false, true);
4646+ if (ret < 0 )
4647+ goto error ;
4648+ }
46314649 }
46324650 else
46334651 {
4634- ret = direct_renameat2 (srcfd , name , destfd ,
4635- newname , flags |RENAME_WHITEOUT );
4636- }
4637- /* If the destination is a whiteout, just overwrite it. */
4638- if (ret < 0 && errno == EEXIST )
4639- ret = direct_renameat2 (srcfd , name , destfd , newname , flags & ~RENAME_NOREPLACE );
4640- if (ret < 0 )
4641- {
4642- ret = direct_renameat2 (srcfd , name , destfd ,
4643- newname , flags );
4652+ ret = direct_renameat2 (srcfd , name , destfd , newname , flags );
46444653 if (ret < 0 )
46454654 goto error ;
4646-
4647- ret = create_whiteout (lo , pnode , name , false, true);
4648- if (ret < 0 )
4649- goto error ;
4650-
4651- pnode -> loaded = 0 ;
46524655 }
46534656
4657+ pnode -> loaded = 0 ;
4658+
4659+ /* If the destination was .wh. style whiteout, it was not replaced automatically, so delete it. */
46544660 if (delete_whiteout (lo , destfd , NULL , newname ) < 0 )
46554661 goto error ;
46564662
4657- done :
46584663 hash_delete (pnode -> children , node );
46594664
46604665 free (node -> name );
0 commit comments