Class: Rush::Connection::Local

Inherits:
Object
  • Object
show all
Defined in:
lib/rush/local.rb

Overview

Rush::Box uses a connection object to execute all rush commands. If the box is local, Rush::Connection::Local is created. The local connection is the heart of rush’s internals. (Users of the rush shell or library need never access the connection object directly, so the docs herein are intended for developers wishing to modify rush.)

The local connection has a series of methods which do the actual work of modifying files, getting process lists, and so on. RushServer creates a local connection to handle incoming requests; the translation from a raw hash of parameters to an executed method is handled by Rush::Connection::Local#receive.

Defined Under Namespace

Classes: UnknownAction

Instance Method Summary collapse

Instance Method Details

#alive?Boolean

Local connections are always alive.

Returns:

  • (Boolean)


371
372
373
# File 'lib/rush/local.rb', line 371

def alive?
	true
end

#bash(command, user = nil, background = false) ⇒ Object

Raises:



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/rush/local.rb', line 291

def bash(command, user=nil, background=false)
	return bash_background(command, user) if background

	require 'session'

	sh = Session::Bash.new

	if user and user != ""
		out, err = sh.execute "cd /; sudo -H -u #{user} bash", :stdin => command
	else
		out, err = sh.execute command
	end

	retval = sh.status
	sh.close!

	raise(Rush::BashFailed, err) if retval != 0

	out
end

#bash_background(command, user) ⇒ Object



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/rush/local.rb', line 312

def bash_background(command, user)
	inpipe, outpipe = IO.pipe

	pid = fork do
		outpipe.write command
		outpipe.close
		STDIN.reopen(inpipe)

		if user and user != ''
			exec "cd /; sudo -H -u #{user} bash"
		else
			exec "bash"
		end
	end
	outpipe.close

	Process::detach pid

	pid
end

#copy(src, dst) ⇒ Object

Copy ane entry from one path to another.



65
66
67
68
69
70
71
72
# File 'lib/rush/local.rb', line 65

def copy(src, dst)
	FileUtils.cp_r(src, dst)
	true
rescue Errno::ENOENT
	raise Rush::DoesNotExist, File.dirname(dst)
rescue RuntimeError
	raise Rush::DoesNotExist, src
end

#create_dir(full_path) ⇒ Object

Create a dir.



49
50
51
52
# File 'lib/rush/local.rb', line 49

def create_dir(full_path)
	FileUtils.mkdir_p(full_path)
	true
end

#destroy(full_path) ⇒ Object

Destroy a file or dir.



32
33
34
35
36
# File 'lib/rush/local.rb', line 32

def destroy(full_path)
	raise "No." if full_path == '/'
	FileUtils.rm_rf(full_path)
	true
end

#ensure_tunnel(options = {}) ⇒ Object

No-op for duck typing with remote connection.



367
368
# File 'lib/rush/local.rb', line 367

def ensure_tunnel(options={})
end

#file_contents(full_path) ⇒ Object

Read raw bytes from a file.



25
26
27
28
29
# File 'lib/rush/local.rb', line 25

def file_contents(full_path)
	::File.read(full_path)
rescue Errno::ENOENT
	raise Rush::DoesNotExist, full_path
end

#index(base_path, glob) ⇒ Object

Get an index of files from the given path with the glob. Could return nested values if the glob contains a doubleglob. The return value is an array of full paths, with directories listed first.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/rush/local.rb', line 91

def index(base_path, glob)
	glob = '*' if glob == '' or glob.nil?
	dirs = []
	files = []
	::Dir.chdir(base_path) do
		::Dir.glob(glob).each do |fname|
			if ::File.directory?(fname)
				dirs << fname + '/'
			else
				files << fname
			end
		end
	end
	dirs.sort + files.sort
rescue Errno::ENOENT
	raise Rush::DoesNotExist, base_path
end

#kill_process(pid) ⇒ Object

Terminate a process, by pid.



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/rush/local.rb', line 275

def kill_process(pid)
	::Process.kill('TERM', pid)

	# keep trying until it's dead (technique borrowed from god)
	5.times do
		return if !process_alive(pid)
		sleep 0.5
		::Process.kill('TERM', pid) rescue nil
	end

	::Process.kill('KILL', pid) rescue nil

rescue Errno::ESRCH
	# if it's dead, great - do nothing
end

#linux_processesObject

Process list on Linux using /proc.



145
146
147
148
149
150
151
152
153
154
155
# File 'lib/rush/local.rb', line 145

