Utility functions.
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.
# File lib/phusion_passenger/utils.rb, line 481 481: def self.passenger_tmpdir(create = true) 482: dir = @@passenger_tmpdir 483: if dir.nil? || dir.empty? 484: dir = "#{Dir.tmpdir}/passenger.#{Process.pid}" 485: @@passenger_tmpdir = dir 486: end 487: if create && !File.exist?(dir) 488: # This is a very minimal implementation of the function 489: # passengerCreateTempDir() in Utils.cpp. This implementation 490: # is only meant to make the unit tests pass. For production 491: # systems one should pre-create the temp directory with 492: # passengerCreateTempDir(). 493: system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", dir) 494: system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", "#{dir}/backends") 495: end 496: return dir 497: end
Assert that app_root is a valid Ruby on Rails application root. Raises InvalidPath if that is not the case.
# File lib/phusion_passenger/utils.rb, line 62 62: def assert_valid_app_root(app_root) 63: assert_valid_directory(app_root) 64: assert_valid_file("#{app_root}/config/environment.rb") 65: end
Assert that path is a directory. Raises InvalidPath if it isn’t.
# File lib/phusion_passenger/utils.rb, line 68 68: def assert_valid_directory(path) 69: if !File.directory?(path) 70: raise InvalidPath, "'#{path}' is not a valid directory." 71: end 72: end
Assert that path is a file. Raises InvalidPath if it isn’t.
# File lib/phusion_passenger/utils.rb, line 75 75: def assert_valid_file(path) 76: if !File.file?(path) 77: raise InvalidPath, "'#{path}' is not a valid file." 78: end 79: end
Assert that groupname is a valid group name. Raises ArgumentError if that is not the case.
# File lib/phusion_passenger/utils.rb, line 90 90: def assert_valid_groupname(groupname) 91: # If groupname does not exist then getgrnam() will raise an ArgumentError. 92: groupname && Etc.getgrnam(groupname) 93: end
Assert that username is a valid username. Raises ArgumentError if that is not the case.
# File lib/phusion_passenger/utils.rb, line 83 83: def assert_valid_username(username) 84: # If username does not exist then getpwnam() will raise an ArgumentError. 85: username && Etc.getpwnam(username) 86: 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.
# File lib/phusion_passenger/utils.rb, line 53 53: def canonicalize_path(path) 54: raise ArgumentError, "The 'path' argument may not be nil" if path.nil? 55: return Pathname.new(path).realpath.to_s 56: rescue Errno::ENOENT => e 57: raise InvalidAPath, e.message 58: end
# File lib/phusion_passenger/utils.rb, line 95 95: def close_all_io_objects_for_fds(file_descriptors_to_leave_open) 96: ObjectSpace.each_object(IO) do |io| 97: begin 98: if !file_descriptors_to_leave_open.include?(io.fileno) && !io.closed? 99: io.close 100: end 101: rescue 102: end 103: end 104: 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.
# File lib/phusion_passenger/utils.rb, line 412 412: def lower_privilege(filename, lowest_user = "nobody") 413: stat = File.lstat(filename) 414: begin 415: if !switch_to_user(stat.uid) 416: switch_to_user(lowest_user) 417: end 418: rescue Errno::EPERM 419: # No problem if we were unable to switch user. 420: end 421: end
# File lib/phusion_passenger/utils.rb, line 106 106: def marshal_exception(exception) 107: data = { 108: :message => exception.message, 109: :class => exception.class.to_s, 110: :backtrace => exception.backtrace 111: } 112: if exception.is_a?(InitializationError) 113: data[:is_initialization_error] = true 114: if exception.child_exception 115: data[:child_exception] = marshal_exception(exception.child_exception) 116: end 117: else 118: begin 119: data[:exception] = Marshal.dump(exception) 120: rescue ArgumentError, TypeError 121: e = UnknownError.new(exception.message, exception.class.to_s, 122: exception.backtrace) 123: data[:exception] = Marshal.dump(e) 124: end 125: end 126: return Marshal.dump(data) 127: end
# File lib/phusion_passenger/utils.rb, line 474 474: def passenger_tmpdir(create = true) 475: PhusionPassenger::Utils.passenger_tmpdir(create) 476: 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.
# File lib/phusion_passenger/utils.rb, line 160 160: def print_exception(current_location, exception, destination = STDERR) 161: if !exception.is_a?(SystemExit) 162: destination.puts(exception.backtrace_string(current_location)) 163: destination.flush if destination.respond_to?(:flush) 164: end 165: 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.
# File lib/phusion_passenger/utils.rb, line 311 311: def report_app_init_status(channel, sink = STDERR) 312: begin 313: old_global_stderr = $stderr 314: old_stderr = STDERR 315: stderr_output = "" 316: 317: pseudo_stderr = PseudoIO.new(sink) 318: Object.send(:remove_const, 'STDERR') rescue nil 319: Object.const_set('STDERR', pseudo_stderr) 320: $stderr = pseudo_stderr 321: 322: begin 323: yield 324: ensure 325: Object.send(:remove_const, 'STDERR') rescue nil 326: Object.const_set('STDERR', old_stderr) 327: $stderr = old_global_stderr 328: stderr_output = pseudo_stderr.done! 329: end 330: 331: channel.write('success') 332: return true 333: rescue StandardError, ScriptError, NoMemoryError => e 334: if ENV['TESTING_PASSENGER'] == '1' 335: print_exception(self.class.to_s, e) 336: end 337: channel.write('exception') 338: channel.write_scalar(marshal_exception(e)) 339: channel.write_scalar(stderr_output) 340: return false 341: rescue SystemExit => e 342: channel.write('exit') 343: channel.write_scalar(marshal_exception(e)) 344: channel.write_scalar(stderr_output) 345: raise 346: end 347: 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.
# File lib/phusion_passenger/utils.rb, line 244 244: def safe_fork(current_location = self.class, double_fork = false) 245: pid = fork 246: if pid.nil? 247: begin 248: if double_fork 249: pid2 = fork 250: if pid2.nil? 251: srand 252: yield 253: end 254: else 255: srand 256: yield 257: end 258: rescue Exception => e 259: print_exception(current_location.to_s, e) 260: ensure 261: exit! 262: end 263: else 264: if double_fork 265: Process.waitpid(pid) rescue nil 266: return pid 267: else 268: return pid 269: end 270: end 271: end
# File lib/phusion_passenger/utils.rb, line 452 452: def sanitize_spawn_options(options) 453: defaults = { 454: "lower_privilege" => true, 455: "lowest_user" => "nobody", 456: "environment" => "production", 457: "app_type" => "rails", 458: "spawn_method" => "smart-lv2", 459: "framework_spawner_timeout" => 1, 460: "app_spawner_timeout" => 1, 461: "print_exceptions" => true 462: } 463: options = defaults.merge(options) 464: options["lower_privilege"] = to_boolean(options["lower_privilege"]) 465: options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i 466: options["app_spawner_timeout"] = options["app_spawner_timeout"].to_i 467: # Force this to be a boolean for easy use with Utils#unmarshal_and_raise_errors. 468: options["print_exceptions"] = to_boolean(options["print_exceptions"]) 469: return options 470: end
# File lib/phusion_passenger/utils.rb, line 167 167: def setup_bundler_support(options = {}) 168: # Rack::ApplicationSpawner depends on the 'rack' library, but the app 169: # might want us to use a bundled version instead of a 170: # gem/apt-get/yum/whatever-installed version. Therefore we must setup 171: # the correct load paths before requiring 'rack'. 172: # 173: # The most popular tool for bundling dependencies is Bundler. Bundler 174: # works as follows: 175: # - If the bundle is locked then a file .bundle/environment.rb exists 176: # which will setup the load paths. 177: # - If the bundle is not locked then the load paths must be set up by 178: # calling Bundler.setup. 179: # - Rails 3's boot.rb automatically loads .bundle/environment.rb or 180: # calls Bundler.setup if that's not available. 181: # - Other Rack apps might not have a boot.rb but we still want to setup 182: # Bundler. 183: # - Some Rails 2 apps might have explicitly added Bundler support. 184: # These apps call Bundler.setup in their preinitializer.rb. 185: # 186: # So the strategy is as follows: 187: 188: # Our strategy might be completely unsuitable for the app or the 189: # developer is using something other than Bundler (or possibly an 190: # older version of Bundler which is API incompatible with the latest 191: # version), so we let the user manually specify a load path setup file. 192: if options["load_path_setup_file"] 193: require File.expand(options["load_path_setup_file"]) 194: 195: # The app developer may also override our strategy with this magic file. 196: elsif File.exist?('config/setup_load_paths.rb') 197: require File.expand_path('config/setup_load_paths') 198: 199: # If the Bundler lock environment file exists then load that. If it 200: # exists then there's a 99.9% chance that loading it is the correct 201: # thing to do. 202: elsif File.exist?('.bundle/environment.rb') 203: require File.expand_path('.bundle/environment') 204: 205: # If the Bundler environment file doesn't exist then there are two 206: # possibilities: 207: # 1. Bundler is not used, in which case we don't have to do anything. 208: # 2. Bundler *is* used, but the gems are not locked and we're supposed 209: # to call Bundler.setup. 210: # 211: # The existence of Gemfile indicates whether (2) is true: 212: elsif File.exist?('Gemfile') 213: # In case of Rails 3, config/boot.rb already calls Bundler.setup. 214: # However older versions of Rails may not so loading boot.rb might 215: # not be the correct thing to do. To be on the safe side we 216: # call Bundler.setup ourselves; calling Bundler.setup twice is 217: # harmless. If this isn't the correct thing to do after all then 218: # there's always the load_path_setup_file option and 219: # setup_load_paths.rb. 220: require 'rubygems' 221: require 'bundler' 222: Bundler.setup 223: end 224: 225: # Bundler might remove Phusion Passenger from the load path in its zealous 226: # attempt to un-require RubyGems, so here we put Phusion Passenger back 227: # into the load path. 228: if $LOAD_PATH.first != LIBDIR 229: $LOAD_PATH.unshift(LIBDIR) 230: $LOAD_PATH.uniq! 231: end 232: end
# File lib/phusion_passenger/utils.rb, line 423 423: def switch_to_user(user) 424: begin 425: if user.is_a?(String) 426: pw = Etc.getpwnam(user) 427: username = user 428: uid = pw.uid 429: gid = pw.gid 430: else 431: pw = Etc.getpwuid(user) 432: username = pw.name 433: uid = user 434: gid = pw.gid 435: end 436: rescue 437: return false 438: end 439: if uid == 0 440: return false 441: else 442: NativeSupport.switch_user(username, uid, gid) 443: ENV['HOME'] = pw.dir 444: return true 445: end 446: end
# File lib/phusion_passenger/utils.rb, line 448 448: def to_boolean(value) 449: return !(value.nil? || value == false || value == "false") 450: 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.
# File lib/phusion_passenger/utils.rb, line 371 371: def unmarshal_and_raise_errors(channel, print_exception = nil, app_type = "rails") 372: args = channel.read 373: if args.nil? 374: raise EOFError, "Unexpected end-of-file detected." 375: end 376: status = args[0] 377: if status == 'exception' 378: child_exception = unmarshal_exception(channel.read_scalar) 379: stderr = channel.read_scalar 380: exception = AppInitError.new( 381: "Application '#{@app_root}' raised an exception: " << 382: "#{child_exception.class} (#{child_exception.message})", 383: child_exception, 384: app_type, 385: stderr.empty? ? nil : stderr) 386: elsif status == 'exit' 387: child_exception = unmarshal_exception(channel.read_scalar) 388: stderr = channel.read_scalar 389: exception = AppInitError.new("Application '#{@app_root}' exited during startup", 390: child_exception, app_type, stderr.empty? ? nil : stderr) 391: else 392: exception = nil 393: end 394: 395: if print_exception && exception 396: if print_exception.respond_to?(:puts) 397: print_exception(self.class.to_s, child_exception, print_exception) 398: elsif print_exception.respond_to?(:to_str) 399: filename = print_exception.to_str 400: File.open(filename, 'a') do |f| 401: print_exception(self.class.to_s, child_exception, f) 402: end 403: else 404: print_exception(self.class.to_s, child_exception) 405: end 406: end 407: raise exception if exception 408: end
# File lib/phusion_passenger/utils.rb, line 129 129: def unmarshal_exception(data) 130: hash = Marshal.load(data) 131: if hash[:is_initialization_error] 132: if hash[:child_exception] 133: child_exception = unmarshal_exception(hash[:child_exception]) 134: else 135: child_exception = nil 136: end 137: 138: case hash[:class] 139: when AppInitError.to_s 140: exception_class = AppInitError 141: when FrameworkInitError.to_s 142: exception_class = FrameworkInitError 143: else 144: exception_class = InitializationError 145: end 146: return exception_class.new(hash[:message], child_exception) 147: else 148: begin 149: return Marshal.load(hash[:exception]) 150: rescue ArgumentError, TypeError 151: return UnknownError.new(hash[:message], hash[:class], hash[:backtrace]) 152: end 153: end 154: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.