diff --git a/ssm b/ssm index 0cbfb84..b066e13 100755 --- a/ssm +++ b/ssm @@ -23,6 +23,8 @@ var() { ('==') mode=compare;; ('=~') mode=regex;; + ('u') mode=includes;; + (*) die 71 \"Syntax error in \$FUNCNAME!\";; esac shift @@ -45,6 +47,14 @@ var() { (*) return 0;; esac ;; + + (includes) + for i in \"\${_var[@]}\"; do + [[ \"\$i\" == \"\$*\" ]] && return 0 + done + + return 1 + ;; esac }; readonly -f \"${varname}\" @@ -90,8 +100,8 @@ svc() { # Cgroups if cgroups; then - mkdir -p "$cgroup_home/$service_name" - echo "$BASHPID" > "$cgroup_home/$service_name/cgroup.procs" + mkdir -p "$cgroup_home/$service_cgroup_name" + echo "$BASHPID" > "$cgroup_home/$service_cgroup_name/cgroup.procs" fi svc::cleanup() { @@ -292,15 +302,23 @@ read_systemd_service() { start() { service_running && return 3 - rm -f "$service_stopped_flag" - [[ -f "${service_command[0]}" ]] || return 9 + if cgroups; then + if service_cgroup_exclusive; then + if ! service_cgroup_empty; then + return 15 + fi + fi + fi + depend "${service_depends[@]}" || return 7 depend_ready "${service_depends_ready[@]}" || return 7 mktmpfiles || return 13 + rm -f "$service_stopped_flag" + if service_managed; then if service_respawn; then svc respawn "${service_command[@]}" & @@ -318,6 +336,12 @@ start() { (( res )) && return "$res" printf '1' > "$service_enabled_flag" else + # Put ourselves into the cgroup, so that even when we die, whatever we started stays in it + if cgroups; then + mkdir -p "$cgroup_home/$service_cgroup_name" + echo "$BASHPID" > "$cgroup_home/$service_cgroup_name/cgroup.procs" + fi + exec "${service_command[@]}" 1>"$service_logfile_out" 2>"$service_logfile_err" & fi @@ -350,6 +374,16 @@ stop() { pid_wait "$service_pid" || return 5 > "$service_stopped_flag" + # Cgroup stuff + if cgroups; then + if service_cgroup_wait; then + for p in "${service_cgroup_procs[@]}"; do + pid_wait "$p" & + wait || return 5 + done + fi + fi + return 0 fi } @@ -384,7 +418,14 @@ info() { "PID" "${service_pid:-none}" fi - printf "%12s: %s\n" "${_info_items[@]}" + # Show the cgroup + if cgroups; then + _info_items += "Cgroup" "$cgroup_home/$service_cgroup_name" \ + "Cgroup procs" "${#service_cgroup_procs[@]}" + fi + + printf "%16s: %s\n" "${_info_items[@]}" + } ## Restart just calls the script twice by default @@ -427,6 +468,8 @@ var service_pid \ service_ready_flag \ service_enabled_flag \ service_stopped_flag \ + service_cgroup_name \ + service_cgroup_procs \ cgroup_home \ failed_deps \ svc_pidfile \ @@ -443,25 +486,31 @@ var XDG_CONFIG_HOME := "$HOME/.config" var XDG_RUNTIME_DIR := "/run/user/$UID" ## Let's set some defaults +# These are meaningful to reconfigure. +var service_respawn = 0 # Respawn the service if it exits +var service_workdir = '/' +var service_stop_timeout = 30 +var service_ready_timeout = 15 +var service_signals = 1 10 12 +var service_reload_signal = 1 +var service_stop_signal = 15 +var service_cgroup_exclusive = 0 # Refuse to start the service if its cgroup is not empty +var service_cgroup_wait = 0 # Wait on all the members of the cgroup to exit when stopping the service. +var systemd = 0 # Enable systemd-related functions. +var systemd_service_path = /etc/systemd/system /run/systemd/system /lib/systemd/system +var cgroups = 0 # Enable cgroup-related functions +var usrdir = '/usr/share/ssm' + +# These are not var service_managed = 1 -var service_respawn = 0 var service_oneshot = 0 var service_running = 0 var service_enabled = 0 var service_stopped = 0 var service_systemd = 0 -var service_workdir = '/' -var service_stop_timeout = 30 -var service_ready_timeout = 15 -var service_stop_signal = 15 -var service_reload_signal = 1 -var service_signals = 1 10 12 var service_nologs = 0 -var usrdir = '/usr/share/ssm' -var systemd = 0 -var systemd_service_path = /etc/systemd/system /run/systemd/system /lib/systemd/system +var service_cgroup_empty = 1 var ssm_config = 0 -var cgroups = 0 ## Figure out our full path case "$0" in @@ -588,6 +637,7 @@ service_logfile_err := "$logdir/${service_name}.err.log" service_ready_flag := "$rundir/$service_name.ready" service_enabled_flag := "$rundir/$service_name.enabled" service_stopped_flag := "$rundir/$service_name.stopped" +service_cgroup_name := "$service_name" # A shortcut for disabling logging if service_nologs; then @@ -617,6 +667,24 @@ if [[ -f "$service_stopped_flag" ]]; then service_stopped = 1 fi +# Check cgroups, if enabled +if cgroups; then + if [[ -d "$cgroup_home/$service_cgroup_name" ]]; then + while read -r line; do + service_cgroup_procs += "$line" + done < "$cgroup_home/$service_cgroup_name/cgroup.procs" + + if (( ${#service_cgroup_procs[@]} )); then + service_cgroup_empty = 0 + fi + fi + + # If there's a service PID, check if it's in the service's cgroup + if service_pid; then + service_cgroup_procs u "$service_pid" || die 29 "Recorded service PID is not in the service's cgroup, bailing!" + fi +fi + # Check if action is even defined is_function "$2" || die 17 "Function $2 is not defined for $service_name." @@ -651,7 +719,8 @@ case "$2" in 5 "Readyness check for $service_name timed out" \ 7 "Failed to start dependencies for $service_name: ${failed_deps[@]}" \ 9 "service_command does not exist: ${service_command[0]}" \ - 13 "Failed to create temporary files for $service_name" + 13 "Failed to create temporary files for $service_name" \ + 15 "Refusing to start $service_name: the service cgroup is not empty and \$service_cgroup_exclusive is set" ;; reload)