def linux_processes
	list = []
	::Dir["/proc/*/stat"].select { |file| file =~ /\/proc\/\d+\// }.each do |file|
		begin
			list << read_proc_file(file)
		rescue
			# process died between the dir listing and accessing the file
		end
	end
	list
end

#os_x_processesObject

Process list on OS X or other unixes without a /proc.



211
212
213
214
215
216
# File 'lib/rush/local.rb', line 211

def os_x_processes
	raw = os_x_raw_ps.split("\n").slice(1, 99999)
	raw.map do |line|
		parse_ps(line)
	end
end

#os_x_raw_psObject

ps command used to generate list of processes on non-/proc unixes.



219
220
221
# File 'lib/rush/local.rb', line 219

def os_x_raw_ps
	`COLUMNS=9999 ps ax -o "pid uid ppid rss cpu command"`
end

#parse_oleprocinfo(proc_info) ⇒ Object

Parse the Windows OLE process info.



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/rush/local.rb', line 248

def parse_oleprocinfo(proc_info)
	command = proc_info.Name
	pid = proc_info.ProcessId
	uid = 0
	cmdline = proc_info.CommandLine
	rss = proc_info.MaximumWorkingSetSize
	time = proc_info.KernelModeTime.to_i + proc_info.UserModeTime.to_i

	{
		:pid => pid,
		:uid => uid,
		:command => command,
		:cmdline => cmdline,
		:mem => rss,
		:cpu => time,
	}
end

#parse_ps(line) ⇒ Object

Parse a single line of the ps command and return the values in a hash suitable for use in the Rush::Process#new.



225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/rush/local.rb', line 225

def parse_ps(line)
	m = line.split(" ", 6)
	params = {}
	params[:pid] = m[0]
	params[:uid] = m[1]
	params[:parent_pid] = m[2].to_i
	params[:mem] = m[3].to_i
	params[:cpu] = m[4].to_i
	params[:cmdline] = m[5]
	params[:command] = params[:cmdline].split(" ").first
	params
end

#process_alive(pid) ⇒ Object

Returns true if the specified pid is running.



267
268
269
270
271
272
# File 'lib/rush/local.rb', line 267

def process_alive(pid)
	::Process.kill(0, pid)
	true
rescue Errno::ESRCH
	false
end

#processesObject

Get the list of processes as an array of hashes.



134
135
136
137
138
139
140
141
142
# File 'lib/rush/local.rb', line 134

def processes
	if ::File.directory? "/proc"
		resolve_unix_uids(linux_processes)
	elsif ::File.directory? "C:/WINDOWS"
		windows_processes
	else
		os_x_processes
	end
end

#purge(full_path) ⇒ Object

Purge the contents of a dir.



39
40
41
42
43
44
45
46
# File 'lib/rush/local.rb', line 39

def purge(full_path)
	raise "No." if full_path == '/'
	Dir.chdir(full_path) do
		all = Dir.glob("*", File::FNM_DOTMATCH).reject { |f| f == '.' or f == '..' }
		FileUtils.rm_rf all
	end
	true
end

#read_archive(full_path) ⇒ Object

Create an in-memory archive (tgz) of a file or dir, which can be transmitted to another server for a copy or move. Note that archive operations have the dir name implicit in the archive.



77
78
79
# File 'lib/rush/local.rb', line 77

def read_archive(full_path)
	`cd #{::File.dirname(full_path)}; tar c #{::File.basename(full_path)}`
end

#read_proc_file(file) ⇒ Object

Read a single file in /proc and store the parsed values in a hash suitable for use in the Rush::Process#new.



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/rush/local.rb', line 186

def read_proc_file(file)
	data = ::File.read(file).split(" ")
	uid = ::File.stat(file).uid
	pid = data[0]
	command = data[1].match(/^\((.*)\)$/)[1]
	cmdline = ::File.read("/proc/#{pid}/cmdline").gsub(/\0/, ' ')
	parent_pid = data[3].to_i
	utime = data[13].to_i
	ktime = data[14].to_i
	vss = data[22].to_i / 1024
	rss = data[23].to_i * 4
	time = utime + ktime

	{
		:pid => pid,
		:uid => uid,
		:command => command,
		:cmdline => cmdline,
		:parent_pid => parent_pid,
		:mem => rss,
		:cpu => time,
	}
end

#receive(params) ⇒ Object

RushServer uses this method to transform a hash (:action plus parameters specific to that action type) into a method call on the connection. The returned value must be text so that it can be transmitted across the wire as an HTTP response.



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/rush/local.rb', line 342

def receive(params)
	case params[:action]
		when 'write_file'     then write_file(params[:full_path], params[:payload])
		when 'file_contents'  then file_contents(params[:full_path])
		when 'destroy'        then destroy(params[:full_path])
		when 'purge'          then purge(params[:full_path])
		when 'create_dir'     then create_dir(params[:full_path])
		when 'rename'         then rename(params[:path], params[:name], params[:new_name])
		when 'copy'           then copy(params[:src], params[:dst])
		when 'read_archive'   then read_archive(params[:full_path])
		when 'write_archive'  then write_archive(params[:payload], params[:dir])
		when 'index'          then index(params[:base_path], params[:glob]).join("\n") + "\n"
		when 'stat'           then YAML.dump(stat(params[:full_path]))
		when 'set_access'     then set_access(params[:full_path], Rush::Access.from_hash(params))
		when 'size'           then size(params[:full_path])
		when 'processes'      then YAML.dump(processes)
		when 'process_alive'  then process_alive(params[:pid]) ? '1' : '0'
		when 'kill_process'   then kill_process(params[:pid].to_i)
		when 'bash'           then bash(params[:payload], params[:user], params[:background] == 'true')
	else
		raise UnknownAction
	end
end

#rename(path, name, new_name) ⇒ Object

Rename an entry within a dir.



55
56
57
58
59
60
61
62
# File 'lib/rush/local.rb', line 55

def rename(path, name, new_name)
	raise(Rush::NameCannotContainSlash, "#{path} rename #{name} to #{new_name}") if new_name.match(/\//)
	old_full_path = "#{path}/#{name}"
	new_full_path = "#{path}/#{new_name}"
	raise(Rush::NameAlreadyExists, "#{path} rename #{name} to #{new_name}") if ::File.exists?(new_full_path)
	FileUtils.mv(old_full_path, new_full_path)
	true
end

#resolve_unix_uid_to_user(uid) ⇒ Object

resolve uid to user



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/rush/local.rb', line 166

def resolve_unix_uid_to_user(uid)
	require 'etc'

	@uid_map ||= {}
	uid = uid.to_i

	return @uid_map[uid] if !@uid_map[uid].nil?
	
	begin
		record = Etc.getpwuid(uid)
	rescue ArgumentError
		return nil
	end

	@uid_map[uid] = record.name
	@uid_map[uid]
end

#resolve_unix_uids(list) ⇒ Object



157
158
159
160
161
162
163
# File 'lib/rush/local.rb', line 157

def resolve_unix_uids(list)
	@uid_map = {} # reset the cache between uid resolutions.
	list.each do |process|
		process[:user] = resolve_unix_uid_to_user(process[:uid])
	end
	list
end

#set_access(full_path, access) ⇒ Object



123
124
125
# File 'lib/rush/local.rb', line 123

def set_access(full_path, access)
	access.apply(full_path)
end

#size(full_path) ⇒ Object

Fetch the size of a dir, since a standard file stat does not include the size of the contents.



129
130
131
# File 'lib/rush/local.rb', line 129

def size(full_path)
	`du -sb #{full_path}`.match(/(\d+)/)[1].to_i
end

#stat(full_path) ⇒ Object

Fetch stats (size, ctime, etc) on an entry. Size will not be accurate for dirs.



110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/rush/local.rb', line 110

def stat(full_path)
	s = ::File.stat(full_path)
	{
		:size => s.size,
		:ctime => s.ctime,
		:atime => s.atime,
		:mtime => s.mtime,
		:mode => s.mode
	}
rescue Errno::ENOENT
	raise Rush::DoesNotExist, full_path
end

#windows_processesObject

Process list on Windows.



239
240
241
242
243
244
245
# File 'lib/rush/local.rb', line 239

def windows_processes
	require 'win32ole'
	wmi = WIN32OLE.connect("winmgmts://")
	wmi.ExecQuery("select * from win32_process").map do |proc_info|
		parse_oleprocinfo(proc_info)
	end
end

#write_archive(archive, dir) ⇒ Object

Extract an in-memory archive to a dir.



82
83
84
85
86
# File 'lib/rush/local.rb', line 82

def write_archive(archive, dir)
	IO.popen("cd #{dir}; tar x", "w") do |p|
		p.write archive
	end
end

#write_file(full_path, contents) ⇒ Object

Write raw bytes to a file.



17
18
19
20
21
22
# File 'lib/rush/local.rb', line 17

def write_file(full_path, contents)
	::File.open(full_path, 'w') do |f|
		f.write contents
	end
	true
end