Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Identify macOS UNIX-specific system directories #18

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions src/HiddenFiles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ include("docs.jl")

# https://github.com/dotnet/runtime/blob/5992145db2cb57956ee444aa0f0c2f3f85ee3673/src/native/libs/System.Native/pal_io.c#L219
# https://github.com/davidkaya/corefx/blob/4fd3d39f831f3e14f311b0cdc0a33d662e684a9c/src/System.IO.FileSystem/src/System/IO/FileStatus.Unix.cs#L88
_isinvisible(f::AbstractString) = (_st_flags(f) & UF_HIDDEN) == UF_HIDDEN
_isinvisible_st_flags(f::AbstractString) = (_st_flags(f) & UF_HIDDEN) == UF_HIDDEN

_ishidden_bsd_related(f::AbstractString) = _ishidden_unix(f) || _isinvisible(f)
_ishidden_bsd_related(f::AbstractString) = _ishidden_unix(f) || _isinvisible_st_flags(f)
end

@static if Sys.isapple() # macOS/Darwin
Expand All @@ -86,15 +86,24 @@ include("docs.jl")
# - `/tmp`—Contains temporary files created by apps and the system.
# - `/usr`—Contains non-essential command-line binaries, libraries, header files, and other data.
# - `/var`—Contains log files and other files whose content is variable. (Log files are typically viewed using the Console app.)
# TODO
_issystemfile(f::AbstractString) = false
_issystemdir(f::AbstractString) = false # TODO

# This _isinvisible function seems to capture some cases (e.g., `/tmp`) that the other _isinvisible function does not
function _isinvisible_macos_item_info(f::AbstractString, str_encoding::Unsigned = CF_STRING_ENCODING, path_style::Integer = K_CF_URL_POSIX_PATH_STYLE)
cfstr = _cfstring_create_with_cstring(f, str_encoding)
url_ref = _cf_url_create_with_file_system_path(cfstr, isdir(f))
item_info = _ls_copy_item_info_for_url(url_ref, K_IS_INVISIBLE)
return !iszero(item_info[1] & K_IS_INVISIBLE)
end


#=== Case 3: Explicitly hidden files and directories ===#
# The Finder may hide specific files or directories that should not be accessed directly by the user. The most notable example of
# this is the /Volumes directory, which contains a subdirectory for each mounted disk in the local file system from the command line.
# (The Finder provides a different user interface for accessing local disks.) In macOS 10.7 and later, the Finder also hides the
# `~/Library` directory—that is, the `Library` directory located in the user’s home directory. This case is handled by `_isinvisible`.
# `~/Library` directory—that is, the `Library` directory located in the user’s home directory.
#
# This case is handled by `_isinvisible_st_flags`.


#=== Case 4: Packages and bundles ===#
Expand Down Expand Up @@ -149,7 +158,7 @@ include("docs.jl")


