Hardlinked file can't be sent twice to the recycle bin

Originator:supergradha
Number:rdar://36198223 Date Originated:December 23 2017, 12:45 AM
Status:Open Resolved:
Product:macOS + SDK Appkit Product Version:10.13.2
Classification:Other bug Reproducible:Always
 
Using High Sierra on an APFS volume, files sent to the recycle bin won't be erased if they are hard linked and one of its instances with the same name already exists in the recycling bin.

Steps to Reproduce:

For the manual version one can create the hard linked files from the command line and place them in different directories:

$ mkdir a b
$ ln some.txt  a
$ ln some.txt  b
$ open a

Now, using the finder, drag some.txt to the recycling bin. Go to the other directory and send the some.txt file to the recycling bin. The file disappears and reappears immediately.

To test this mechanically, you can compile and run the following program which uses NSWorkspace's NSWorkspaceRecycleOperation:

#import <AppKit/AppKit.h>
#import <stdio.h>


#define ORIGINAL "original file.txt"
#define TARGET "Linked version.txt"


int exists_file(const char* src)
{
	FILE* out = fopen(src, "rb");
	if (NULL == out) return 0;
	fclose(out);

	return 1;
}

int recycle(const char* src)
{
	NSString *path = [[NSString alloc] initWithUTF8String:TARGET];
	NSString *folder = [path stringByDeletingLastPathComponent];
	NSArray *files = [NSArray arrayWithObject:[path lastPathComponent]];
	[path release];

	NSInteger tag = 0;
	const BOOL ret = [[NSWorkspace sharedWorkspace]
		performFileOperation:NSWorkspaceRecycleOperation
		source:folder destination:nil files:files tag:&tag];

	printf("Recycling Success? %d\n", (int)tag);
	if (tag) return tag;

	if (exists_file(src)) {
		printf("Ooopes, %s still exists!\n", src);
		return -1;
	}

	return 0;
}

// Returns zero on success, non zero on failure.
int main(int argc, char *argv[])
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

	FILE* out = fopen(ORIGINAL, "wb");
	assert(out);
	fwrite("Hello", 1, 3, out);
	fclose(out);

	int result = 0;
	for (int f = 0; f < 10; f++) {
		printf("Run %d\n", f);
		link(ORIGINAL, TARGET);
		if (recycle(TARGET))
			break;
	}

	[pool release];

	return result;
}

Compile with gcc -o test test.m -framework AppKit

Expected Results:

Running the automatic program on an old machine with 10.11.6 the output of the program is:

$ ./test 
Run 0
Recycling Success? 0
Run 1
Recycling Success? 0
Run 2
Recycling Success? 0
Run 3
Recycling Success? 0
Run 4
Recycling Success? 0
Run 5
Recycling Success? 0
Run 6
Recycling Success? 0
Run 7
Recycling Success? 0
Run 8
Recycling Success? 0
Run 9
Recycling Success? 0

Meaning that all the copies of the hard linked file are moved to the recycle bin, where they are put as renamed versions. Running this on a MBPr with 10.13.2 shows:

$ ./test 
Run 0
Recycling Success? 0
Run 1
Recycling Success? 0
Ooopes, Linked version.txt still exists!

Meaning that after the first successful recycle operation, the second says it succeeds but leaves the file where it was, effectively not recycling it.

As a bonus, a mounted USB with HFS+ exhibits a similar problem on High Sierra, but after a few runs of the test, suggesting there is a race condition. APFS is deterministic.

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!