bluepill 0.0.68 → 0.0.69
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|