CSBackupIsItemExcluded inconsistently reports path exclusions

Originator:nriley
Number:rdar://15909530 Date Originated:25-Jan-2014 12:52 PM
Status:Open Resolved:
Product:OS X SDK Product Version:Xcode 5.0.2 (5A3005); OS X 10.9.1 (13B42)
Classification:Serious Bug Reproducible:Always
 
Summary:
CSBackupIsItemExcluded has several issues affecting its accuracy with path exclusions, including relative paths, symbolic links and user-excluded versus programmatically-excluded paths.

Steps to Reproduce:
1. Create a folder named "exclusions" in your home folder, which should not be excluded from Time Machine.

% mkdir exclusions

2. Create a symbolic link from "exclusions-link" to this folder:

% ln -s exclusions exclusions-link

3. Compile the attached source file in the folder:

% clang -framework CoreServices -o excluded excluded.c

4. Create three folders in exclusions-link.  Exclude one from Time Machine by path with Time Machine System Preferences, one by path with tmutil, and one by "stickiness" with tmutil:

% cd exclusions-link
% mkdir ExcludedByPathWithTimeMachineSystemPreferences ExcludedByPathWithTmutil ExcludedByStickiness
% sudo tmutil addexclusion -p ExcludedByPathWithTmutil
Password:
% tmutil addexclusion ExcludedByStickiness           
% open .
[drag "ExcludedByPathWithTimeMachineSystemPreferences" into System Preferences > Time Machine > Options…]

5. Examine these folders for exclusion status with tmutil and with CSBackupIsItemExcluded, with relative, absolute and canonicalized paths:

% tmutil isexcluded Excluded*
% echo Excluded* | xargs -n 1 ./excluded
% echo $PWD/Excluded* | xargs -n 1 ./excluded
% cd ../exclusions
% echo $PWD/Excluded* | xargs -n 1 ./excluded

Expected Results:
All four methods correctly show the paths are excluded.

Actual Results:
tmutil returns as expected; note that it canonicalizes paths as it prints them:

% tmutil isexcluded Excluded*
[Excluded]	/Users/local/exclusions/ExcludedByPathWithTimeMachineSystemPreferences
[Excluded]	/Users/local/exclusions/ExcludedByPathWithTmutil
[Excluded]	/Users/local/exclusions/ExcludedByStickiness

CSBackupIsItemExcluded only returns true for the "sticky" folder, for absolute and relative paths.  Note that the base URL printed below has already been canonicalized:

% echo Excluded* | xargs -n 1 ./excluded
<CFURL 0x7fe2b2c083d0 [0x7fff77512eb0]>{string = ExcludedByPathWithTimeMachineSystemPreferences, encoding = 134217984
	base = <CFURL 0x7fe2b2c08450 [0x7fff77512eb0]>{string = file:///Users/local/exclusions/, encoding = 134217984, base = (null)}}
not excluded
<CFURL 0x7ff0b1d04c20 [0x7fff77512eb0]>{string = ExcludedByPathWithTmutil, encoding = 134217984
	base = <CFURL 0x7ff0b1d04c60 [0x7fff77512eb0]>{string = file:///Users/local/exclusions/, encoding = 134217984, base = (null)}}
not excluded
<CFURL 0x7fcce0e04900 [0x7fff77512eb0]>{string = ExcludedByStickiness, encoding = 134217984
	base = <CFURL 0x7fcce0e04940 [0x7fff77512eb0]>{string = file:///Users/local/exclusions/, encoding = 134217984, base = (null)}}
excluded
% echo $PWD/Excluded* | xargs -n 1 ./excluded
<CFURL 0x7fd368504c50 [0x7fff77512eb0]>{string = file:///Users/local/exclusions-link/ExcludedByPathWithTimeMachineSystemPreferences, encoding = 134217984, base = (null)}
not excluded
<CFURL 0x7fc54b504c40 [0x7fff77512eb0]>{string = file:///Users/local/exclusions-link/ExcludedByPathWithTmutil, encoding = 134217984, base = (null)}
not excluded
<CFURL 0x7fa4f1504bf0 [0x7fff77512eb0]>{string = file:///Users/local/exclusions-link/ExcludedByStickiness, encoding = 134217984, base = (null)}
excluded

With a canonicalized path, CSBackupIsItemExcluded returns true for the programmatically excluded path, but still not the user-excluded one:

% cd ../exclusions
% echo $PWD/Excluded* | xargs -n 1 ./excluded
<CFURL 0x7fa00ac083b0 [0x7fff77512eb0]>{string = file:///Users/local/exclusions/ExcludedByPathWithTimeMachineSystemPreferences, encoding = 134217984, base = (null)}
not excluded
<CFURL 0x7f9513e049d0 [0x7fff77512eb0]>{string = file:///Users/local/exclusions/ExcludedByPathWithTmutil, encoding = 134217984, base = (null)}
excluded by path
<CFURL 0x7f9048c083d0 [0x7fff77512eb0]>{string = file:///Users/local/exclusions/ExcludedByStickiness, encoding = 134217984, base = (null)}
excluded

Version:
Xcode 5.0.2 (5A3005); OS X 10.9.1 (13B42)

Notes:
Canonicalizing the path before calling CSBackupIsItemExcluded would work around the issue with the programmatically-excluded path.  Reading the SkipPaths default from com.apple.TimeMachine directly would allow me to match against the user-excluded path.

% defaults read /Library/Preferences/com.apple.TimeMachine SkipPaths
(
[…]    "~local/exclusions/ExcludedByPathWithTimeMachineSystemPreferences"
)

Configuration:
Mac mini Mid 2011, 2.5 GHz Core i5, 8 GB RAM, RADEON HD 6630M, OS X 10.9.1 (13B42) 
Mac mini Late 2012, 2.6 GHz Core i7, 16 GB RAM, Intel HD4000, OS X 10.8.5 (12F45)

Attachments:
'excluded.c' was successfully uploaded.

[Attached file is uploaded to <http://sabi.net/temp/15909530.c>.]

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!