module Locd::Launchctl
Thin wrapper to run `launchctl` commands and capture outputs via {Cmds}.
Basically just centralizes this stuff all in one place and presents a friendly API, does some common debug logging, etc.
Constants
- OPTS_MAP
Mappings between any option keyword names we use to the ones `launchctl` expects.
@return [Hash<Symbol, Symbol>]
- STATUS_CACHE_TIMEOUT_SEC
Max number of seconds to
- STATUS_RE
Regexp to parse `launchd list` output for {#status} method.
@return [Regexp]
Public Class Methods
# File lib/locd/launchctl.rb, line 160 def self.disabled uid = Cmds.chomp! 'id -u' result = execute :'print-disabled', "user/#{ uid }" lines = if match = /disabled\ services\ =\ \{([^\}]+)\}/.match( result.out ) match[1].lines else raise ParseError.new binding.erb <<~END Unable to find 'disabled services' section output Command: <%= result.cmd %> Output: <%= result.out %> END end lines end
Run a `launchctl` subcommand with positional args and options.
@param [Symbol | String] subcmd
Subcommand to run, like `:load`.
@param [Array<String>] *args
Positional args, will be added on end of command.
@param [Hash<Symbol, V>] **opts
Options, will be added between subcommand and positional args. Keys are mapped via {.map_opts}.
@return [Cmds::Result]
# File lib/locd/launchctl.rb, line 119 def self.execute subcmd, *args, **opts logger.debug "Executing `#{ @@bin } #{ subcmd }` command", args: args, opts: opts result = Cmds "#{ @@bin } #{ subcmd } <%= opts %> <%= *args %>", *args, opts: map_opts( **opts ) logger.debug "`#{ @@bin }` command result", cmd: result.cmd, status: result.status, out: NRSER.ellipsis( result.out.lines, 20 ), err: NRSER.ellipsis( result.err.lines, 20 ) result end
Just like {#execute} but will raise if the exit status is not `0`.
@param (see .execute) @return (see .execute) @raise (see Cmds::Result#assert)
# File lib/locd/launchctl.rb, line 144 def self.execute! *args execute( *args ).assert end
Swap any keys in {OPTS_MAP}.
@param [Hash<Symbol, V>] **opts
Options as passed to {.execute}, etc.
@return [Hash<Symbol, V>]
Options ready for `launchctl`.
# File lib/locd/launchctl.rb, line 93 def self.map_opts **opts opts.map { |key, value| if OPTS_MAP[key] [OPTS_MAP[key], value] else [key, value] end }.to_h end
# File lib/locd/launchctl.rb, line 185 def self.refresh_status? @last_status_time.nil? || (Time.now - @last_status_time) > STATUS_CACHE_TIMEOUT_SEC end
@param [Boolean] refresh:
@return [Hash<String, {pid: Fixnum?, status: Fixnum?}>]
Map of agent labels to hash with process ID (if any) and last exit code (if any).
# File lib/locd/launchctl.rb, line 199 def self.status refresh: refresh_status? if refresh || @status.nil? @status = execute!( :list ).out.lines.rest. map { |line| if match = STATUS_RE.match( line ) pid = case match[:pid] when '-' nil when /^\d+$/ match[:pid].to_i else logger.error "Unexpected `pid` parse in {#status}", captures: match.captures, line: line nil end status = case match[:status] when /^\-?\d+$/ match[:status].to_i else logger.error "Unexpected `status` parse in {#status}", captures: match.captures, line: line nil end label = match[:label].freeze [label, {pid: pid, status: status}] else logger.error "FAILED to parse status line", line: line nil end }. reject( &:nil? ). to_h # Set the time so we can compare next call @last_status_time = Time.now end @status end