PageRenderTime 62ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/plugins/runnables/vendor/session-2.4.0/lib/session.rb

https://github.com/wcherry/redcar
Ruby | 757 lines | 561 code | 73 blank | 123 comment | 56 complexity | d7b8b9d0e01905051f845575d21cd3a2 MD5 | raw file
  1. require 'open3'
  2. require 'tmpdir'
  3. require 'thread'
  4. require 'yaml'
  5. require 'tempfile'
  6. module Session
  7. #--{{{
  8. VERSION = '2.4.0'
  9. @track_history = ENV['SESSION_HISTORY'] || ENV['SESSION_TRACK_HISTORY']
  10. @use_spawn = ENV['SESSION_USE_SPAWN']
  11. @use_open3 = ENV['SESSION_USE_OPEN3']
  12. @use_open4 = ENV['SESSION_USE_OPEN4']
  13. @debug = ENV['SESSION_DEBUG']
  14. class << self
  15. #--{{{
  16. attr :track_history, true
  17. attr :use_spawn, true
  18. attr :use_open3, true
  19. attr :use_open4, true
  20. attr :debug, true
  21. def new(*a, &b)
  22. #--{{{
  23. Sh::new(*a, &b)
  24. #--}}}
  25. end
  26. alias [] new
  27. #--}}}
  28. end
  29. class PipeError < StandardError; end
  30. class ExecutionError < StandardError; end
  31. class History
  32. #--{{{
  33. def initialize; @a = []; end
  34. def method_missing(m,*a,&b); @a.send(m,*a,&b); end
  35. def to_yaml(*a,&b); @a.to_yaml(*a,&b); end
  36. alias to_s to_yaml
  37. alias to_str to_yaml
  38. #--}}}
  39. end # class History
  40. class Command
  41. #--{{{
  42. class << self
  43. #--{{{
  44. def cmdno; @cmdno ||= 0; end
  45. def cmdno= n; @cmdno = n; end
  46. #--}}}
  47. end
  48. # attributes
  49. #--{{{
  50. attr :cmd
  51. attr :cmdno
  52. attr :out,true
  53. attr :err,true
  54. attr :cid
  55. attr :begin_out
  56. attr :end_out
  57. attr :begin_out_pat
  58. attr :end_out_pat
  59. attr :begin_err
  60. attr :end_err
  61. attr :begin_err_pat
  62. attr :end_err_pat
  63. #--}}}
  64. def initialize(command)
  65. #--{{{
  66. @cmd = command.to_s
  67. @cmdno = self.class.cmdno
  68. self.class.cmdno += 1
  69. @err = ''
  70. @out = ''
  71. @cid = "%d_%d_%d" % [$$, cmdno, rand(Time.now.usec)]
  72. @begin_out = "__CMD_OUT_%s_BEGIN__" % cid
  73. @end_out = "__CMD_OUT_%s_END__" % cid
  74. @begin_out_pat = %r/#{ Regexp.escape(@begin_out) }/
  75. @end_out_pat = %r/#{ Regexp.escape(@end_out) }/
  76. @begin_err = "__CMD_ERR_%s_BEGIN__" % cid
  77. @end_err = "__CMD_ERR_%s_END__" % cid
  78. @begin_err_pat = %r/#{ Regexp.escape(@begin_err) }/
  79. @end_err_pat = %r/#{ Regexp.escape(@end_err) }/
  80. #--}}}
  81. end
  82. def to_hash
  83. #--{{{
  84. %w(cmdno cmd out err cid).inject({}){|h,k| h.update k => send(k) }
  85. #--}}}
  86. end
  87. def to_yaml(*a,&b)
  88. #--{{{
  89. to_hash.to_yaml(*a,&b)
  90. #--}}}
  91. end
  92. alias to_s to_yaml
  93. alias to_str to_yaml
  94. #--}}}
  95. end # class Command
  96. class AbstractSession
  97. #--{{{
  98. # class methods
  99. class << self
  100. #--{{{
  101. def default_prog
  102. #--{{{
  103. return @default_prog if defined? @default_prog and @default_prog
  104. if defined? self::DEFAULT_PROG
  105. return @default_prog = self::DEFAULT_PROG
  106. else
  107. @default_prog = ENV["SESSION_#{ self }_PROG"]
  108. end
  109. nil
  110. #--}}}
  111. end
  112. def default_prog= prog
  113. #--{{{
  114. @default_prog = prog
  115. #--}}}
  116. end
  117. attr :track_history, true
  118. attr :use_spawn, true
  119. attr :use_open3, true
  120. attr :use_open4, true
  121. attr :debug, true
  122. def init
  123. #--{{{
  124. @track_history = nil
  125. @use_spawn = nil
  126. @use_open3 = nil
  127. @use_open4 = nil
  128. @debug = nil
  129. #--}}}
  130. end
  131. alias [] new
  132. #--}}}
  133. end
  134. # class init
  135. init
  136. # attributes
  137. #--{{{
  138. attr :opts
  139. attr :prog
  140. attr :stdin
  141. alias i stdin
  142. attr :stdout
  143. alias o stdout
  144. attr :stderr
  145. alias e stderr
  146. attr :history
  147. attr :track_history
  148. attr :outproc, true
  149. attr :errproc, true
  150. attr :use_spawn
  151. attr :use_open3
  152. attr :use_open4
  153. attr :debug, true
  154. alias debug? debug
  155. attr :threads
  156. attr :pid
  157. #--}}}
  158. # instance methods
  159. def initialize(*args)
  160. #--{{{
  161. @opts = hashify(*args)
  162. @prog = getopt('prog', opts, getopt('program', opts, self.class::default_prog))
  163. raise(ArgumentError, "no program specified") unless @prog
  164. @track_history = nil
  165. @track_history = Session::track_history unless Session::track_history.nil?
  166. @track_history = self.class::track_history unless self.class::track_history.nil?
  167. @track_history = getopt('history', opts) if hasopt('history', opts)
  168. @track_history = getopt('track_history', opts) if hasopt('track_history', opts)
  169. @use_spawn = nil
  170. @use_spawn = Session::use_spawn unless Session::use_spawn.nil?
  171. @use_spawn = self.class::use_spawn unless self.class::use_spawn.nil?
  172. @use_spawn = getopt('use_spawn', opts) if hasopt('use_spawn', opts)
  173. @use_open3 = nil
  174. @use_open3 = Session::use_open3 unless Session::use_open3.nil?
  175. @use_open3 = self.class::use_open3 unless self.class::use_open3.nil?
  176. @use_open3 = getopt('use_open3', opts) if hasopt('use_open3', opts)
  177. @use_open4 = nil
  178. @use_open4 = Session::use_open4 unless Session::use_open4.nil?
  179. @use_open4 = self.class::use_open4 unless self.class::use_open4.nil?
  180. @use_open4 = getopt('use_open4', opts) if hasopt('use_open4', opts)
  181. @debug = nil
  182. @debug = Session::debug unless Session::debug.nil?
  183. @debug = self.class::debug unless self.class::debug.nil?
  184. @debug = getopt('debug', opts) if hasopt('debug', opts)
  185. @history = nil
  186. @history = History::new if @track_history
  187. @outproc = nil
  188. @errproc = nil
  189. if @use_open4
  190. @pid, @stdin, @stdout, @stderr = IO::popen4 @prog
  191. else
  192. if @use_spawn
  193. Spawn::spawn @prog
  194. elsif @use_open3
  195. Open3::popen3 @prog
  196. else
  197. __popen3 @prog
  198. end
  199. end
  200. @threads = []
  201. clear
  202. if block_given?
  203. ret = nil
  204. begin
  205. ret = yield self
  206. ensure
  207. self.close!
  208. end
  209. return ret
  210. end
  211. return self
  212. #--}}}
  213. end
  214. def getopt opt, hash, default = nil
  215. #--{{{
  216. key = opt
  217. return hash[key] if hash.has_key? key
  218. key = "#{ key }"
  219. return hash[key] if hash.has_key? key
  220. key = key.intern
  221. return hash[key] if hash.has_key? key
  222. return default
  223. #--}}}
  224. end
  225. def hasopt opt, hash
  226. #--{{{
  227. key = opt
  228. return key if hash.has_key? key
  229. key = "#{ key }"
  230. return key if hash.has_key? key
  231. key = key.intern
  232. return key if hash.has_key? key
  233. return false
  234. #--}}}
  235. end
  236. def __popen3(*cmd)
  237. #--{{{
  238. pw = IO::pipe # pipe[0] for read, pipe[1] for write
  239. pr = IO::pipe
  240. pe = IO::pipe
  241. pid =
  242. __fork{
  243. # child
  244. pw[1].close
  245. STDIN.reopen(pw[0])
  246. pw[0].close
  247. pr[0].close
  248. STDOUT.reopen(pr[1])
  249. pr[1].close
  250. pe[0].close
  251. STDERR.reopen(pe[1])
  252. pe[1].close
  253. exec(*cmd)
  254. }
  255. Process::detach pid # avoid zombies
  256. pw[0].close
  257. pr[1].close
  258. pe[1].close
  259. pi = [pw[1], pr[0], pe[0]]
  260. pw[1].sync = true
  261. if defined? yield
  262. begin
  263. return yield(*pi)
  264. ensure
  265. pi.each{|p| p.close unless p.closed?}
  266. end
  267. end
  268. pi
  269. #--}}}
  270. end
  271. def __fork(*a, &b)
  272. #--{{{
  273. verbose = $VERBOSE
  274. begin
  275. $VERBOSE = nil
  276. Kernel::fork(*a, &b)
  277. ensure
  278. $VERBOSE = verbose
  279. end
  280. #--}}}
  281. end
  282. # abstract methods
  283. def clear
  284. #--{{{
  285. raise NotImplementedError
  286. #--}}}
  287. end
  288. alias flush clear
  289. def path
  290. #--{{{
  291. raise NotImplementedError
  292. #--}}}
  293. end
  294. def path=
  295. #--{{{
  296. raise NotImplementedError
  297. #--}}}
  298. end
  299. def send_command cmd
  300. #--{{{
  301. raise NotImplementedError
  302. #--}}}
  303. end
  304. # concrete methods
  305. def track_history= bool
  306. #--{{{
  307. @history ||= History::new
  308. @track_history = bool
  309. #--}}}
  310. end
  311. def ready?
  312. #--{{{
  313. (stdin and stdout and stderr) and
  314. (IO === stdin and IO === stdout and IO === stderr) and
  315. (not (stdin.closed? or stdout.closed? or stderr.closed?))
  316. #--}}}
  317. end
  318. def close!
  319. #--{{{
  320. [stdin, stdout, stderr].each{|pipe| pipe.close}
  321. stdin, stdout, stderr = nil, nil, nil
  322. true
  323. #--}}}
  324. end
  325. alias close close!
  326. def hashify(*a)
  327. #--{{{
  328. a.inject({}){|o,h| o.update(h)}
  329. #--}}}
  330. end
  331. private :hashify
  332. def execute(command, redirects = {})
  333. #--{{{
  334. $session_command = command if @debug
  335. raise(PipeError, command) unless ready?
  336. # clear buffers
  337. clear
  338. # setup redirects
  339. rerr = redirects[:e] || redirects[:err] || redirects[:stderr] ||
  340. redirects['stderr'] || redirects['e'] || redirects['err'] ||
  341. redirects[2] || redirects['2']
  342. rout = redirects[:o] || redirects[:out] || redirects[:stdout] ||
  343. redirects['stdout'] || redirects['o'] || redirects['out'] ||
  344. redirects[1] || redirects['1']
  345. # create cmd object and add to history
  346. cmd = Command::new command.to_s
  347. # store cmd if tracking history
  348. history << cmd if track_history
  349. # mutex for accessing shared data
  350. mutex = Mutex::new
  351. # io data for stderr and stdout
  352. err = {
  353. :io => stderr,
  354. :cmd => cmd.err,
  355. :name => 'stderr',
  356. :begin => false,
  357. :end => false,
  358. :begin_pat => cmd.begin_err_pat,
  359. :end_pat => cmd.end_err_pat,
  360. :redirect => rerr,
  361. :proc => errproc,
  362. :yield => lambda{|buf| yield(nil, buf)},
  363. :mutex => mutex,
  364. }
  365. out = {
  366. :io => stdout,
  367. :cmd => cmd.out,
  368. :name => 'stdout',
  369. :begin => false,
  370. :end => false,
  371. :begin_pat => cmd.begin_out_pat,
  372. :end_pat => cmd.end_out_pat,
  373. :redirect => rout,
  374. :proc => outproc,
  375. :yield => lambda{|buf| yield(buf, nil)},
  376. :mutex => mutex,
  377. }
  378. begin
  379. # send command in the background so we can begin processing output
  380. # immediately - thanks to tanaka akira for this suggestion
  381. threads << Thread::new { send_command cmd }
  382. # init
  383. main = Thread::current
  384. exceptions = []
  385. # fire off reader threads
  386. [err, out].each do |iodat|
  387. threads <<
  388. Thread::new(iodat, main) do |iodat, main|
  389. loop do
  390. main.raise(PipeError, command) unless ready?
  391. main.raise ExecutionError, iodat[:name] if iodat[:end] and not iodat[:begin]
  392. break if iodat[:end] or iodat[:io].eof?
  393. line = iodat[:io].gets
  394. buf = nil
  395. case line
  396. when iodat[:end_pat]
  397. iodat[:end] = true
  398. # handle the special case of non-newline terminated output
  399. if((m = %r/(.+)__CMD/o.match(line)) and (pre = m[1]))
  400. buf = pre
  401. end
  402. when iodat[:begin_pat]
  403. iodat[:begin] = true
  404. else
  405. next unless iodat[:begin] and not iodat[:end] # ignore chaff
  406. buf = line
  407. end
  408. if buf
  409. iodat[:mutex].synchronize do
  410. iodat[:cmd] << buf
  411. iodat[:redirect] << buf if iodat[:redirect]
  412. iodat[:proc].call buf if iodat[:proc]
  413. iodat[:yield].call buf if block_given?
  414. end
  415. end
  416. end
  417. true
  418. end
  419. end
  420. ensure
  421. # reap all threads - accumulating and rethrowing any exceptions
  422. begin
  423. while((t = threads.shift))
  424. t.join
  425. raise ExecutionError, 'iodat thread failure' unless t.value
  426. end
  427. rescue => e
  428. exceptions << e
  429. retry unless threads.empty?
  430. ensure
  431. unless exceptions.empty?
  432. meta_message = '<' << exceptions.map{|e| "#{ e.message } - (#{ e.class })"}.join('|') << '>'
  433. meta_backtrace = exceptions.map{|e| e.backtrace}.flatten
  434. raise ExecutionError, meta_message, meta_backtrace
  435. end
  436. end
  437. end
  438. # this should only happen if eof was reached before end pat
  439. [err, out].each do |iodat|
  440. raise ExecutionError, iodat[:name] unless iodat[:begin] and iodat[:end]
  441. end
  442. # get the exit status
  443. get_status if respond_to? :get_status
  444. out = err = iodat = nil
  445. return [cmd.out, cmd.err]
  446. #--}}}
  447. end
  448. #--}}}
  449. end # class AbstractSession
  450. class Sh < AbstractSession
  451. #--{{{
  452. DEFAULT_PROG = 'sh'
  453. ECHO = 'echo'
  454. attr :status
  455. alias exit_status status
  456. alias exitstatus status
  457. def clear
  458. #--{{{
  459. stdin.puts "#{ ECHO } __clear__ 1>&2"
  460. stdin.puts "#{ ECHO } __clear__"
  461. stdin.flush
  462. while((line = stderr.gets) and line !~ %r/__clear__/o); end
  463. while((line = stdout.gets) and line !~ %r/__clear__/o); end
  464. self
  465. #--}}}
  466. end
  467. def send_command cmd
  468. #--{{{
  469. stdin.printf "%s '%s' 1>&2\n", ECHO, cmd.begin_err
  470. stdin.printf "%s '%s' \n", ECHO, cmd.begin_out
  471. stdin.printf "%s\n", cmd.cmd
  472. stdin.printf "export __exit_status__=$?\n"
  473. stdin.printf "%s '%s' 1>&2\n", ECHO, cmd.end_err
  474. stdin.printf "%s '%s' \n", ECHO, cmd.end_out
  475. stdin.flush
  476. #--}}}
  477. end
  478. def get_status
  479. #--{{{
  480. @status = get_var '__exit_status__'
  481. unless @status =~ /^\s*\d+\s*$/o
  482. raise ExecutionError, "could not determine exit status from <#{ @status.inspect }>"
  483. end
  484. @status = Integer @status
  485. #--}}}
  486. end
  487. def set_var name, value
  488. #--{{{
  489. stdin.puts "export #{ name }=#{ value }"
  490. stdin.flush
  491. #--}}}
  492. end
  493. def get_var name
  494. #--{{{
  495. stdin.puts "#{ ECHO } \"#{ name }=${#{ name }}\""
  496. stdin.flush
  497. var = nil
  498. while((line = stdout.gets))
  499. m = %r/#{ name }\s*=\s*(.*)/.match line
  500. if m
  501. var = m[1]
  502. raise ExecutionError, "could not determine <#{ name }> from <#{ line.inspect }>" unless var
  503. break
  504. end
  505. end
  506. var
  507. #--}}}
  508. end
  509. def path
  510. #--{{{
  511. var = get_var 'PATH'
  512. var.strip.split %r/:/o
  513. #--}}}
  514. end
  515. def path= arg
  516. #--{{{
  517. case arg
  518. when Array
  519. arg = arg.join ':'
  520. else
  521. arg = arg.to_s.strip
  522. end
  523. set_var 'PATH', "'#{ arg }'"
  524. self.path
  525. #--}}}
  526. end
  527. def execute(command, redirects = {}, &block)
  528. #--{{{
  529. # setup redirect on stdin
  530. rin = redirects[:i] || redirects[:in] || redirects[:stdin] ||
  531. redirects['stdin'] || redirects['i'] || redirects['in'] ||
  532. redirects[0] || redirects['0']
  533. if rin
  534. tmp =
  535. begin
  536. Tempfile::new rand.to_s
  537. rescue
  538. Tempfile::new rand.to_s
  539. end
  540. begin
  541. tmp.write(
  542. if rin.respond_to? 'read'
  543. rin.read
  544. elsif rin.respond_to? 'to_s'
  545. rin.to_s
  546. else
  547. rin
  548. end
  549. )
  550. tmp.flush
  551. command = "{ #{ command } ;} < #{ tmp.path }"
  552. #puts command
  553. super(command, redirects, &block)
  554. ensure
  555. tmp.close! if tmp
  556. end
  557. else
  558. super
  559. end
  560. #--}}}
  561. end
  562. #--}}}
  563. end # class Sh
  564. class Bash < Sh
  565. #--{{{
  566. DEFAULT_PROG = 'bash'
  567. class Login < Bash
  568. DEFAULT_PROG = 'bash --login'
  569. end
  570. #--}}}
  571. end # class Bash
  572. class Shell < Bash; end
  573. # IDL => interactive data language - see http://www.rsinc.com/
  574. class IDL < AbstractSession
  575. #--{{{
  576. class LicenseManagerError < StandardError; end
  577. DEFAULT_PROG = 'idl'
  578. MAX_TRIES = 32
  579. def initialize(*args)
  580. #--{{{
  581. tries = 0
  582. ret = nil
  583. begin
  584. ret = super
  585. rescue LicenseManagerError => e
  586. tries += 1
  587. if tries < MAX_TRIES
  588. sleep 1
  589. retry
  590. else
  591. raise LicenseManagerError, "<#{ MAX_TRIES }> attempts <#{ e.message }>"
  592. end
  593. end
  594. ret
  595. #--}}}
  596. end
  597. def clear
  598. #--{{{
  599. stdin.puts "retall"
  600. stdin.puts "printf, -2, '__clear__'"
  601. stdin.puts "printf, -1, '__clear__'"
  602. stdin.flush
  603. while((line = stderr.gets) and line !~ %r/__clear__/o)
  604. raise LicenseManagerError, line if line =~ %r/license\s*manager/io
  605. end
  606. while((line = stdout.gets) and line !~ %r/__clear__/o)
  607. raise LicenseManagerError, line if line =~ %r/license\s*manager/io
  608. end
  609. self
  610. #--}}}
  611. end
  612. def send_command cmd
  613. #--{{{
  614. stdin.printf "printf, -2, '%s'\n", cmd.begin_err
  615. stdin.printf "printf, -1, '%s'\n", cmd.begin_out
  616. stdin.printf "%s\n", cmd.cmd
  617. stdin.printf "retall\n"
  618. stdin.printf "printf, -2, '%s'\n", cmd.end_err
  619. stdin.printf "printf, -1, '%s'\n", cmd.end_out
  620. stdin.flush
  621. #--}}}
  622. end
  623. def path
  624. #--{{{
  625. stdout, stderr = execute "print, !path"
  626. stdout.strip.split %r/:/o
  627. #--}}}
  628. end
  629. def path= arg
  630. #--{{{
  631. case arg
  632. when Array
  633. arg = arg.join ':'
  634. else
  635. arg = arg.to_s.strip
  636. end
  637. stdout, stderr = execute "!path='#{ arg }'"
  638. self.path
  639. #--}}}
  640. end
  641. #--}}}
  642. end # class IDL
  643. module Spawn
  644. #--{{{
  645. class << self
  646. def spawn command
  647. #--{{{
  648. ipath = tmpfifo
  649. opath = tmpfifo
  650. epath = tmpfifo
  651. cmd = "#{ command } < #{ ipath } 1> #{ opath } 2> #{ epath } &"
  652. system cmd
  653. i = open ipath, 'w'
  654. o = open opath, 'r'
  655. e = open epath, 'r'
  656. [i,o,e]
  657. #--}}}
  658. end
  659. def tmpfifo
  660. #--{{{
  661. path = nil
  662. 42.times do |i|
  663. tpath = File::join(Dir::tmpdir, "#{ $$ }.#{ rand }.#{ i }")
  664. v = $VERBOSE
  665. begin
  666. $VERBOSE = nil
  667. system "mkfifo #{ tpath }"
  668. ensure
  669. $VERBOSE = v
  670. end
  671. next unless $? == 0
  672. path = tpath
  673. at_exit{ File::unlink(path) rescue STDERR.puts("rm <#{ path }> failed") }
  674. break
  675. end
  676. raise "could not generate tmpfifo" unless path
  677. path
  678. #--}}}
  679. end
  680. end
  681. #--}}}
  682. end # module Spawn
  683. #--}}}
  684. end # module Session