-
Notifications
You must be signed in to change notification settings - Fork 14.2k
/
Copy pathpg_ctl.rb
202 lines (169 loc) · 6 KB
/
pg_ctl.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
require 'msfdb_helpers/db_interface'
module MsfdbHelpers
class PgCtl < DbInterface
def initialize(db_path:, options:, localconf:, db_conf:)
@db = db_path
@options = options
@localconf = localconf
@db_conf = db_conf
@socket_directory = db_path
super(options)
end
def init(msf_pass, msftest_pass)
puts "Creating database at #{@db}"
Dir.mkdir(@db)
run_cmd("initdb --auth-host=trust --auth-local=trust -E UTF8 #{@db.shellescape}")
File.open("#{@db}/postgresql.conf", 'a') do |f|
f.puts "port = #{@options[:db_port]}"
end
# Try creating a test file at {Dir.tmpdir},
# Else fallback to creation at @{db}
# Else fail with error.
if test_executable_file("#{Dir.tmpdir}")
@socket_directory = Dir.tmpdir
elsif test_executable_file("#{@db}")
@socket_directory = @db
else
print_error("Attempt to create DB socket file at Temporary Directory and `~/.msf4/db` failed. Possibly because they are mounted with NOEXEC flags. Database initialization failed.")
end
start
create_db_users(msf_pass, msftest_pass)
write_db_client_auth_config
restart
end
# Creates and attempts to execute a testfile in the specified directory,
# to determine if it is mounted with NOEXEC flags.
def test_executable_file(path)
begin
file_name = File.join(path, 'msfdb_testfile')
File.open(file_name, 'w') do |f|
f.puts "#!/bin/bash\necho exec"
end
File.chmod(0744, file_name)
if run_cmd(file_name)
File.open("#{@db}/postgresql.conf", 'a') do |f|
f.puts "unix_socket_directories = \'#{path}\'"
end
puts "Creating db socket file at #{path}"
end
return true
rescue => e
return false
ensure
begin
File.delete(file_name)
rescue
print_error("Unable to delete test file #{file_name}")
end
end
end
def delete
if exists?
stop
if @options[:delete_existing_data]
puts "Deleting all data at #{@db}"
FileUtils.rm_rf(@db)
end
if @options[:delete_existing_data]
FileUtils.rm_r(@db_conf, force: true)
end
else
puts "No data at #{@db}, doing nothing"
end
end
def start
if status == DatabaseStatus::RUNNING
puts "Database already started at #{@db}"
return true
end
print "Starting database at #{@db}..."
pg_ctl_spawn_cmd = "pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db.shellescape} -l #{@db.shellescape}/log start &"
puts "spawn_cmd: #{pg_ctl_spawn_cmd}" if @options[:debug]
pg_ctl_pid = Process.spawn(pg_ctl_spawn_cmd)
Process.detach(pg_ctl_pid)
is_database_running = retry_until_truthy(timeout: 60) do
status == DatabaseStatus::RUNNING
end
if is_database_running
puts 'success'.green.bold.to_s
true
else
begin
Process.kill(:KILL, pg_ctl_pid)
rescue => e
puts "Failed to kill pg_ctl_pid=#{pg_ctl_pid} - #{e.class} #{e.message}" if @options[:debug]
end
puts 'failed'.red.bold.to_s
false
end
end
def stop
if status == DatabaseStatus::RUNNING
puts "Stopping database at #{@db}"
run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db.shellescape} stop")
else
puts "Database is no longer running at #{@db}"
end
end
def restart
stop
start
end
def exists?
Dir.exist?(@db)
end
def status
if exists?
if run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db.shellescape} status") == 0
DatabaseStatus::RUNNING
else
DatabaseStatus::INACTIVE
end
else
DatabaseStatus::NOT_FOUND
end
end
def create_db_users(msf_pass, msftest_pass)
puts 'Creating database users'
run_psql("create user #{@options[:msf_db_user].shellescape} with password '#{msf_pass}'", @socket_directory)
run_psql("create user #{@options[:msftest_db_user].shellescape} with password '#{msftest_pass}'", @socket_directory)
run_psql("alter role #{@options[:msf_db_user].shellescape} createdb", @socket_directory)
run_psql("alter role #{@options[:msftest_db_user].shellescape} createdb", @socket_directory)
run_psql("alter role #{@options[:msf_db_user].shellescape} with password '#{msf_pass}'", @socket_directory)
run_psql("alter role #{@options[:msftest_db_user].shellescape} with password '#{msftest_pass}'", @socket_directory)
conn = PG.connect(host: @options[:db_host], dbname: 'postgres', port: @options[:db_port], user: @options[:msf_db_user], password: msf_pass)
conn.exec("CREATE DATABASE #{@options[:msf_db_name]}")
conn.exec("CREATE DATABASE #{@options[:msftest_db_name]}")
conn.finish
end
def write_db_client_auth_config
client_auth_config = "#{@db}/pg_hba.conf"
super(client_auth_config)
end
def self.requirements
%w[psql pg_ctl initdb createdb]
end
protected
def retry_until_truthy(timeout:)
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
ending_time = start_time + timeout
retry_count = 0
while Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) < ending_time
result = yield
return result if result
retry_count += 1
remaining_time_budget = ending_time - Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
break if remaining_time_budget <= 0
delay = 2**retry_count
if delay >= remaining_time_budget
delay = remaining_time_budget
puts("Final attempt. Sleeping for the remaining #{delay} seconds out of total timeout #{timeout}") if @options[:debug]
else
puts("Sleeping for #{delay} seconds before attempting again") if @options[:debug]
end
sleep delay
end
nil
end
end
end