Skip to content

Commit

Permalink
Merge pull request #18865 from Homebrew/systemd-quote
Browse files Browse the repository at this point in the history
Fix systemd command line quoting
  • Loading branch information
MikeMcQuaid authored Dec 4, 2024
2 parents 15c8508 + 36d06c5 commit 2d8a19d
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Library/Homebrew/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ def to_plist
sig { returns(String) }
def to_systemd_unit
# command needs to be first because it initializes all other values
cmd = command&.map { |arg| Utils::Shell.sh_quote(arg) }
cmd = command&.map { |arg| Utils::Service.systemd_quote(arg) }
&.join(" ")

options = []
Expand Down
6 changes: 3 additions & 3 deletions Library/Homebrew/test/service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ def stub_formula_with_service_sockets(sockets_var)
[Service]
Type=simple
ExecStart=#{HOMEBREW_PREFIX}/opt/#{name}/bin/beanstalkd test
ExecStart="#{HOMEBREW_PREFIX}/opt/#{name}/bin/beanstalkd" "test"
Restart=always
RestartSec=30
WorkingDirectory=#{HOMEBREW_PREFIX}/var
Expand Down Expand Up @@ -760,7 +760,7 @@ def stub_formula_with_service_sockets(sockets_var)
[Service]
Type=oneshot
ExecStart=#{HOMEBREW_PREFIX}/opt/#{name}/bin/beanstalkd
ExecStart="#{HOMEBREW_PREFIX}/opt/#{name}/bin/beanstalkd"
SYSTEMD
expect(unit).to eq(unit_expect)
end
Expand All @@ -783,7 +783,7 @@ def stub_formula_with_service_sockets(sockets_var)
[Service]
Type=simple
ExecStart=#{HOMEBREW_PREFIX}/opt/#{name}/bin/beanstalkd
ExecStart="#{HOMEBREW_PREFIX}/opt/#{name}/bin/beanstalkd"
WorkingDirectory=#{Dir.home}
SYSTEMD
expect(unit).to eq(unit_expect)
Expand Down
24 changes: 24 additions & 0 deletions Library/Homebrew/test/utils/service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

require "utils/service"

RSpec.describe Utils::Service do
describe "::systemd_quote" do
it "quotes empty strings correctly" do
expect(described_class.systemd_quote("")).to eq '""'
end

it "quotes strings with special characters escaped correctly" do
expect(described_class.systemd_quote("\a\b\f\n\r\t\v\\"))
.to eq '"\\a\\b\\f\\n\\r\\t\\v\\\\"'
expect(described_class.systemd_quote("\"' ")).to eq "\"\\\"' \""
end

it "does not escape characters that do not need escaping" do
expect(described_class.systemd_quote("daemon off;")).to eq '"daemon off;"'
expect(described_class.systemd_quote("--timeout=3")).to eq '"--timeout=3"'
expect(described_class.systemd_quote("--answer=foo bar"))
.to eq '"--answer=foo bar"'
end
end
end
25 changes: 25 additions & 0 deletions Library/Homebrew/utils/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,30 @@ def self.launchctl?
def self.systemctl?
!systemctl.nil?
end

# Quote a string for use in systemd command lines, e.g., in `ExecStart`.
# https://www.freedesktop.org/software/systemd/man/latest/systemd.syntax.html#Quoting
sig { params(str: String).returns(String) }
def self.systemd_quote(str)
result = +"\""
# No need to escape single quotes and spaces, as we're always double
# quoting the entire string.
str.each_char do |char|
result << case char
when "\a" then "\\a"
when "\b" then "\\b"
when "\f" then "\\f"
when "\n" then "\\n"
when "\r" then "\\r"
when "\t" then "\\t"
when "\v" then "\\v"
when "\\" then "\\\\"
when "\"" then "\\\""
else char
end
end
result << "\""
result.freeze
end
end
end

0 comments on commit 2d8a19d

Please sign in to comment.