Skip to content
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
49 changes: 31 additions & 18 deletions Library/Homebrew/cask/quarantine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ class SigningIdentity < T::Struct
QUARANTINE_ATTRIBUTE = "com.apple.quarantine"
USER_APPROVED_FLAG = 0x0040

QUARANTINE_SCRIPT = T.let((HOMEBREW_LIBRARY_PATH/"cask/utils/quarantine.swift").freeze, Pathname)
COPY_XATTRS_SCRIPT = T.let((HOMEBREW_LIBRARY_PATH/"cask/utils/copy-xattrs.swift").freeze, Pathname)

sig { returns(T.nilable(T.any(String, Pathname))) }
Expand Down Expand Up @@ -70,7 +69,7 @@ def self.check_quarantine_support
raise "unexpected nil swift" unless s

api_check = system_command(s,
args: [*swift_target_args, QUARANTINE_SCRIPT],
args: [*swift_target_args, COPY_XATTRS_SCRIPT],
print_stderr: false)

exit_status = api_check.exit_status
Expand Down Expand Up @@ -205,26 +204,40 @@ def self.cask!(cask: nil, download_path: nil, action: true)

odebug "Quarantining #{download_path}"

raise "unexpected nil swift" unless swift
require "os/mac/ffi"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably shouldn't import os/mac stuff inside cask/quarantine now casks support macOS and Linux.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing code already was entirely macOS specific. I suppose because Quarantine.available? is only ever true on macOS.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeh, still feels a bit weird and perhaps a chance for some refactoring? Don't feel super strongly though.


quarantiner = system_command(T.must(swift),
args: [
*swift_target_args,
QUARANTINE_SCRIPT,
download_path,
cask.url.to_s,
cask.homepage.to_s,
],
print_stderr: false)
path_cf_string = MacOS::FFI::CoreFoundation.string_create(download_path.to_s)
Comment thread
Bo98 marked this conversation as resolved.
raise CaskQuarantineError.new(download_path, "Failed to create CFString for path") if path_cf_string.null?

return if quarantiner.success?
path_cf_url = MacOS::FFI::CoreFoundation.url_create_with_file_system_path(path_cf_string)
raise CaskQuarantineError.new(download_path, "Failed to create CFURL for path") if path_cf_url.null?

case quarantiner.exit_status
when 2
raise CaskQuarantineError.new(download_path, "Insufficient parameters")
else
raise CaskQuarantineError.new(download_path, quarantiner.stderr)
quarantine_agent_name = MacOS::FFI::CoreFoundation.string_create("Homebrew Cask")
quarantine_data_url = MacOS::FFI::CoreFoundation.string_create(cask.url.to_s)
quarantine_origin_url = MacOS::FFI::CoreFoundation.string_create(cask.homepage.to_s)
if quarantine_agent_name.null? || quarantine_data_url.null? || quarantine_origin_url.null?
raise CaskQuarantineError.new(download_path, "Failed to create CFString for quarantine properties")
end

quarantine_dictionary = MacOS::FFI::CoreFoundation.dictionary_create(
MacOS::FFI::LaunchServices.quarantine_agent_name_key => quarantine_agent_name,
MacOS::FFI::LaunchServices.quarantine_type_key => MacOS::FFI::LaunchServices.quarantine_type_web_download,
MacOS::FFI::LaunchServices.quarantine_data_url_key => quarantine_data_url,
MacOS::FFI::LaunchServices.quarantine_origin_url_key => quarantine_origin_url,
Comment thread
Bo98 marked this conversation as resolved.
)
if quarantine_dictionary.null?
raise CaskQuarantineError.new(download_path, "Failed to create quarantine dictionary")
end

success = MacOS::FFI::CoreFoundation.url_set_resource_property_for_key(
path_cf_url,
MacOS::FFI::CoreFoundation.url_quarantine_properties_key,
quarantine_dictionary,
)

return if success

raise CaskQuarantineError.new(download_path, "Failed to set quarantine properties for URL")
end

sig { params(from: T.nilable(Pathname), to: T.nilable(Pathname)).void }
Expand Down
47 changes: 0 additions & 47 deletions Library/Homebrew/cask/utils/quarantine.swift

This file was deleted.

24 changes: 7 additions & 17 deletions Library/Homebrew/extend/os/mac/linkage_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,18 @@
module OS
module Mac
module LinkageChecker
private
extend T::Helpers

sig { returns(T::Boolean) }
def system_libraries_exist_in_cache?
# In macOS Big Sur and later, system libraries do not exist on-disk and instead exist in a cache.
MacOS.version >= :big_sur
end
requires_ancestor { ::LinkageChecker }

private

sig { params(dylib: String).returns(T::Boolean) }
def dylib_found_in_shared_cache?(dylib)
Kernel.require "fiddle"
@dyld_shared_cache_contains_path ||= T.let(begin
libc = Fiddle.dlopen("/usr/lib/libSystem.B.dylib")

Fiddle::Function.new(
libc["_dyld_shared_cache_contains_path"],
[Fiddle::TYPE_CONST_STRING],
Fiddle::TYPE_BOOL,
)
end, T.nilable(Fiddle::Function))
return false if MacOS.version < :big_sur

