vcseif 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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