Skip to content

Commit

Permalink
Merge pull request #18434 from Homebrew/unix-socket
Browse files Browse the repository at this point in the history
Allow sockets to use longer paths on macOS
  • Loading branch information
MikeMcQuaid authored Sep 27, 2024
2 parents 7b1e2d3 + 567f5eb commit b38cbbc
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 17 deletions.
4 changes: 2 additions & 2 deletions Library/Homebrew/build.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
require "keg"
require "extend/ENV"
require "fcntl"
require "socket"
require "utils/socket"
require "cmd/install"
require "json/add/exception"

Expand Down Expand Up @@ -221,7 +221,7 @@ def fixopt(formula)
args = Homebrew::Cmd::InstallCmd.new.args
Context.current = args.context

error_pipe = UNIXSocket.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io)
error_pipe = Utils::UNIXSocketExt.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io)
error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)

trap("INT", old_trap)
Expand Down
30 changes: 30 additions & 0 deletions Library/Homebrew/extend/os/mac/utils/socket.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# typed: strict
# frozen_string_literal: true

require "socket"

module OS
module Mac
# Wrapper around UNIXSocket to allow > 104 characters on macOS.
module UNIXSocketExt
extend T::Helpers

requires_ancestor { Kernel }

sig { params(path: String).returns(String) }
def sockaddr_un(path)
if path.bytesize > 252 # largest size that can fit into a single-byte length
raise ArgumentError, "too long unix socket path (#{path.bytesize} bytes given but 252 bytes max)"
end

[
path.bytesize + 3, # = length (1 byte) + family (1 byte) + path (variable) + null terminator (1 byte)
1, # AF_UNIX
path,
].pack("CCZ*")
end
end
end
end

Utils::UNIXSocketExt.singleton_class.prepend(OS::Mac::UNIXSocketExt)
4 changes: 4 additions & 0 deletions Library/Homebrew/extend/os/utils/socket.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# typed: strict
# frozen_string_literal: true

require "extend/os/mac/utils/socket" if OS.mac?
4 changes: 2 additions & 2 deletions Library/Homebrew/postinstall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
require_relative "global"

require "fcntl"
require "socket"
require "utils/socket"
require "cli/parser"
require "cmd/postinstall"
require "json/add/exception"

begin
ENV.delete("HOMEBREW_FORBID_PACKAGES_FROM_PATHS")
args = Homebrew::Cmd::Postinstall.new.args
error_pipe = UNIXSocket.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io)
error_pipe = Utils::UNIXSocketExt.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io)
error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)

trap("INT", old_trap)
Expand Down
4 changes: 2 additions & 2 deletions Library/Homebrew/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
require "formula_assertions"
require "formula_free_port"
require "fcntl"
require "socket"
require "utils/socket"
require "cli/parser"
require "dev-cmd/test"
require "json/add/exception"
Expand All @@ -23,7 +23,7 @@
args = Homebrew::DevCmd::Test.new.args
Context.current = args.context

error_pipe = UNIXSocket.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io)
error_pipe = Utils::UNIXSocketExt.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io)
error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)

trap("INT", old_trap)
Expand Down
11 changes: 2 additions & 9 deletions Library/Homebrew/test/support/lib/startup/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@

HOMEBREW_BREW_FILE = Pathname.new(ENV.fetch("HOMEBREW_BREW_FILE")).freeze

homebrew_temp = ENV.fetch("HOMEBREW_TEMP")
TEST_TMPDIR = ENV.fetch("HOMEBREW_TEST_TMPDIR") do |k|
dir = Dir.mktmpdir("homebrew-tests-", homebrew_temp)
dir = Dir.mktmpdir("homebrew-tests-", ENV.fetch("HOMEBREW_TEMP"))
at_exit do
# Child processes inherit this at_exit handler, but we don't want them
# to clean TEST_TMPDIR up prematurely (i.e. when they exit early for a test).
Expand All @@ -16,13 +15,6 @@
ENV[k] = dir
end.freeze

# Use a shorter HOMEBREW_TEMP path so Sequoia doesn't error out as often on long paths (> 104 bytes).
# Use the minimal amount of randomness to avoid collisions while allowing parallel tests.
require "securerandom"
random_hex = SecureRandom.hex(2)
HOMEBREW_TEMP = Pathname("#{homebrew_temp}/brewtests#{random_hex}".squeeze("/")).freeze
HOMEBREW_TEMP.mkpath

# Paths pointing into the Homebrew code base that persist across test runs
HOMEBREW_SHIMS_PATH = (HOMEBREW_LIBRARY_PATH/"shims").freeze

Expand All @@ -40,6 +32,7 @@
HOMEBREW_LOCKS = (HOMEBREW_PREFIX.parent/"locks").freeze
HOMEBREW_CELLAR = (HOMEBREW_PREFIX.parent/"cellar").freeze
HOMEBREW_LOGS = (HOMEBREW_PREFIX.parent/"logs").freeze
HOMEBREW_TEMP = (HOMEBREW_PREFIX.parent/"temp").freeze
HOMEBREW_TAP_DIRECTORY = (HOMEBREW_LIBRARY/"Taps").freeze
HOMEBREW_RUBY_EXEC_ARGS = [
RUBY_PATH,
Expand Down
4 changes: 2 additions & 2 deletions Library/Homebrew/utils/fork.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# frozen_string_literal: true

require "fcntl"
require "socket"
require "utils/socket"

module Utils
def self.rewrite_child_error(child_error)
Expand Down Expand Up @@ -37,7 +37,7 @@ def self.safe_fork(directory: nil, yield_parent: false)
require "json/add/exception"

block = proc do |tmpdir|
UNIXServer.open("#{tmpdir}/socket") do |server|
UNIXServerExt.open("#{tmpdir}/socket") do |server|
read, write = IO.pipe

pid = fork do
Expand Down
57 changes: 57 additions & 0 deletions Library/Homebrew/utils/socket.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# typed: strict
# frozen_string_literal: true

require "socket"

module Utils
# Wrapper around UNIXSocket to allow > 104 characters on macOS.
module UNIXSocketExt
extend T::Generic

sig {
type_parameters(:U).params(
path: String,
_block: T.proc.params(arg0: UNIXSocket).returns(T.type_parameter(:U)),
).returns(T.type_parameter(:U))
}
def self.open(path, &_block)
socket = Socket.new(:UNIX, :STREAM)
socket.connect(sockaddr_un(path))
unix_socket = UNIXSocket.for_fd(socket.fileno)
socket.autoclose = false # Transfer autoclose responsibility to UNIXSocket
yield unix_socket
end

sig { params(path: String).returns(String) }
def self.sockaddr_un(path)
Socket.sockaddr_un(path)
end
end

# Wrapper around UNIXServer to allow > 104 characters on macOS.
class UNIXServerExt < Socket
extend T::Generic

Elem = type_member(:out) { { fixed: String } }

sig { returns(String) }
attr_reader :path

sig { params(path: String).void }
def initialize(path)
super(:UNIX, :STREAM)
bind(UNIXSocketExt.sockaddr_un(path))
listen(Socket::SOMAXCONN)
@path = path
end

sig { returns(UNIXSocket) }
def accept_nonblock
socket, = super
socket.autoclose = false # Transfer autoclose responsibility to UNIXSocket
UNIXSocket.for_fd(socket.fileno)
end
end
end

require "extend/os/utils/socket"

0 comments on commit b38cbbc

Please sign in to comment.