Module: PhusionPassenger::Utils

Module PhusionPassenger::Utils |
In: | lib/phusion_passenger/utils.rb lib/phusion_passenger/utils/file_system_watcher.rb lib/phusion_passenger/utils/hosts_file_parser.rb lib/phusion_passenger/utils/rewindable_input.rb lib/phusion_passenger/utils/tmpdir.rb lib/phusion_passenger/utils/unseekable_socket.rb

Utility functions.

Methods

- after_handling_requests
- after_loading_app_code
- assert_valid_directory
- assert_valid_file
- assert_valid_groupname
- assert_valid_username
- at_exit
- before_handling_requests
- canonicalize_path
- check_directory_tree_permissions
- close_all_io_objects_for_fds
- connect_to_server
- generate_random_id
- get_socket_address_type
- global_backtrace_report
- local_socket_address?
- lower_privilege
- lower_privilege_called
- marshal_exception
- new
- opens_files?
- passenger_tmpdir
- passenger_tmpdir
- passenger_tmpdir=
- prepare_app_process
- print_exception
- private_class_method
- process_is_alive?
- report_app_init_status
- safe_fork
- sanitize_spawn_options
- split_by_null_into_hash
- split_by_null_into_hash
- to_boolean
- unmarshal_and_raise_errors
- unmarshal_exception

Classes and Modules
Class PhusionPassenger::Utils::FileSystemWatcher
Class PhusionPassenger::Utils::HostsFileParser
Class PhusionPassenger::Utils::PseudoIO
Class PhusionPassenger::Utils::RewindableInput
Class PhusionPassenger::Utils::UnseekableSocket
Constants

PADDING | = | "_"
NULL | = | "\0"
FileSystemWatcher | = | NativeSupport::FileSystemWatcher

Public Class methods
new(filenames, termination_pipe = nil)

[ show source ]

        # File lib/phusion_passenger/utils/file_system_watcher.rb, line 60
    60:                 def self.new(filenames, termination_pipe = nil)
    61:                         # Default parameter values, type conversion and exception
    62:                         # handling in C is too much of a pain.
    63:                         filenames = filenames.map do |filename|
    64:                                 filename.to_s
    65:                         end
    66:                         return _new(filenames, termination_pipe)
    67:                 end

opens_files?()

[ show source ]

        # File lib/phusion_passenger/utils/file_system_watcher.rb, line 69
    69:                 def self.opens_files?
    70:                         return true
    71:                 end

Protected Class methods
lower_privilege_called()

No-op, hook for unit tests.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 649
    649:         def self.lower_privilege_called
    650:         end

