Class: Rush::Connection::Local
- Inherits:
-
Object
- Object
- Rush::Connection::Local
- 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
-
#alive? ⇒ Boolean
Local connections are always alive.
-
#append_to_file(full_path, contents) ⇒ Object
Append contents to a file.
- #bash(command, user = nil, background = false, reset_environment = false) ⇒ Object
- #bash_background(command, user, reset_environment) ⇒ Object
-
#chown(full_path, user = nil, group = nil, options = {}) ⇒ Object
A frontend for FileUtils::chown.
- #close_all_descriptors(keep_open = []) ⇒ Object
-
#copy(src, dst) ⇒ Object
Copy ane entry from one path to another.
-
#create_dir(full_path) ⇒ Object
Create a dir.
-
#destroy(full_path) ⇒ Object
Destroy a file or dir.
-
#ensure_tunnel(options = {}) ⇒ Object
No-op for duck typing with remote connection.
-
#file_contents(full_path) ⇒ Object
Read raw bytes from a file.
-
#index(base_path, glob) ⇒ Object
Get an index of files from the given path with the glob.
-
#kill_process(pid, options = {}) ⇒ Object
Terminate a process, by pid.
-
#linux_processes ⇒ Object
Process list on Linux using /proc.
-
#ln(src, dst, options = {}) ⇒ Object
Create a hard link to another file.
-
#ln_s(src, dst, options = {}) ⇒ Object
Create a symbolic link to another file.
-
#os_x_processes ⇒ Object
Process list on OS X or other unixes without a /proc.
-
#os_x_raw_ps ⇒ Object
ps command used to generate list of processes on non-/proc unixes.
-
#parse_oleprocinfo(proc_info) ⇒ Object
Parse the Windows OLE process info.
-
#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.
-
#process_alive(pid) ⇒ Object
Returns true if the specified pid is running.
-
#processes ⇒ Object
Get the list of processes as an array of hashes.
-
#purge(full_path) ⇒ Object
Purge the contents of a dir.
-
#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.
-
#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.
-
#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.
-
#rename(path, name, new_name) ⇒ Object
Rename an entry within a dir.
-
#resolve_unix_uid_to_user(uid) ⇒ Object
resolve uid to user.
- #resolve_unix_uids(list) ⇒ Object
- #set_access(full_path, access) ⇒ Object
-
#size(full_path) ⇒ Object
Fetch the size of a dir, since a standard file stat does not include the size of the contents.
-
#stat(full_path) ⇒ Object
Fetch stats (size, ctime, etc) on an entry.
- #sudo(user, shell) ⇒ Object
-
#symlink?(path) ⇒ Boolean
Is this path a symlink?.
-
#windows_processes ⇒ Object
Process list on Windows.
-
#write_archive(archive, dir) ⇒ Object
Extract an in-memory archive to a dir.
-
#write_file(full_path, contents) ⇒ Object
Write raw bytes to a file.
Instance Method Details
#alive? ⇒ Boolean
Local connections are always alive.
401 402 403 |
# File 'lib/rush/local.rb', line 401 def alive? true end |
#append_to_file(full_path, contents) ⇒ Object
Append contents to a file
25 26 27 28 |
# File 'lib/rush/local.rb', line 25 def append_to_file(full_path, contents) ::File.open(full_path, 'a') { |f| f.write contents } true end |
#bash(command, user = nil, background = false, reset_environment = false) ⇒ Object
322 323 324 325 326 327 328 329 330 331 |
# File 'lib/rush/local.rb', line 322 def bash(command, user=nil, background=false, reset_environment=false) return bash_background(command, user, reset_environment) if background sh = Session::Bash.new shell = reset_environment ? "env -i bash" : "bash" out, err = sh.execute sudo(user, shell), :stdin => command retval = sh.status sh.close! raise(Rush::BashFailed, err) if retval != 0 out end |
#bash_background(command, user, reset_environment) ⇒ Object
333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/rush/local.rb', line 333 def bash_background(command, user, reset_environment) pid = fork do inpipe, outpipe = IO.pipe outpipe.write command outpipe.close STDIN.reopen(inpipe) close_all_descriptors([inpipe.to_i]) shell = reset_environment ? "env -i bash" : "bash" exec sudo(user, shell) end Process::detach pid pid end |
#chown(full_path, user = nil, group = nil, options = {}) ⇒ Object
A frontend for FileUtils::chown
155 156 157 158 159 160 161 |
# File 'lib/rush/local.rb', line 155 def chown( full_path, user=nil, group=nil, ={} ) if [:recursive] FileUtils.chown_R(user, group, full_path, ) else FileUtils.chown(user, group, full_path, ) end end |
#close_all_descriptors(keep_open = []) ⇒ Object
352 353 354 355 356 357 |
# File 'lib/rush/local.rb', line 352 def close_all_descriptors(keep_open = []) 3.upto(256) do |fd| next if keep_open.include?(fd) IO::new(fd).close rescue nil end end |
#copy(src, dst) ⇒ Object
Copy ane entry from one path to another.
72 73 74 75 76 77 |
# File 'lib/rush/local.rb', line 72 def copy(src, dst) raise(Rush::DoesNotExist, src) unless File.exists?(src) raise(Rush::DoesNotExist, File.dirname(dst)) unless File.exists?(File.dirname(dst)) FileUtils.cp_r(src, dst) true end |
#create_dir(full_path) ⇒ Object
Create a dir.
56 57 58 59 |
# File 'lib/rush/local.rb', line 56 def create_dir(full_path) FileUtils.mkdir_p(full_path) true end |
#destroy(full_path) ⇒ Object
Destroy a file or dir.
38 39 40 41 42 |
# File 'lib/rush/local.rb', line 38 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.
397 398 |
# File 'lib/rush/local.rb', line 397 def ensure_tunnel(={}) end |
#file_contents(full_path) ⇒ Object
Read raw bytes from a file.
31 32 33 34 35 |
# File 'lib/rush/local.rb', line 31 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.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/rush/local.rb', line 117 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, options = {}) ⇒ Object
Terminate a process, by pid.
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/rush/local.rb', line 296 def kill_process(pid, ={}) # time to wait before terminating the process, in seconds wait = [:wait] || 3 if wait > 0 ::Process.kill('TERM', pid) # keep trying until it's dead (technique borrowed from god) begin Timeout.timeout(wait) do loop do return if !process_alive(pid) sleep 0.5 ::Process.kill('TERM', pid) rescue nil end end rescue Timeout::Error end end ::Process.kill('KILL', pid) rescue nil rescue Errno::ESRCH # if it's dead, great - do nothing end |
#linux_processes ⇒ Object
Process list on Linux using /proc.
185 186 187 188 189 |
# File 'lib/rush/local.rb', line 185 def linux_processes ::Dir["/proc/*/stat"]. select { |file| file =~ /\/proc\/\d+\// }. inject([]) { |list, file| (list << read_proc_file(file)) rescue list } end |
#ln(src, dst, options = {}) ⇒ Object
Create a hard link to another file
80 81 82 83 84 85 |
# File 'lib/rush/local.rb', line 80 def ln(src, dst, = {}) raise(Rush::DoesNotExist, src) unless File.exists?(src) raise(Rush::DoesNotExist, File.dirname(dst)) unless File.exists?(File.dirname(dst)) FileUtils.ln(src, dst, ) true end |
#ln_s(src, dst, options = {}) ⇒ Object
Create a symbolic link to another file
88 89 90 91 92 93 |
# File 'lib/rush/local.rb', line 88 def ln_s(src, dst, = {}) raise(Rush::DoesNotExist, src) unless File.exists?(src) raise(Rush::DoesNotExist, File.dirname(dst)) unless File.exists?(File.dirname(dst)) FileUtils.ln_s(src, dst, ) true end |
#os_x_processes ⇒ Object
Process list on OS X or other unixes without a /proc.
239 240 241 242 243 244 |
# File 'lib/rush/local.rb', line 239 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_ps ⇒ Object
ps command used to generate list of processes on non-/proc unixes.
247 248 249 |
# File 'lib/rush/local.rb', line 247 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.
276 277 278 279 280 281 282 283 284 285 |
# File 'lib/rush/local.rb', line 276 def parse_oleprocinfo(proc_info) { pid: proc_info.ProcessId, uid: 0, command: proc_info.Name, cmdline: proc_info.CommandLine, mem: proc_info.MaximumWorkingSetSize, cpu: proc_info.KernelModeTime.to_i + proc_info.UserModeTime.to_i } 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.
253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/rush/local.rb', line 253 def parse_ps(line) m = line.split(" ", 6) { pid: m[0], uid: m[1], parent_pid: m[2].to_i, mem: m[3].to_i, cpu: m[4].to_i, cmdline: m[5], command: m[5].split(' ').first } end |
#process_alive(pid) ⇒ Object
Returns true if the specified pid is running.
288 289 290 291 292 293 |
# File 'lib/rush/local.rb', line 288 def process_alive(pid) ::Process.kill(0, pid) true rescue Errno::ESRCH false end |
#processes ⇒ Object
Get the list of processes as an array of hashes.
174 175 176 177 178 179 180 181 182 |
# File 'lib/rush/local.rb', line 174 def processes if ::File.directory? "/proc" resolve_unix_uids(linux_processes) elsif ::File.directory? "C:/WINDOWS" windows_processes else os_x_processes end.uniq end |
#purge(full_path) ⇒ Object
Purge the contents of a dir.
45 46 47 48 49 50 51 52 53 |
# File 'lib/rush/local.rb', line 45 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.
103 104 105 |
# File 'lib/rush/local.rb', line 103 def read_archive(full_path) `cd #{Rush.quote(::File.dirname(full_path))}; tar c #{Rush.quote(::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.
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/rush/local.rb', line 212 def read_proc_file(file) stat_contents = ::File.read(file) stat_contents.gsub!(/\((.*)\)/, "") command = $1 data = stat_contents.split(" ") uid = ::File.stat(file).uid pid = data[0] cmdline = ::File.read("/proc/#{pid}/cmdline").gsub(/\0/, ' ') parent_pid = data[2].to_i utime = data[12].to_i ktime = data[13].to_i vss = data[21].to_i / 1024 rss = data[22].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.
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 |
# File 'lib/rush/local.rb', line 368 def receive(params) case params[:action] when 'write_file' then write_file(params[:full_path], params[:payload]) when 'append_to_file' then append_to_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 'ln' then ln(params[:src], params[:dst], params[:options]) when 'ln_s' then ln_s(params[:src], params[:dst], params[:options]) 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 'chown' then chown(params[:full_path], params[:user], params[:group], params[:options]) 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, YAML.load(params[:payload])) when 'bash' then bash(params[:payload], params[:user], params[:background] == 'true', params[:reset_environment] == 'true') else raise UnknownAction end end |
#rename(path, name, new_name) ⇒ Object
Rename an entry within a dir.
62 63 64 65 66 67 68 69 |
# File 'lib/rush/local.rb', line 62 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
200 201 202 203 204 205 206 207 208 |
# File 'lib/rush/local.rb', line 200 def resolve_unix_uid_to_user(uid) uid = uid.to_i require 'etc' @uid_map ||= {} return @uid_map[uid] if @uid_map[uid] record = Etc.getpwuid(uid) rescue (return nil) @uid_map.merge uid => record.name record.name end |
#resolve_unix_uids(list) ⇒ Object
191 192 193 194 195 196 197 |
# File 'lib/rush/local.rb', line 191 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
149 150 151 |
# File 'lib/rush/local.rb', line 149 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.
165 166 167 168 169 170 171 |
# File 'lib/rush/local.rb', line 165 def size(full_path) if RUBY_PLATFORM.match(/darwin/) `find #{Rush.quote(full_path)} -print0 | xargs -0 stat -f%z`.split(/\n/).map(&:to_i).reduce(:+) else `du -sb #{Rush.quote(full_path)}`.match(/(\d+)/)[1].to_i end end |
#stat(full_path) ⇒ Object
Fetch stats (size, ctime, etc) on an entry. Size will not be accurate for dirs.
136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/rush/local.rb', line 136 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 |
#sudo(user, shell) ⇒ Object
347 348 349 350 |
# File 'lib/rush/local.rb', line 347 def sudo(user, shell) return shell if user.nil? || user.empty? "cd /; sudo -H -u #{user} \"#{shell}\"" end |
#symlink?(path) ⇒ Boolean
Is this path a symlink?
96 97 98 |
# File 'lib/rush/local.rb', line 96 def symlink?(path) File.symlink? path end |
#windows_processes ⇒ Object
Process list on Windows.
267 268 269 270 271 272 273 |
# File 'lib/rush/local.rb', line 267 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.
108 109 110 111 112 |
# File 'lib/rush/local.rb', line 108 def write_archive(archive, dir) IO.popen("cd #{Rush::quote(dir)}; tar x", "w") do |p| p.write archive end end |
#write_file(full_path, contents) ⇒ Object
Write raw bytes to a file.
19 20 21 22 |
# File 'lib/rush/local.rb', line 19 def write_file(full_path, contents) ::File.open(full_path, 'w') { |f| f.write contents } true end |