right_agent 0.17.2 → 1.0.1
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.
- data/lib/right_agent.rb +0 -1
- data/lib/right_agent/agent_config.rb +1 -1
- data/lib/right_agent/minimal.rb +8 -7
- data/lib/right_agent/monkey_patches.rb +4 -2
- data/lib/right_agent/monkey_patches/ruby_patch.rb +9 -9
- data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +2 -2
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +21 -51
- data/lib/right_agent/packets.rb +5 -1
- data/lib/right_agent/platform.rb +727 -299
- data/lib/right_agent/platform/unix/darwin/platform.rb +102 -0
- data/lib/right_agent/platform/unix/linux/platform.rb +305 -0
- data/lib/right_agent/platform/unix/platform.rb +226 -0
- data/lib/right_agent/platform/windows/mingw/platform.rb +447 -0
- data/lib/right_agent/platform/windows/mswin/platform.rb +236 -0
- data/lib/right_agent/platform/windows/platform.rb +1808 -0
- data/right_agent.gemspec +13 -8
- data/spec/platform/spec_helper.rb +216 -0
- data/spec/platform/unix/darwin/platform_spec.rb +181 -0
- data/spec/platform/unix/linux/platform_spec.rb +540 -0
- data/spec/platform/unix/spec_helper.rb +149 -0
- data/spec/platform/windows/mingw/platform_spec.rb +222 -0
- data/spec/platform/windows/mswin/platform_spec.rb +259 -0
- data/spec/platform/windows/spec_helper.rb +720 -0
- metadata +45 -30
- data/lib/right_agent/platform/darwin.rb +0 -285
- data/lib/right_agent/platform/linux.rb +0 -537
- data/lib/right_agent/platform/windows.rb +0 -1384
- data/spec/platform/darwin_spec.rb +0 -13
- data/spec/platform/linux_spec.rb +0 -38
- data/spec/platform/linux_volume_manager_spec.rb +0 -201
- data/spec/platform/platform_spec.rb +0 -80
- data/spec/platform/windows_spec.rb +0 -13
- data/spec/platform/windows_volume_manager_spec.rb +0 -318
@@ -1,1384 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Copyright (c) 2009-2011 RightScale Inc
|
3
|
-
#
|
4
|
-
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
-
# a copy of this software and associated documentation files (the
|
6
|
-
# "Software"), to deal in the Software without restriction, including
|
7
|
-
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
-
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
-
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
-
# the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be
|
13
|
-
# included in all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
-
|
23
|
-
require 'rubygems'
|
24
|
-
require 'fileutils'
|
25
|
-
require 'tmpdir'
|
26
|
-
require 'rbconfig'
|
27
|
-
|
28
|
-
begin
|
29
|
-
require 'win32/dir'
|
30
|
-
require 'windows/api'
|
31
|
-
require 'windows/error'
|
32
|
-
require 'windows/handle'
|
33
|
-
require 'windows/security'
|
34
|
-
require 'windows/system_info'
|
35
|
-
require 'Win32API'
|
36
|
-
rescue LoadError => e
|
37
|
-
raise e if !!(RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i)
|
38
|
-
end
|
39
|
-
|
40
|
-
module RightScale
|
41
|
-
# Throw when a Win32 API fails. Message will contain the last error message
|
42
|
-
class Win32Error < Exception
|
43
|
-
include ::Windows::Error
|
44
|
-
|
45
|
-
def initialize(msg = "")
|
46
|
-
super(msg)
|
47
|
-
@last_error = get_last_error
|
48
|
-
end
|
49
|
-
|
50
|
-
def message
|
51
|
-
original_message = super
|
52
|
-
result = ""
|
53
|
-
result << "#{original_message}\n Error Detail: " unless original_message.nil? || original_message.empty?
|
54
|
-
result << "#{@last_error}"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# Windows specific implementation
|
59
|
-
class Platform
|
60
|
-
|
61
|
-
attr_reader :flavor, :release
|
62
|
-
|
63
|
-
def init
|
64
|
-
getversionex = Win32API.new("kernel32", "GetVersionEx", 'P', 'L')
|
65
|
-
osversioninfo = [
|
66
|
-
148, # size of this struct (IN)
|
67
|
-
0, # major version (OUT)
|
68
|
-
0, # minor version (OUT)
|
69
|
-
0, # build (OUT)
|
70
|
-
0, # platform (OUT)
|
71
|
-
"\0" * 128 # additional info (OUT)
|
72
|
-
].pack('LLLLLa128')
|
73
|
-
|
74
|
-
raise 'Failed to detect Windows version' if 0 == getversionex.call(osversioninfo) # zero is failure
|
75
|
-
version = osversioninfo.unpack('LLLLLZ128') # 'Z' means ASCIIZ string
|
76
|
-
@flavor = 'windows' # there can be only one
|
77
|
-
@release = "#{version[1]}.#{version[2]}.#{version[3]}"
|
78
|
-
end
|
79
|
-
|
80
|
-
class Filesystem
|
81
|
-
MAX_PATH = 260
|
82
|
-
|
83
|
-
@@get_temp_dir_api = nil
|
84
|
-
|
85
|
-
def initialize
|
86
|
-
@temp_dir = nil
|
87
|
-
end
|
88
|
-
|
89
|
-
# Is given command available in the PATH?
|
90
|
-
#
|
91
|
-
# === Parameters
|
92
|
-
# command_name(String):: Name of command to be tested with
|
93
|
-
# or without the expected windows file extension.
|
94
|
-
#
|
95
|
-
# === Return
|
96
|
-
# true:: If command is in path
|
97
|
-
# false:: Otherwise
|
98
|
-
def has_executable_in_path(command_name)
|
99
|
-
return nil != find_executable_in_path(command_name)
|
100
|
-
end
|
101
|
-
|
102
|
-
# Finds the given command name in the PATH. this emulates the 'which'
|
103
|
-
# command from linux (without the terminating newline).
|
104
|
-
#
|
105
|
-
# === Parameters
|
106
|
-
# command_name(String):: Name of command to be tested with
|
107
|
-
# or without the expected windows file extension.
|
108
|
-
#
|
109
|
-
# === Return
|
110
|
-
# path to first matching executable file in PATH or nil
|
111
|
-
def find_executable_in_path(command_name)
|
112
|
-
# must search all known (executable) path extensions unless the
|
113
|
-
# explicit extension was given. this handles a case such as 'curl'
|
114
|
-
# which can either be on the path as 'curl.exe' or as a command shell
|
115
|
-
# shortcut called 'curl.cmd', etc.
|
116
|
-
use_path_extensions = 0 == File.extname(command_name).length
|
117
|
-
path_extensions = use_path_extensions ? ENV['PATHEXT'].split(/;/) : nil
|
118
|
-
|
119
|
-
# must check the current working directory first just to be completely
|
120
|
-
# sure what would happen if the command were executed. note that linux
|
121
|
-
# ignores the CWD, so this is platform-specific behavior for windows.
|
122
|
-
cwd = Dir.getwd
|
123
|
-
path = ENV['PATH']
|
124
|
-
path = (path.nil? || 0 == path.length) ? cwd : (cwd + ';' + path)
|
125
|
-
path.split(/;/).each do |dir|
|
126
|
-
if use_path_extensions
|
127
|
-
path_extensions.each do |path_extension|
|
128
|
-
path = File.join(dir, command_name + path_extension)
|
129
|
-
return path if File.executable?(path)
|
130
|
-
end
|
131
|
-
else
|
132
|
-
path = File.join(dir, command_name)
|
133
|
-
return path if File.executable?(path)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
return nil
|
137
|
-
end
|
138
|
-
|
139
|
-
# Directory containing generated agent configuration files
|
140
|
-
# @deprecated
|
141
|
-
def cfg_dir
|
142
|
-
warn "cfg_dir is deprecated; please use right_agent_cfg_dir"
|
143
|
-
right_agent_cfg_dir
|
144
|
-
end
|
145
|
-
|
146
|
-
# RightScale state directory for the current platform
|
147
|
-
# @deprecated
|
148
|
-
def right_scale_state_dir
|
149
|
-
warn "right_scale_state_dir is deprecated; please use either right_scale_static_state_dir or right_agent_dynamic_state_dir"
|
150
|
-
right_scale_static_state_dir
|
151
|
-
end
|
152
|
-
|
153
|
-
# Directory containing generated agent configuration files
|
154
|
-
def right_agent_cfg_dir
|
155
|
-
return pretty_path(File.join(Dir::COMMON_APPDATA, 'RightScale', 'right_agent'))
|
156
|
-
end
|
157
|
-
|
158
|
-
# Static (time-invariant) state that is common to all RightScale apps/agents
|
159
|
-
def right_scale_static_state_dir
|
160
|
-
return pretty_path(File.join(Dir::COMMON_APPDATA, 'RightScale', 'rightscale.d'))
|
161
|
-
end
|
162
|
-
|
163
|
-
# Static (time-invariant) state that is specific to RightLink
|
164
|
-
def right_link_static_state_dir
|
165
|
-
return pretty_path(File.join(Dir::COMMON_APPDATA, 'RightScale', 'rightscale.d', 'right_link'))
|
166
|
-
end
|
167
|
-
|
168
|
-
# Dynamic, persistent runtime state that is specific to RightLink
|
169
|
-
def right_link_dynamic_state_dir
|
170
|
-
return pretty_path(File.join(Dir::COMMON_APPDATA, 'RightScale', 'right_link'))
|
171
|
-
end
|
172
|
-
|
173
|
-
# Spool directory for the current platform
|
174
|
-
def spool_dir
|
175
|
-
return pretty_path(File.join(Dir::COMMON_APPDATA, 'RightScale', 'spool'))
|
176
|
-
end
|
177
|
-
|
178
|
-
def ssh_cfg_dir
|
179
|
-
return pretty_path(File.join(ENV['USERPROFILE'] || temp_dir, '.ssh'))
|
180
|
-
end
|
181
|
-
|
182
|
-
# Cache directory for the current platform
|
183
|
-
def cache_dir
|
184
|
-
return pretty_path(File.join(Dir::COMMON_APPDATA, 'RightScale', 'cache'))
|
185
|
-
end
|
186
|
-
|
187
|
-
# Log directory for the current platform
|
188
|
-
def log_dir
|
189
|
-
return pretty_path(File.join(Dir::COMMON_APPDATA, 'RightScale', 'log'))
|
190
|
-
end
|
191
|
-
|
192
|
-
# Source code, for reference purposes and for development.
|
193
|
-
def source_code_dir
|
194
|
-
return pretty_path(File.join(Dir::COMMON_APPDATA, 'RightScale', 'src'))
|
195
|
-
end
|
196
|
-
|
197
|
-
# Temp directory for the current platform
|
198
|
-
def temp_dir
|
199
|
-
if @temp_dir.nil?
|
200
|
-
@@get_temp_dir_api = Win32::API.new('GetTempPath', 'LP', 'L') unless @@get_temp_dir_api
|
201
|
-
buffer = 0.chr * MAX_PATH
|
202
|
-
@@get_temp_dir_api.call(buffer.length, buffer)
|
203
|
-
@temp_dir = pretty_path(buffer.unpack('A*').first.chomp('\\'))
|
204
|
-
end
|
205
|
-
rescue
|
206
|
-
@temp_dir = File.join(Dir::WINDOWS, "temp")
|
207
|
-
ensure
|
208
|
-
return @temp_dir
|
209
|
-
end
|
210
|
-
|
211
|
-
# Path to place pid files
|
212
|
-
def pid_dir
|
213
|
-
return pretty_path(File.join(Dir::COMMON_APPDATA, 'RightScale', 'run'))
|
214
|
-
end
|
215
|
-
|
216
|
-
def right_link_home_dir
|
217
|
-
unless @right_link_home_dir
|
218
|
-
@right_link_home_dir = ENV['RS_RIGHT_LINK_HOME'] ||
|
219
|
-
File.normalize_path(File.join(company_program_files_dir, 'RightLink'))
|
220
|
-
end
|
221
|
-
@right_link_home_dir
|
222
|
-
end
|
223
|
-
|
224
|
-
# Path to right link configuration and internal usage scripts
|
225
|
-
def private_bin_dir
|
226
|
-
return pretty_path(File.join(right_link_home_dir, 'bin'))
|
227
|
-
end
|
228
|
-
|
229
|
-
def sandbox_dir
|
230
|
-
return pretty_path(File.join(right_link_home_dir, 'sandbox'))
|
231
|
-
end
|
232
|
-
|
233
|
-
# System root path
|
234
|
-
def system_root
|
235
|
-
return pretty_path(ENV['SystemRoot'])
|
236
|
-
end
|
237
|
-
|
238
|
-
# converts a long path to a short path. in windows terms, this means
|
239
|
-
# taking any file/folder name over 8 characters in length and truncating
|
240
|
-
# it to 6 characters with ~1..~n appended depending on how many similar
|
241
|
-
# names exist in the same directory. file extensions are simply chopped
|
242
|
-
# at three letters. the short name is equivalent for all API calls to
|
243
|
-
# the long path but requires no special quoting, etc. the path must
|
244
|
-
# exist at least partially for the API call to succeed.
|
245
|
-
#
|
246
|
-
# === Parameters
|
247
|
-
# long_path(String):: fully or partially existing long path to be
|
248
|
-
# converted to its short path equivalent.
|
249
|
-
#
|
250
|
-
# === Return
|
251
|
-
# short_path(String):: short path equivalent or same path if non-existent
|
252
|
-
def long_path_to_short_path(long_path)
|
253
|
-
return File.long_path_to_short_path(long_path)
|
254
|
-
end
|
255
|
-
|
256
|
-
# specific to the windows environment to aid in resolving paths to
|
257
|
-
# executables in test scenarios.
|
258
|
-
def company_program_files_dir
|
259
|
-
return pretty_path(File.join(Dir::PROGRAM_FILES, 'RightScale'))
|
260
|
-
end
|
261
|
-
|
262
|
-
# pretties up paths which assists Dir.glob() and Dir[] calls which will
|
263
|
-
# return empty if the path contains any \ characters. windows doesn't
|
264
|
-
# care (most of the time) about whether you use \ or / in paths. as
|
265
|
-
# always, there are exceptions to this rule (such as "del c:/xyz" which
|
266
|
-
# fails while "del c:\xyz" succeeds)
|
267
|
-
#
|
268
|
-
# === Parameters
|
269
|
-
# path(String):: path to make pretty
|
270
|
-
# native_fs_flag(Boolean):: true if path is pretty for native file
|
271
|
-
# system (i.e. file system calls more likely to succeed), false if
|
272
|
-
# pretty for Ruby interpreter (default).
|
273
|
-
def pretty_path(path, native_fs_flag = false)
|
274
|
-
return native_fs_flag ? path.gsub("/", "\\") : path.gsub("\\", "/")
|
275
|
-
end
|
276
|
-
|
277
|
-
# Ensures a local drive location for the file or folder given by path
|
278
|
-
# by copying to a local temp directory given by name only if the item
|
279
|
-
# does not appear on the home drive. This method is useful because
|
280
|
-
# secure applications refuse to run scripts from network locations, etc.
|
281
|
-
# Replaces any similar files in temp dir to ensure latest updates.
|
282
|
-
#
|
283
|
-
# === Parameters
|
284
|
-
# path(String):: path to file or directory to be placed locally.
|
285
|
-
#
|
286
|
-
# temp_dir_name(String):: name (or relative path) of temp directory to
|
287
|
-
# use only if the file or folder is not on a local drive.
|
288
|
-
#
|
289
|
-
# === Returns
|
290
|
-
# result(String):: local drive path
|
291
|
-
def ensure_local_drive_path(path, temp_dir_name)
|
292
|
-
homedrive = ENV['HOMEDRIVE']
|
293
|
-
if homedrive && homedrive.upcase != path[0,2].upcase
|
294
|
-
local_dir = ::File.join(temp_dir, temp_dir_name)
|
295
|
-
FileUtils.mkdir_p(local_dir)
|
296
|
-
local_path = ::File.join(local_dir, ::File.basename(path))
|
297
|
-
if ::File.directory?(path)
|
298
|
-
FileUtils.rm_rf(local_path) if ::File.directory?(local_path)
|
299
|
-
FileUtils.cp_r(::File.join(path, '.'), local_path)
|
300
|
-
else
|
301
|
-
FileUtils.cp(path, local_path)
|
302
|
-
end
|
303
|
-
path = local_path
|
304
|
-
end
|
305
|
-
return path
|
306
|
-
end
|
307
|
-
|
308
|
-
# Ruby 1.8.7 on Windows does not support File.symlink. Windows Vista and newer
|
309
|
-
# versions of Windows do support symlinks via CreateSymbolicLink, so we will use CreateSymbolicLink if it
|
310
|
-
# is available, otherwise throw (on 2003 and earlier)
|
311
|
-
#
|
312
|
-
# === Parameters
|
313
|
-
# old_name (String):: the path to the real file/directory
|
314
|
-
# new_name (String):: the path to the link
|
315
|
-
#
|
316
|
-
# === Results
|
317
|
-
# always 0 as does File.symlink
|
318
|
-
#
|
319
|
-
# === Raises
|
320
|
-
# Win32Error:: if failed to create the link
|
321
|
-
# PlatformNotSupported:: if the current platform does not support the CreateSymbolicLink API
|
322
|
-
def create_symlink(old_name, new_name)
|
323
|
-
raise ::RightScale::PlatformNotSupported, "Cannot create symlinks on this platform" unless defined?(::Windows::File::CreateSymbolicLink)
|
324
|
-
flags = File.directory?(old_name) ? ::Windows::File::SYMBOLIC_LINK_FLAG_DIRECTORY : 0
|
325
|
-
result = ::Windows::File::CreateSymbolicLink.call(new_name, old_name, flags)
|
326
|
-
raise ::RightScale::Win32Error, "failed to create link from #{old_name} to #{new_name}" unless (result == 1)
|
327
|
-
0
|
328
|
-
end
|
329
|
-
end # Filesystem
|
330
|
-
|
331
|
-
# Provides utilities for managing volumes (disks).
|
332
|
-
class VolumeManager
|
333
|
-
|
334
|
-
class ParserError < Exception; end
|
335
|
-
class VolumeError < Exception; end
|
336
|
-
|
337
|
-
def initialize
|
338
|
-
@os_info = OSInformation.new
|
339
|
-
|
340
|
-
@assignable_disk_regex = /^[D-Zd-z]:[\/\\]?$/
|
341
|
-
@assignable_path_regex = /^[A-Za-z]:[\/\\][\/\\\w\s\d\-_\.~]+$/
|
342
|
-
end
|
343
|
-
|
344
|
-
# Determines if the given path is valid for a Windows volume attachemnt
|
345
|
-
# (excluding the reserved A: B: C: drives).
|
346
|
-
#
|
347
|
-
# === Return
|
348
|
-
# result(Boolean):: true if path is a valid volume root
|
349
|
-
def is_attachable_volume_path?(path)
|
350
|
-
return (nil != (path =~ @assignable_disk_regex) || nil != (path =~ @assignable_path_regex))
|
351
|
-
end
|
352
|
-
|
353
|
-
# Gets a list of physical or virtual disks in the form:
|
354
|
-
# [{:index, :status, :total_size, :free_size, :dynamic, :gpt}*]
|
355
|
-
#
|
356
|
-
# where
|
357
|
-
# :index >= 0
|
358
|
-
# :status = 'Online' | 'Offline'
|
359
|
-
# :total_size = bytes used by partitions
|
360
|
-
# :free_size = bytes not used by partitions
|
361
|
-
# :dynamic = true | false
|
362
|
-
# :gpt = true | false
|
363
|
-
#
|
364
|
-
# GPT = GUID partition table
|
365
|
-
#
|
366
|
-
# === Parameters
|
367
|
-
# conditions{Hash):: hash of conditions to match or nil (default)
|
368
|
-
#
|
369
|
-
# === Return
|
370
|
-
# volumes(Array):: array of hashes detailing visible volumes.
|
371
|
-
#
|
372
|
-
# === Raise
|
373
|
-
# VolumeError:: on failure to list disks
|
374
|
-
# ParserError:: on failure to parse disks from output
|
375
|
-
def disks(conditions = nil)
|
376
|
-
script = <<EOF
|
377
|
-
rescan
|
378
|
-
list disk
|
379
|
-
EOF
|
380
|
-
exit_code, output_text = run_script(script)
|
381
|
-
raise VolumeError.new("Failed to list disks: exit code = #{exit_code}\n#{script}\n#{output_text}") if exit_code != 0
|
382
|
-
return parse_disks(output_text, conditions)
|
383
|
-
end
|
384
|
-
|
385
|
-
# Gets a list of currently visible volumes in the form:
|
386
|
-
# [{:index, :device, :label, :filesystem, :type, :total_size, :status, :info}*]
|
387
|
-
#
|
388
|
-
# where
|
389
|
-
# :index >= 0
|
390
|
-
# :device = "[A-Z]:"
|
391
|
-
# :label = up to 11 characters
|
392
|
-
# :filesystem = nil | 'NTFS' | <undocumented>
|
393
|
-
# :type = 'NTFS' | <undocumented>
|
394
|
-
# :total_size = size in bytes
|
395
|
-
# :status = 'Healthy' | <undocumented>
|
396
|
-
# :info = 'System' | empty | <undocumented>
|
397
|
-
#
|
398
|
-
# note that a strange aspect of diskpart is that it won't correlate
|
399
|
-
# disks to volumes in any list even though partition lists are always
|
400
|
-
# in the context of a selected disk.
|
401
|
-
#
|
402
|
-
# volume order can change as volumes are created/destroyed between
|
403
|
-
# diskpart sessions so volume 0 can represent C: in one session and
|
404
|
-
# then be represented as volume 1 in the next call to diskpart.
|
405
|
-
#
|
406
|
-
# volume labels are truncated to 11 characters by diskpart even though
|
407
|
-
# NTFS allows up to 32 characters.
|
408
|
-
#
|
409
|
-
# === Parameters
|
410
|
-
# conditions{Hash):: hash of conditions to match or nil (default)
|
411
|
-
#
|
412
|
-
# === Return
|
413
|
-
# volumes(Array):: array of hashes detailing visible volumes.
|
414
|
-
#
|
415
|
-
# === Raise
|
416
|
-
# VolumeError:: on failure to list volumes
|
417
|
-
# ParserError:: on failure to parse volumes from output
|
418
|
-
def volumes(conditions = nil)
|
419
|
-
script = <<EOF
|
420
|
-
rescan
|
421
|
-
list volume
|
422
|
-
EOF
|
423
|
-
exit_code, output_text = run_script(script)
|
424
|
-
raise VolumeError.new("Failed to list volumes exit code = #{exit_code}\n#{script}\n#{output_text}") if exit_code != 0
|
425
|
-
return parse_volumes(output_text, conditions)
|
426
|
-
end
|
427
|
-
|
428
|
-
# Gets a list of partitions for the disk given by index in the form:
|
429
|
-
# {:index, :type, :size, :offset}
|
430
|
-
#
|
431
|
-
# where
|
432
|
-
# :index >= 0
|
433
|
-
# :type = 'OEM' | 'Primary' | <undocumented>
|
434
|
-
# :size = size in bytes used by partition on disk
|
435
|
-
# :offset = offset of partition in bytes from head of disk
|
436
|
-
#
|
437
|
-
# === Parameters
|
438
|
-
# disk_index(int):: disk index to query
|
439
|
-
# conditions{Hash):: hash of conditions to match or nil (default)
|
440
|
-
#
|
441
|
-
# === Return
|
442
|
-
# result(Array):: list of partitions or empty
|
443
|
-
#
|
444
|
-
# === Raise
|
445
|
-
# VolumeError:: on failure to list partitions
|
446
|
-
# ParserError:: on failure to parse partitions from output
|
447
|
-
def partitions(disk_index, conditions = nil)
|
448
|
-
script = <<EOF
|
449
|
-
rescan
|
450
|
-
select disk #{disk_index}
|
451
|
-
list partition
|
452
|
-
EOF
|
453
|
-
exit_code, output_text = run_script(script)
|
454
|
-
raise VolumeError.new("Failed to list partitions exit code = #{exit_code}\n#{script}\n#{output_text}") if exit_code != 0
|
455
|
-
return parse_partitions(output_text, conditions)
|
456
|
-
end
|
457
|
-
|
458
|
-
# Formats a disk given by disk index and the device (e.g. "D:") for the
|
459
|
-
# volume on the primary NTFS partition which will be created.
|
460
|
-
#
|
461
|
-
# === Parameters
|
462
|
-
# disk_index(int): zero-based disk index (from disks list, etc.)
|
463
|
-
# device(String):: disk letter or mount path specified for the volume to create
|
464
|
-
#
|
465
|
-
# === Return
|
466
|
-
# always true
|
467
|
-
#
|
468
|
-
# === Raise
|
469
|
-
# ArgumentError:: on invalid parameters
|
470
|
-
# VolumeError:: on failure to format
|
471
|
-
def format_disk(disk_index, device)
|
472
|
-
if device.match(@assignable_path_regex) && @os_info.major < 6
|
473
|
-
raise ArgumentError.new("Mount path assignment is not supported in this version of windows")
|
474
|
-
end
|
475
|
-
# note that creating the primary partition automatically creates and
|
476
|
-
# selects a new volume, which can be assigned a letter before the
|
477
|
-
# partition has actually been formatted.
|
478
|
-
raise ArgumentError.new("Invalid index = #{disk_index}") unless disk_index >= 0
|
479
|
-
raise ArgumentError.new("Invalid device = #{device}") unless is_attachable_volume_path?(device)
|
480
|
-
|
481
|
-
# note that Windows 2003 server version of diskpart doesn't support
|
482
|
-
# format so that has to be done separately.
|
483
|
-
format_command = if @os_info.major < 6; ""; else; "format FS=NTFS quick"; end
|
484
|
-
script = <<EOF
|
485
|
-
rescan
|
486
|
-
list disk
|
487
|
-
select disk #{disk_index}
|
488
|
-
#{get_clear_readonly_command('disk')}
|
489
|
-
#{get_online_disk_command}
|
490
|
-
clean
|
491
|
-
create partition primary
|
492
|
-
#{get_assign_command_for_device(device)}
|
493
|
-
#{format_command}
|
494
|
-
EOF
|
495
|
-
exit_code, output_text = run_script(script)
|
496
|
-
raise VolumeError.new("Failed to format disk #{disk_index} for device #{device}: exit code = #{exit_code}\n#{script}\n#{output_text}") if exit_code != 0
|
497
|
-
|
498
|
-
# must format using command shell's FORMAT command before 2008 server.
|
499
|
-
if @os_info.major < 6
|
500
|
-
command = "echo Y | format #{device[0,1]}: /Q /V: /FS:NTFS"
|
501
|
-
output_text = `#{command}`
|
502
|
-
exit_code = $?.exitstatus
|
503
|
-
raise VolumeError.new("Failed to format disk #{disk_index} for device #{device}: exit code = #{exit_code}\n#{output_text}") if exit_code != 0
|
504
|
-
end
|
505
|
-
true
|
506
|
-
end
|
507
|
-
|
508
|
-
# Brings the disk given by index online and clears the readonly
|
509
|
-
# attribute, if necessary. The latter is required for some kinds of
|
510
|
-
# disks to online successfully and SAN volumes may be readonly when
|
511
|
-
# initially attached. As this change may bring additional volumes online
|
512
|
-
# the updated volumes list is returned.
|
513
|
-
#
|
514
|
-
# === Parameters
|
515
|
-
# disk_index(int):: zero-based disk index
|
516
|
-
# options(Hash):: A hash of options which allows different behavior while onlining a drive. Possible options are;
|
517
|
-
# :idempotent(Bool) - Checks the current disk statuses before attempting to online the disk. If the drive is already online, it bails out making this method idempotent.
|
518
|
-
#
|
519
|
-
# === Return
|
520
|
-
# always true
|
521
|
-
#
|
522
|
-
# === Raise
|
523
|
-
# ArgumentError:: on invalid parameters
|
524
|
-
# VolumeError:: on failure to online disk
|
525
|
-
# ParserError:: on failure to parse volume list
|
526
|
-
def online_disk(disk_index, options={})
|
527
|
-
raise ArgumentError.new("Invalid disk_index = #{disk_index}") unless disk_index >= 0
|
528
|
-
# Set some defaults for backward compatibility, allow user specified options to override defaults
|
529
|
-
options = {:idempotent => false}.merge(options)
|
530
|
-
script = <<EOF
|
531
|
-
rescan
|
532
|
-
list disk
|
533
|
-
select disk #{disk_index}
|
534
|
-
#{get_clear_readonly_command('disk')}
|
535
|
-
#{get_online_disk_command}
|
536
|
-
EOF
|
537
|
-
|
538
|
-
if(options[:idempotent])
|
539
|
-
disk = disks(:index => disk_index)
|
540
|
-
return true if disk && disk[:status] == "Online"
|
541
|
-
end
|
542
|
-
|
543
|
-
exit_code, output_text = run_script(script)
|
544
|
-
raise VolumeError.new("Failed to online disk #{disk_index}: exit code = #{exit_code}\n#{script}\n#{output_text}") if exit_code != 0
|
545
|
-
true
|
546
|
-
end
|
547
|
-
|
548
|
-
# Brings the disk given by index offline
|
549
|
-
#
|
550
|
-
# === Parameters
|
551
|
-
# disk_index(int):: zero-based disk index
|
552
|
-
#
|
553
|
-
# === Return
|
554
|
-
# always true
|
555
|
-
#
|
556
|
-
# === Raise
|
557
|
-
# ArgumentError:: on invalid parameters
|
558
|
-
# VolumeError:: on failure to online disk
|
559
|
-
# ParserError:: on failure to parse volume list
|
560
|
-
def offline_disk(disk_index)
|
561
|
-
raise ArgumentError.new("Invalid disk_index = #{disk_index}") unless disk_index >= 0
|
562
|
-
# Set some defaults for backward compatibility, allow user specified options to override defaults
|
563
|
-
script = <<EOF
|
564
|
-
rescan
|
565
|
-
list disk
|
566
|
-
select disk #{disk_index}
|
567
|
-
offline disk noerr
|
568
|
-
EOF
|
569
|
-
|
570
|
-
exit_code, output_text = run_script(script)
|
571
|
-
raise VolumeError.new("Failed to offline disk #{disk_index}: exit code = #{exit_code}\n#{script}\n#{output_text}") if exit_code != 0
|
572
|
-
true
|
573
|
-
end
|
574
|
-
|
575
|
-
# Assigns the given device name to the volume given by index and clears
|
576
|
-
# the readonly attribute, if necessary. The device must not currently be
|
577
|
-
# in use.
|
578
|
-
#
|
579
|
-
# === Parameters
|
580
|
-
# volume_device_or_index(int):: old device or zero-based volume index (from volumes list, etc.) to select for assignment.
|
581
|
-
# device(String):: disk letter or mount path specified for the volume to create
|
582
|
-
# options(Hash):: A hash of options which allows different behavior while assigning a device. Possible options are:
|
583
|
-
# :clear_readonly(Bool) - Set to true by default, since the previous implementation of this method always cleared readonly
|
584
|
-
# :remove_all(Bool) - Removes all previously assigned devices and paths, essentially a big RESET button for volume assignment
|
585
|
-
# :idempotent(Bool) - Checks the current device assignments before assigning the device according to the specified parameters. If the device is already assigned, it bails out making this method idempotent.
|
586
|
-
#
|
587
|
-
# === Return
|
588
|
-
# always true
|
589
|
-
#
|
590
|
-
# === Raise
|
591
|
-
# ArgumentError:: on invalid parameters
|
592
|
-
# VolumeError:: on failure to assign device name
|
593
|
-
# ParserError:: on failure to parse volume list
|
594
|
-
def assign_device(volume_device_or_index, device, options={})
|
595
|
-
# Set some defaults for backward compatibility, allow user specified options to override defaults
|
596
|
-
options = {:clear_readonly => true, :idempotent => false}.merge(options)
|
597
|
-
|
598
|
-
if device.match(@assignable_path_regex) && @os_info.major < 6
|
599
|
-
raise ArgumentError.new("Mount path assignment is not supported in this version of windows")
|
600
|
-
end
|
601
|
-
# Volume selector for drive letter assignments
|
602
|
-
volume_selector_match = volume_device_or_index.to_s.match(/^([D-Zd-z]|\d+):?$/)
|
603
|
-
# Volume selector for mount path assignments
|
604
|
-
volume_selector_match = volume_device_or_index.to_s.match(@assignable_path_regex) unless volume_selector_match
|
605
|
-
raise ArgumentError.new("Invalid volume_device_or_index = #{volume_device_or_index}") unless volume_selector_match
|
606
|
-
volume_selector = volume_selector_match[1]
|
607
|
-
raise ArgumentError.new("Invalid device = #{device}") unless is_attachable_volume_path?(device)
|
608
|
-
if(options[:idempotent])
|
609
|
-
# Device already assigned?
|
610
|
-
vols = volumes
|
611
|
-
already_assigned = vols.select do |k,v|
|
612
|
-
# The volume is specified by it's index and is already mounted to the specified device/path
|
613
|
-
(k == :index && v == volume_device_or_index && vols[:device] == device) ||
|
614
|
-
# The volume is specified by it's current device/path assignment and is already mounted to the specified device/path
|
615
|
-
(k == :device && v == volume_device_or_index)
|
616
|
-
end
|
617
|
-
|
618
|
-
return true if already_assigned.length > 0
|
619
|
-
end
|
620
|
-
# Validation ends here, and real stuff starts to happen
|
621
|
-
|
622
|
-
script = <<EOF
|
623
|
-
rescan
|
624
|
-
list volume
|
625
|
-
select volume "#{volume_selector}"
|
626
|
-
#{get_clear_readonly_command('volume') if options[:clear_readonly]}
|
627
|
-
#{"remove all noerr" if options[:remove_all]}
|
628
|
-
#{get_assign_command_for_device(device)}
|
629
|
-
EOF
|
630
|
-
|
631
|
-
exit_code, output_text = run_script(script)
|
632
|
-
raise VolumeError.new("Failed to assign device \"#{device}\" for volume \"#{volume_device_or_index}\": exit code = #{exit_code}\n#{script}\n#{output_text}") if exit_code != 0
|
633
|
-
true
|
634
|
-
end
|
635
|
-
|
636
|
-
protected
|
637
|
-
|
638
|
-
# Parses raw output from diskpart looking for the (first) disk list.
|
639
|
-
#
|
640
|
-
# Example of raw output from diskpart (column width is dictated by the
|
641
|
-
# header and some columns can be empty):
|
642
|
-
#
|
643
|
-
# Disk ### Status Size Free Dyn Gpt
|
644
|
-
# -------- ---------- ------- ------- --- ---
|
645
|
-
# Disk 0 Online 80 GB 0 B
|
646
|
-
#* Disk 1 Offline 4096 MB 4096 MB
|
647
|
-
# Disk 2 Online 4096 MB 4096 MB *
|
648
|
-
#
|
649
|
-
# === Parameters
|
650
|
-
# output_text(String):: raw output from diskpart
|
651
|
-
# conditions{Hash):: hash of conditions to match or nil (default)
|
652
|
-
#
|
653
|
-
# === Return
|
654
|
-
# result(Array):: volumes or empty
|
655
|
-
#
|
656
|
-
# === Raise
|
657
|
-
# ParserError:: on failure to parse disk list
|
658
|
-
def parse_disks(output_text, conditions = nil)
|
659
|
-
result = []
|
660
|
-
line_regex = nil
|
661
|
-
header_regex = / -------- (-+) ------- ------- --- ---/
|
662
|
-
header_match = nil
|
663
|
-
output_text.lines.each do |line|
|
664
|
-
line = line.chomp
|
665
|
-
if line_regex
|
666
|
-
if line.strip.empty?
|
667
|
-
break
|
668
|
-
end
|
669
|
-
match_data = line.match(line_regex)
|
670
|
-
raise ParserError.new("Failed to parse disk info from #{line.inspect} using #{line_regex.inspect}") unless match_data
|
671
|
-
data = {:index => match_data[1].to_i,
|
672
|
-
:status => match_data[2].strip,
|
673
|
-
:total_size => size_factor_to_bytes(match_data[3], match_data[4]),
|
674
|
-
:free_size => size_factor_to_bytes(match_data[5], match_data[6]),
|
675
|
-
:dynamic => match_data[7].strip[0,1] == '*',
|
676
|
-
:gpt => match_data[8].strip[0,1] == '*'}
|
677
|
-
if conditions
|
678
|
-
matched = true
|
679
|
-
conditions.each do |key, value|
|
680
|
-
unless data[key] == value
|
681
|
-
matched = false
|
682
|
-
break
|
683
|
-
end
|
684
|
-
end
|
685
|
-
result << data if matched
|
686
|
-
else
|
687
|
-
result << data
|
688
|
-
end
|
689
|
-
elsif header_match = line.match(header_regex)
|
690
|
-
# account for some fields being variable width between versions of the OS.
|
691
|
-
status_width = header_match[1].length
|
692
|
-
line_regex_text = "^[\\* ] Disk (\\d[\\d ]\{2\}) (.\{#{status_width}\}) "\
|
693
|
-
"[ ]?([\\d ]\{3\}\\d) (.?B) [ ]?([\\d ]\{3\}\\d) (.?B) ([\\* ]) ([\\* ])"
|
694
|
-
line_regex = Regexp.compile(line_regex_text)
|
695
|
-
else
|
696
|
-
# one or more lines of ignored headers
|
697
|
-
end
|
698
|
-
end
|
699
|
-
raise ParserError.new("Failed to parse disk list header from output #{output_text.inspect} using #{header_regex.inspect}") unless header_match
|
700
|
-
return result
|
701
|
-
end
|
702
|
-
|
703
|
-
# Parses raw output from diskpart looking for the (first) volume list.
|
704
|
-
#
|
705
|
-
# Example of raw output from diskpart (column width is dictated by the
|
706
|
-
# header and some columns can be empty):
|
707
|
-
#
|
708
|
-
# Volume ### Ltr Label Fs Type Size Status Info
|
709
|
-
# ---------- --- ----------- ----- ---------- ------- --------- --------
|
710
|
-
# Volume 0 C 2008Boot NTFS Partition 80 GB Healthy System
|
711
|
-
#* Volume 1 D NTFS Partition 4094 MB Healthy
|
712
|
-
# Volume 2 NTFS Partition 4094 MB Healthy
|
713
|
-
#
|
714
|
-
# === Parameters
|
715
|
-
# output_text(String):: raw output from diskpart
|
716
|
-
# conditions{Hash):: hash of conditions to match or nil (default)
|
717
|
-
#
|
718
|
-
# === Return
|
719
|
-
# result(Array):: volumes or empty. Drive letters are appended with ':' even though they aren't
|
720
|
-
# returned that way from diskpart
|
721
|
-
#
|
722
|
-
# === Raise
|
723
|
-
# ParserError:: on failure to parse volume list
|
724
|
-
def parse_volumes(output_text, conditions = nil)
|
725
|
-
result = []
|
726
|
-
header_regex = / ---------- --- (-+) (-+) (-+) ------- (-+) (-+)/
|
727
|
-
header_match = nil
|
728
|
-
line_regex = nil
|
729
|
-
output_text.lines.each do |line|
|
730
|
-
line = line.chomp
|
731
|
-
if line_regex
|
732
|
-
if line.strip.empty?
|
733
|
-
break
|
734
|
-
end
|
735
|
-
match_data = line.match(line_regex)
|
736
|
-
unless match_data
|
737
|
-
path_match_regex = /([A-Za-z]:[\/\\][\/\\\w\s\d]+)/
|
738
|
-
match_data = line.match(path_match_regex)
|
739
|
-
if match_data
|
740
|
-
result.last[:device] = match_data[1]
|
741
|
-
next
|
742
|
-
end
|
743
|
-
end
|
744
|
-
raise ParserError.new("Failed to parse volume info from #{line.inspect} using #{line_regex.inspect}") unless match_data
|
745
|
-
letter = nil_if_empty(match_data[2])
|
746
|
-
device = "#{letter.upcase}:" if letter
|
747
|
-
data = {:index => match_data[1].to_i,
|
748
|
-
:device => device,
|
749
|
-
:label => nil_if_empty(match_data[3]),
|
750
|
-
:filesystem => nil_if_empty(match_data[4]),
|
751
|
-
:type => nil_if_empty(match_data[5]),
|
752
|
-
:total_size => size_factor_to_bytes(match_data[6], match_data[7]),
|
753
|
-
:status => nil_if_empty(match_data[8]),
|
754
|
-
:info => nil_if_empty(match_data[9])}
|
755
|
-
if conditions
|
756
|
-
matched = true
|
757
|
-
conditions.each do |key, value|
|
758
|
-
unless data[key] == value
|
759
|
-
matched = false
|
760
|
-
break
|
761
|
-
end
|
762
|
-
end
|
763
|
-
result << data if matched
|
764
|
-
else
|
765
|
-
result << data
|
766
|
-
end
|
767
|
-
elsif header_match = line.match(header_regex)
|
768
|
-
# account for some fields being variable width between versions of the OS.
|
769
|
-
label_width = header_match[1].length
|
770
|
-
filesystem_width = header_match[2].length
|
771
|
-
type_width = header_match[3].length
|
772
|
-
status_width = header_match[4].length
|
773
|
-
info_width = header_match[5].length
|
774
|
-
line_regex_text = "^[\\* ] Volume (\\d[\\d ]\{2\}) ([A-Za-z ]) "\
|
775
|
-
"(.\{#{label_width}\}) (.\{#{filesystem_width}\}) "\
|
776
|
-
"(.\{#{type_width}\}) [ ]?([\\d ]\{3\}\\d) (.?B)\\s{0,2}"\
|
777
|
-
"(.\{#{status_width}\})\\s{0,2}(.\{0,#{info_width}\})"
|
778
|
-
line_regex = Regexp.compile(line_regex_text)
|
779
|
-
else
|
780
|
-
# one or more lines of ignored headers
|
781
|
-
end
|
782
|
-
end
|
783
|
-
raise ParserError.new("Failed to parse volume list header from output #{output_text.inspect} using #{header_regex.inspect}") unless header_match
|
784
|
-
return result
|
785
|
-
end
|
786
|
-
|
787
|
-
# Parses raw output from diskpart looking for the (first) partition list.
|
788
|
-
#
|
789
|
-
# Example of raw output from diskpart (column width is dictated by the
|
790
|
-
# header and some columns can be empty):
|
791
|
-
#
|
792
|
-
# Partition ### Type Size Offset
|
793
|
-
# ------------- ---------------- ------- -------
|
794
|
-
# Partition 1 OEM 39 MB 31 KB
|
795
|
-
#* Partition 2 Primary 14 GB 40 MB
|
796
|
-
# Partition 3 Primary 451 GB 14 GB
|
797
|
-
#
|
798
|
-
# === Parameters
|
799
|
-
# output_text(String):: raw output from diskpart
|
800
|
-
# conditions{Hash):: hash of conditions to match or nil (default)
|
801
|
-
#
|
802
|
-
# === Return
|
803
|
-
# result(Array):: volumes or empty
|
804
|
-
#
|
805
|
-
# === Raise
|
806
|
-
# ParserError:: on failure to parse volume list
|
807
|
-
def parse_partitions(output_text, conditions = nil)
|
808
|
-
result = []
|
809
|
-
header_regex = / ------------- (-+) ------- -------/
|
810
|
-
header_match = nil
|
811
|
-
line_regex = nil
|
812
|
-
output_text.lines.each do |line|
|
813
|
-
line = line.chomp
|
814
|
-
if line_regex
|
815
|
-
if line.strip.empty?
|
816
|
-
break
|
817
|
-
end
|
818
|
-
match_data = line.match(line_regex)
|
819
|
-
raise ParserError.new("Failed to parse partition info from #{line.inspect} using #{line_regex.inspect}") unless match_data
|
820
|
-
data = {:index => match_data[1].to_i,
|
821
|
-
:type => nil_if_empty(match_data[2]),
|
822
|
-
:size => size_factor_to_bytes(match_data[3], match_data[4]),
|
823
|
-
:offset => size_factor_to_bytes(match_data[5], match_data[6])}
|
824
|
-
if conditions
|
825
|
-
matched = true
|
826
|
-
conditions.each do |key, value|
|
827
|
-
unless data[key] == value
|
828
|
-
matched = false
|
829
|
-
break
|
830
|
-
end
|
831
|
-
end
|
832
|
-
result << data if matched
|
833
|
-
else
|
834
|
-
result << data
|
835
|
-
end
|
836
|
-
elsif header_match = line.match(header_regex)
|
837
|
-
# account for some fields being variable width between versions of the OS.
|
838
|
-
type_width = header_match[1].length
|
839
|
-
line_regex_text = "^[\\* ] Partition (\\d[\\d ]\{2\}) (.\{#{type_width}\}) "\
|
840
|
-
"[ ]?([\\d ]\{3\}\\d) (.?B) [ ]?([\\d ]\{3\}\\d) (.?B)"
|
841
|
-
line_regex = Regexp.compile(line_regex_text)
|
842
|
-
elsif line.start_with?("There are no partitions on this disk")
|
843
|
-
return []
|
844
|
-
else
|
845
|
-
# one or more lines of ignored headers
|
846
|
-
end
|
847
|
-
end
|
848
|
-
raise ParserError.new("Failed to parse volume list header from output #{output_text.inspect} using #{header_regex.inspect}") unless header_match
|
849
|
-
return result
|
850
|
-
end
|
851
|
-
|
852
|
-
# Run a diskpart script and get the exit code and text output. See also
|
853
|
-
# technet and search for "DiskPart Command-Line Options" or else
|
854
|
-
# "http://technet.microsoft.com/en-us/library/cc766465%28WS.10%29.aspx".
|
855
|
-
# Note that there are differences between 2003 and 2008 server versions
|
856
|
-
# of this utility.
|
857
|
-
#
|
858
|
-
# === Parameters
|
859
|
-
# script(String):: diskpart script with commands delimited by newlines
|
860
|
-
#
|
861
|
-
# === Return
|
862
|
-
# result(Array):: tuple of [exit_code, output_text]
|
863
|
-
def run_script(script)
|
864
|
-
Dir.mktmpdir do |temp_dir_path|
|
865
|
-
script_file_path = File.join(temp_dir_path, "rs_diskpart_script.txt")
|
866
|
-
File.open(script_file_path, "w") { |f| f.puts(script.strip) }
|
867
|
-
executable_path = "diskpart.exe"
|
868
|
-
executable_arguments = ["/s", File.normalize_path(script_file_path)]
|
869
|
-
shell = RightScale::Platform.shell
|
870
|
-
executable_path, executable_arguments = shell.format_right_run_path(executable_path, executable_arguments)
|
871
|
-
command = shell.format_executable_command(executable_path, executable_arguments)
|
872
|
-
output_text = `#{command}`
|
873
|
-
return $?.exitstatus, output_text
|
874
|
-
end
|
875
|
-
end
|
876
|
-
|
877
|
-
# Determines if the given value is empty and returns nil in that case.
|
878
|
-
#
|
879
|
-
# === Parameters
|
880
|
-
# value(String):: value to chec
|
881
|
-
#
|
882
|
-
# === Return
|
883
|
-
# result(String):: trimmed value or nil
|
884
|
-
def nil_if_empty(value)
|
885
|
-
value = value.strip
|
886
|
-
return nil if value.empty?
|
887
|
-
return value
|
888
|
-
end
|
889
|
-
|
890
|
-
# Multiplies a raw size value by a size factor given as a standardized
|
891
|
-
# bytes acronym.
|
892
|
-
#
|
893
|
-
# === Parameters
|
894
|
-
# size_by(String or Number):: value to multiply
|
895
|
-
# size_factor(String):: multiplier acronym
|
896
|
-
#
|
897
|
-
# === Return
|
898
|
-
# result(int):: bytes
|
899
|
-
def size_factor_to_bytes(size_by, size_factor)
|
900
|
-
value = size_by.to_i
|
901
|
-
case size_factor
|
902
|
-
when 'KB' then return value * 1024
|
903
|
-
when 'MB' then return value * 1024 * 1024
|
904
|
-
when 'GB' then return value * 1024 * 1024 * 1024
|
905
|
-
when 'TB' then return value * 1024 * 1024 * 1024 * 1024
|
906
|
-
else return value # assume bytes
|
907
|
-
end
|
908
|
-
end
|
909
|
-
|
910
|
-
# Returns the correct diskpart assignment command for the specified device (either drive letter, or path)
|
911
|
-
#
|
912
|
-
# === Parameters
|
913
|
-
# device(String):: Either a drive letter or mount path
|
914
|
-
#
|
915
|
-
# === Return
|
916
|
-
# result(String):: The correct diskpart assignment command
|
917
|
-
def get_assign_command_for_device(device)
|
918
|
-
if device.match(@assignable_disk_regex)
|
919
|
-
"assign letter=#{device[0,1]}"
|
920
|
-
elsif device.match(@assignable_path_regex)
|
921
|
-
"assign mount=\"#{device}\""
|
922
|
-
end
|
923
|
-
end
|
924
|
-
|
925
|
-
# Returns the correct 'online disk' diskpart command based on the OS version
|
926
|
-
#
|
927
|
-
# === Return
|
928
|
-
# result(String):: Either "online noerr" or "online disk noerr" depending upon the OS version
|
929
|
-
def get_online_disk_command()
|
930
|
-
if @os_info.major < 6; "online noerr" else; "online disk noerr" end
|
931
|
-
end
|
932
|
-
|
933
|
-
# Returns the correct 'attribute disk clear readonly' diskpart command based on the OS version
|
934
|
-
#
|
935
|
-
# === Parameters
|
936
|
-
# object_type(String):: One of "disk" or "volume" to clear read only for
|
937
|
-
#
|
938
|
-
# === Return
|
939
|
-
# result(String):: Either a blank string or "attribute #{object_type} clear readonly noerr" depending upon the OS version
|
940
|
-
def get_clear_readonly_command(object_type)
|
941
|
-
if @os_info.major < 6; "" else; "attribute #{object_type} clear readonly noerr" end
|
942
|
-
end
|
943
|
-
|
944
|
-
end # VolumeManager
|
945
|
-
|
946
|
-
# Provides utilities for formatting executable shell commands, etc.
|
947
|
-
class Shell
|
948
|
-
POWERSHELL_V1x0_EXECUTABLE_PATH = "powershell.exe"
|
949
|
-
POWERSHELL_V1x0_SCRIPT_EXTENSION = ".ps1"
|
950
|
-
RUBY_SCRIPT_EXTENSION = ".rb"
|
951
|
-
NULL_OUTPUT_NAME = "nul"
|
952
|
-
|
953
|
-
@@executable_extensions = nil
|
954
|
-
@@right_run_path = nil
|
955
|
-
|
956
|
-
# Formats an executable path and arguments by inserting a reference to
|
957
|
-
# RightRun.exe on platforms only when necessary.
|
958
|
-
#
|
959
|
-
# === Parameters
|
960
|
-
# executable_path(String):: 64-bit executable path
|
961
|
-
# executable_arguments(Array):: arguments for 64-bit executable
|
962
|
-
#
|
963
|
-
# === Return
|
964
|
-
# result(Array):: tuple for updated [executable_path, executable_arguments]
|
965
|
-
def format_right_run_path(executable_path, executable_arguments)
|
966
|
-
if @@right_run_path.nil?
|
967
|
-
@@right_run_path = ""
|
968
|
-
if ENV['ProgramW6432'] && (@@right_run_path = ENV['RS_RIGHT_RUN_EXE'].to_s).empty?
|
969
|
-
temp_path = File.join(ENV['ProgramW6432'], 'RightScale', 'Shared', 'RightRun.exe')
|
970
|
-
if File.file?(temp_path)
|
971
|
-
@@right_run_path = File.normalize_path(temp_path).gsub("/", "\\")
|
972
|
-
end
|
973
|
-
end
|
974
|
-
end
|
975
|
-
unless @@right_run_path.empty?
|
976
|
-
executable_arguments.unshift(executable_path)
|
977
|
-
executable_path = @@right_run_path
|
978
|
-
end
|
979
|
-
|
980
|
-
return executable_path, executable_arguments
|
981
|
-
end
|
982
|
-
|
983
|
-
# Formats a script file name to ensure it is executable on the current
|
984
|
-
# platform.
|
985
|
-
#
|
986
|
-
# === Parameters
|
987
|
-
# partial_script_file_path(String):: full or partial script file path
|
988
|
-
#
|
989
|
-
# default_extension(String):: default script extension for platforms
|
990
|
-
# which require a known file extension to execute.
|
991
|
-
#
|
992
|
-
# === Returns
|
993
|
-
# executable_script_file_path(String):: executable path
|
994
|
-
def format_script_file_name(partial_script_file_path, default_extension = POWERSHELL_V1x0_SCRIPT_EXTENSION)
|
995
|
-
extension = File.extname(partial_script_file_path)
|
996
|
-
if 0 == extension.length
|
997
|
-
return partial_script_file_path + default_extension
|
998
|
-
end
|
999
|
-
|
1000
|
-
# quick out for default extension.
|
1001
|
-
if 0 == (extension <=> default_extension)
|
1002
|
-
return partial_script_file_path
|
1003
|
-
end
|
1004
|
-
|
1005
|
-
# confirm that the "extension" is really something understood by
|
1006
|
-
# the command shell as being executable.
|
1007
|
-
if @@executable_extensions.nil?
|
1008
|
-
@@executable_extensions = ENV['PATHEXT'].downcase.split(';')
|
1009
|
-
end
|
1010
|
-
if @@executable_extensions.include?(extension.downcase)
|
1011
|
-
return partial_script_file_path
|
1012
|
-
end
|
1013
|
-
|
1014
|
-
# not executable; use default extension.
|
1015
|
-
return partial_script_file_path + default_extension
|
1016
|
-
end
|
1017
|
-
|
1018
|
-
# Formats an executable command by quoting any of the arguments as
|
1019
|
-
# needed and building an executable command string.
|
1020
|
-
#
|
1021
|
-
# === Parameters
|
1022
|
-
# executable_file_path(String):: full or partial executable file path
|
1023
|
-
#
|
1024
|
-
# arguments(Array):: variable stringizable arguments
|
1025
|
-
#
|
1026
|
-
# === Returns
|
1027
|
-
# executable_command(String):: executable command string
|
1028
|
-
def format_executable_command(executable_file_path, *arguments)
|
1029
|
-
escaped = []
|
1030
|
-
[executable_file_path, arguments].flatten.each do |arg|
|
1031
|
-
value = arg.to_s
|
1032
|
-
escaped << (value.index(' ') ? "\"#{value}\"" : value)
|
1033
|
-
end
|
1034
|
-
|
1035
|
-
# let cmd do the extension resolution if no extension was given
|
1036
|
-
ext = File.extname(executable_file_path)
|
1037
|
-
if ext.nil? || ext.empty?
|
1038
|
-
"cmd.exe /C \"#{escaped.join(" ")}\""
|
1039
|
-
else
|
1040
|
-
escaped.join(" ")
|
1041
|
-
end
|
1042
|
-
end
|
1043
|
-
|
1044
|
-
# Formats a powershell command using the given script path and arguments.
|
1045
|
-
# Allows for specifying powershell from a specific installed location.
|
1046
|
-
# This method is only implemented for Windows.
|
1047
|
-
#
|
1048
|
-
# === Parameters
|
1049
|
-
# shell_script_file_path(String):: shell script file path
|
1050
|
-
# arguments(Array):: variable stringizable arguments
|
1051
|
-
#
|
1052
|
-
# === Returns
|
1053
|
-
# executable_command(string):: executable command string
|
1054
|
-
def format_powershell_command(shell_script_file_path, *arguments)
|
1055
|
-
return format_powershell_command4(POWERSHELL_V1x0_EXECUTABLE_PATH, nil, nil, shell_script_file_path, *arguments)
|
1056
|
-
end
|
1057
|
-
|
1058
|
-
# Formats a ruby command using the given script path and arguments.
|
1059
|
-
# This method is only implemented for Windows since ruby scripts on
|
1060
|
-
# linux should rely on shebang to indicate the ruby bin path.
|
1061
|
-
#
|
1062
|
-
# === Parameters
|
1063
|
-
# shell_script_file_path(String):: shell script file path
|
1064
|
-
# arguments(Array):: variable stringizable arguments
|
1065
|
-
#
|
1066
|
-
# === Returns
|
1067
|
-
# executable_command(string):: executable command string
|
1068
|
-
def format_ruby_command(shell_script_file_path, *arguments)
|
1069
|
-
return format_executable_command(sandbox_ruby, *([shell_script_file_path] + arguments))
|
1070
|
-
end
|
1071
|
-
|
1072
|
-
# Formats a powershell command using the given script path and arguments.
|
1073
|
-
# Allows for specifying powershell from a specific installed location.
|
1074
|
-
# This method is only implemented for Windows.
|
1075
|
-
#
|
1076
|
-
# === Parameters
|
1077
|
-
# powershell_exe_path(String):: path to powershell executable
|
1078
|
-
# shell_script_file_path(String):: shell script file path
|
1079
|
-
# arguments(Array):: variable stringizable arguments
|
1080
|
-
#
|
1081
|
-
# === Returns
|
1082
|
-
# executable_command(string):: executable command string
|
1083
|
-
def format_powershell_command4(powershell_exe_path,
|
1084
|
-
lines_before_script,
|
1085
|
-
lines_after_script,
|
1086
|
-
shell_script_file_path,
|
1087
|
-
*arguments)
|
1088
|
-
# special case for powershell scripts.
|
1089
|
-
escaped = []
|
1090
|
-
[shell_script_file_path, arguments].flatten.each do |arg|
|
1091
|
-
value = arg.to_s
|
1092
|
-
# note that literal ampersand must be quoted on the powershell command
|
1093
|
-
# line because it otherwise means 'execute what follows'.
|
1094
|
-
escaped << ((value.index(' ') || value.index('&')) ? "'#{value.gsub("'", "''")}'" : value)
|
1095
|
-
end
|
1096
|
-
|
1097
|
-
# resolve lines before & after script.
|
1098
|
-
defaulted_lines_after_script = lines_after_script.nil?
|
1099
|
-
lines_before_script ||= []
|
1100
|
-
lines_after_script ||= []
|
1101
|
-
|
1102
|
-
# execute powershell with RemoteSigned execution policy. the issue
|
1103
|
-
# is that powershell by default will only run digitally-signed
|
1104
|
-
# scripts.
|
1105
|
-
# FIX: search for any attempt to alter execution policy in lines
|
1106
|
-
# before insertion.
|
1107
|
-
# FIX: support digitally signed scripts and/or signing on the fly by
|
1108
|
-
# checking for a signature file side-by-side with script.
|
1109
|
-
lines_before_script.insert(0, "set-executionpolicy -executionPolicy RemoteSigned -Scope Process")
|
1110
|
-
|
1111
|
-
# insert error checking only in case of defaulted "lines after script"
|
1112
|
-
# to be backward compatible with existing scripts.
|
1113
|
-
if defaulted_lines_after_script
|
1114
|
-
# ensure for a generic powershell script that any errors left in the
|
1115
|
-
# global $Error list are noted and result in script failure. the
|
1116
|
-
# best practice is for the script to handle errors itself (and clear
|
1117
|
-
# the $Error list if necessary), so this is a catch-all for any
|
1118
|
-
# script which does not handle errors "properly".
|
1119
|
-
lines_after_script << "if ($NULL -eq $LastExitCode) { $LastExitCode = 0 }"
|
1120
|
-
lines_after_script << "if ((0 -eq $LastExitCode) -and ($Error.Count -gt 0)) { $RS_message = 'Script exited successfully but $Error contained '+($Error.Count)+' error(s):'; write-output $RS_message; write-output $Error; $LastExitCode = 1 }"
|
1121
|
-
end
|
1122
|
-
|
1123
|
-
# ensure last exit code gets marshalled.
|
1124
|
-
marshall_last_exit_code_cmd = "exit $LastExitCode"
|
1125
|
-
if defaulted_lines_after_script || (lines_after_script.last != marshall_last_exit_code_cmd)
|
1126
|
-
lines_after_script << marshall_last_exit_code_cmd
|
1127
|
-
end
|
1128
|
-
|
1129
|
-
# format powershell command string.
|
1130
|
-
powershell_command = "&{#{lines_before_script.join("; ")}; &#{escaped.join(" ")}; #{lines_after_script.join("; ")}}"
|
1131
|
-
|
1132
|
-
# in order to run 64-bit powershell from this 32-bit ruby process, we need to launch it using
|
1133
|
-
# our special RightRun utility from program files, if it is installed (it is not installed for
|
1134
|
-
# 32-bit instances and perhaps not for test/dev environments).
|
1135
|
-
executable_path = powershell_exe_path
|
1136
|
-
executable_arguments = ["-command", powershell_command]
|
1137
|
-
executable_path, executable_arguments = format_right_run_path(executable_path, executable_arguments)
|
1138
|
-
|
1139
|
-
# combine command string with powershell executable and arguments.
|
1140
|
-
return format_executable_command(executable_path, executable_arguments)
|
1141
|
-
end
|
1142
|
-
|
1143
|
-
# Formats a shell command using the given script path and arguments.
|
1144
|
-
#
|
1145
|
-
# === Parameters
|
1146
|
-
# shell_script_file_path(String):: shell script file path
|
1147
|
-
# arguments(Array):: variable stringizable arguments
|
1148
|
-
#
|
1149
|
-
# === Returns
|
1150
|
-
# executable_command(string):: executable command string
|
1151
|
-
def format_shell_command(shell_script_file_path, *arguments)
|
1152
|
-
# special case for powershell scripts and ruby scripts (because we
|
1153
|
-
# don't necessarily setup the association for .rb with our sandbox
|
1154
|
-
# ruby in the environment).
|
1155
|
-
extension = File.extname(shell_script_file_path)
|
1156
|
-
unless extension.to_s.empty?
|
1157
|
-
if 0 == POWERSHELL_V1x0_SCRIPT_EXTENSION.casecmp(extension)
|
1158
|
-
return format_powershell_command(shell_script_file_path, *arguments)
|
1159
|
-
elsif 0 == RUBY_SCRIPT_EXTENSION.casecmp(extension)
|
1160
|
-
return format_ruby_command(shell_script_file_path, *arguments)
|
1161
|
-
end
|
1162
|
-
end
|
1163
|
-
|
1164
|
-
# execution is based on script extension (.bat, .cmd, .js, .vbs, etc.)
|
1165
|
-
return format_executable_command(shell_script_file_path, *arguments)
|
1166
|
-
end
|
1167
|
-
|
1168
|
-
# Formats a command string to redirect stdout to the given target.
|
1169
|
-
#
|
1170
|
-
# === Parameters
|
1171
|
-
# cmd(String):: executable command string
|
1172
|
-
#
|
1173
|
-
# target(String):: target file (optional, defaults to nul redirection)
|
1174
|
-
def format_redirect_stdout(cmd, target = NULL_OUTPUT_NAME)
|
1175
|
-
return cmd + " 1>#{target}"
|
1176
|
-
end
|
1177
|
-
|
1178
|
-
# Formats a command string to redirect stderr to the given target.
|
1179
|
-
#
|
1180
|
-
# === Parameters
|
1181
|
-
# cmd(String):: executable command string
|
1182
|
-
#
|
1183
|
-
# target(String):: target file (optional, defaults to nul redirection)
|
1184
|
-
def format_redirect_stderr(cmd, target = NULL_OUTPUT_NAME)
|
1185
|
-
return cmd + " 2>#{target}"
|
1186
|
-
end
|
1187
|
-
|
1188
|
-
# Formats a command string to redirect both stdout and stderr to the
|
1189
|
-
# given target.
|
1190
|
-
#
|
1191
|
-
# === Parameters
|
1192
|
-
# cmd(String):: executable command string
|
1193
|
-
#
|
1194
|
-
# target(String):: target file (optional, defaults to nul redirection)
|
1195
|
-
def format_redirect_both(cmd, target = NULL_OUTPUT_NAME)
|
1196
|
-
return cmd + " 1>#{target} 2>&1"
|
1197
|
-
end
|
1198
|
-
|
1199
|
-
# Returns path to sandbox ruby executable.
|
1200
|
-
def sandbox_ruby
|
1201
|
-
unless @sandbox_ruby
|
1202
|
-
@sandbox_ruby = ENV['RS_RUBY_EXE'] ||
|
1203
|
-
File.normalize_path(File.join(RightScale::Platform.filesystem.sandbox_dir, 'Ruby', 'bin', 'ruby.exe'))
|
1204
|
-
end
|
1205
|
-
@sandbox_ruby
|
1206
|
-
end
|
1207
|
-
|
1208
|
-
# Gets the current system uptime.
|
1209
|
-
#
|
1210
|
-
# === Return
|
1211
|
-
# the time the machine has been up in seconds, 0 if there was an error.
|
1212
|
-
def uptime
|
1213
|
-
begin
|
1214
|
-
return Time.now.to_i.to_f - booted_at.to_f
|
1215
|
-
rescue Exception
|
1216
|
-
return 0.0
|
1217
|
-
end
|
1218
|
-
end
|
1219
|
-
|
1220
|
-
# Gets the time at which the system was booted
|
1221
|
-
#
|
1222
|
-
# === Return
|
1223
|
-
# the UTC timestamp at which the system was booted
|
1224
|
-
def booted_at
|
1225
|
-
begin
|
1226
|
-
Dir.mktmpdir do |temp_dir_path|
|
1227
|
-
Dir.chdir(temp_dir_path) do
|
1228
|
-
wmic_output = `echo | wmic OS Get LastBootUpTime`
|
1229
|
-
end
|
1230
|
-
end
|
1231
|
-
|
1232
|
-
match = /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.\d{6}([+-]\d{3})/.match(wmic_output)
|
1233
|
-
|
1234
|
-
year, mon, day, hour, min, sec, tz = match[1..-1]
|
1235
|
-
|
1236
|
-
#Convert timezone from [+-]mmm to [+-]hh:mm
|
1237
|
-
tz = "#{tz[0...1]}#{(tz.to_i.abs / 60).to_s.rjust(2,'0')}:#{(tz.to_i.abs % 60).to_s.rjust(2,'0')}"
|
1238
|
-
|
1239
|
-
#Finally, parse the WMIC output as an XML-schema time, which is the only reliable
|
1240
|
-
#way to parse a time with arbitrary zone in Ruby (?!)
|
1241
|
-
return Time.xmlschema("#{year}-#{mon}-#{day}T#{hour}:#{min}:#{sec}#{tz}").to_i
|
1242
|
-
rescue Exception
|
1243
|
-
return nil
|
1244
|
-
end
|
1245
|
-
end
|
1246
|
-
|
1247
|
-
end # Shell
|
1248
|
-
|
1249
|
-
class Controller
|
1250
|
-
include ::Windows::Process
|
1251
|
-
include ::Windows::Error
|
1252
|
-
include ::Windows::Handle
|
1253
|
-
include ::Windows::Security
|
1254
|
-
|
1255
|
-
@@initiate_system_shutdown_api = nil
|
1256
|
-
|
1257
|
-
# Shutdown machine now
|
1258
|
-
def shutdown
|
1259
|
-
initiate_system_shutdown(false)
|
1260
|
-
end
|
1261
|
-
|
1262
|
-
# Reboot machine now
|
1263
|
-
def reboot
|
1264
|
-
initiate_system_shutdown(true)
|
1265
|
-
end
|
1266
|
-
|
1267
|
-
private
|
1268
|
-
|
1269
|
-
def initiate_system_shutdown(reboot_after_shutdown)
|
1270
|
-
|
1271
|
-
@@initiate_system_shutdown_api = Win32::API.new('InitiateSystemShutdown', 'PPLLL', 'B', 'advapi32') unless @@initiate_system_shutdown_api
|
1272
|
-
|
1273
|
-
# get current process token.
|
1274
|
-
token_handle = 0.chr * 4
|
1275
|
-
unless OpenProcessToken(process_handle = GetCurrentProcess(),
|
1276
|
-
desired_access = TOKEN_ADJUST_PRIVILEGES + TOKEN_QUERY,
|
1277
|
-
token_handle)
|
1278
|
-
raise get_last_error
|
1279
|
-
end
|
1280
|
-
token_handle = token_handle.unpack('V')[0]
|
1281
|
-
|
1282
|
-
begin
|
1283
|
-
# lookup shutdown privilege ID.
|
1284
|
-
luid = 0.chr * 8
|
1285
|
-
unless LookupPrivilegeValue(system_name = nil,
|
1286
|
-
priviledge_name = 'SeShutdownPrivilege',
|
1287
|
-
luid)
|
1288
|
-
raise get_last_error
|
1289
|
-
end
|
1290
|
-
luid = luid.unpack('VV')
|
1291
|
-
|
1292
|
-
# adjust token privilege to enable shutdown.
|
1293
|
-
token_privileges = 0.chr * 16 # TOKEN_PRIVILEGES tokenPrivileges;
|
1294
|
-
token_privileges[0,4] = [1].pack("V") # tokenPrivileges.PrivilegeCount = 1;
|
1295
|
-
token_privileges[4,8] = luid.pack("VV") # tokenPrivileges.Privileges[0].Luid = luid;
|
1296
|
-
token_privileges[12,4] = [SE_PRIVILEGE_ENABLED].pack("V") # tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
1297
|
-
unless AdjustTokenPrivileges(token_handle,
|
1298
|
-
disable_all_privileges = 0,
|
1299
|
-
token_privileges,
|
1300
|
-
new_state = 0,
|
1301
|
-
previous_state = nil,
|
1302
|
-
return_length = nil)
|
1303
|
-
raise get_last_error
|
1304
|
-
end
|
1305
|
-
unless @@initiate_system_shutdown_api.call(machine_name = nil,
|
1306
|
-
message = nil,
|
1307
|
-
timeout_secs = 1,
|
1308
|
-
force_apps_closed = 1,
|
1309
|
-
reboot_after_shutdown ? 1 : 0)
|
1310
|
-
raise get_last_error
|
1311
|
-
end
|
1312
|
-
ensure
|
1313
|
-
CloseHandle(token_handle)
|
1314
|
-
end
|
1315
|
-
true
|
1316
|
-
end
|
1317
|
-
|
1318
|
-
end # Controller
|
1319
|
-
|
1320
|
-
class Rng
|
1321
|
-
def pseudorandom_bytes(count)
|
1322
|
-
bytes = ''
|
1323
|
-
count.times do
|
1324
|
-
bytes << rand(0xff)
|
1325
|
-
end
|
1326
|
-
|
1327
|
-
bytes
|
1328
|
-
end
|
1329
|
-
end
|
1330
|
-
|
1331
|
-
class Process
|
1332
|
-
include ::Windows::Process
|
1333
|
-
|
1334
|
-
@@get_process_memory_info = nil
|
1335
|
-
|
1336
|
-
# see PROCESS_MEMORY_COUNTERS structure: "http://msdn.microsoft.com/en-us/library/ms684877%28VS.85%29.aspx"
|
1337
|
-
SIZEOF_PROCESS_MEMORY_COUNTERS = 10 * 4
|
1338
|
-
|
1339
|
-
# queries resident set size (current working set size in Windows).
|
1340
|
-
#
|
1341
|
-
# === Parameters
|
1342
|
-
# pid(Fixnum):: process ID or nil for current process
|
1343
|
-
#
|
1344
|
-
# === Return
|
1345
|
-
# result(Fixnum):: current set size in KB
|
1346
|
-
def resident_set_size(pid=nil)
|
1347
|
-
@@get_process_memory_info = ::Win32::API.new("GetProcessMemoryInfo", 'LPL', 'B', 'psapi') unless @@get_process_memory_info
|
1348
|
-
|
1349
|
-
# FIX: call OpenProcess and ensure proper access and close if given PID.
|
1350
|
-
raise NotImplementedError.new("pid != nil not yet implemented") if pid
|
1351
|
-
process_handle = GetCurrentProcess()
|
1352
|
-
process_memory_counters = "\0" * SIZEOF_PROCESS_MEMORY_COUNTERS
|
1353
|
-
result = @@get_process_memory_info.call(process_handle, process_memory_counters, process_memory_counters.size)
|
1354
|
-
# note that the 'B' return type is a Fixnum (i.e. not TrueClass or FalseClass) of 'zero' on failure or 'non-zero' on success
|
1355
|
-
raise ::RightScale::Win32Error.new("Failed to get resident set size for process") if 0 == result
|
1356
|
-
|
1357
|
-
# current .WorkingSetSize (bytes) is equivalent of Linux' ps resident set size (KB)
|
1358
|
-
return process_memory_counters[12..16].unpack("L")[0] / 1024 # bytes to KB
|
1359
|
-
end
|
1360
|
-
end
|
1361
|
-
|
1362
|
-
class Installer
|
1363
|
-
def install(packages)
|
1364
|
-
raise ::RightScale::Win32Error.new("Not implemented yet")
|
1365
|
-
end
|
1366
|
-
end
|
1367
|
-
|
1368
|
-
# internal class for querying OS version, etc.
|
1369
|
-
class OSInformation
|
1370
|
-
include ::Windows::SystemInfo
|
1371
|
-
|
1372
|
-
attr_reader :version, :major, :minor, :build
|
1373
|
-
|
1374
|
-
def initialize
|
1375
|
-
@version = GetVersion()
|
1376
|
-
@major = LOBYTE(LOWORD(version))
|
1377
|
-
@minor = HIBYTE(LOWORD(version))
|
1378
|
-
@build = HIWORD(version)
|
1379
|
-
end
|
1380
|
-
end
|
1381
|
-
|
1382
|
-
end # Platform
|
1383
|
-
|
1384
|
-
end # RightScale
|