@dyld_shared_cache_contains_path.call(dylib)
require "os/mac/ffi"
MacOS::FFI.dyld_shared_cache_contains_path(dylib)
end
end
end
Expand Down
10 changes: 2 additions & 8 deletions Library/Homebrew/linkage_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,8 @@ def check_dylibs(rebuild_cache:)
if (dep = dylib_to_dep(dylib))
broken_dep = (@broken_deps[dep] ||= [])
broken_dep << dylib unless broken_dep.include?(dylib)
elsif system_libraries_exist_in_cache? && dylib_found_in_shared_cache?(dylib)
# If we cannot associate the dylib with a dependency, then it may be a system library.
# Check the dylib shared cache for the library to verify this.
elsif dylib_found_in_shared_cache?(dylib)
# In macOS Big Sur and later, system libraries do not exist on-disk and instead exist in a cache.
@system_dylibs << dylib
elsif !system_framework?(dylib) && !broken_dylibs_allowed?(file.to_s)
@broken_dylibs << dylib
Expand All @@ -242,11 +241,6 @@ def check_dylibs(rebuild_cache:)
store.update!(keg_files_dylibs:)
end

sig { returns(T::Boolean) }
def system_libraries_exist_in_cache?
false
end

sig { params(_dylib: String).returns(T::Boolean) }
def dylib_found_in_shared_cache?(_dylib)
false
Expand Down
208 changes: 208 additions & 0 deletions Library/Homebrew/os/mac/ffi.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# typed: strict
# frozen_string_literal: true

require "fiddle"

module OS
module Mac
# Wrapping module for FFI calls to system libraries.
module FFI
# NativeLibrary provides helper methods for loading system libraries and accessing functions and constants.
# Functions and constants are cached so they only need to be looked up once.
module NativeLibrary
private

sig { params(path: String).void }
def use_library(path)
@library_path = T.let(path.freeze, T.nilable(String))
end

sig { returns(Fiddle::Handle) }
def handle
@handle ||= T.let(Fiddle.dlopen(T.must(@library_path)), T.nilable(Fiddle::Handle))
end

sig { params(name: String, argument_types: T::Array[Integer], return_type: Integer).returns(Fiddle::Function) }
def function(name, argument_types, return_type)
@functions ||= T.let({}, T.nilable(T::Hash[String, Fiddle::Function]))
@functions[name] ||= Fiddle::Function.new(handle[name], argument_types, return_type)
end

sig { params(name: String, dereference: T::Boolean).returns(Fiddle::Pointer) }
def constant(name, dereference: false)
@constants ||= T.let({}, T.nilable(T::Hash[[String, T::Boolean], Fiddle::Pointer]))
@constants[[name, dereference]] ||= begin
pointer = Fiddle::Pointer.new(handle[name])
dereference ? pointer.ptr : pointer
end
end
end

extend NativeLibrary

use_library "/usr/lib/libSystem.B.dylib"

# mach-o/dyld.h:
# bool _dyld_shared_cache_contains_path(const char* path);
sig { params(path: String).returns(T::Boolean) }
def self.dyld_shared_cache_contains_path(path)
function(
"_dyld_shared_cache_contains_path",
[Fiddle::TYPE_CONST_STRING],
Fiddle::TYPE_BOOL,
).call(path)
end

# CoreFoundation.framework wrapper
module CoreFoundation
extend NativeLibrary

use_library "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"

sig { params(ptr: Fiddle::Pointer).returns(Fiddle::Pointer) }
private_class_method def self.autorelease(ptr)
return ptr if ptr.null?

