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
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 420 420: def self.passenger_tmpdir(create = true) 421: dir = @@passenger_tmpdir 422: if dir.nil? || dir.empty? 423: dir = "#{Dir.tmpdir}/passenger.#{Process.pid}" 424: @@passenger_tmpdir = dir 425: end 426: if create && !File.exist?(dir) 427: # This is a very minimal implementation of the function 428: # passengerCreateTempDir() in Utils.cpp. This implementation 429: # is only meant to make the unit tests pass. For production 430: # systems one should pre-create the temp directory with 431: # passengerCreateTempDir(). 432: system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", dir) 433: system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", "#{dir}/backends") 434: end 435: return dir 436: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 438 438: def self.passenger_tmpdir=(dir) 439: @@passenger_tmpdir = dir 440: 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 60 60: def assert_valid_app_root(app_root) 61: assert_valid_directory(app_root) 62: assert_valid_file("#{app_root}/config/environment.rb") 63: end
Assert that path is a directory. Raises InvalidPath if it isn‘t.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 66 66: def assert_valid_directory(path) 67: if !File.directory?(path) 68: raise InvalidPath, "'#{path}' is not a valid directory." 69: end 70: end
Assert that path is a file. Raises InvalidPath if it isn‘t.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 73 73: def assert_valid_file(path) 74: if !File.file?(path) 75: raise InvalidPath, "'#{path}' is not a valid file." 76: end 77: 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 88 88: def assert_valid_groupname(groupname) 89: # If groupname does not exist then getgrnam() will raise an ArgumentError. 90: groupname && Etc.getgrnam(groupname) 91: 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 81 81: def assert_valid_username(username) 82: # If username does not exist then getpwnam() will raise an ArgumentError. 83: username && Etc.getpwnam(username) 84: 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 51 51: def canonicalize_path(path) 52: raise ArgumentError, "The 'path' argument may not be nil" if path.nil? 53: return Pathname.new(path).realpath.to_s 54: rescue Errno::ENOENT => e 55: raise InvalidAPath, e.message 56: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 93 93: def close_all_io_objects_for_fds(file_descriptors_to_leave_open) 94: ObjectSpace.each_object(IO) do |io| 95: begin 96: if !file_descriptors_to_leave_open.include?(io.fileno) && !io.closed? 97: io.close 98: end 99: rescue 100: end 101: end 102: 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 343 343: def lower_privilege(filename, lowest_user = "nobody") 344: stat = File.lstat(filename) 345: begin 346: if !switch_to_user(stat.uid) 347: switch_to_user(lowest_user) 348: end 349: rescue Errno::EPERM 350: # No problem if we were unable to switch user. 351: end 352: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 104 104: def marshal_exception(exception) 105: data = { 106: :message => exception.message, 107: :class => exception.class.to_s, 108: :backtrace => exception.backtrace 109: } 110: if exception.is_a?(InitializationError) 111: data[:is_initialization_error] = true 112: if exception.child_exception 113: data[:child_exception] = marshal_exception(exception.child_exception) 114: end 115: else 116: begin 117: data[:exception] = Marshal.dump(exception) 118: rescue ArgumentError, TypeError 119: e = UnknownError.new(exception.message, exception.class.to_s, 120: exception.backtrace) 121: data[:exception] = Marshal.dump(e) 122: end 123: end 124: return Marshal.dump(data) 125: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 413 413: def passenger_tmpdir(create = true) 414: PhusionPassenger::Utils.passenger_tmpdir(create) 415: 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 158 158: def print_exception(current_location, exception, destination = STDERR) 159: if !exception.is_a?(SystemExit) 160: destination.puts(exception.backtrace_string(current_location)) 161: destination.flush if destination.respond_to?(:flush) 162: end 163: 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 242 242: def report_app_init_status(channel, sink = STDERR) 243: begin 244: old_global_stderr = $stderr 245: old_stderr = STDERR 246: stderr_output = "" 247: 248: pseudo_stderr = PseudoIO.new(sink) 249: Object.send(:remove_const, 'STDERR') rescue nil 250: Object.const_set('STDERR', pseudo_stderr) 251: $stderr = pseudo_stderr 252: 253: begin 254: yield 255: ensure 256: Object.send(:remove_const, 'STDERR') rescue nil 257: Object.const_set('STDERR', old_stderr) 258: $stderr = old_global_stderr 259: stderr_output = pseudo_stderr.done! 260: end 261: 262: channel.write('success') 263: return true 264: rescue StandardError, ScriptError, NoMemoryError => e 265: if ENV['TESTING_PASSENGER'] == '1' 266: print_exception(self.class.to_s, e) 267: end 268: channel.write('exception') 269: channel.write_scalar(marshal_exception(e)) 270: channel.write_scalar(stderr_output) 271: return false 272: rescue SystemExit => e 273: channel.write('exit') 274: channel.write_scalar(marshal_exception(e)) 275: channel.write_scalar(stderr_output) 276: raise 277: end 278: 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 175 175: def safe_fork(current_location = self.class, double_fork = false) 176: pid = fork 177: if pid.nil? 178: begin 179: if double_fork 180: pid2 = fork 181: if pid2.nil? 182: srand 183: yield 184: end 185: else 186: srand 187: yield 188: end 189: rescue Exception => e 190: print_exception(current_location.to_s, e) 191: ensure 192: exit! 193: end 194: else 195: if double_fork 196: Process.waitpid(pid) rescue nil 197: return pid 198: else 199: return pid 200: end 201: end 202: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 391 391: def sanitize_spawn_options(options) 392: defaults = { 393: "lower_privilege" => true, 394: "lowest_user" => "nobody", 395: "environment" => "production", 396: "app_type" => "rails", 397: "spawn_method" => "smart-lv2", 398: "framework_spawner_timeout" => -1, 399: "app_spawner_timeout" => -1, 400: "print_exceptions" => true 401: } 402: options = defaults.merge(options) 403: options["lower_privilege"] = to_boolean(options["lower_privilege"]) 404: options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i 405: options["app_spawner_timeout"] = options["app_spawner_timeout"].to_i 406: # Force this to be a boolean for easy use with Utils#unmarshal_and_raise_errors. 407: options["print_exceptions"] = to_boolean(options["print_exceptions"]) 408: return options 409: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 354 354: def switch_to_user(user) 355: begin 356: if user.is_a?(String) 357: pw = Etc.getpwnam(user) 358: username = user 359: uid = pw.uid 360: gid = pw.gid 361: else 362: pw = Etc.getpwuid(user) 363: username = pw.name 364: uid = user 365: gid = pw.gid 366: end 367: rescue 368: return false 369: end 370: if uid == 0 371: return false 372: else 373: # Some systems are broken. initgroups can fail because of 374: # all kinds of stupid reasons. So we ignore any errors 375: # raised by initgroups. 376: begin 377: Process.groups = Process.initgroups(username, gid) 378: rescue 379: end 380: Process::Sys.setgid(gid) 381: Process::Sys.setuid(uid) 382: ENV['HOME'] = pw.dir 383: return true 384: end 385: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 387 387: def to_boolean(value) 388: return !(value.nil? || value == false || value == "false") 389: 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 302 302: def unmarshal_and_raise_errors(channel, print_exception = nil, app_type = "rails") 303: args = channel.read 304: if args.nil? 305: raise EOFError, "Unexpected end-of-file detected." 306: end 307: status = args[0] 308: if status == 'exception' 309: child_exception = unmarshal_exception(channel.read_scalar) 310: stderr = channel.read_scalar 311: exception = AppInitError.new( 312: "Application '#{@app_root}' raised an exception: " << 313: "#{child_exception.class} (#{child_exception.message})", 314: child_exception, 315: app_type, 316: stderr.empty? ? nil : stderr) 317: elsif status == 'exit' 318: child_exception = unmarshal_exception(channel.read_scalar) 319: stderr = channel.read_scalar 320: exception = AppInitError.new("Application '#{@app_root}' exited during startup", 321: child_exception, app_type, stderr.empty? ? nil : stderr) 322: else 323: exception = nil 324: end 325: 326: if print_exception && exception 327: if print_exception.respond_to?(:puts) 328: print_exception(self.class.to_s, child_exception, print_exception) 329: elsif print_exception.respond_to?(:to_str) 330: filename = print_exception.to_str 331: File.open(filename, 'a') do |f| 332: print_exception(self.class.to_s, child_exception, f) 333: end 334: else 335: print_exception(self.class.to_s, child_exception) 336: end 337: end 338: raise exception if exception 339: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 127 127: def unmarshal_exception(data) 128: hash = Marshal.load(data) 129: if hash[:is_initialization_error] 130: if hash[:child_exception] 131: child_exception = unmarshal_exception(hash[:child_exception]) 132: else 133: child_exception = nil 134: end 135: 136: case hash[:class] 137: when AppInitError.to_s 138: exception_class = AppInitError 139: when FrameworkInitError.to_s 140: exception_class = FrameworkInitError 141: else 142: exception_class = InitializationError 143: end 144: return exception_class.new(hash[:message], child_exception) 145: else 146: begin 147: return Marshal.load(hash[:exception]) 148: rescue ArgumentError, TypeError 149: return UnknownError.new(hash[:message], hash[:class], hash[:backtrace]) 150: end 151: end 152: end