#=== All macOS cases ===#
_ishidden_macos(f::AbstractString) = _ishidden_bsd_related(f) || _issystemfile(f) || _exists_inside_package_or_bundle(f)
_ishidden_macos(f::AbstractString) = _ishidden_bsd_related(f) || _issystemdir(f) || _isinvisible_macos_item_info(f) || _exists_inside_package_or_bundle(f)
_ishidden = _ishidden_macos
elseif Sys.isbsd() # BSD; this excludes macOS through control flow (as macOS is checked for first)
_ishidden_bsd(f::AbstractString) = _ishidden_bsd_related(f)
Expand Down
48 changes: 48 additions & 0 deletions src/utils/darwin.jl
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,51 @@ function _string_from_cf_string(cfstr::Cstring, encoding::Unsigned = CF_STRING_E
return String(take!(cfio))
end

#===============================================#

# https://developer.apple.com/documentation/coreservices/lsiteminfoflags/klsiteminfoisinvisible
# TODO: convert this to enum: https://developer.apple.com/documentation/coreservices/lsiteminfoflags: https://github.com/phracker/MacOSX-SDKs/blob/041600eda65c6a668f66cb7d56b7d1da3e8bcc93/MacOSX10.6.sdk/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Headers/LSInfo.h#L95-L111
const KLS_ITEM_INFO_IS_INVISIBLE = 0x00000040

# https://developer.apple.com/documentation/coreservices/1429609-anonymous/kisinvisible
const K_IS_INVISIBLE = 0x00000040

# https://developer.apple.com/documentation/corefoundation/cfurlpathstyle
const K_CF_URL_POSIX_PATH_STYLE = zero(Int8)

# https://developer.apple.com/documentation/corefoundation/1543250-cfurlcreatewithfilesystempath
function _cf_url_create_with_file_system_path(cfstr::Cstring, is_directory::Bool, path_style::Integer = K_CF_URL_POSIX_PATH_STYLE)
# TODO: handle error codes
# CFURLRef CFURLCreateWithFileSystemPath(CFAllocatorRef allocator, CFStringRef filePath, CFURLPathStyle pathStyle, Boolean isDirectory);
url_ref = ccall(:CFURLCreateWithFileSystemPath, Ptr{UInt32},
(Ptr{Cvoid}, Cstring, Int32, Bool),
C_NULL, cfstr, path_style, is_directory)
return url_ref
end

# https://developer.apple.com/documentation/coreservices/lsiteminforecord
function _ls_item_info_record()
@warn "LSItemInfoRecord has been deprecated since macOS 10.11"
error("not yet implemented")
end

# https://developer.apple.com/documentation/coreservices/lsrequestedinfo/klsrequestallflags
const K_LS_REQUEST_ALL_FLAGS = 0x00000010
# TODO: convert this to enum: https://developer.apple.com/documentation/coreservices/lsrequestedinfo: https://github.com/phracker/MacOSX-SDKs/blob/041600eda65c6a668f66cb7d56b7d1da3e8bcc93/MacOSX10.6.sdk/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Headers/LSInfo.h#L83-L93

# https://developer.apple.com/documentation/coreservices/1445685-lscopyiteminfoforurl
function _ls_copy_item_info_for_url(url_ref::Ptr{UInt32}, requested_info::Unsigned = K_LS_REQUEST_ALL_FLAGS)
# TODO: handle error codes
requested_info == K_LS_REQUEST_ALL_FLAGS && @warn "kLSRequestAllFlags has been deprecated since macOS 10.11"
requested_info == KLS_ITEM_INFO_IS_INVISIBLE && @warn "kLSItemInfoIsInvisible has been deprecated since macOS 10.11; ensure you are using kIsInvisible instead"
@warn "LSCopyItemInfoForURL has been deprecated since macOS 10.11"
# buf = Vector{UInt32}(undef, 100)
buf = zeros(UInt32, 100)
# OSStatus LSCopyItemInfoForURL(CFURLRef inURL, LSRequestedInfo inWhichInfo, LSItemInfoRecord *outItemInfo);
ptr = ccall(:LSCopyItemInfoForURL, Ptr{UInt32},
(Ptr{UInt32}, UInt32, Ptr{Cvoid}),
url_ref, requested_info, buf)
return buf
end


36 changes: 26 additions & 10 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,39 @@ using Test

# Case 2: UNIX-specific directories
# TODO: complete this case
@test HiddenFiles.ishidden("/bin/")
@test HiddenFiles.ishidden("/dev/")
@test HiddenFiles.ishidden("/usr/")
@test !HiddenFiles.ishidden("/tmp/")

@test HiddenFiles.ishidden("/bin")
@test HiddenFiles.ishidden("/dev")
@test HiddenFiles.ishidden("/dev")
@test HiddenFiles.ishidden("/etc")
@test HiddenFiles.ishidden("/sbin")
@test HiddenFiles.ishidden("/sbin")
@test HiddenFiles.ishidden("/tmp")
@test HiddenFiles.ishidden("/usr")
@test HiddenFiles.ishidden("/var")
@test HiddenFiles._isinvisible_macos_item_info("/bin")
@test HiddenFiles._isinvisible_macos_item_info("/dev")
@test HiddenFiles._isinvisible_macos_item_info("/dev")
@test HiddenFiles._isinvisible_macos_item_info("/etc")
@test HiddenFiles._isinvisible_macos_item_info("/sbin")
@test HiddenFiles._isinvisible_macos_item_info("/sbin")
@test HiddenFiles._isinvisible_macos_item_info("/tmp")
@test HiddenFiles._isinvisible_macos_item_info("/usr")
@test HiddenFiles._isinvisible_macos_item_info("/var")
@test !HiddenFiles._isinvisible_macos_item_info("/bin/bash")

# Case 3: Explicitly hidden files and directories
@test HiddenFiles._isinvisible("/Volumes")
@test HiddenFiles._isinvisible_st_flags("/Volumes")
@test ishidden("/Volumes")
@test !HiddenFiles._isinvisible(p′)
@test !HiddenFiles._isinvisible_st_flags(p′)

# Case 4: Packages and bundles
@test !ishidden("/System/Applications/Utilities/Terminal.app")
@test ishidden("/System/Applications/Utilities/Terminal.app/Contents")
@test ishidden("/System/Applications/Utilities/Terminal.app/Contents/") # This should be the same as above, as we expand all paths using realpath
@test !HiddenFiles._ispackage_or_bundle("/System/Applications/Utilities/Terminal.app/Contents/")
@test HiddenFiles._exists_inside_package_or_bundle("/System/Applications/Utilities/Terminal.app/Contents/")
@test !HiddenFiles._exists_inside_package_or_bundle("/bin/")
@test !HiddenFiles._exists_inside_package_or_bundle("/bin")
@test !HiddenFiles._exists_inside_package_or_bundle("/tmp")
f = String(rand(Char, 32)) # this path shouldn't exist
cfstr_nonexistent = HiddenFiles._cfstring_create_with_cstring(f)
@test_throws Exception HiddenFiles._mditem_create(cfstr_nonexistent)
Expand All @@ -56,9 +72,9 @@ using Test
# TODO: should we not only support FreeBSD? Are we testing on other BSD systems? OpenBSD?
@testset "HiddenFiles.jl—FreeBSD" begin
@test ishidden(p)
@test !HiddenFiles._isinvisible(p)
@test !HiddenFiles._isinvisible_st_flags(p)
@test ishidden(p′)
@test !HiddenFiles._isinvisible(p′)
@test !HiddenFiles._isinvisible_st_flags(p′)
@test !ishidden(homedir())
@test !ishidden("/bin/")
@test !ishidden("/dev/")
Expand Down