vcseif 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,90 @@
1
+ # Copyright 2001-2013 Rally Software Development Corp. All Rights Reserved.
2
+
3
+ require 'time'
4
+
5
+ require_relative "exceptions"
6
+ require_relative "proctbl"
7
+
8
+ class LockFile
9
+
10
+ def self.exists?(filename)
11
+ begin
12
+ if File.exists?(filename) and File.readable?(filename)
13
+ return true
14
+ else
15
+ return false
16
+ end
17
+ rescue => ex
18
+ lockex = VCSEIF_Exceptions::UnrecoverableException.new("#{ex.message}")
19
+ raise lockex, ex.message.to_s
20
+ end
21
+ end
22
+
23
+ def self.locker_is_running?(filename, locker)
24
+ # return false if no lock file exists OR the PID that wrote the lock file is no longer running
25
+ return false if !File.exists?(filename)
26
+ holder = self.current_lock_holder(filename)
27
+ return false if holder == "" or holder.nil?
28
+ locker_pid = 0
29
+ started = ""
30
+ holder.scan(/^(\d+)/) {|p| locker_pid = p[0]}
31
+ running = ProcTable.target_process(locker_pid)
32
+
33
+ return false if running.nil?
34
+ return true
35
+ end
36
+
37
+ def self.current_lock_holder(filename)
38
+ holder = ""
39
+ if !File.exists?(filename)
40
+ raise VCSEIF_Exceptions::UnrecoverableException.new("lock file %s does not exists" % filename)
41
+ end
42
+ begin
43
+ lf = File.open(filename, "r")
44
+ entry = lf.read()
45
+ if !entry.nil? and entry.length > 0 and entry.strip().length > 0
46
+ entry = entry.strip()
47
+ chunks = entry.split(' ')
48
+ if (not chunks.empty?) and (chunks.length == 4)
49
+ holder = chunks.first
50
+ end
51
+ end
52
+ lf.close
53
+ rescue => ex
54
+ lockex = VCSEIF_Exceptions::UnrecoverableException.new("#{ex.message}")
55
+ raise lockex, ex.message.to_s
56
+ end
57
+ if holder.empty?
58
+ raise VCSEIF_Exceptions::UnrecoverableException.new("lock file is empty or contains non-standard content")
59
+ end
60
+ return holder
61
+ end
62
+
63
+ def self.create_lock(filename)
64
+ # remove any existing lock
65
+ self.destroy_lock(filename)
66
+
67
+ # and write the lock file with the PID of this process and a current timestamp
68
+ begin
69
+ info = "%d %s\n" % [$$, Time.now.utc.strftime("%Y-%m-%d %H:%M:%S Z")]
70
+ lf = File.new(filename, "w")
71
+ lf.write("%s\n" % info)
72
+ lf.close
73
+ rescue => ex
74
+ lockex = VCSEIF_Exceptions::UnrecoverableException.new("#{ex.message}")
75
+ raise lockex, ex.message.to_s
76
+ end
77
+ end
78
+
79
+
80
+ def self.destroy_lock(filename)
81
+ # remove the existing lock file
82
+ begin
83
+ File.delete(filename) if File.exists?(filename)
84
+ rescue => ex
85
+ lockex = VCSEIF_Exceptions::UnrecoverableException.new("#{ex.message}")
86
+ raise lockex, ex.message.to_s
87
+ end
88
+ end
89
+
90
+ end
@@ -0,0 +1,146 @@
1
+ # Copyright 2001-2013 Rally Software Development Corp. All Rights Reserved.
2
+
3
+ require 'socket'
4
+ require 'date'
5
+
6
+ $os = 'posix'
7
+ begin
8
+ require 'win32ole'
9
+ $os = 'windows'
10
+ rescue LoadError => ex
11
+ end
12
+
13
+ class ProcessInfo
14
+
15
+ attr_accessor :uid, :pid, :ppid, :started, :cmdline
16
+
17
+ def initialize(fields)
18
+ @uid = 0 # default to a Windows "safe" value
19
+ user_field = fields[0]
20
+ @pid = fields[1].to_i
21
+ @ppid = fields[2].to_i
22
+ junk = fields[3]
23
+ @started = fields[4]
24
+ @cmdline = fields[5]
25
+ begin
26
+ uid = user_field.to_i
27
+ @uid = uid
28
+ rescue Exception => ex
29
+ begin
30
+ require 'etc'
31
+ user = Etc.getpwnam(user_field)
32
+ @uid = user.uid.to_i
33
+ rescue
34
+ # do nothing, we'll limp along with the default value established earlier
35
+ end
36
+ end
37
+ end
38
+
39
+ def to_s
40
+ "%5s %5s %5s %12s %s" % [@pid, @uid, @ppid, @started, @cmdline]
41
+ end
42
+ end
43
+
44
+
45
+ class ProcTable
46
+
47
+ def self.all_processes()
48
+ all_procs = []
49
+
50
+ case $os
51
+ when 'posix'
52
+ all_procs = ProcTable.posix_processes()
53
+ when 'windows'
54
+ all_procs = ProcTable.windows_processes()
55
+ end
56
+
57
+ #all_procs.each do |proc|
58
+ # puts "%5d %s" % [proc['pid'], proc['cmdline']]
59
+ #end
60
+
61
+ return all_procs
62
+ end
63
+
64
+ def self.posix_processes()
65
+ all_procs = []
66
+ ps_output = `ps -ef` # ps_output is single string
67
+ pslines = ps_output.split("\n")
68
+ hdr_line = pslines[0]
69
+ cmd_column_ix = hdr_line.index('CMD')
70
+ proc_lines = pslines[1..pslines.length-1]
71
+ proc_lines.each do |ps_entry|
72
+ #puts "|#{ps_entry}|"
73
+ fields = ps_entry.lstrip().split(/\s+/)
74
+ #uid, pid, ppid, junk, started = fields[0], fields[1], fields[2], fields[3], fields[4]
75
+ fields[5] = ps_entry[cmd_column_ix..ps_entry.length-1]
76
+ proc_info = ProcessInfo.new(fields)
77
+ all_procs << proc_info
78
+ #puts "#{proc.inspect}"
79
+ end
80
+ return all_procs
81
+ end
82
+
83
+ def self.windows_processes()
84
+ all_procs = []
85
+ host = Socket.gethostname()
86
+ wmi = WIN32OLE.connect("winmgmts://#{host}/root/cimv2")
87
+ wmi.InstancesOf("Win32_Process").each do |wproc|
88
+ if wproc.CreationDate.nil?
89
+ startDate = nil
90
+ else
91
+ startDate = Date.parse(wproc.CreationDate.split('.').first)
92
+ end
93
+ fields = [0, wproc.ProcessId, wproc.ParentProcessId, "", startDate, wproc.CommandLine]
94
+ proc_info = ProcessInfo.new(fields)
95
+ all_procs << proc_info
96
+ end
97
+ return all_procs
98
+ end
99
+
100
+ def self.target_process(target_pid)
101
+ aps = ProcTable.all_processes
102
+ target_proc = aps.select{|proc| proc.pid == target_pid.to_i}
103
+ return target_proc.first if target_proc.length > 0
104
+ return nil
105
+ end
106
+
107
+ def self.processes_owned_by(target_uid=Process.uid)
108
+ aps = ProcTable.all_processes
109
+ tups = aps.select{|proc| proc.uid == target_uid.to_i}
110
+ return tups
111
+ end
112
+
113
+ def self.processes_matching_pattern(pattern)
114
+ # this is not a true and foolproof regex pattern matching operation
115
+ # The unfortunate truth is that any pattern arg that has a backslash in it
116
+ # is probably going to fail, even though we'd like things like "foo\w+niski" to work.
117
+ # So, given that we expect most callers to provide a simple string, we'll first
118
+ # "adjust" their pattern by replacing backslashes with a token string of "<BACKWHACK>"
119
+ # and then also "adjust" each process cmdline text that same way (mostly to compensate
120
+ # for the MSWIN unfortunate choice of '\' as the path element separator char).
121
+ # and only then do we do a regex check for a match.
122
+ # If there are any matches using this algorithm, we'll return the results.
123
+ # Otherwise we will go ahead and perform the normal regex without any adjustments
124
+ # on the off chance that the caller actually provided a valid regex pattern string
125
+ # instead of a string literal with no regex entities embedded.
126
+
127
+ aps = ProcTable.all_processes
128
+
129
+ matches = []
130
+ aux_pattern = pattern.gsub('\\', '<BACKWHACK>')
131
+ aps.each do |proc|
132
+ next if proc.cmdline == nil # seems to only happen in Windoze land...
133
+ command = proc['cmdline']
134
+ aux_cmdline = command.gsub('\\', '<BACKWHACK>')
135
+ matches << proc if aux_cmdline =! /#{aux_pattern}/
136
+ end
137
+
138
+ return matches if matches.length > 0
139
+
140
+ mps = aps.select{|proc| proc.cmdline =~ /#{pattern}/}
141
+ return mps
142
+ end
143
+
144
+ end
145
+
146
+
@@ -0,0 +1,223 @@
1
+ # Copyright 2001-2013 Rally Software Development Corp. All Rights Reserved.
2
+
3
+ require "logger"
4
+ require "time"
5
+
6
+ # Tweak logger message format, with thanks to
7
+ # blog.grayproductions.net/articles/the_books_are_wrong_about_logger+ruby+logger+format
8
+ class CustomLogFormat < Logger::Formatter
9
+ def call(severity, time, program_name, message)
10
+ datetime = time.utc.strftime("%Y-%m-%d %H:%M:%S Z")
11
+ return "[%s] %5s : %s\n" % [datetime, severity, message]
12
+ end
13
+ end
14
+
15
+ class RallyLogger
16
+
17
+ attr_accessor :errors_cache, :cache_errors, :warnings_cache, :cache_warnings
18
+ attr_reader :logger
19
+
20
+ VCSEIF_MAX_LOGFILES = 10
21
+ VCSEIF_MAX_LOGFILE_SIZE = 5*1024*1024 # 5 MB
22
+
23
+ def initialize(logfile_name=nil, max_logfiles=VCSEIF_MAX_LOGFILES, max_file_size=VCSEIF_MAX_LOGFILE_SIZE)
24
+ if logfile_name.nil?
25
+ @logger = Logger.new(STDOUT)
26
+ else
27
+ @logger = Logger.new(logfile_name, max_logfiles, max_file_size)
28
+ end
29
+
30
+ @cache_errors = false
31
+ @cache_warnings = false
32
+ @errors_cache = []
33
+ @warnings_cache = []
34
+
35
+ @logger.formatter = CustomLogFormat.new # Install custom formatter
36
+ @logger.level = Logger::DEBUG
37
+ end
38
+
39
+ public
40
+ def set_level(level)
41
+ case level
42
+ when 'Fatal'
43
+ @logger.level = Logger::FATAL
44
+ when 'Error'
45
+ @logger.level = Logger::ERROR
46
+ when 'Warn'
47
+ @logger.level = Logger::WARN
48
+ when 'Warning'
49
+ @logger.level = Logger::WARN
50
+ when 'Info'
51
+ @logger.level = Logger::INFO
52
+ when 'Debug'
53
+ @logger.level = Logger::DEBUG
54
+ end
55
+ end
56
+
57
+ def fatal(text, exception=nil)
58
+ context = !exception.nil? ? exception_context(exception) : nominal_context()
59
+ @logger.fatal("#{context} - " + text)
60
+ end
61
+
62
+ def error(text, exception=nil)
63
+ context = !exception.nil? ? exception_context(exception) : nominal_context()
64
+ message = "#{context} - " + text
65
+ @logger.error(message)
66
+ if @cache_errors
67
+ datetime = Time.now.utc.strftime("%Y-%m-%d %H:%M:%S Z")
68
+ info = {:time => datetime, :message => message}
69
+ @errors_cache.push(info)
70
+ end
71
+ end
72
+
73
+ def warning(text, exception=nil)
74
+ context = !exception.nil? ? exception_context(exception) : nominal_context()
75
+ message = "#{context} - " + text
76
+ @logger.warn(message)
77
+ if @cache_warnings
78
+ datetime = Time.now.utc.strftime("%Y-%m-%d %H:%M:%S Z")
79
+ info = {:time => datetime, :message => message}
80
+ @warnings_cache.push(info)
81
+ end
82
+ end
83
+
84
+ alias_method :warn, :warning
85
+
86
+ def info(text, exception=nil)
87
+ @logger.info("#{nominal_context()} - " + text)
88
+ end
89
+
90
+ def debug(text, exception=nil)
91
+ context = !exception.nil? ? exception_context(exception) : nominal_context()
92
+ @logger.debug("#{context} - " + text)
93
+ end
94
+
95
+ def unknown(text, exception=nil)
96
+ context = !exception.nil? ? exception_context(exception) : nominal_context()
97
+ @logger.unknown("#{context} - " + text)
98
+ end
99
+
100
+ def write(text)
101
+ @logger << (text + "\n")
102
+ end
103
+
104
+ def entry(text)
105
+ timestamp = Time.now.utc.strftime("%Y-%m-%d %H:%M:%S Z")
106
+ twig = "[%s] %s" % [timestamp, text]
107
+ write(twig)
108
+ end
109
+
110
+ # def exception(ex)
111
+ # if (ex.kind_of? RecoverableException)
112
+ # warn("Message " + ex.message)
113
+ # else
114
+ # error("Message " + ex.message)
115
+ # error( "Stack Trace")
116
+ # ex.backtrace.each {|trace| write(trace) }
117
+ # end
118
+ # end
119
+
120
+ def cache_errors(cache_on)
121
+ if cache_on === true
122
+ @cache_errors = true
123
+ else
124
+ @cache_errors = false
125
+ end
126
+ end
127
+
128
+ def cache_warnings(cache_on)
129
+ if cache_on === true
130
+ @cache_warnings = true
131
+ else
132
+ @cache_warnings = false
133
+ end
134
+ end
135
+
136
+ def clear_caches_and_flags
137
+ @warnings_cache = []
138
+ @errors_cache = []
139
+ @cache_errors = false
140
+ @cache_warnings = false
141
+ end
142
+
143
+ private
144
+
145
+ def caller_info()
146
+ ##
147
+ ## stack_frames = caller(0)
148
+ ## stack_frames.each_with_index do |stack, ix|
149
+ ## puts "%d) %s" % [ix+1, stack]
150
+ ## end
151
+ ##
152
+ file, line, method = ["", "", ""]
153
+ whence = caller(3).first
154
+ if whence =~ /^(.+?):(\d+)(?::in `(.*)')?/
155
+ file, line, method = $1, $2.to_i, $3
156
+ [file, line, method]
157
+ end
158
+ return [file, line, method]
159
+ end
160
+
161
+ def nominal_context()
162
+ # ideally, we'd like to include the file_name:line_number only if this is being
163
+ # called because an exception has been raised
164
+ target_class = "<main>"
165
+ file_name, line_number, method = caller_info()
166
+ begin
167
+ file_content = File.open(file_name, "r").read()
168
+ file_content.gsub(/\r\n?/, "\n")
169
+ lines = file_content.split("\n")
170
+ line_number.downto(0).each do |ix|
171
+ if lines[ix] =~ /^\s*class\s+(\w+)/
172
+ target_class = $1
173
+ #puts "found target_class #{target_class}"
174
+ break
175
+ else
176
+ #puts "no class in line: #{file_content[ix]}"
177
+ end
178
+ end
179
+ rescue Exception => ex
180
+ puts "unable to open or read #{file_name}"
181
+ end
182
+
183
+ # squish out any leading 'block in ' text in method
184
+ # or rearrange if it mentions 'rescue in block in ' or 'block (n levels) in '
185
+ method = method.sub(/^block in /, '')
186
+ if method =~ /^block \(\d+ levels\) in /
187
+ method = method.sub(/^block \(\d+ levels\) in /, '')
188
+ end
189
+ if method =~ /rescue in block in /
190
+ method = method.sub(/rescue in block in/, '') + "- rescue block(#{line_number})"
191
+ end
192
+ #return "<class_name>.<method_name>(<file_name> line <line_number>)"
193
+ #return "%s.%s (%s line %d)" % [target_class, method, file_name, line_number]
194
+ return "%s.%s" % [target_class, method]
195
+ end
196
+
197
+ def exception_context(exception)
198
+ stack_frames = caller(0)
199
+ ##
200
+ ## puts "Exception context stack_frames to follow ..."
201
+ ## ix = 0
202
+ ## for frame in stack_frames.reverse do
203
+ ## ix += 1
204
+ ## path, line, method = frame.split(':', 3)
205
+ ## path_elements = path.split('/')
206
+ ## puts "%d) %s" % [ix, frame] unless path_elements.count('gems') > 1
207
+ ## end
208
+ ## puts "!- " * 25
209
+ ## puts "(identified target frame as third to last)"
210
+ ##
211
+ target_frame = stack_frames[2] # the third item in the stack
212
+ file_name, colon, line_number = target_frame.rpartition(':')
213
+ tattle = "%s line %s" % [file_name, line_number]
214
+ ex_class_name = exception.class.name
215
+ if ex_class_name.count(':') == 2
216
+ ex_module_name, ex_class_name = ex_class_name.split('::')
217
+ end
218
+ context = "#{ex_class_name} (#{tattle})"
219
+ return context
220
+ end
221
+
222
+
223
+ end