Class: Aspera::Environment
- Inherits:
-
Object
- Object
- Aspera::Environment
- Includes:
- Singleton
- Defined in:
- lib/aspera/environment.rb
Overview
detect OS, architecture, and specific stuff
Constant Summary collapse
- OS_WINDOWS =
:windows- OS_MACOS =
:osx- OS_LINUX =
:linux- OS_AIX =
:aix- OS_LIST =
[OS_WINDOWS, OS_MACOS, OS_LINUX, OS_AIX].freeze
- CPU_X86_64 =
:x86_64- CPU_ARM64 =
:arm64- CPU_PPC64 =
:ppc64- CPU_PPC64LE =
:ppc64le- CPU_S390 =
:s390- CPU_LIST =
[CPU_X86_64, CPU_ARM64, CPU_PPC64, CPU_PPC64LE, CPU_S390].freeze
- BITS_PER_BYTE =
8- MEBI =
1024 * 1024
- BYTES_PER_MEBIBIT =
MEBI / BITS_PER_BYTE
- I18N_VARS =
%w(LC_ALL LC_CTYPE LANG).freeze
- WINDOWS_FILENAME_INVALID_CHARACTERS =
“/” is invalid on both Unix and Windows, other are Windows special characters See: learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
'<>:"/\\|?*'- REPLACE_CHARACTER =
'_'- RB_EXT =
'.rb'
Instance Attribute Summary collapse
-
#cpu ⇒ Object
readonly
Returns the value of attribute cpu.
-
#default_gui_mode ⇒ Object
readonly
Returns the value of attribute default_gui_mode.
-
#file_illegal_characters ⇒ Object
Returns the value of attribute file_illegal_characters.
-
#os ⇒ Object
readonly
Returns the value of attribute os.
-
#url_method ⇒ Object
Returns the value of attribute url_method.
Class Method Summary collapse
-
.empty_binding ⇒ Object
empty variable binding for secure eval.
-
.force_terminal_c ⇒ Object
force locale to C so that unicode characters are not used.
-
.log_spawn(exec:, args: nil, env: nil) ⇒ String
Generate log line for external program with arguments.
-
.restrict_file_access(path, mode: nil) ⇒ Object
restrict access to a file or folder to user only.
- .ruby_version ⇒ Object
-
.secure_capture(exec:, args: [], exception: true, **opts) ⇒ Object
Execute process and capture stdout.
-
.secure_eval(code, file, line) ⇒ Object
secure execution of Ruby code.
-
.secure_execute(exec:, args: nil, env: nil, **system_args) ⇒ String
start process and wait for completion.
-
.secure_spawn(exec:, args: nil, env: nil, **options) ⇒ String
Start process in background caller can call Process.wait on returned value.
-
.terminal? ⇒ Boolean
True if we are in a terminal.
- .terminal_supports_unicode? ⇒ Boolean
-
.write_file_restricted(path, force: false, mode: nil) ⇒ Object
Write content to a file, with restricted access.
Instance Method Summary collapse
-
#architecture ⇒ Object
Normalized architecture name See constants: OS_* and CPU_*.
-
#exe_file(name = nil) ⇒ String
Add executable file extension (e.g. “.exe”) for current OS.
-
#fix_home ⇒ Object
on Windows, the env var %USERPROFILE% provides the path to user’s home more reliably than %HOMEDRIVE%%HOMEPATH% so, tell Ruby the right way.
- #graphical? ⇒ Boolean
-
#initialize ⇒ Environment
constructor
A new instance of Environment.
-
#initialize_fields ⇒ Object
initialize fields from environment.
-
#open_editor(file_path) ⇒ Object
open a file in an editor.
-
#open_uri(the_url) ⇒ Object
Allows a user to open a URL if method is :text, then URL is displayed on terminal if method is :graphical, then the URL will be opened with the default browser.
-
#open_uri_graphical(uri) ⇒ Object
Open a URI in a graphical browser Command must be non blocking.
-
#safe_filename_character ⇒ Object
Replacement character for illegal filename characters Can also be used as safe “join” character.
-
#sanitized_filename(filename) ⇒ String
Sanitize a filename by replacing illegal characters.
Constructor Details
#initialize ⇒ Environment
Returns a new instance of Environment.
193 194 195 |
# File 'lib/aspera/environment.rb', line 193 def initialize initialize_fields end |
Instance Attribute Details
#cpu ⇒ Object (readonly)
Returns the value of attribute cpu.
191 192 193 |
# File 'lib/aspera/environment.rb', line 191 def cpu @cpu end |
#default_gui_mode ⇒ Object (readonly)
Returns the value of attribute default_gui_mode.
191 192 193 |
# File 'lib/aspera/environment.rb', line 191 def default_gui_mode @default_gui_mode end |
#file_illegal_characters ⇒ Object
Returns the value of attribute file_illegal_characters.
190 191 192 |
# File 'lib/aspera/environment.rb', line 190 def file_illegal_characters @file_illegal_characters end |
#os ⇒ Object (readonly)
Returns the value of attribute os.
191 192 193 |
# File 'lib/aspera/environment.rb', line 191 def os @os end |
#url_method ⇒ Object
Returns the value of attribute url_method.
190 191 192 |
# File 'lib/aspera/environment.rb', line 190 def url_method @url_method end |
Class Method Details
.empty_binding ⇒ Object
empty variable binding for secure eval
49 50 51 |
# File 'lib/aspera/environment.rb', line 49 def empty_binding return Kernel.binding end |
.force_terminal_c ⇒ Object
force locale to C so that unicode characters are not used
179 180 181 |
# File 'lib/aspera/environment.rb', line 179 def force_terminal_c I18N_VARS.each{ |var| ENV[var] = 'C'} end |
.log_spawn(exec:, args: nil, env: nil) ⇒ String
Generate log line for external program with arguments
63 64 65 66 67 68 69 70 |
# File 'lib/aspera/environment.rb', line 63 def log_spawn(exec:, args: nil, env: nil) [ 'execute:'.red, env&.map{ |k, v| "#{k}=#{Shellwords.shellescape(v)}"}, Shellwords.shellescape(exec), args&.map{ |a| Shellwords.shellescape(a)} ].compact.flatten.join(' ') end |
.restrict_file_access(path, mode: nil) ⇒ Object
restrict access to a file or folder to user only
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/aspera/environment.rb', line 157 def restrict_file_access(path, mode: nil) if mode.nil? # or FileUtils ? if File.file?(path) mode = 0o600 elsif File.directory?(path) mode = 0o700 else Log.log.debug{"No restriction can be set for #{path}"} end end File.chmod(mode, path) unless mode.nil? rescue => e Log.log.warn(e.) end |
.ruby_version ⇒ Object
44 45 46 |
# File 'lib/aspera/environment.rb', line 44 def ruby_version return RbConfig::CONFIG['RUBY_PROGRAM_VERSION'] end |
.secure_capture(exec:, args: [], exception: true, **opts) ⇒ Object
Execute process and capture stdout
125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/aspera/environment.rb', line 125 def secure_capture(exec:, args: [], exception: true, **opts) Aspera.assert_type(exec, String) Aspera.assert_type(args, Array) Aspera.assert_type(opts, Hash) Log.log.debug{log_spawn(exec: exec, args: args)} Log.dump(:opts, opts, level: :trace2) Log.dump(:ENV, ENV.to_h, level: :trace1) stdout, stderr, status = Open3.capture3(exec, *args, **opts) Log.log.debug{"status=#{status}, stderr=#{stderr}"} Log.log.trace1{"stdout=#{stdout}"} raise "process failed: #{status.exitstatus} (#{stderr})" if !status.success? && exception return stdout end |
.secure_eval(code, file, line) ⇒ Object
secure execution of Ruby code
54 55 56 |
# File 'lib/aspera/environment.rb', line 54 def secure_eval(code, file, line) Kernel.send('lave'.reverse, code, empty_binding, file, line) end |
.secure_execute(exec:, args: nil, env: nil, **system_args) ⇒ String
start process and wait for completion
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/aspera/environment.rb', line 103 def secure_execute(exec:, args: nil, env: nil, **system_args) Aspera.assert_type(exec, String) Aspera.assert_type(args, Array, NilClass) Aspera.assert_type(env, Hash, NilClass) Log.log.debug{log_spawn(exec: exec, args: args, env: env)} # start in separate process spawn_args = [] spawn_args.push(env) unless env.nil? # ensure no shell expansion spawn_args.push([exec, exec]) spawn_args.concat(args) unless args.nil? kwargs = {exception: true} kwargs.merge!(system_args) Kernel.system(*spawn_args, **kwargs) nil end |
.secure_spawn(exec:, args: nil, env: nil, **options) ⇒ String
Start process in background caller can call Process.wait on returned value
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/aspera/environment.rb', line 80 def secure_spawn(exec:, args: nil, env: nil, **) Aspera.assert_type(exec, String) Aspera.assert_type(args, Array, NilClass) Aspera.assert_type(env, Hash, NilClass) Aspera.assert_type(, Hash, NilClass) Log.log.debug{log_spawn(exec: exec, args: args, env: env)} # start ascp in separate process spawn_args = [] spawn_args.push(env) unless env.nil? spawn_args.push([exec, exec]) spawn_args.concat(args) unless args.nil? opts = {close_others: true} opts.merge!() unless .nil? ascp_pid = Process.spawn(*spawn_args, **opts) Log.log.debug{"pid: #{ascp_pid}"} return ascp_pid end |
.terminal? ⇒ Boolean
Returns true if we are in a terminal.
174 175 176 |
# File 'lib/aspera/environment.rb', line 174 def terminal? $stdout.tty? end |
.terminal_supports_unicode? ⇒ Boolean
186 187 188 |
# File 'lib/aspera/environment.rb', line 186 def terminal_supports_unicode? terminal? && I18N_VARS.any?{ |var| ENV[var]&.include?('UTF-8')} end |
.write_file_restricted(path, force: false, mode: nil) ⇒ Object
Write content to a file, with restricted access
144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/aspera/environment.rb', line 144 def write_file_restricted(path, force: false, mode: nil) Aspera.assert(block_given?, type: Aspera::InternalError) if force || !File.exist?(path) # Windows may give error File.unlink(path) rescue nil # content provided by block File.write(path, yield) restrict_file_access(path, mode: mode) end return path end |
Instance Method Details
#architecture ⇒ Object
Normalized architecture name See constants: OS_* and CPU_*
240 241 242 |
# File 'lib/aspera/environment.rb', line 240 def architecture "#{@os}-#{@cpu}" end |
#exe_file(name = nil) ⇒ String
Add executable file extension (e.g. “.exe”) for current OS
247 248 249 250 |
# File 'lib/aspera/environment.rb', line 247 def exe_file(name = nil) return name unless @executable_extension return "#{name}#{@executable_extension}" end |
#fix_home ⇒ Object
on Windows, the env var %USERPROFILE% provides the path to user’s home more reliably than %HOMEDRIVE%%HOMEPATH% so, tell Ruby the right way
254 255 256 257 258 |
# File 'lib/aspera/environment.rb', line 254 def fix_home return unless @os.eql?(OS_WINDOWS) && ENV.key?('USERPROFILE') && Dir.exist?(ENV.fetch('USERPROFILE', nil)) ENV['HOME'] = ENV.fetch('USERPROFILE', nil) Log.log.debug{"Windows: set HOME to USERPROFILE: #{Dir.home}"} end |
#graphical? ⇒ Boolean
260 261 262 |
# File 'lib/aspera/environment.rb', line 260 def graphical? @default_gui_mode == :graphical end |
#initialize_fields ⇒ Object
initialize fields from environment
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/aspera/environment.rb', line 198 def initialize_fields @os = case RbConfig::CONFIG['host_os'] when /mswin/, /msys/, /mingw/, /cygwin/, /bccwin/, /wince/, /emc/ OS_WINDOWS when /darwin/, /mac os/ OS_MACOS when /linux/ OS_LINUX when /aix/ OS_AIX else Aspera.error_unexpected_value(RbConfig::CONFIG['host_os']){'host_os'} end @cpu = case RbConfig::CONFIG['host_cpu'] when /x86_64/, /x64/ CPU_X86_64 when /powerpc/, /ppc64/ @os.eql?(OS_LINUX) ? CPU_PPC64LE : CPU_PPC64 when /s390/ CPU_S390 when /arm/, /aarch64/ CPU_ARM64 else Aspera.error_unexpected_value(RbConfig::CONFIG['host_cpu']){'host_cpu'} end @executable_extension = @os.eql?(OS_WINDOWS) ? '.exe' : nil # :text or :graphical depending on the environment @default_gui_mode = if [Environment::OS_WINDOWS, Environment::OS_MACOS].include?(os) || (ENV.key?('DISPLAY') && !ENV['DISPLAY'].empty?) # assume not remotely connected on macos and windows or unix family :graphical else :text end @url_method = @default_gui_mode @file_illegal_characters = REPLACE_CHARACTER + WINDOWS_FILENAME_INVALID_CHARACTERS nil end |
#open_editor(file_path) ⇒ Object
open a file in an editor
277 278 279 280 281 282 283 284 285 |
# File 'lib/aspera/environment.rb', line 277 def open_editor(file_path) if ENV.key?('EDITOR') self.class.secure_execute(exec: ENV['EDITOR'], args: [file_path.to_s]) elsif @os.eql?(Environment::OS_WINDOWS) self.class.secure_execute(exec: 'notepad.exe', args: [%Q{"#{file_path}"}]) else open_uri_graphical(file_path.to_s) end end |
#open_uri(the_url) ⇒ Object
Allows a user to open a URL if method is :text, then URL is displayed on terminal if method is :graphical, then the URL will be opened with the default browser. this is non blocking
291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/aspera/environment.rb', line 291 def open_uri(the_url) case @url_method when :graphical open_uri_graphical(the_url) when :text case the_url.to_s when /^http/ puts "USER ACTION: please enter this URL in a browser:\n#{the_url.to_s.red}\n" else puts "USER ACTION: open this:\n#{the_url.to_s.red}\n" end else Aspera.error_unexpected_value(@url_method){'URL open method'} end end |
#open_uri_graphical(uri) ⇒ Object
Open a URI in a graphical browser Command must be non blocking
267 268 269 270 271 272 273 274 |
# File 'lib/aspera/environment.rb', line 267 def open_uri_graphical(uri) case @os when Environment::OS_MACOS then return self.class.secure_execute(exec: 'open', args: [uri.to_s]) when Environment::OS_WINDOWS then return self.class.secure_execute(exec: 'start', args: ['explorer', %Q{"#{uri}"}]) when Environment::OS_LINUX then return self.class.secure_execute(exec: 'xdg-open', args: [uri.to_s]) else Assert.error_unexpected_value(os){'no graphical open method'} end end |
#safe_filename_character ⇒ Object
Replacement character for illegal filename characters Can also be used as safe “join” character
308 309 310 311 |
# File 'lib/aspera/environment.rb', line 308 def safe_filename_character return REPLACE_CHARACTER if @file_illegal_characters.nil? || @file_illegal_characters.empty? @file_illegal_characters[0] end |
#sanitized_filename(filename) ⇒ String
Sanitize a filename by replacing illegal characters
316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
# File 'lib/aspera/environment.rb', line 316 def sanitized_filename(filename) safe_char = safe_filename_character # Windows does not allow file name: # - with control characters anywhere # - ending with space or dot filename = filename.gsub(/[\x00-\x1F\x7F]/, safe_char) filename = filename.chop while filename.end_with?(' ', '.') if @file_illegal_characters&.size.to_i >= 2 # replace all illegal characters with safe_char filename = filename.tr(@file_illegal_characters[1..-1], safe_char) end # ensure only one safe_char is used at a time return filename.gsub(/#{Regexp.escape(safe_char)}+/, safe_char).chomp(safe_char) end |