Utility functions.
- assert_valid_app_root
- assert_valid_directory
- assert_valid_file
- assert_valid_groupname
- assert_valid_username
- canonicalize_path
- close_all_io_objects_for_fds
- lower_privilege
- marshal_exception
- passenger_tmpdir
- passenger_tmpdir
- passenger_tmpdir=
- print_exception
- report_app_init_status
- safe_fork
- sanitize_spawn_options
- switch_to_user
- to_boolean
- unmarshal_and_raise_errors
- unmarshal_exception
Class PhusionPassenger::Utils::RewindableInput
Returns the directory in which to store Phusion Passenger-specific temporary files. If create is true, then this method creates the directory if it doesn‘t exist.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 421 421: def self.passenger_tmpdir(create = true) 422: dir = @@passenger_tmpdir 423: if dir.nil? || dir.empty? 424: dir = "#{Dir.tmpdir}/passenger.#{Process.pid}" 425: @@passenger_tmpdir = dir 426: end 427: if create && !File.exist?(dir) 428: # This is a very minimal implementation of the function 429: # passengerCreateTempDir() in Utils.cpp. This implementation 430: # is only meant to make the unit tests pass. For production 431: # systems one should pre-create the temp directory with 432: # passengerCreateTempDir(). 433: system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", dir) 434: system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", "#{dir}/backends") 435: end 436: return dir 437: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 439 439: def self.passenger_tmpdir=(dir) 440: @@passenger_tmpdir = dir 441: end
Assert that app_root is a valid Ruby on Rails application root. Raises InvalidPath if that is not the case.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 61 61: def assert_valid_app_root(app_root) 62: assert_valid_directory(app_root) 63: assert_valid_file("#{app_root}/config/environment.rb") 64: end
Assert that path is a directory. Raises InvalidPath if it isn‘t.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 67 67: def assert_valid_directory(path) 68: if !File.directory?(path) 69: raise InvalidPath, "'#{path}' is not a valid directory." 70: end 71: end
Assert that path is a file. Raises InvalidPath if it isn‘t.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 74 74: def assert_valid_file(path) 75: if !File.file?(path) 76: raise InvalidPath, "'#{path}' is not a valid file." 77: end 78: end
Assert that groupname is a valid group name. Raises ArgumentError if that is not the case.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 89 89: def assert_valid_groupname(groupname) 90: # If groupname does not exist then getgrnam() will raise an ArgumentError. 91: groupname && Etc.getgrnam(groupname) 92: end
Assert that username is a valid username. Raises ArgumentError if that is not the case.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 82 82: def assert_valid_username(username) 83: # If username does not exist then getpwnam() will raise an ArgumentError. 84: username && Etc.getpwnam(username) 85: end
Return the canonicalized version of path. This path is guaranteed to to be "normal", i.e. it doesn‘t contain stuff like ".." or "/", and it fully resolves symbolic links.
Raises SystemCallError if something went wrong. Raises ArgumentError if path is nil. Raises InvalidPath if path does not appear to be a valid path.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 52 52: def canonicalize_path(path) 53: raise ArgumentError, "The 'path' argument may not be nil" if path.nil? 54: return Pathname.new(path).realpath.to_s 55: rescue Errno::ENOENT => e 56: raise InvalidAPath, e.message 57: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 94 94: def close_all_io_objects_for_fds(file_descriptors_to_leave_open) 95: ObjectSpace.each_object(IO) do |io| 96: begin 97: if !file_descriptors_to_leave_open.include?(io.fileno) && !io.closed? 98: io.close 99: end 100: rescue 101: end 102: end 103: end
Lower the current process‘s privilege to the owner of the given file. No exceptions will be raised in the event that privilege lowering fails.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 344 344: def lower_privilege(filename, lowest_user = "nobody") 345: stat = File.lstat(filename) 346: begin 347: if !switch_to_user(stat.uid) 348: switch_to_user(lowest_user) 349: end 350: rescue Errno::EPERM 351: # No problem if we were unable to switch user. 352: end 353: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 105 105: def marshal_exception(exception) 106: data = { 107: :message => exception.message, 108: :class => exception.class.to_s, 109: :backtrace => exception.backtrace 110: } 111: if exception.is_a?(InitializationError) 112: data[:is_initialization_error] = true 113: if exception.child_exception 114: data[:child_exception] = marshal_exception(exception.child_exception) 115: end 116: else 117: begin 118: data[:exception] = Marshal.dump(exception) 119: rescue ArgumentError, TypeError 120: e = UnknownError.new(exception.message, exception.class.to_s, 121: exception.backtrace) 122: data[:exception] = Marshal.dump(e) 123: end 124: end 125: return Marshal.dump(data) 126: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 414 414: def passenger_tmpdir(create = true) 415: PhusionPassenger::Utils.passenger_tmpdir(create) 416: end
Print the given exception, including the stack trace, to STDERR.
current_location is a string which describes where the code is currently at. Usually the current class name will be enough.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 159 159: def print_exception(current_location, exception, destination = STDERR) 160: if !exception.is_a?(SystemExit) 161: destination.puts(exception.backtrace_string(current_location)) 162: destination.flush if destination.respond_to?(:flush) 163: end 164: end
Run the given block. A message will be sent through channel (a MessageChannel object), telling the remote side whether the block raised an exception, called exit(), or succeeded.
If sink is non-nil, then every operation on $stderr/STDERR inside the block will be performed on sink as well. If sink is nil then all operations on $stderr/STDERR inside the block will be silently discarded, i.e. if one writes to $stderr/STDERR then nothing will be actually written to the console.
Returns whether the block succeeded, i.e. whether it didn‘t raise an exception.
Exceptions are not propagated, except SystemExit and a few non-StandardExeption classes such as SignalException. Of the exceptions that are propagated, only SystemExit will be reported.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 243 243: def report_app_init_status(channel, sink = STDERR) 244: begin 245: old_global_stderr = $stderr 246: old_stderr = STDERR 247: stderr_output = "" 248: 249: pseudo_stderr = PseudoIO.new(sink) 250: Object.send(:remove_const, 'STDERR') rescue nil 251: Object.const_set('STDERR', pseudo_stderr) 252: $stderr = pseudo_stderr 253: 254: begin 255: yield 256: ensure 257: Object.send(:remove_const, 'STDERR') rescue nil 258: Object.const_set('STDERR', old_stderr) 259: $stderr = old_global_stderr 260: stderr_output = pseudo_stderr.done! 261: end 262: 263: channel.write('success') 264: return true 265: rescue StandardError, ScriptError, NoMemoryError => e 266: if ENV['TESTING_PASSENGER'] == '1' 267: print_exception(self.class.to_s, e) 268: end 269: channel.write('exception') 270: channel.write_scalar(marshal_exception(e)) 271: channel.write_scalar(stderr_output) 272: return false 273: rescue SystemExit => e 274: channel.write('exit') 275: channel.write_scalar(marshal_exception(e)) 276: channel.write_scalar(stderr_output) 277: raise 278: end 279: end
Fork a new process and run the given block inside the child process, just like fork(). Unlike fork(), this method is safe, i.e. there‘s no way for the child process to escape the block. Any uncaught exceptions in the child process will be printed to standard output, citing current_location as the source. Futhermore, the child process will exit by calling Kernel#exit!, thereby bypassing any at_exit or ensure blocks.
If double_fork is true, then the child process will fork and immediately exit. This technique can be used to avoid zombie processes, at the expense of not being able to waitpid() the second child.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 176 176: def safe_fork(current_location = self.class, double_fork = false) 177: pid = fork 178: if pid.nil? 179: begin 180: if double_fork 181: pid2 = fork 182: if pid2.nil? 183: srand 184: yield 185: end 186: else 187: srand 188: yield 189: end 190: rescue Exception => e 191: print_exception(current_location.to_s, e) 192: ensure 193: exit! 194: end 195: else 196: if double_fork 197: Process.waitpid(pid) rescue nil 198: return pid 199: else 200: return pid 201: end 202: end 203: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 392 392: def sanitize_spawn_options(options) 393: defaults = { 394: "lower_privilege" => true, 395: "lowest_user" => "nobody", 396: "environment" => "production", 397: "app_type" => "rails", 398: "spawn_method" => "smart-lv2", 399: "framework_spawner_timeout" => -1, 400: "app_spawner_timeout" => -1, 401: "print_exceptions" => true 402: } 403: options = defaults.merge(options) 404: options["lower_privilege"] = to_boolean(options["lower_privilege"]) 405: options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i 406: options["app_spawner_timeout"] = options["app_spawner_timeout"].to_i 407: # Force this to be a boolean for easy use with Utils#unmarshal_and_raise_errors. 408: options["print_exceptions"] = to_boolean(options["print_exceptions"]) 409: return options 410: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 355 355: def switch_to_user(user) 356: begin 357: if user.is_a?(String) 358: pw = Etc.getpwnam(user) 359: username = user 360: uid = pw.uid 361: gid = pw.gid 362: else 363: pw = Etc.getpwuid(user) 364: username = pw.name 365: uid = user 366: gid = pw.gid 367: end 368: rescue 369: return false 370: end 371: if uid == 0 372: return false 373: else 374: # Some systems are broken. initgroups can fail because of 375: # all kinds of stupid reasons. So we ignore any errors 376: # raised by initgroups. 377: begin 378: Process.groups = Process.initgroups(username, gid) 379: rescue 380: end 381: Process::Sys.setgid(gid) 382: Process::Sys.setuid(uid) 383: ENV['HOME'] = pw.dir 384: return true 385: end 386: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 388 388: def to_boolean(value) 389: return !(value.nil? || value == false || value == "false") 390: end
Receive status information that was sent to channel by report_app_init_status. If an error occured according to the received information, then an appropriate exception will be raised.
If print_exception evaluates to true, then the exception message and the backtrace will also be printed. Where it is printed to depends on the type of print_exception:
- If it responds to #puts, then the exception information will be printed using this method.
- If it responds to #to_str, then the exception information will be appended to the file whose filename equals the return value of the #to_str call.
- Otherwise, it will be printed to STDERR.
Raises:
- AppInitError: this class wraps the exception information received through the channel.
- IOError, SystemCallError, SocketError: these errors are raised if an error occurred while receiving the information through the channel.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 303 303: def unmarshal_and_raise_errors(channel, print_exception = nil, app_type = "rails") 304: args = channel.read 305: if args.nil? 306: raise EOFError, "Unexpected end-of-file detected." 307: end 308: status = args[0] 309: if status == 'exception' 310: child_exception = unmarshal_exception(channel.read_scalar) 311: stderr = channel.read_scalar 312: exception = AppInitError.new( 313: "Application '#{@app_root}' raised an exception: " << 314: "#{child_exception.class} (#{child_exception.message})", 315: child_exception, 316: app_type, 317: stderr.empty? ? nil : stderr) 318: elsif status == 'exit' 319: child_exception = unmarshal_exception(channel.read_scalar) 320: stderr = channel.read_scalar 321: exception = AppInitError.new("Application '#{@app_root}' exited during startup", 322: child_exception, app_type, stderr.empty? ? nil : stderr) 323: else 324: exception = nil 325: end 326: 327: if print_exception && exception 328: if print_exception.respond_to?(:puts) 329: print_exception(self.class.to_s, child_exception, print_exception) 330: elsif print_exception.respond_to?(:to_str) 331: filename = print_exception.to_str 332: File.open(filename, 'a') do |f| 333: print_exception(self.class.to_s, child_exception, f) 334: end 335: else 336: print_exception(self.class.to_s, child_exception) 337: end 338: end 339: raise exception if exception 340: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 128 128: def unmarshal_exception(data) 129: hash = Marshal.load(data) 130: if hash[:is_initialization_error] 131: if hash[:child_exception] 132: child_exception = unmarshal_exception(hash[:child_exception]) 133: else 134: child_exception = nil 135: end 136: 137: case hash[:class] 138: when AppInitError.to_s 139: exception_class = AppInitError 140: when FrameworkInitError.to_s 141: exception_class = FrameworkInitError 142: else 143: exception_class = InitializationError 144: end 145: return exception_class.new(hash[:message], child_exception) 146: else 147: begin 148: return Marshal.load(hash[:exception]) 149: rescue ArgumentError, TypeError 150: return UnknownError.new(hash[:message], hash[:class], hash[:backtrace]) 151: end 152: end 153: end