bluepill 0.0.68 → 0.0.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -2
- data/bin/bluepill +19 -20
- data/bin/sample_forking_server +26 -13
- data/bluepill.gemspec +12 -18
- data/lib/bluepill.rb +12 -12
- data/lib/bluepill/application.rb +64 -71
- data/lib/bluepill/application/client.rb +0 -2
- data/lib/bluepill/application/server.rb +1 -3
- data/lib/bluepill/condition_watch.rb +12 -7
- data/lib/bluepill/controller.rb +37 -42
- data/lib/bluepill/dsl.rb +1 -2
- data/lib/bluepill/dsl/app_proxy.rb +3 -4
- data/lib/bluepill/dsl/process_factory.rb +40 -44
- data/lib/bluepill/dsl/process_proxy.rb +4 -5
- data/lib/bluepill/group.rb +15 -21
- data/lib/bluepill/logger.rb +4 -4
- data/lib/bluepill/process.rb +107 -109
- data/lib/bluepill/process_conditions.rb +1 -3
- data/lib/bluepill/process_conditions/always_true.rb +2 -3
- data/lib/bluepill/process_conditions/cpu_usage.rb +0 -1
- data/lib/bluepill/process_conditions/file_time.rb +5 -6
- data/lib/bluepill/process_conditions/http.rb +11 -9
- data/lib/bluepill/process_conditions/mem_usage.rb +6 -7
- data/lib/bluepill/process_conditions/process_condition.rb +4 -5
- data/lib/bluepill/process_conditions/running_time.rb +1 -2
- data/lib/bluepill/process_conditions/zombie_process.rb +1 -2
- data/lib/bluepill/process_journal.rb +18 -21
- data/lib/bluepill/process_statistics.rb +2 -4
- data/lib/bluepill/socket.rb +13 -16
- data/lib/bluepill/system.rb +57 -63
- data/lib/bluepill/trigger.rb +9 -11
- data/lib/bluepill/triggers/flapping.rb +12 -16
- data/lib/bluepill/util/rotational_array.rb +1 -2
- data/lib/bluepill/version.rb +1 -2
- metadata +4 -28
- data/.gitignore +0 -12
- data/.rspec +0 -1
- data/.travis.yml +0 -17
- data/Gemfile +0 -27
- data/Rakefile +0 -38
- data/examples/example.rb +0 -87
- data/examples/new_example.rb +0 -89
- data/examples/new_runit_example.rb +0 -29
- data/examples/runit_example.rb +0 -26
- data/local-bluepill +0 -130
- data/spec/lib/bluepill/application_spec.rb +0 -51
- data/spec/lib/bluepill/logger_spec.rb +0 -3
- data/spec/lib/bluepill/process_spec.rb +0 -135
- data/spec/lib/bluepill/process_statistics_spec.rb +0 -24
- data/spec/lib/bluepill/system_spec.rb +0 -45
- data/spec/spec_helper.rb +0 -26
@@ -1,4 +1,3 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
module Bluepill
|
3
2
|
module ProcessConditions
|
4
3
|
def self.[](name)
|
@@ -7,8 +6,7 @@ module Bluepill
|
|
7
6
|
end
|
8
7
|
end
|
9
8
|
|
10
|
-
require
|
9
|
+
require 'bluepill/process_conditions/process_condition'
|
11
10
|
Dir["#{File.dirname(__FILE__)}/process_conditions/*.rb"].each do |pc|
|
12
11
|
require pc
|
13
12
|
end
|
14
|
-
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
module Bluepill
|
3
2
|
module ProcessConditions
|
4
3
|
class AlwaysTrue < ProcessCondition
|
@@ -6,11 +5,11 @@ module Bluepill
|
|
6
5
|
@below = options[:below]
|
7
6
|
end
|
8
7
|
|
9
|
-
def run(
|
8
|
+
def run(_pid, _include_children)
|
10
9
|
1
|
11
10
|
end
|
12
11
|
|
13
|
-
def check(
|
12
|
+
def check(_value)
|
14
13
|
true
|
15
14
|
end
|
16
15
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
module Bluepill
|
3
2
|
module ProcessConditions
|
4
3
|
class FileTime < ProcessCondition
|
@@ -7,19 +6,19 @@ module Bluepill
|
|
7
6
|
@filename = options[:filename]
|
8
7
|
end
|
9
8
|
|
10
|
-
def run(
|
11
|
-
if File.
|
12
|
-
Time.now
|
9
|
+
def run(_pid, _include_children)
|
10
|
+
if File.exist?(@filename)
|
11
|
+
Time.now - File.mtime(@filename)
|
13
12
|
else
|
14
13
|
nil
|
15
14
|
end
|
16
15
|
rescue
|
17
|
-
|
16
|
+
$ERROR_INFO
|
18
17
|
end
|
19
18
|
|
20
19
|
def check(value)
|
21
20
|
return false if value.nil?
|
22
|
-
|
21
|
+
value < @below
|
23
22
|
end
|
24
23
|
end
|
25
24
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
require 'net/http'
|
3
2
|
require 'uri'
|
4
3
|
|
@@ -8,8 +7,10 @@ module Bluepill
|
|
8
7
|
def initialize(options = {})
|
9
8
|
@uri = URI.parse(options[:url])
|
10
9
|
@kind = case options[:kind]
|
11
|
-
|
12
|
-
|
10
|
+
when Fixnum
|
11
|
+
Net::HTTPResponse::CODE_TO_OBJ[options[:kind].to_s]
|
12
|
+
when String, Symbol
|
13
|
+
Net.const_get("HTTP#{options[:kind].to_s.camelize}")
|
13
14
|
else
|
14
15
|
Net::HTTPSuccess
|
15
16
|
end
|
@@ -18,11 +19,11 @@ module Bluepill
|
|
18
19
|
@read_timeout = (options[:read_timeout] || options[:timeout] || 5).to_i
|
19
20
|
end
|
20
21
|
|
21
|
-
def run(
|
22
|
+
def run(_pid, _include_children)
|
22
23
|
session = Net::HTTP.new(@uri.host, @uri.port)
|
23
24
|
if @uri.scheme == 'https'
|
24
25
|
require 'net/https'
|
25
|
-
session.use_ssl=true
|
26
|
+
session.use_ssl = true
|
26
27
|
session.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
27
28
|
end
|
28
29
|
session.open_timeout = @open_timeout
|
@@ -33,22 +34,23 @@ module Bluepill
|
|
33
34
|
end
|
34
35
|
end
|
35
36
|
rescue
|
36
|
-
|
37
|
+
$ERROR_INFO
|
37
38
|
end
|
38
39
|
|
39
40
|
def check(value)
|
40
|
-
return false unless value.
|
41
|
+
return false unless value.is_a?(@kind)
|
41
42
|
return true unless @pattern
|
42
43
|
return false unless value.class.body_permitted?
|
43
44
|
@pattern === value.body
|
44
45
|
end
|
45
46
|
|
46
47
|
private
|
48
|
+
|
47
49
|
def hide_net_http_bug
|
48
50
|
yield
|
49
51
|
rescue NoMethodError => e
|
50
|
-
if e.to_s =~ /#{Regexp.escape(
|
51
|
-
raise
|
52
|
+
if e.to_s =~ /#{Regexp.escape("undefined method `closed?' for nil:NilClass")}/
|
53
|
+
raise(Errno::ECONNREFUSED.new("Connection refused attempting to contact #{@uri.scheme}://#{@uri.host}:#{@uri.port}"))
|
52
54
|
else
|
53
55
|
raise
|
54
56
|
end
|
@@ -1,11 +1,10 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
module Bluepill
|
3
2
|
module ProcessConditions
|
4
3
|
class MemUsage < ProcessCondition
|
5
|
-
MB = 1024
|
6
|
-
FORMAT_STR =
|
7
|
-
MB_LABEL =
|
8
|
-
KB_LABEL =
|
4
|
+
MB = 1024**2
|
5
|
+
FORMAT_STR = '%d%s'
|
6
|
+
MB_LABEL = 'MB'
|
7
|
+
KB_LABEL = 'KB'
|
9
8
|
|
10
9
|
def initialize(options = {})
|
11
10
|
@below = options[:below]
|
@@ -22,9 +21,9 @@ module Bluepill
|
|
22
21
|
|
23
22
|
def format_value(value)
|
24
23
|
if value.kilobytes >= MB
|
25
|
-
FORMAT_STR
|
24
|
+
format(FORMAT_STR, (value / 1024).round, MB_LABEL)
|
26
25
|
else
|
27
|
-
FORMAT_STR
|
26
|
+
format(FORMAT_STR, value, KB_LABEL)
|
28
27
|
end
|
29
28
|
end
|
30
29
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
module Bluepill
|
3
2
|
module ProcessConditions
|
4
3
|
class ProcessCondition
|
@@ -6,12 +5,12 @@ module Bluepill
|
|
6
5
|
@options = options
|
7
6
|
end
|
8
7
|
|
9
|
-
def run(
|
10
|
-
|
8
|
+
def run(_pid, _include_children)
|
9
|
+
fail 'Implement in subclass!'
|
11
10
|
end
|
12
11
|
|
13
|
-
def check(
|
14
|
-
|
12
|
+
def check(_value)
|
13
|
+
fail 'Implement in subclass!'
|
15
14
|
end
|
16
15
|
|
17
16
|
def format_value(value)
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
module Bluepill
|
3
2
|
module ProcessConditions
|
4
3
|
class RunningTime < ProcessCondition
|
@@ -6,7 +5,7 @@ module Bluepill
|
|
6
5
|
@below = options[:below]
|
7
6
|
end
|
8
7
|
|
9
|
-
def run(pid,
|
8
|
+
def run(pid, _include_children)
|
10
9
|
System.running_time(pid)
|
11
10
|
end
|
12
11
|
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
module Bluepill
|
3
2
|
module ProcessConditions
|
4
3
|
# Process must have cache_actual_pid set to false to function correctly:
|
@@ -7,7 +6,7 @@ module Bluepill
|
|
7
6
|
# process.cache_actual_pid = false
|
8
7
|
|
9
8
|
class ZombieProcess < ProcessCondition
|
10
|
-
def run(pid,
|
9
|
+
def run(pid, _include_children)
|
11
10
|
System.command(pid)
|
12
11
|
end
|
13
12
|
|
@@ -2,7 +2,7 @@ require 'bluepill/system'
|
|
2
2
|
|
3
3
|
module Bluepill
|
4
4
|
module ProcessJournal
|
5
|
-
|
5
|
+
module_function
|
6
6
|
|
7
7
|
class << self
|
8
8
|
attr_reader :logger
|
@@ -13,8 +13,8 @@ module Bluepill
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def base_dir=(base_dir)
|
16
|
-
@journal_base_dir ||= File.join(base_dir,
|
17
|
-
FileUtils.mkdir_p(@journal_base_dir) unless File.
|
16
|
+
@journal_base_dir ||= File.join(base_dir, 'journals')
|
17
|
+
FileUtils.mkdir_p(@journal_base_dir) unless File.exist?(@journal_base_dir)
|
18
18
|
FileUtils.chmod(0777, @journal_base_dir)
|
19
19
|
end
|
20
20
|
end
|
@@ -39,7 +39,7 @@ module Bluepill
|
|
39
39
|
times += 1
|
40
40
|
logger.debug("Waiting for lock #{name}")
|
41
41
|
sleep 1
|
42
|
-
|
42
|
+
if times < 10
|
43
43
|
retry
|
44
44
|
else
|
45
45
|
logger.info("Timeout waiting for lock #{name}")
|
@@ -65,7 +65,7 @@ module Bluepill
|
|
65
65
|
|
66
66
|
def pid_journal(filename)
|
67
67
|
logger.debug("pid journal file: #{filename}")
|
68
|
-
result = File.open(filename, 'r').readlines.map(&:to_i).reject {|pid| skip_pid?(pid)}
|
68
|
+
result = File.open(filename, 'r').readlines.map(&:to_i).reject { |pid| skip_pid?(pid) }
|
69
69
|
logger.debug("pid journal = #{result.join(' ')}")
|
70
70
|
result
|
71
71
|
rescue Errno::ENOENT
|
@@ -74,7 +74,7 @@ module Bluepill
|
|
74
74
|
|
75
75
|
def pgid_journal(filename)
|
76
76
|
logger.debug("pgid journal file: #{filename}")
|
77
|
-
result = File.open(filename, 'r').readlines.map(&:to_i).reject {|pgid| skip_pgid?(pgid)}
|
77
|
+
result = File.open(filename, 'r').readlines.map(&:to_i).reject { |pgid| skip_pgid?(pgid) }
|
78
78
|
logger.debug("pgid journal = #{result.join(' ')}")
|
79
79
|
result
|
80
80
|
rescue Errno::ENOENT
|
@@ -82,18 +82,15 @@ module Bluepill
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def clear_atomic_fs_lock(name)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
85
|
+
return unless File.directory?(name)
|
86
|
+
Dir.rmdir(name)
|
87
|
+
logger.debug("Cleared lock #{name}")
|
89
88
|
end
|
90
89
|
|
91
90
|
def kill_all_from_all_journals
|
92
|
-
Dir[
|
93
|
-
|
94
|
-
|
95
|
-
y =~ /\.lock$/
|
96
|
-
}.each do |journal_name|
|
91
|
+
pids = Dir['.bluepill_pids_journal.*'].map { |p| p.sub(/^\.bluepill_pids_journal\./, '') }
|
92
|
+
pids.reject! { |p| p =~ /\.lock$/ }
|
93
|
+
pids.each do |journal_name|
|
97
94
|
kill_all_from_journal(journal_name)
|
98
95
|
end
|
99
96
|
end
|
@@ -177,13 +174,13 @@ module Bluepill
|
|
177
174
|
|
178
175
|
filename = pgid_journal_filename(journal_name)
|
179
176
|
acquire_atomic_fs_lock(filename) do
|
180
|
-
|
177
|
+
if pgid_journal(filename).include?(pgid)
|
178
|
+
logger.debug("Skipping duplicate pgid #{pgid} already in journal #{journal_name}")
|
179
|
+
else
|
181
180
|
logger.debug("Saving pgid #{pgid} to process journal #{journal_name}")
|
182
181
|
File.open(filename, 'a+', 0600) { |f| f.puts(pgid) }
|
183
182
|
logger.info("Saved pgid #{pgid} to journal #{journal_name}")
|
184
183
|
logger.debug("Journal now = #{File.open(filename, 'r').read}")
|
185
|
-
else
|
186
|
-
logger.debug("Skipping duplicate pgid #{pgid} already in journal #{journal_name}")
|
187
184
|
end
|
188
185
|
end
|
189
186
|
end
|
@@ -200,13 +197,13 @@ module Bluepill
|
|
200
197
|
|
201
198
|
filename = pid_journal_filename(journal_name)
|
202
199
|
acquire_atomic_fs_lock(filename) do
|
203
|
-
|
200
|
+
if pid_journal(filename).include?(pid)
|
201
|
+
logger.debug("Skipping duplicate pid #{pid} already in journal #{journal_name}")
|
202
|
+
else
|
204
203
|
logger.debug("Saving pid #{pid} to process journal #{journal_name}")
|
205
204
|
File.open(filename, 'a+', 0600) { |f| f.puts(pid) }
|
206
205
|
logger.info("Saved pid #{pid} to journal #{journal_name}")
|
207
206
|
logger.debug("Journal now = #{File.open(filename, 'r').read}")
|
208
|
-
else
|
209
|
-
logger.debug("Skipping duplicate pid #{pid} already in journal #{journal_name}")
|
210
207
|
end
|
211
208
|
end
|
212
209
|
end
|
@@ -1,7 +1,6 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
module Bluepill
|
3
2
|
class ProcessStatistics
|
4
|
-
STRFTIME =
|
3
|
+
STRFTIME = '%m/%d/%Y %H:%I:%S'.freeze
|
5
4
|
EVENTS_TO_PERSIST = 10
|
6
5
|
|
7
6
|
attr_reader :events
|
@@ -17,11 +16,10 @@ module Bluepill
|
|
17
16
|
|
18
17
|
def to_s
|
19
18
|
str = events.reverse.map do |(event, reason, time)|
|
20
|
-
" #{event} at #{time.strftime(STRFTIME)} - #{reason ||
|
19
|
+
" #{event} at #{time.strftime(STRFTIME)} - #{reason || 'unspecified'}"
|
21
20
|
end.join("\n")
|
22
21
|
|
23
22
|
"event history:\n#{str}"
|
24
23
|
end
|
25
24
|
end
|
26
25
|
end
|
27
|
-
|
data/lib/bluepill/socket.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
require 'socket'
|
3
2
|
|
4
3
|
module Bluepill
|
@@ -6,7 +5,7 @@ module Bluepill
|
|
6
5
|
TIMEOUT = 60 # Used for client commands
|
7
6
|
MAX_ATTEMPTS = 5
|
8
7
|
|
9
|
-
|
8
|
+
module_function
|
10
9
|
|
11
10
|
def client(base_dir, name, &block)
|
12
11
|
UNIXSocket.open(socket_path(base_dir, name), &block)
|
@@ -25,7 +24,7 @@ module Bluepill
|
|
25
24
|
break
|
26
25
|
rescue EOFError, Timeout::Error
|
27
26
|
if current_attempt == MAX_ATTEMPTS - 1
|
28
|
-
abort(
|
27
|
+
abort('Socket Timeout: Server may not be responding')
|
29
28
|
end
|
30
29
|
puts "Retry #{current_attempt + 1} of #{MAX_ATTEMPTS}"
|
31
30
|
end
|
@@ -35,24 +34,22 @@ module Bluepill
|
|
35
34
|
|
36
35
|
def server(base_dir, name)
|
37
36
|
socket_path = self.socket_path(base_dir, name)
|
37
|
+
UNIXServer.open(socket_path)
|
38
|
+
rescue Errno::EADDRINUSE
|
38
39
|
begin
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
else
|
48
|
-
logger.err("Server is already running!")
|
49
|
-
exit(7)
|
50
|
-
end
|
40
|
+
# if sock file has been created, test to see if there is a server
|
41
|
+
UNIXSocket.open(socket_path)
|
42
|
+
rescue Errno::ECONNREFUSED
|
43
|
+
File.delete(socket_path)
|
44
|
+
return UNIXServer.open(socket_path)
|
45
|
+
else
|
46
|
+
logger.err('Server is already running!')
|
47
|
+
exit(7)
|
51
48
|
end
|
52
49
|
end
|
53
50
|
|
54
51
|
def socket_path(base_dir, name)
|
55
|
-
File.join(base_dir, 'socks', name +
|
52
|
+
File.join(base_dir, 'socks', name + '.sock')
|
56
53
|
end
|
57
54
|
end
|
58
55
|
end
|
data/lib/bluepill/system.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
require 'etc'
|
3
|
-
require
|
2
|
+
require 'shellwords'
|
4
3
|
|
5
4
|
module Bluepill
|
6
5
|
# This class represents the system that bluepill is running on.. It's mainly used to memoize
|
7
6
|
# results of running ps auxx etc so that every watch in the every process will not result in a fork
|
8
7
|
module System
|
9
|
-
APPEND_MODE =
|
10
|
-
|
8
|
+
APPEND_MODE = 'a'
|
9
|
+
|
10
|
+
module_function
|
11
11
|
|
12
12
|
# The position of each field in ps output
|
13
13
|
IDX_MAP = {
|
@@ -16,27 +16,25 @@ module Bluepill
|
|
16
16
|
:pcpu => 2,
|
17
17
|
:rss => 3,
|
18
18
|
:etime => 4,
|
19
|
-
:command => 5
|
19
|
+
:command => 5,
|
20
20
|
}
|
21
21
|
|
22
22
|
def pid_alive?(pid)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
false
|
30
|
-
end
|
23
|
+
::Process.kill(0, pid)
|
24
|
+
true
|
25
|
+
rescue Errno::EPERM # no permission, but it is definitely alive
|
26
|
+
true
|
27
|
+
rescue Errno::ESRCH
|
28
|
+
false
|
31
29
|
end
|
32
30
|
|
33
31
|
def cpu_usage(pid, include_children)
|
34
32
|
ps = ps_axu
|
35
33
|
return unless ps[pid]
|
36
34
|
cpu_used = ps[pid][IDX_MAP[:pcpu]].to_f
|
37
|
-
get_children(pid).each
|
35
|
+
get_children(pid).each do |child_pid|
|
38
36
|
cpu_used += ps[child_pid][IDX_MAP[:pcpu]].to_f if ps[child_pid]
|
39
|
-
|
37
|
+
end if include_children
|
40
38
|
cpu_used
|
41
39
|
end
|
42
40
|
|
@@ -44,9 +42,9 @@ module Bluepill
|
|
44
42
|
ps = ps_axu
|
45
43
|
return unless ps[pid]
|
46
44
|
mem_used = ps[pid][IDX_MAP[:rss]].to_f
|
47
|
-
get_children(pid).each
|
45
|
+
get_children(pid).each do |child_pid|
|
48
46
|
mem_used += ps[child_pid][IDX_MAP[:rss]].to_f if ps[child_pid]
|
49
|
-
|
47
|
+
end if include_children
|
50
48
|
mem_used
|
51
49
|
end
|
52
50
|
|
@@ -63,11 +61,11 @@ module Bluepill
|
|
63
61
|
end
|
64
62
|
|
65
63
|
def get_children(parent_pid)
|
66
|
-
child_pids =
|
67
|
-
ps_axu.each_pair do |
|
64
|
+
child_pids = []
|
65
|
+
ps_axu.each_pair do |_pid, chunks|
|
68
66
|
child_pids << chunks[IDX_MAP[:pid]].to_i if chunks[IDX_MAP[:ppid]].to_i == parent_pid.to_i
|
69
67
|
end
|
70
|
-
grand_children = child_pids.map{|pid| get_children(pid)}.flatten
|
68
|
+
grand_children = child_pids.map { |pid| get_children(pid) }.flatten
|
71
69
|
child_pids.concat grand_children
|
72
70
|
end
|
73
71
|
|
@@ -75,8 +73,9 @@ module Bluepill
|
|
75
73
|
def daemonize(cmd, options = {})
|
76
74
|
rd, wr = IO.pipe
|
77
75
|
|
78
|
-
|
79
|
-
|
76
|
+
child = Daemonize.safefork
|
77
|
+
if child
|
78
|
+
# we don't want to create zombies, so detach ourselves from the child exit status
|
80
79
|
::Process.detach(child)
|
81
80
|
|
82
81
|
# parent
|
@@ -86,7 +85,6 @@ module Bluepill
|
|
86
85
|
rd.close
|
87
86
|
|
88
87
|
return daemon_id if daemon_id > 0
|
89
|
-
|
90
88
|
else
|
91
89
|
# child
|
92
90
|
rd.close
|
@@ -98,7 +96,7 @@ module Bluepill
|
|
98
96
|
|
99
97
|
to_daemonize = lambda do
|
100
98
|
# Setting end PWD env emulates bash behavior when dealing with symlinks
|
101
|
-
Dir.chdir(ENV[
|
99
|
+
Dir.chdir(ENV['PWD'] = options[:working_dir].to_s) if options[:working_dir]
|
102
100
|
options[:environment].each { |key, value| ENV[key.to_s] = value.to_s } if options[:environment]
|
103
101
|
|
104
102
|
redirect_io(*options.values_at(:stdin, :stdout, :stderr))
|
@@ -109,12 +107,12 @@ module Bluepill
|
|
109
107
|
|
110
108
|
daemon_id = Daemonize.call_as_daemon(to_daemonize, nil, cmd)
|
111
109
|
|
112
|
-
File.open(options[:pid_file],
|
110
|
+
File.open(options[:pid_file], 'w') { |f| f.write(daemon_id) }
|
113
111
|
|
114
112
|
wr.write daemon_id
|
115
113
|
wr.close
|
116
114
|
|
117
|
-
::Process
|
115
|
+
::Process.exit!(true)
|
118
116
|
end
|
119
117
|
end
|
120
118
|
|
@@ -122,7 +120,7 @@ module Bluepill
|
|
122
120
|
tries = 0
|
123
121
|
|
124
122
|
begin
|
125
|
-
File.unlink(filename) if filename && File.
|
123
|
+
File.unlink(filename) if filename && File.exist?(filename)
|
126
124
|
rescue IOError, Errno::ENOENT
|
127
125
|
rescue Errno::EACCES
|
128
126
|
retry if (tries += 1) < 3
|
@@ -134,7 +132,8 @@ module Bluepill
|
|
134
132
|
def execute_blocking(cmd, options = {})
|
135
133
|
rd, wr = IO.pipe
|
136
134
|
|
137
|
-
|
135
|
+
child = Daemonize.safefork
|
136
|
+
if child
|
138
137
|
# parent
|
139
138
|
wr.close
|
140
139
|
|
@@ -152,12 +151,12 @@ module Bluepill
|
|
152
151
|
cmd_out_read, cmd_out_write = IO.pipe
|
153
152
|
cmd_err_read, cmd_err_write = IO.pipe
|
154
153
|
|
155
|
-
pid = fork
|
154
|
+
pid = fork do
|
156
155
|
begin
|
157
156
|
# grandchild
|
158
157
|
drop_privileges(options[:uid], options[:gid], options[:supplementary_groups])
|
159
158
|
|
160
|
-
Dir.chdir(ENV[
|
159
|
+
Dir.chdir(ENV['PWD'] = options[:working_dir].to_s) if options[:working_dir]
|
161
160
|
options[:environment].each { |key, value| ENV[key.to_s] = value.to_s } if options[:environment]
|
162
161
|
|
163
162
|
# close unused fds so ancestors wont hang. This line is the only reason we are not
|
@@ -166,7 +165,7 @@ module Bluepill
|
|
166
165
|
wr.close
|
167
166
|
|
168
167
|
# we do not care about stdin of cmd
|
169
|
-
STDIN.reopen(
|
168
|
+
STDIN.reopen('/dev/null')
|
170
169
|
|
171
170
|
# point stdout of cmd to somewhere we can read
|
172
171
|
cmd_out_read.close
|
@@ -180,12 +179,12 @@ module Bluepill
|
|
180
179
|
|
181
180
|
# finally, replace grandchild with cmd
|
182
181
|
::Kernel.exec(*Shellwords.shellwords(cmd))
|
183
|
-
rescue
|
184
|
-
(cmd_err_write.closed? ? STDERR : cmd_err_write).puts "Exception in grandchild: #{e
|
182
|
+
rescue => e
|
183
|
+
(cmd_err_write.closed? ? STDERR : cmd_err_write).puts "Exception in grandchild: #{e}."
|
185
184
|
(cmd_err_write.closed? ? STDERR : cmd_err_write).puts e.backtrace
|
186
185
|
exit 1
|
187
186
|
end
|
188
|
-
|
187
|
+
end
|
189
188
|
|
190
189
|
# we do not use these ends of the pipes in the child
|
191
190
|
cmd_out_write.close
|
@@ -198,7 +197,7 @@ module Bluepill
|
|
198
197
|
result = {
|
199
198
|
:stdout => cmd_out_read.read,
|
200
199
|
:stderr => cmd_err_read.read,
|
201
|
-
:exit_code =>
|
200
|
+
:exit_code => $CHILD_STATUS.exitstatus,
|
202
201
|
}
|
203
202
|
|
204
203
|
# We're done with these ends of the pipes as well
|
@@ -214,7 +213,7 @@ module Bluepill
|
|
214
213
|
end
|
215
214
|
|
216
215
|
def store
|
217
|
-
@store ||=
|
216
|
+
@store ||= {}
|
218
217
|
end
|
219
218
|
|
220
219
|
def reset_data
|
@@ -229,7 +228,7 @@ module Bluepill
|
|
229
228
|
|
230
229
|
lines.inject(Hash.new) do |mem, line|
|
231
230
|
chunks = line.split(/\s+/)
|
232
|
-
chunks.delete_if {|c| c.strip.empty? }
|
231
|
+
chunks.delete_if { |c| c.strip.empty? }
|
233
232
|
pid = chunks[IDX_MAP[:pid]].strip.to_i
|
234
233
|
command = chunks.slice!(IDX_MAP[:command], chunks.length).join ' '
|
235
234
|
chunks[IDX_MAP[:command]] = command
|
@@ -242,11 +241,11 @@ module Bluepill
|
|
242
241
|
def parse_elapsed_time(str)
|
243
242
|
# [[dd-]hh:]mm:ss
|
244
243
|
if str =~ /(?:(?:(\d+)-)?(\d\d):)?(\d\d):(\d\d)/
|
245
|
-
days = (
|
246
|
-
hours = (
|
247
|
-
mins =
|
248
|
-
secs =
|
249
|
-
((days*24 + hours)*60 + mins)*60 + secs
|
244
|
+
days = (Regexp.last_match[1] || 0).to_i
|
245
|
+
hours = (Regexp.last_match[2] || 0).to_i
|
246
|
+
mins = Regexp.last_match[3].to_i
|
247
|
+
secs = Regexp.last_match[4].to_i
|
248
|
+
((days * 24 + hours) * 60 + mins) * 60 + secs
|
250
249
|
else
|
251
250
|
0
|
252
251
|
end
|
@@ -255,33 +254,28 @@ module Bluepill
|
|
255
254
|
# be sure to call this from a fork otherwise it will modify the attributes
|
256
255
|
# of the bluepill daemon
|
257
256
|
def drop_privileges(uid, gid, supplementary_groups)
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
group_nums = supplementary_groups.map do |group|
|
265
|
-
Etc.getgrnam(group).gid
|
266
|
-
end
|
267
|
-
|
268
|
-
::Process.groups = [gid_num] if gid
|
269
|
-
::Process.groups |= group_nums unless group_nums.empty?
|
270
|
-
::Process::Sys.setgid(gid_num) if gid
|
271
|
-
::Process::Sys.setuid(uid_num) if uid
|
272
|
-
ENV['HOME'] = Etc.getpwuid(uid_num).try(:dir) || ENV['HOME'] if uid
|
257
|
+
return unless ::Process::Sys.geteuid.zero?
|
258
|
+
uid_num = Etc.getpwnam(uid).uid if uid
|
259
|
+
gid_num = Etc.getgrnam(gid).gid if gid
|
260
|
+
supplementary_groups ||= []
|
261
|
+
group_nums = supplementary_groups.map do |group|
|
262
|
+
Etc.getgrnam(group).gid
|
273
263
|
end
|
264
|
+
::Process.groups = [gid_num] if gid
|
265
|
+
::Process.groups |= group_nums unless group_nums.empty?
|
266
|
+
::Process::Sys.setgid(gid_num) if gid
|
267
|
+
::Process::Sys.setuid(uid_num) if uid
|
268
|
+
ENV['HOME'] = Etc.getpwuid(uid_num).try(:dir) || ENV['HOME'] if uid
|
274
269
|
end
|
275
270
|
|
276
271
|
def can_write_pid_file(pid_file, logger)
|
277
272
|
FileUtils.touch(pid_file)
|
278
273
|
File.unlink(pid_file)
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
return false
|
274
|
+
true
|
275
|
+
rescue => e
|
276
|
+
logger.warning(format('%s - %s', e.class.name, e.message))
|
277
|
+
e.backtrace.each { |l| logger.warning l }
|
278
|
+
false
|
285
279
|
end
|
286
280
|
|
287
281
|
def redirect_io(io_in, io_out, io_err)
|