library/systemd/src/lib/yast2/systemd/service.rb
require "yast"
require "yast2/systemd/unit"
require "yast2/systemd/unit_prop_map"
require "yast2/systemd/socket"
require "shellwords"
module Yast2
module Systemd
# Represent a missing service
class ServiceNotFound < StandardError
def initialize(service_name)
super "Service unit '#{service_name}' not found"
end
end
# API to manage a systemd.service unit
#
# @example How to use it in other yast libraries
# require 'yast'
# require 'yast2/systemd/service'
#
# ## Get a service unit by its name
# ## If the service unit can't be found, you'll get nil
#
# service = Yast2::Systemd::Service.find('sshd') # service unit object
#
# # or using the full unit id 'sshd.service'
#
# service = Yast2::Systemd::Service.find('sshd.service')
#
# ## If you can't handle any nil at the place of calling,
# ## use the finder with exclamation mark;
# ## Systemd::ServiceNotFound exception will be raised
#
# service = Yast2::Systemd::Service.find!('IcanHasMoar') # Systemd::ServiceNotFound: Service unit 'IcanHasMoar' not found
#
# ## Get basic unit properties
#
# service.unit_name # 'sshd'
# service.unit_type # 'service'
# service.id # 'sshd.service'
# service.description # 'OpenSSH Daemon'
# service.path # '/usr/lib/systemd/system/sshd.service'
# service.loaded? # true if it's loaded, false otherwise
# service.running? # true if it's active and running
# service.enabled? # true if enabled, false otherwise
# service.disabled? # true if disabled, false otherwise
# service.status # the same text output you get with `systemctl status sshd.service`
# service.show # equivalent of calling `systemctl show sshd.service`
#
# ## Service unit file commands
#
# # Unit file commands do modifications on the service unit. Calling them triggers
# # service properties reloading. If a command fails, the error message is available
# # through the method #error as a string.
#
# service.start # true if unit has been activated successfully
# service.stop # true if unit has been deactivated successfully
# service.enable # true if unit has been enabled successfully
# service.disable # true if unit has been disabled successfully
# service.error # error string available if some of the actions above fails
#
# ## Extended service properties
#
# # In case you need more details about the service unit than the default ones,
# # you can extend the parameters for .find method. Those properties are
# # then available on the service unit object under the #properties instance method.
# # An extended property is always a string, you must convert it manually,
# # no automatical casting is done by yast.
# # To get an overview of available service properties, try e.g., `systemctl show sshd.service`
#
# service = Yast2::Systemd::Service.find('sshd', :type=>'Type')
# service.properties.type # 'simple'
class Service < Unit
Yast.import "Stage"
include Yast::Logger
UNIT_SUFFIX = ".service".freeze
class << self
# @param service_name [String] "foo" or "foo.service"
# @param propmap [Yast2::Systemd::UnitPropMap]
# @return [Service,nil] `nil` if not found
def find(service_name, propmap = UnitPropMap.new)
service = build(service_name, propmap)
return nil if service.properties.not_found?
service
end
# @param service_name [String] "foo" or "foo.service"
# @param propmap [Yast2::Systemd::UnitPropMap]
# @return [Service]
# @raise [Systemd::ServiceNotFound]
def find!(service_name, propmap = UnitPropMap.new)
find(service_name, propmap) || raise(Systemd::ServiceNotFound, service_name)
end
# @param service_names [Array<String>] "foo" or "foo.service"
# @param propmap [Yast2::Systemd::UnitPropMap]
# @return [Array<Service,nil>] `nil` if a service is not found,
# [] if this helper cannot be used:
# either we're in the inst-sys without systemctl,
# or it has returned fewer services than requested
# (and we cannot match them up)
def find_many_at_once(service_names, propmap = UnitPropMap.new)
return [] if Yast::Stage.initial
snames = service_names.map { |n| n + UNIT_SUFFIX unless n.end_with?(UNIT_SUFFIX) }
snames_s = snames.join(" ")
pnames_s = UnitPropMap::DEFAULT.merge(propmap).values.join(",")
out = Systemctl.execute("show --property=#{pnames_s} #{snames_s}")
log.error "returned #{out.exit}, #{out.stderr}" unless out.exit.zero? && out.stderr.empty?
property_texts = out.stdout.split("\n\n")
return [] unless snames.size == property_texts.size
snames.zip(property_texts).each_with_object([]) do |(name, property_text), memo|
service = new(name, propmap, property_text)
memo << service unless service.not_found?
end
end
# @param service_names [Array<String>] "foo" or "foo.service"
# @param propmap [Yast2::Systemd::UnitPropMap]
# @return [Array<Service,nil>] `nil` if not found
def find_many(service_names, propmap = UnitPropMap.new)
services = find_many_at_once(service_names, propmap)
return services unless services.empty?
log.info "Retrying one by one"
service_names.map { |n| find(n, propmap) }
end
# @param propmap [Yast2::Systemd::UnitPropMap]
# @return [Array<Service>]
def all(propmap = UnitPropMap.new)
Systemctl.service_units.map { |s| new(s, propmap) }
end
# Instantiate a Systemd::Service object based on the given name
#
# Use with caution as the service might exist or not. If you need to
# react when the service does not exist, use Systemd::Service.find.
#
# @param service_name [String] "foo" or "foo.service"
# @param propmap [Yast2::Systemd::UnitPropMap]
# @return [Service] System service with the given name
def build(service_name, propmap = UnitPropMap.new)
service_name += UNIT_SUFFIX unless service_name.end_with?(UNIT_SUFFIX)
new(service_name, propmap)
end
end
private_class_method :find_many_at_once
# Available only on installation system
START_SERVICE_INSTSYS_COMMAND = "/bin/service_start".freeze
# @return [String]
def pid
properties.pid
end
def running?
properties.running?
end
def static?
properties.static?
end
def start
command = "#{START_SERVICE_INSTSYS_COMMAND} #{unit_name.shellescape}"
installation_system? ? run_instsys_command(command) : super
end
def stop
command = "#{START_SERVICE_INSTSYS_COMMAND} --stop #{unit_name.shellescape}"
installation_system? ? run_instsys_command(command) : super
end
def restart
# Delegate to Systemd::Unit#restart if not within installation
return super unless installation_system?
stop
sleep(1)
start
end
# Returns socket associated with service or nil if there is no such socket
#
# @return [Yast2::Systemd::Socket,nil]
# @see Yast2::Systemd::Socket.for_service
def socket
@socket ||= Socket.for_service(name)
end
# Determines whether the service has an associated socket
#
# @return [Boolean] true if an associated socket exists; false otherwise.
def socket?
!socket.nil?
end
private
def installation_system?
File.exist?(START_SERVICE_INSTSYS_COMMAND)
end
def run_instsys_command(command)
log.info("Running command '#{command}'")
error.clear
result = OpenStruct.new(
Yast::SCR.Execute(Yast::Path.new(".target.bash_output"), command)
)
error << result.stderr
result.exit.zero?
end
end
end
end