passenger_tmpdir(create = true)

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/tmpdir.rb, line 37
    37:         def self.passenger_tmpdir(create = true)
    38:                 dir = @@passenger_tmpdir
    39:                 if dir.nil? || dir.empty?
    40:                         tmpdir = "/tmp"
    41:                         ["PASSENGER_TEMP_DIR", "PASSENGER_TMPDIR"].each do |name|
    42:                                 if ENV.has_key?(name) && !ENV[name].empty?
    43:                                         tmpdir = ENV[name]
    44:                                         break
    45:                                 end
    46:                         end
    47:                         dir = "#{tmpdir}/passenger.1.0.#{Process.pid}"
    48:                         dir.gsub!(%r{//+}, '/')
    49:                         @@passenger_tmpdir = dir
    50:                 end
    51:                 if create && !File.exist?(dir)
    52:                         # This is a very minimal implementation of the subdirectory
    53:                         # creation logic in ServerInstanceDir.h. This implementation
    54:                         # is only meant to make the unit tests pass. For production
    55:                         # systems one should pre-create the temp directory with
    56:                         # ServerInstanceDir.h.
    57:                         system("mkdir", "-p", "-m", "u=rwxs,g=rwx,o=rwx", dir)
    58:                         system("mkdir", "-p", "-m", "u=rwxs,g=rwx,o=rwx", "#{dir}/generation-0")
    59:                         system("mkdir", "-p", "-m", "u=rwxs,g=rwx,o=rwx", "#{dir}/backends")
    60:                         system("mkdir", "-p", "-m", "u=rwxs,g=rwx,o=rwx", "#{dir}/spawn-server")
    61:                 end
    62:                 return dir
    63:         end

passenger_tmpdir=(dir)

[ show source ]

        # File lib/phusion_passenger/utils/tmpdir.rb, line 65
    65:         def self.passenger_tmpdir=(dir)
    66:                 @@passenger_tmpdir = dir
    67:         end

Protected Instance methods
after_handling_requests()

To be called after the request handler main loop is exited. This function will fire off necessary events perform necessary cleanup tasks.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 420
    420:         def after_handling_requests
    421:                 PhusionPassenger.call_event(:stopping_worker_process)
    422:                 Kernel.passenger_call_at_exit_blocks
    423:         end

after_loading_app_code(options)

This method is to be called after loading the application code but before forking a worker process.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 357
    357:         def after_loading_app_code(options)
    358:                 # Even though prepare_app_process() restores the Phusion Passenger
    359:                 # load path after setting up Bundler, the app itself might also
    360:                 # remove Phusion Passenger from the load path for whatever reason,
    361:                 # so here we restore the load path again.
    362:                 if $LOAD_PATH.first != LIBDIR
    363:                         $LOAD_PATH.unshift(LIBDIR)
    364:                         $LOAD_PATH.uniq!
    365:                 end
    366:
    367:                 # Post-install framework extensions. Possibly preceded by a call to
    368:                 # PhusionPassenger.install_framework_extensions!
    369:                 require 'rails/version' if defined?(::Rails) && !defined?(::Rails::VERSION)
    370:                 if defined?(::Rails) && ::Rails::VERSION::MAJOR <= 2
    371:                         require 'phusion_passenger/classic_rails_extensions/init'
    372:                         ClassicRailsExtensions.init!(options)
    373:                         # Rails 3 extensions are installed by
    374:                         # PhusionPassenger.install_framework_extensions!
    375:                 end
    376:
    377:                 PhusionPassenger._spawn_options = nil
    378:         end

assert_valid_directory(path)

Assert that `path` is a directory. Raises `InvalidPath` if it isn‘t.

[ show source ]

        # File lib/phusion_passenger/utils.rb, line 64
    64:         def assert_valid_directory(path)
    65:                 if !File.directory?(path)
    66:                         raise InvalidPath, "'#{path}' is not a valid directory."
    67:                 end
    68:         end

assert_valid_file(path)

Assert that `path` is a file. Raises `InvalidPath` if it isn‘t.

[ show source ]

        # File lib/phusion_passenger/utils.rb, line 71
    71:         def assert_valid_file(path)
    72:                 if !File.file?(path)
    73:                         raise InvalidPath, "'#{path}' is not a valid file."
    74:                 end
    75:         end

assert_valid_groupname(groupname)

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 86
    86:         def assert_valid_groupname(groupname)
    87:                 # If groupname does not exist then getgrnam() will raise an ArgumentError.
    88:                 groupname && Etc.getgrnam(groupname)
    89:         end

assert_valid_username(username)

Assert that `username` is a valid username. Raises ArgumentError if that is not the case.

[ show source ]

        # File lib/phusion_passenger/utils.rb, line 79
    79:         def assert_valid_username(username)
    80:                 # If username does not exist then getpwnam() will raise an ArgumentError.
    81:                 username && Etc.getpwnam(username)
    82:         end

at_exit(&block)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 267
    267:                         def at_exit(&block)
    268:                                 return Kernel.passenger_at_exit(&block)
    269:                         end

before_handling_requests(forked, options)

To be called before the request handler main loop is entered, but after the app startup file has been loaded. This function will fire off necessary events and perform necessary preparation tasks.

`forked` indicates whether the current worker process is forked off from an ApplicationSpawner that has preloaded the app code. `options` are the spawn options that were passed.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 387
    387:         def before_handling_requests(forked, options)
    388:                 if forked && options["analytics_logger"]
    389:                         options["analytics_logger"].clear_connection
    390:                 end
    391:
    392:                 # If we were forked from a preloader process then clear or
    393:                 # re-establish ActiveRecord database connections. This prevents
    394:                 # child processes from concurrently accessing the same
    395:                 # database connection handles.
    396:                 if forked && defined?(::ActiveRecord::Base)
    397:                         if ::ActiveRecord::Base.respond_to?(:clear_all_connections!)
    398:                                 ::ActiveRecord::Base.clear_all_connections!
    399:                         elsif ::ActiveRecord::Base.respond_to?(:clear_active_connections!)
    400:                                 ::ActiveRecord::Base.clear_active_connections!
    401:                         elsif ::ActiveRecord::Base.respond_to?(:connected?) &&
    402:                               ::ActiveRecord::Base.connected?
    403:                                 ::ActiveRecord::Base.establish_connection
    404:                         end
    405:                 end
    406:
    407:                 # Fire off events.
    408:                 PhusionPassenger.call_event(:starting_worker_process, forked)
    409:                 if options["pool_account_username"] && options["pool_account_password_base64"]
    410:                         password = options["pool_account_password_base64"].unpack('m').first
    411:                         PhusionPassenger.call_event(:credentials,
    412:                                 options["pool_account_username"], password)
    413:                 else
    414:                         PhusionPassenger.call_event(:credentials, nil, nil)
    415:                 end
    416:         end

canonicalize_path(path)

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 56
    56:         def canonicalize_path(path)
    57:                 raise ArgumentError, "The 'path' argument may not be nil" if path.nil?
    58:                 return Pathname.new(path).realpath.to_s
    59:         rescue Errno::ENOENT => e
    60:                 raise InvalidAPath, e.message
    61:         end

check_directory_tree_permissions(dir)

Checks the permissions of all parent directories of `dir` as well as `dir` itself.

`dir` must be a canonical path.

If one of the parent directories has wrong permissions, causing `dir` to be inaccessible by the current process, then this function returns [path, true] where `path` is the path of the top-most directory with wrong permissions.

If `dir` itself is not executable by the current process then this function returns [dir, false].

Otherwise, nil is returned.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 749
    749:         def check_directory_tree_permissions(dir)
    750:                 components = dir.split("/")
    751:                 components.shift
    752:                 i = 0
    753:                 # We can't use File.readable() and friends here because they
    754:                 # don't always work right with ACLs. Instead of we use 'real'
    755:                 # checks.
    756:                 while i < components.size
    757:                         path = "/" + components[0..i].join("/")
    758:                         begin
    759:                                 File.stat(path)
    760:                         rescue Errno::EACCES
    761:                                 return [File.dirname(path), true]
    762:                         end
    763:                         i += 1
    764:                 end
    765:                 begin
    766:                         Dir.chdir(dir) do
    767:                                 return nil
    768:                         end
    769:                 rescue Errno::EACCES
    770:                         return [dir, false]
    771:                 end
    772:         end

close_all_io_objects_for_fds(file_descriptors_to_leave_open)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 109
    109:         def close_all_io_objects_for_fds(file_descriptors_to_leave_open)
    110:                 ObjectSpace.each_object(IO) do |io|
    111:                         begin
    112:                                 if !file_descriptors_to_leave_open.include?(io.fileno) && !io.closed?
    113:                                         io.close
    114:                                 end
    115:                         rescue
    116:                         end
    117:                 end
    118:         end

connect_to_server(address)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 435
    435:         def connect_to_server(address)
    436:                 case get_socket_address_type(address)
    437:                 when :unix
    438:                         return UNIXSocket.new(address.sub(/^unix:/, ''))
    439:                 when :tcp
    440:                         host, port = address.sub(%r{^tcp://}, '').split(':', 2)
    441:                         port = port.to_i
    442:                         return TCPSocket.new(host, port)
    443:                 else
    444:                         raise ArgumentError, "Unknown socket address type for '#{address}'."
    445:                 end
    446:         end

generate_random_id(method)

Generate a long, cryptographically secure random ID string, which is also a valid filename.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 93
     93:         def generate_random_id(method)
     94:                 case method
     95:                 when :base64
     96:                         data = [File.read("/dev/urandom", 64)].pack('m')
     97:                         data.gsub!("\n", '')
     98:                         data.gsub!("+", '')
     99:                         data.gsub!("/", '')
    100:                         data.gsub!(/==$/, '')
    101:                         return data
    102:                 when :hex
    103:                         return File.read("/dev/urandom", 64).unpack('H*')[0]
    104:                 else
    105:                         raise ArgumentError, "Invalid method #{method.inspect}"
    106:                 end
    107:         end

get_socket_address_type(address)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 425
    425:         def get_socket_address_type(address)
    426:                 if address =~ %r{^unix:.}
    427:                         return :unix
    428:                 elsif address =~ %r{^tcp://.}
    429:                         return :tcp
    430:                 else
    431:                         return :unknown
    432:                 end
    433:         end

global_backtrace_report()

Returns a string which reports the backtraces for all threads, or if that‘s not supported the backtrace for the current thread.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 776
    776:         def global_backtrace_report
    777:                 if Kernel.respond_to?(:caller_for_all_threads)
    778:                         output = "========== Process #{Process.pid}: backtrace dump ==========\n"
    779:                         caller_for_all_threads.each_pair do |thread, stack|
    780:                                 output << ("-" * 60) << "\n"
    781:                                 output << "# Thread: #{thread.inspect}, "
    782:                                 if thread == Thread.main
    783:                                         output << "[main thread], "
    784:                                 end
    785:                                 if thread == Thread.current
    786:                                         output << "[current thread], "
    787:                                 end
    788:                                 output << "alive = #{thread.alive?}\n"
    789:                                 output << ("-" * 60) << "\n"
    790:                                 output << "    " << stack.join("\n    ")
    791:                                 output << "\n\n"
    792:                         end
    793:                 else
    794:                         output = "========== Process #{Process.pid}: backtrace dump ==========\n"
    795:                         output << ("-" * 60) << "\n"
    796:                         output << "# Current thread: #{Thread.current.inspect}\n"
    797:                         output << ("-" * 60) << "\n"
    798:                         output << "    " << caller.join("\n    ")
    799:                 end
    800:                 return output
    801:         end

local_socket_address?(address)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 448
    448:         def local_socket_address?(address)
    449:                 case get_socket_address_type(address)
    450:                 when :unix
    451:                         return true
    452:                 when :tcp
    453:                         host, port = address.sub(%r{^tcp://}, '').split(':', 2)
    454:                         return host == "127.0.0.1" || host == "::1" || host == "localhost"
    455:                 else
    456:                         raise ArgumentError, "Unknown socket address type for '#{address}'."
    457:                 end
    458:         end

lower_privilege(startup_file, options)

Lowers the current process‘s privilege based on the documented rules for the "user", "group", "default_user" and "default_group" options.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 654
    654:         def lower_privilege(startup_file, options)
    655:                 Utils.lower_privilege_called
    656:                 return if Process.euid != 0
    657:
    658:                 if options["default_user"] && !options["default_user"].empty?
    659:                         default_user = options["default_user"]
    660:                 else
    661:                         default_user = "nobody"
    662:                 end
    663:                 if options["default_group"] && !options["default_group"].empty?
    664:                         default_group = options["default_group"]
    665:                 else
    666:                         default_group = Etc.getgrgid(Etc.getpwnam(default_user).gid).name
    667:                 end
    668:
    669:                 if options["user"] && !options["user"].empty?
    670:                         begin
    671:                                 user_info = Etc.getpwnam(options["user"])
    672:                         rescue ArgumentError
    673:                                 user_info = nil
    674:                         end
    675:                 else
    676:                         uid = File.lstat(startup_file).uid
    677:                         begin
    678:                                 user_info = Etc.getpwuid(uid)
    679:                         rescue ArgumentError
    680:                                 user_info = nil
    681:                         end
    682:                 end
    683:                 if !user_info || user_info.uid == 0
    684:                         begin
    685:                                 user_info = Etc.getpwnam(default_user)
    686:                         rescue ArgumentError
    687:                                 user_info = nil
    688:                         end
    689:                 end
    690:
    691:                 if options["group"] && !options["group"].empty?
    692:                         if options["group"] == "!STARTUP_FILE!"
    693:                                 gid = File.lstat(startup_file).gid
    694:                                 begin
    695:                                         group_info = Etc.getgrgid(gid)
    696:                                 rescue ArgumentError
    697:                                         group_info = nil
    698:                                 end
    699:                         else
    700:                                 begin
    701:                                         group_info = Etc.getgrnam(options["group"])
    702:                                 rescue ArgumentError
    703:                                         group_info = nil
    704:                                 end
    705:                         end
    706:                 elsif user_info
    707:                         begin
    708:                                 group_info = Etc.getgrgid(user_info.gid)
    709:                         rescue ArgumentError
    710:                                 group_info = nil
    711:                         end
    712:                 else
    713:                         group_info = nil
    714:                 end
    715:                 if !group_info || group_info.gid == 0
    716:                         begin
    717:                                 group_info = Etc.getgrnam(default_group)
    718:                         rescue ArgumentError
    719:                                 group_info = nil
    720:                         end
    721:                 end
    722:
    723:                 if !user_info
    724:                         raise SecurityError, "Cannot determine a user to lower privilege to"
    725:                 end
    726:                 if !group_info
    727:                         raise SecurityError, "Cannot determine a group to lower privilege to"
    728:                 end
    729:
    730:                 NativeSupport.switch_user(user_info.name, user_info.uid, group_info.gid)
    731:                 ENV['USER'] = user_info.name
    732:                 ENV['HOME'] = user_info.dir
    733:         end

marshal_exception(exception)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 120
    120:         def marshal_exception(exception)
    121:                 data = {
    122:                         :message => exception.message,
    123:                         :class => exception.class.to_s,
    124:                         :backtrace => exception.backtrace
    125:                 }
    126:                 if exception.is_a?(InitializationError)
    127:                         data[:is_initialization_error] = true
    128:                         if exception.child_exception
    129:                                 data[:child_exception] = marshal_exception(exception.child_exception)
    130:                                 child_exception = exception.child_exception
    131:                                 exception.child_exception = nil
    132:                                 data[:exception] = Marshal.dump(exception)
    133:                                 exception.child_exception = child_exception
    134:                         end
    135:                 else
    136:                         begin
    137:                                 data[:exception] = Marshal.dump(exception)
    138:                         rescue ArgumentError, TypeError
    139:                                 e = UnknownError.new(exception.message, exception.class.to_s,
    140:                                                         exception.backtrace)
    141:                                 data[:exception] = Marshal.dump(e)
    142:                         end
    143:                 end
    144:                 return Marshal.dump(data)
    145:         end

passenger_tmpdir(create = true)

[ show source ]

        # File lib/phusion_passenger/utils/tmpdir.rb, line 30
    30:         def passenger_tmpdir(create = true)
    31:                 PhusionPassenger::Utils.passenger_tmpdir(create)
    32:         end

prepare_app_process(startup_file, options)

Prepare an application process using rules for the given spawn options. This method is to be called before loading the application code.

`startup_file` is the application type‘s startup file, e.g. "config/environment.rb" for Rails apps and "config.ru" for Rack apps. See SpawnManager#spawn_application for options.

This function may modify `options`. The modified options are to be passed to the request handler.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 194
    194:         def prepare_app_process(startup_file, options)
    195:                 options["app_root"] = canonicalize_path(options["app_root"])
    196:                 Dir.chdir(options["app_root"])
    197:
    198:                 lower_privilege(startup_file, options)
    199:                 path, is_parent = check_directory_tree_permissions(options["app_root"])
    200:                 if path
    201:                         username = Etc.getpwuid(Process.euid).name
    202:                         groupname = Etc.getgrgid(Process.egid).name
    203:                         message = "This application process is currently running as " +
    204:                                 "user '#{username}' and group '#{groupname}' and must be " +
    205:                                 "able to access its application root directory " +
    206:                                 "'#{options["app_root"]}'. "
    207:                         if is_parent
    208:                                 message << "However the parent directory '#{path}' " +
    209:                                         "has wrong permissions, thereby preventing " +
    210:                                         "this process from accessing its application " +
    211:                                         "root directory. Please fix the permissions " +
    212:                                         "of the directory '#{path}' first."
    213:                         else
    214:                                 message << "However this directory is not accessible " +
    215:                                         "because it has wrong permissions. Please fix " +
    216:                                         "these permissions first."
    217:                         end
    218:                         raise(message)
    219:                 end
    220:
    221:                 ENV["RAILS_ENV"] = ENV["RACK_ENV"] = options["environment"]
    222:
    223:                 base_uri = options["base_uri"]
    224:                 if base_uri && !base_uri.empty? && base_uri != "/"
    225:                         ENV["RAILS_RELATIVE_URL_ROOT"] = base_uri
    226:                         ENV["RACK_BASE_URI"] = base_uri
    227:                 end
    228:
    229:                 encoded_environment_variables = options["environment_variables"]
    230:                 if encoded_environment_variables
    231:                         env_vars_string = encoded_environment_variables.unpack("m").first
    232:                         env_vars_array  = env_vars_string.split("\0", -1)
    233:                         env_vars_array.pop
    234:                         env_vars = Hash[*env_vars_array]
    235:                         env_vars.each_pair do |key, value|
    236:                                 ENV[key] = value
    237:                         end
    238:                 end
    239:
    240:                 # Instantiate the analytics logger if requested. Can be nil.
    241:                 require 'phusion_passenger/analytics_logger'
    242:                 options["analytics_logger"] = AnalyticsLogger.new_from_options(options)
    243:
    244:                 # Make sure RubyGems uses any new environment variable values
    245:                 # that have been set now (e.g. $HOME, $GEM_HOME, etc) and that
    246:                 # it is able to detect newly installed gems.
    247:                 Gem.clear_paths
    248:
    249:                 # Because spawned app processes exit using #exit!, #at_exit
    250:                 # blocks aren't called. Here we ninja patch Kernel so that
    251:                 # we can call #at_exit blocks during app process shutdown.
    252:                 class << Kernel
    253:                         def passenger_call_at_exit_blocks
    254:                                 @passenger_at_exit_blocks ||= []
    255:                                 @passenger_at_exit_blocks.reverse_each do |block|
    256:                                         block.call
    257:                                 end
    258:                         end
    259:
    260:                         def passenger_at_exit(&block)
    261:                                 @passenger_at_exit_blocks ||= []
    262:                                 @passenger_at_exit_blocks << block
    263:                                 return block
    264:                         end
    265:                 end
    266:                 Kernel.class_eval do
    267:                         def at_exit(&block)
    268:                                 return Kernel.passenger_at_exit(&block)
    269:                         end
    270:                 end
    271:
    272:
    273:                 # Rack::ApplicationSpawner depends on the 'rack' library, but the app
    274:                 # might want us to use a bundled version instead of a
    275:                 # gem/apt-get/yum/whatever-installed version. Therefore we must setup
    276:                 # the correct load paths before requiring 'rack'.
    277:                 #
    278:                 # The most popular tool for bundling dependencies is Bundler. Bundler
    279:                 # works as follows:
    280:                 # - If the bundle is locked then a file .bundle/environment.rb exists
    281:                 #   which will setup the load paths.
    282:                 # - If the bundle is not locked then the load paths must be set up by
    283:                 #   calling Bundler.setup.
    284:                 # - Rails 3's boot.rb automatically loads .bundle/environment.rb or
    285:                 #   calls Bundler.setup if that's not available.
    286:                 # - Other Rack apps might not have a boot.rb but we still want to setup
    287:                 #   Bundler.
    288:                 # - Some Rails 2 apps might have explicitly added Bundler support.
    289:                 #   These apps call Bundler.setup in their preinitializer.rb.
    290:                 #
    291:                 # So the strategy is as follows:
    292:
    293:                 # Our strategy might be completely unsuitable for the app or the
    294:                 # developer is using something other than Bundler, so we let the user
    295:                 # manually specify a load path setup file.
    296:                 if options["load_path_setup_file"]
    297:                         require File.expand_path(options["load_path_setup_file"])
    298:
    299:                 # The app developer may also override our strategy with this magic file.
    300:                 elsif File.exist?('config/setup_load_paths.rb')
    301:                         require File.expand_path('config/setup_load_paths')
    302:
    303:                 # If the Bundler lock environment file exists then load that. If it
    304:                 # exists then there's a 99.9% chance that loading it is the correct
    305:                 # thing to do.
    306:                 elsif File.exist?('.bundle/environment.rb')
    307:                         require File.expand_path('.bundle/environment')
    308:
    309:                 # If the Bundler environment file doesn't exist then there are two
    310:                 # possibilities:
    311:                 # 1. Bundler is not used, in which case we don't have to do anything.
    312:                 # 2. Bundler *is* used, but the gems are not locked and we're supposed
    313:                 #    to call Bundler.setup.
    314:                 #
    315:                 # The existence of Gemfile indicates whether (2) is true:
    316:                 elsif File.exist?('Gemfile')
    317:                         # In case of Rails 3, config/boot.rb already calls Bundler.setup.
    318:                         # However older versions of Rails may not so loading boot.rb might
    319:                         # not be the correct thing to do. To be on the safe side we
    320:                         # call Bundler.setup ourselves; calling Bundler.setup twice is
    321:                         # harmless. If this isn't the correct thing to do after all then
    322:                         # there's always the load_path_setup_file option and
    323:                         # setup_load_paths.rb.
    324:                         require 'rubygems'
    325:                         require 'bundler'
    326:                         Bundler.setup
    327:                 end
    328:
    329:                 # Bundler might remove Phusion Passenger from the load path in its zealous
    330:                 # attempt to un-require RubyGems, so here we put Phusion Passenger back
    331:                 # into the load path. This must be done before loading the app's startup
    332:                 # file because the app might require() Phusion Passenger files.
    333:                 if $LOAD_PATH.first != LIBDIR
    334:                         $LOAD_PATH.unshift(LIBDIR)
    335:                         $LOAD_PATH.uniq!
    336:                 end
    337:
    338:
    339:                 # !!! NOTE !!!
    340:                 # If the app is using Bundler then any dependencies required past this
    341:                 # point must be specified in the Gemfile. Like ruby-debug if debugging is on...
    342:
    343:                 if options["debugger"]
    344:                         require 'ruby-debug'
    345:                         if !Debugger.respond_to?(:ctrl_port)
    346:                                 raise "Your version of ruby-debug is too old. Please upgrade to the latest version."
    347:                         end
    348:                         Debugger.start_remote('127.0.0.1', [0, 0])
    349:                         Debugger.start
    350:                 end
    351:
    352:                 PhusionPassenger._spawn_options = options
    353:         end

print_exception(current_location, exception, destination = nil)

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 172
    172:         def print_exception(current_location, exception, destination = nil)
    173:                 if !exception.is_a?(SystemExit)
    174:                         data = exception.backtrace_string(current_location)
    175:                         if defined?(DebugLogging) && self.is_a?(DebugLogging)
    176:                                 error(data)
    177:                         else
    178:                                 destination ||= STDERR
    179:                                 destination.puts(data)
    180:                                 destination.flush if destination.respond_to?(:flush)
    181:                         end
    182:                 end
    183:         end

private_class_method(name)

[ show source ]

        # File lib/phusion_passenger/utils.rb, line 44
    44:         def private_class_method(name)
    45:                 metaclass = class << self; self; end
    46:                 metaclass.send(:private, name)
    47:         end

process_is_alive?(pid)

Checks whether the given process exists.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 502
    502:         def process_is_alive?(pid)
    503:                 begin
    504:                         Process.kill(0, pid)
    505:                         return true
    506:                 rescue Errno::ESRCH
    507:                         return false
    508:                 rescue SystemCallError => e
    509:                         return true
    510:                 end
    511:         end

report_app_init_status(channel, sink = STDERR) {|| ...}

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 552
    552:         def report_app_init_status(channel, sink = STDERR)
    553:                 begin
    554:                         old_global_stderr = $stderr
    555:                         old_stderr = STDERR
    556:                         stderr_output = ""
    557:
    558:                         pseudo_stderr = PseudoIO.new(sink)
    559:                         Object.send(:remove_const, 'STDERR') rescue nil
    560:                         Object.const_set('STDERR', pseudo_stderr)
    561:                         $stderr = pseudo_stderr
    562:
    563:                         begin
    564:                                 yield
    565:                         ensure
    566:                                 Object.send(:remove_const, 'STDERR') rescue nil
    567:                                 Object.const_set('STDERR', old_stderr)
    568:                                 $stderr = old_global_stderr
    569:                                 stderr_output = pseudo_stderr.done!
    570:                         end
    571:
    572:                         channel.write('success')
    573:                         return true
    574:                 rescue StandardError, ScriptError, NoMemoryError => e
    575:                         channel.write('exception')
    576:                         channel.write_scalar(marshal_exception(e))
    577:                         channel.write_scalar(stderr_output)
    578:                         return false
    579:                 rescue SystemExit => e
    580:                         channel.write('exit')
    581:                         channel.write_scalar(marshal_exception(e))
    582:                         channel.write_scalar(stderr_output)
    583:                         raise
    584:                 end
    585:         end

safe_fork(current_location = self.class, double_fork = false) {|| ...}

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 470
    470:         def safe_fork(current_location = self.class, double_fork = false)
    471:                 pid = fork
    472:                 if pid.nil?
    473:                         has_exception = false
    474:                         begin
    475:                                 if double_fork
    476:                                         pid2 = fork
    477:                                         if pid2.nil?
    478:                                                 srand
    479:                                                 yield
    480:                                         end
    481:                                 else
    482:                                         srand
    483:                                         yield
    484:                                 end
    485:                         rescue Exception => e
    486:                                 has_exception = true
    487:                                 print_exception(current_location.to_s, e)
    488:                         ensure
    489:                                 exit!(has_exception ? 1 : 0)
    490:                         end
    491:                 else
    492:                         if double_fork
    493:                                 Process.waitpid(pid) rescue nil
    494:                                 return pid
    495:                         else
    496:                                 return pid
    497:                         end
    498:                 end
    499:         end

sanitize_spawn_options(options)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 807
    807:         def sanitize_spawn_options(options)
    808:                 defaults = {
    809:                         "app_type"         => "rails",
    810:                         "environment"      => "production",
    811:                         "spawn_method"     => "smart-lv2",
    812:                         "framework_spawner_timeout" => -1,
    813:                         "app_spawner_timeout"       => -1,
    814:                         "print_exceptions" => true
    815:                 }
    816:                 options = defaults.merge(options)
    817:                 options["app_group_name"]            = options["app_root"] if !options["app_group_name"]
    818:                 options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i
    819:                 options["app_spawner_timeout"]       = options["app_spawner_timeout"].to_i
    820:                 if options.has_key?("print_framework_loading_exceptions")
    821:                         options["print_framework_loading_exceptions"] = to_boolean(options["print_framework_loading_exceptions"])
    822:                 end
    823:                 # Force this to be a boolean for easy use with Utils#unmarshal_and_raise_errors.
    824:                 options["print_exceptions"]          = to_boolean(options["print_exceptions"])
    825:
    826:                 options["analytics"]                 = to_boolean(options["analytics"])
    827:                 options["show_version_in_header"]    = to_boolean(options["show_version_in_header"])
    828:
    829:                 # Smart spawning is not supported when using ruby-debug.
    830:                 options["debugger"]     = to_boolean(options["debugger"])
    831:                 options["spawn_method"] = "conservative" if options["debugger"]
    832:
    833:                 return options
    834:         end

split_by_null_into_hash(data)

Split the given string into an hash. Keys and values are obtained by splitting the string using the null character as the delimitor.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 839
    839:                 def split_by_null_into_hash(data)
    840:                         return PhusionPassenger::NativeSupport.split_by_null_into_hash(data)
    841:                 end

split_by_null_into_hash(data)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 846
    846:                 def split_by_null_into_hash(data)
    847:                         data << PADDING
    848:                         array = data.split(NULL)
    849:                         array.pop
    850:                         data.slice!(data.size - 1, data.size - 1)
    851:                         return Hash[*array]
    852:                 end

to_boolean(value)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 803
    803:         def to_boolean(value)
    804:                 return !(value.nil? || value == false || value == "false")
    805:         end

unmarshal_and_raise_errors(channel, print_exception = nil, app_type = "rails")

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 609
    609:         def unmarshal_and_raise_errors(channel, print_exception = nil, app_type = "rails")
    610:                 args = channel.read
    611:                 if args.nil?
    612:                         raise EOFError, "Unexpected end-of-file detected."
    613:                 end
    614:                 status = args[0]
    615:                 if status == 'exception'
    616:                         child_exception = unmarshal_exception(channel.read_scalar)
    617:                         stderr = channel.read_scalar
    618:                         exception = AppInitError.new(
    619:                                 "Application '#{@app_root}' raised an exception: " <<
    620:                                 "#{child_exception.class} (#{child_exception.message})",
    621:                                 child_exception,
    622:                                 app_type,
    623:                                 stderr.empty? ? nil : stderr)
    624:                 elsif status == 'exit'
    625:                         child_exception = unmarshal_exception(channel.read_scalar)
    626:                         stderr = channel.read_scalar
    627:                         exception = AppInitError.new("Application '#{@app_root}' exited during startup",
    628:                                 child_exception, app_type, stderr.empty? ? nil : stderr)
    629:                 else
    630:                         exception = nil
    631:                 end
    632:
    633:                 if print_exception && exception
    634:                         if print_exception.respond_to?(:puts)
    635:                                 print_exception(self.class.to_s, child_exception, print_exception)
    636:                         elsif print_exception.respond_to?(:to_str)
    637:                                 filename = print_exception.to_str
    638:                                 File.open(filename, 'a') do |f|
    639:                                         print_exception(self.class.to_s, child_exception, f)
    640:                                 end
    641:                         else
    642:                                 print_exception(self.class.to_s, child_exception)
    643:                         end
    644:                 end
    645:                 raise exception if exception
    646:         end

unmarshal_exception(data)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 147
    147:         def unmarshal_exception(data)
    148:                 hash = Marshal.load(data)
    149:                 if hash[:is_initialization_error]
    150:                         if hash[:child_exception]
    151:                                 child_exception = unmarshal_exception(hash[:child_exception])
    152:                         else
    153:                                 child_exception = nil
    154:                         end
    155:
    156:                         exception = Marshal.load(hash[:exception])
    157:                         exception.child_exception = child_exception
    158:                         return exception
    159:                 else
    160:                         begin
    161:                                 return Marshal.load(hash[:exception])
    162:                         rescue ArgumentError, TypeError
    163:                                 return UnknownError.new(hash[:message], hash[:class], hash[:backtrace])
    164:                         end
    165:                 end
    166:         end