# CoreFoundation/CFBase.h:
# void CFRelease(CFTypeRef cf);
ptr.free = function("CFRelease", [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOID)
ptr
end

# CoreFoundation/CFDictionary.h:
# extern const CFDictionaryKeyCallBacks kCFTypeDictionaryKeyCallBacks;
sig { returns(Fiddle::Pointer) }
def self.type_dictionary_key_call_backs = constant("kCFTypeDictionaryKeyCallBacks")

# CoreFoundation/CFDictionary.h:
# extern const CFDictionaryValueCallBacks kCFTypeDictionaryValueCallBacks
sig { returns(Fiddle::Pointer) }
def self.type_dictionary_value_call_backs = constant("kCFTypeDictionaryValueCallBacks")

# CoreFoundation/CFURL.h:
# extern const CFStringRef kCFURLQuarantinePropertiesKey;
sig { returns(Fiddle::Pointer) }
def self.url_quarantine_properties_key = constant("kCFURLQuarantinePropertiesKey", dereference: true)

# CoreFoundation/CFString.h:
# CFStringRef CFStringCreateWithCString(CFAllocatorRef alloc, const char *cStr, CFStringEncoding encoding);
sig { params(string: String).returns(Fiddle::Pointer) }
def self.string_create(string)
cf_encoding = case string.encoding
when Encoding::UTF_8
0x08000100 # kCFStringEncodingUTF8
when Encoding::US_ASCII
0x0600 # kCFStringEncodingASCII
when Encoding::ASCII_8BIT, Encoding::ISO8859_1
# ASCII-8BIT could be anything, so just use Latin-1
0x0201 # kCFStringEncodingISOLatin1
else
# Try convert to UTF-8 and move on
string = string.encode(Encoding::UTF_8)
0x08000100
end

autorelease(
function(
"CFStringCreateWithCString",
[Fiddle::TYPE_VOIDP, Fiddle::TYPE_CONST_STRING, Fiddle::TYPE_UINT32_T],
Fiddle::TYPE_VOIDP,
).call(nil, string, cf_encoding),
)
end

# CoreFoundation/CFDictionary.h:
# CFDictionaryRef CFDictionaryCreate(
# CFAllocatorRef allocator,
# const void **keys,
# const void **values,
# CFIndex numValues,
# const CFDictionaryKeyCallBacks *keyCallBacks,
# const CFDictionaryValueCallBacks *valueCallBacks);
sig { params(hash: T::Hash[Fiddle::Pointer, Fiddle::Pointer]).returns(Fiddle::Pointer) }
def self.dictionary_create(hash)
size = Fiddle::SIZEOF_VOIDP * hash.size
Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE) do |keys|
Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE) do |values|
# Convert array of pointers to continous stream of pointers in the C buffer
keys[0, size] = hash.keys.pack("J*")
values[0, size] = hash.values.pack("J*")
return autorelease(
function(
"CFDictionaryCreate",
[
Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP,
Fiddle::TYPE_LONG, Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP
],
Fiddle::TYPE_VOIDP,
).call(
nil, keys, values, hash.size, type_dictionary_key_call_backs, type_dictionary_value_call_backs
),
)
end
end
end

# CoreFoundation/CFURL.h:
# CFURLRef CFURLCreateWithFileSystemPath(CFAllocatorRef allocator,
# CFStringRef filePath, CFURLPathStyle pathStyle, Boolean isDirectory);
sig { params(path: Fiddle::Pointer).returns(Fiddle::Pointer) }
def self.url_create_with_file_system_path(path)
autorelease(
function(
"CFURLCreateWithFileSystemPath",
[Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_LONG, Fiddle::TYPE_BOOL],
Fiddle::TYPE_VOIDP,
).call(nil, path, 0, false),
)
end

# CoreFoundation/CFURL.h:
# Boolean CFURLSetResourcePropertyForKey(CFURLRef url, CFStringRef key, CFTypeRef value, CFErrorRef *error);
sig { params(url: Fiddle::Pointer, key: Fiddle::Pointer, value: Fiddle::Pointer).returns(T::Boolean) }
def self.url_set_resource_property_for_key(url, key, value)
function(
"CFURLSetResourcePropertyForKey",
[Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP],
Fiddle::TYPE_BOOL,
).call(url, key, value, nil)
end
end

# LaunchServices.framework wrapper
module LaunchServices
extend NativeLibrary

use_library(
"/System/Library/Frameworks/CoreServices.framework/Versions/A/" \
"Frameworks/LaunchServices.framework/Versions/A/LaunchServices",
)

# LaunchServices/LSQuarantine.h:
# extern const CFStringRef kLSQuarantineAgentNameKey;
sig { returns(Fiddle::Pointer) }
def self.quarantine_agent_name_key = constant("kLSQuarantineAgentNameKey", dereference: true)

# LaunchServices/LSQuarantine.h:
# extern const CFStringRef kLSQuarantineTypeKey;
sig { returns(Fiddle::Pointer) }
def self.quarantine_type_key = constant("kLSQuarantineTypeKey", dereference: true)

# LaunchServices/LSQuarantine.h:
# extern const CFStringRef kLSQuarantineTypeWebDownload;
sig { returns(Fiddle::Pointer) }
def self.quarantine_type_web_download = constant("kLSQuarantineTypeWebDownload", dereference: true)

# LaunchServices/LSQuarantine.h:
# extern const CFStringRef kLSQuarantineDataURLKey;
sig { returns(Fiddle::Pointer) }
def self.quarantine_data_url_key = constant("kLSQuarantineDataURLKey", dereference: true)

# LaunchServices/LSQuarantine.h:
# extern const CFStringRef kLSQuarantineOriginURLKey;
sig { returns(Fiddle::Pointer) }
def self.quarantine_origin_url_key = constant("kLSQuarantineOriginURLKey", dereference: true)
end
end
end
end
1 change: 1 addition & 0 deletions Library/Homebrew/sorbet/rbi/shims/rspec.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class RSpec::Core::ExampleGroup
include RSpec::SharedContext
include RSpec::Matchers
include RSpec::Mocks::ExampleMethods
include Test::Helper::MkTmpDir
end

# The rspec-mocks RBI defines `ExpectHost#expect(target)` with a required
Expand Down
Loading
Loading