Utility functions.
# File lib/phusion_passenger/utils/file_system_watcher.rb, line 60 def self.new(filenames, termination_pipe = nil) # Default parameter values, type conversion and exception # handling in C is too much of a pain. filenames = filenames.map do |filename| filename.to_s end return _new(filenames, termination_pipe) end
No-op, hook for unit tests.
# File lib/phusion_passenger/utils.rb, line 647 def self.lower_privilege_called end
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/tmpdir.rb, line 37 def self.passenger_tmpdir(create = true) dir = @@passenger_tmpdir if dir.nil? || dir.empty? tmpdir = "/tmp" ["PASSENGER_TEMP_DIR", "PASSENGER_TMPDIR"].each do |name| if ENV.has_key?(name) && !ENV[name].empty? tmpdir = ENV[name] break end end dir = "#{tmpdir}/passenger.1.0.#{Process.pid}" dir.gsub!(%{//+}, '/') @@passenger_tmpdir = dir end if create && !File.exist?(dir) # This is a very minimal implementation of the subdirectory # creation logic in ServerInstanceDir.h. This implementation # is only meant to make the unit tests pass. For production # systems one should pre-create the temp directory with # ServerInstanceDir.h. system("mkdir", "-p", "-m", "u=rwxs,g=rwx,o=rwx", dir) system("mkdir", "-p", "-m", "u=rwxs,g=rwx,o=rwx", "#{dir}/generation-0") system("mkdir", "-p", "-m", "u=rwxs,g=rwx,o=rwx", "#{dir}/backends") system("mkdir", "-p", "-m", "u=rwxs,g=rwx,o=rwx", "#{dir}/spawn-server") end return dir end
To be called after the request handler main loop is exited. This function will fire off necessary events perform necessary cleanup tasks.
# File lib/phusion_passenger/utils.rb, line 410 def after_handling_requests PhusionPassenger.call_event(:stopping_worker_process) Kernel.passenger_call_at_exit_blocks end
This method is to be called after loading the application code but before forking a worker process.
# File lib/phusion_passenger/utils.rb, line 347 def after_loading_app_code(options) # Even though prepare_app_process() restores the Phusion Passenger # load path after setting up Bundler, the app itself might also # remove Phusion Passenger from the load path for whatever reason, # so here we restore the load path again. if !$LOAD_PATH.include?(LIBDIR) $LOAD_PATH.unshift(LIBDIR) $LOAD_PATH.uniq! end # Post-install framework extensions. Possibly preceded by a call to # PhusionPassenger.install_framework_extensions! require 'rails/version' if defined?(::Rails) && !defined?(::Rails::VERSION) if defined?(::Rails) && ::Rails::VERSION::MAJOR <= 2 require 'phusion_passenger/classic_rails_extensions/init' ClassicRailsExtensions.init!(options) # Rails 3 extensions are installed by # PhusionPassenger.install_framework_extensions! end PhusionPassenger._spawn_options = nil end
Assert that path is a directory. Raises InvalidPath if it isn't.
# File lib/phusion_passenger/utils.rb, line 63 def assert_valid_directory(path) if !File.directory?(path) raise InvalidPath, "'#{path}' is not a valid directory." end end
Assert that path is a file. Raises InvalidPath if it isn't.
# File lib/phusion_passenger/utils.rb, line 70 def assert_valid_file(path) if !File.file?(path) raise InvalidPath, "'#{path}' is not a valid file." end end
Assert that groupname is a valid group name. Raises ArgumentError if that is not the case.
# File lib/phusion_passenger/utils.rb, line 85 def assert_valid_groupname(groupname) # If groupname does not exist then getgrnam() will raise an ArgumentError. groupname && Etc.getgrnam(groupname) end
Assert that username is a valid username. Raises ArgumentError if that is not the case.
# File lib/phusion_passenger/utils.rb, line 78 def assert_valid_username(username) # If username does not exist then getpwnam() will raise an ArgumentError. username && Etc.getpwnam(username) end
# File lib/phusion_passenger/utils.rb, line 266 def at_exit(&block) return Kernel.passenger_at_exit(&block) end
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.
# File lib/phusion_passenger/utils.rb, line 377 def before_handling_requests(forked, options) if forked && options["analytics_logger"] options["analytics_logger"].clear_connection end # If we were forked from a preloader process then clear or # re-establish ActiveRecord database connections. This prevents # child processes from concurrently accessing the same # database connection handles. if forked && defined?(::ActiveRecord::Base) if ::ActiveRecord::Base.respond_to?(:clear_all_connections!) ::ActiveRecord::Base.clear_all_connections! elsif ::ActiveRecord::Base.respond_to?(:clear_active_connections!) ::ActiveRecord::Base.clear_active_connections! elsif ::ActiveRecord::Base.respond_to?(:connected?) && ::ActiveRecord::Base.connected? ::ActiveRecord::Base.establish_connection end end # Fire off events. PhusionPassenger.call_event(:starting_worker_process, forked) if options["pool_account_username"] && options["pool_account_password_base64"] password = options["pool_account_password_base64"].unpack('m').first PhusionPassenger.call_event(:credentials, options["pool_account_username"], password) else PhusionPassenger.call_event(:credentials, nil, nil) end 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 55 def canonicalize_path(path) raise ArgumentError, "The 'path' argument may not be nil" if path.nil? return Pathname.new(path).realpath.to_s rescue Errno::ENOENT => e raise InvalidPath, e.message end
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.
# File lib/phusion_passenger/utils.rb, line 747 def check_directory_tree_permissions(dir) components = dir.split("/") components.shift i = 0 # We can't use File.readable() and friends here because they # don't always work right with ACLs. Instead of we use 'real' # checks. while i < components.size path = "/" + components[0..i].join("/") begin File.stat(path) rescue Errno::EACCES return [File.dirname(path), true] end i += 1 end begin Dir.chdir(dir) do return nil end rescue Errno::EACCES return [dir, false] end end
# File lib/phusion_passenger/utils.rb, line 108 def close_all_io_objects_for_fds(file_descriptors_to_leave_open) ObjectSpace.each_object(IO) do |io| begin if !file_descriptors_to_leave_open.include?(io.fileno) && !io.closed? io.close end rescue end end end
# File lib/phusion_passenger/utils.rb, line 425 def connect_to_server(address) case get_socket_address_type(address) when :unix return UNIXSocket.new(address.sub(/^unix:/, '')) when :tcp host, port = address.sub(%{^tcp://}, '').split(':', 2) port = port.to_i return TCPSocket.new(host, port) else raise ArgumentError, "Unknown socket address type for '#{address}'." end end
Generate a long, cryptographically secure random ID string, which is also a valid filename.
# File lib/phusion_passenger/utils.rb, line 92 def generate_random_id(method) case method when :base64 data = [File.read("/dev/urandom", 64)].pack('m') data.gsub!("\n", '') data.gsub!("+", '') data.gsub!("/", '') data.gsub!(/==$/, '') return data when :hex return File.read("/dev/urandom", 64).unpack('H*')[0] else raise ArgumentError, "Invalid method #{method.inspect}" end end
# File lib/phusion_passenger/utils.rb, line 415 def get_socket_address_type(address) if address =~ %{^unix:.} return :unix elsif address =~ %{^tcp://.} return :tcp else return :unknown end end
Returns a string which reports the backtraces for all threads, or if that's not supported the backtrace for the current thread.
# File lib/phusion_passenger/utils.rb, line 774 def global_backtrace_report if Kernel.respond_to?(:caller_for_all_threads) output = "========== Process #{Process.pid}: backtrace dump ==========\n" caller_for_all_threads.each_pair do |thread, stack| output << ("-" * 60) << "\n" output << "# Thread: #{thread.inspect}, " if thread == Thread.main output << "[main thread], " end if thread == Thread.current output << "[current thread], " end output << "alive = #{thread.alive?}\n" output << ("-" * 60) << "\n" output << " " << stack.join("\n ") output << "\n\n" end else output = "========== Process #{Process.pid}: backtrace dump ==========\n" output << ("-" * 60) << "\n" output << "# Current thread: #{Thread.current.inspect}\n" output << ("-" * 60) << "\n" output << " " << caller.join("\n ") end return output end
# File lib/phusion_passenger/utils.rb, line 438 def local_socket_address?(address) case get_socket_address_type(address) when :unix return true when :tcp host, port = address.sub(%{^tcp://}, '').split(':', 2) return host == "127.0.0.1" || host == "::1" || host == "localhost" else raise ArgumentError, "Unknown socket address type for '#{address}'." end end
Lowers the current process's privilege based on the documented rules for the "user", "group", "default_user" and "default_group" options.
# File lib/phusion_passenger/utils.rb, line 652 def lower_privilege(startup_file, options) Utils.lower_privilege_called return if Process.euid != 0 if options["default_user"] && !options["default_user"].empty? default_user = options["default_user"] else default_user = "nobody" end if options["default_group"] && !options["default_group"].empty? default_group = options["default_group"] else default_group = Etc.getgrgid(Etc.getpwnam(default_user).gid).name end if options["user"] && !options["user"].empty? begin user_info = Etc.getpwnam(options["user"]) rescue ArgumentError user_info = nil end else uid = File.lstat(startup_file).uid begin user_info = Etc.getpwuid(uid) rescue ArgumentError user_info = nil end end if !user_info || user_info.uid == 0 begin user_info = Etc.getpwnam(default_user) rescue ArgumentError user_info = nil end end if options["group"] && !options["group"].empty? if options["group"] == "!STARTUP_FILE!" gid = File.lstat(startup_file).gid begin group_info = Etc.getgrgid(gid) rescue ArgumentError group_info = nil end else begin group_info = Etc.getgrnam(options["group"]) rescue ArgumentError group_info = nil end end elsif user_info begin group_info = Etc.getgrgid(user_info.gid) rescue ArgumentError group_info = nil end else group_info = nil end if !group_info || group_info.gid == 0 begin group_info = Etc.getgrnam(default_group) rescue ArgumentError group_info = nil end end if !user_info raise SecurityError, "Cannot determine a user to lower privilege to" end if !group_info raise SecurityError, "Cannot determine a group to lower privilege to" end NativeSupport.switch_user(user_info.name, user_info.uid, group_info.gid) ENV['USER'] = user_info.name ENV['HOME'] = user_info.dir end
# File lib/phusion_passenger/utils.rb, line 119 def marshal_exception(exception) data = { :message => exception.message, :class => exception.class.to_s, :backtrace => exception.backtrace } if exception.is_a?(InitializationError) data[:is_initialization_error] = true if exception.child_exception data[:child_exception] = marshal_exception(exception.child_exception) child_exception = exception.child_exception exception.child_exception = nil data[:exception] = Marshal.dump(exception) exception.child_exception = child_exception end else begin data[:exception] = Marshal.dump(exception) rescue ArgumentError, TypeError e = UnknownError.new(exception.message, exception.class.to_s, exception.backtrace) data[:exception] = Marshal.dump(e) end end return Marshal.dump(data) end
# File lib/phusion_passenger/utils/tmpdir.rb, line 30 def passenger_tmpdir(create = true) PhusionPassenger::Utils.passenger_tmpdir(create) end
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.
# File lib/phusion_passenger/utils.rb, line 193 def prepare_app_process(startup_file, options) options["app_root"] = canonicalize_path(options["app_root"]) Dir.chdir(options["app_root"]) lower_privilege(startup_file, options) path, is_parent = check_directory_tree_permissions(options["app_root"]) if path username = Etc.getpwuid(Process.euid).name groupname = Etc.getgrgid(Process.egid).name message = "This application process is currently running as " + "user '#{username}' and group '#{groupname}' and must be " + "able to access its application root directory " + "'#{options["app_root"]}'. " if is_parent message << "However the parent directory '#{path}' " + "has wrong permissions, thereby preventing " + "this process from accessing its application " + "root directory. Please fix the permissions " + "of the directory '#{path}' first." else message << "However this directory is not accessible " + "because it has wrong permissions. Please fix " + "these permissions first." end raise(message) end ENV["RAILS_ENV"] = ENV["RACK_ENV"] = options["environment"] base_uri = options["base_uri"] if base_uri && !base_uri.empty? && base_uri != "/" ENV["RAILS_RELATIVE_URL_ROOT"] = base_uri ENV["RACK_BASE_URI"] = base_uri end encoded_environment_variables = options["environment_variables"] if encoded_environment_variables env_vars_string = encoded_environment_variables.unpack("m").first env_vars_array = env_vars_string.split("\00"", -1) env_vars_array.pop env_vars = Hash[*env_vars_array] env_vars.each_pair do |key, value| ENV[key] = value end end # Instantiate the analytics logger if requested. Can be nil. require 'phusion_passenger/analytics_logger' options["analytics_logger"] = AnalyticsLogger.new_from_options(options) # Make sure RubyGems uses any new environment variable values # that have been set now (e.g. $HOME, $GEM_HOME, etc) and that # it is able to detect newly installed gems. Gem.clear_paths # Because spawned app processes exit using #exit!, #at_exit # blocks aren't called. Here we ninja patch Kernel so that # we can call #at_exit blocks during app process shutdown. class << Kernel def passenger_call_at_exit_blocks @passenger_at_exit_blocks ||= [] @passenger_at_exit_blocks.reverse_each do |block| block.call end end def passenger_at_exit(&block) @passenger_at_exit_blocks ||= [] @passenger_at_exit_blocks << block return block end end Kernel.class_eval do def at_exit(&block) return Kernel.passenger_at_exit(&block) end end # Rack::ApplicationSpawner depends on the 'rack' library, but the app # might want us to use a bundled version instead of a # gem/apt-get/yum/whatever-installed version. Therefore we must setup # the correct load paths before requiring 'rack'. # # The most popular tool for bundling dependencies is Bundler. Bundler # works as follows: # - If the bundle is locked then a file .bundle/environment.rb exists # which will setup the load paths. # - If the bundle is not locked then the load paths must be set up by # calling Bundler.setup. # - Rails 3's boot.rb automatically loads .bundle/environment.rb or # calls Bundler.setup if that's not available. # - Other Rack apps might not have a boot.rb but we still want to setup # Bundler. # - Some Rails 2 apps might have explicitly added Bundler support. # These apps call Bundler.setup in their preinitializer.rb. # # So the strategy is as follows: # Our strategy might be completely unsuitable for the app or the # developer is using something other than Bundler, so we let the user # manually specify a load path setup file. if options["load_path_setup_file"] require File.expand_path(options["load_path_setup_file"]) # The app developer may also override our strategy with this magic file. elsif File.exist?('config/setup_load_paths.rb') require File.expand_path('config/setup_load_paths') # If the Bundler lock environment file exists then load that. If it # exists then there's a 99.9% chance that loading it is the correct # thing to do. elsif File.exist?('.bundle/environment.rb') require File.expand_path('.bundle/environment') # If the Bundler environment file doesn't exist then there are two # possibilities: # 1. Bundler is not used, in which case we don't have to do anything. # 2. Bundler *is* used, but the gems are not locked and we're supposed # to call Bundler.setup. # # The existence of Gemfile indicates whether (2) is true: elsif File.exist?('Gemfile') # In case of Rails 3, config/boot.rb already calls Bundler.setup. # However older versions of Rails may not so loading boot.rb might # not be the correct thing to do. To be on the safe side we # call Bundler.setup ourselves; calling Bundler.setup twice is # harmless. If this isn't the correct thing to do after all then # there's always the load_path_setup_file option and # setup_load_paths.rb. require 'rubygems' require 'bundler' Bundler.setup end # Bundler might remove Phusion Passenger from the load path in its zealous # attempt to un-require RubyGems, so here we put Phusion Passenger back # into the load path. This must be done before loading the app's startup # file because the app might require() Phusion Passenger files. if !$LOAD_PATH.include?(LIBDIR) $LOAD_PATH.unshift(LIBDIR) $LOAD_PATH.uniq! end # !!! NOTE !!! # If the app is using Bundler then any dependencies required past this # point must be specified in the Gemfile. Like ruby-debug if debugging is on... PhusionPassenger._spawn_options = options 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 171 def print_exception(current_location, exception, destination = nil) if !exception.is_a?(SystemExit) data = exception.backtrace_string(current_location) if defined?(DebugLogging) && self.is_a?(DebugLogging) error(data) else destination ||= STDERR destination.puts(data) destination.flush if destination.respond_to?(:flush) end end end
# File lib/phusion_passenger/utils.rb, line 43 def private_class_method(name) metaclass = class << self; self; end metaclass.send(:private, name) 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 550 def report_app_init_status(channel, sink = STDERR) begin old_global_stderr = $stderr old_stderr = STDERR stderr_output = "" pseudo_stderr = PseudoIO.new(sink) Object.send(:remove_const, 'STDERR') rescue nil Object.const_set('STDERR', pseudo_stderr) $stderr = pseudo_stderr begin yield ensure Object.send(:remove_const, 'STDERR') rescue nil Object.const_set('STDERR', old_stderr) $stderr = old_global_stderr stderr_output = pseudo_stderr.done! end channel.write('success') return true rescue StandardError, ScriptError, NoMemoryError => e channel.write('exception') channel.write_scalar(marshal_exception(e)) channel.write_scalar(stderr_output) return false rescue SystemExit => e channel.write('exit') channel.write_scalar(marshal_exception(e)) channel.write_scalar(stderr_output) raise end 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 460 def safe_fork(current_location = self.class, double_fork = false) pid = fork if pid.nil? has_exception = false begin if double_fork pid2 = fork if pid2.nil? srand yield end else srand yield end rescue Exception => e has_exception = true print_exception(current_location.to_s, e) ensure exit!(has_exception ? 1 : 0) end else if double_fork Process.waitpid(pid) rescue nil return pid else return pid end end end
# File lib/phusion_passenger/utils.rb, line 805 def sanitize_spawn_options(options) defaults = { "app_type" => "rails", "environment" => "production", "spawn_method" => "smart-lv2", "framework_spawner_timeout" => -1, "app_spawner_timeout" => -1, "print_exceptions" => true } options = defaults.merge(options) options["app_group_name"] = options["app_root"] if !options["app_group_name"] options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i options["app_spawner_timeout"] = options["app_spawner_timeout"].to_i if options.has_key?("print_framework_loading_exceptions") options["print_framework_loading_exceptions"] = to_boolean(options["print_framework_loading_exceptions"]) end # Force this to be a boolean for easy use with Utils#unmarshal_and_raise_errors. options["print_exceptions"] = to_boolean(options["print_exceptions"]) options["analytics"] = to_boolean(options["analytics"]) options["show_version_in_header"] = to_boolean(options["show_version_in_header"]) # Smart spawning is not supported when using ruby-debug. options["debugger"] = to_boolean(options["debugger"]) options["spawn_method"] = "conservative" if options["debugger"] return options end
Split the given string into an hash. Keys and values are obtained by splitting the string using the null character as the delimitor.
# File lib/phusion_passenger/utils.rb, line 837 def split_by_null_into_hash(data) return PhusionPassenger::NativeSupport.split_by_null_into_hash(data) end
# File lib/phusion_passenger/utils.rb, line 801 def to_boolean(value) return !(value.nil? || value == false || value == "false") 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 607 def unmarshal_and_raise_errors(channel, print_exception = nil, app_type = "rails") args = channel.read if args.nil? raise EOFError, "Unexpected end-of-file detected." end status = args[0] if status == 'exception' child_exception = unmarshal_exception(channel.read_scalar) stderr = channel.read_scalar exception = AppInitError.new( "Application '#{@app_root}' raised an exception: " << "#{child_exception.class} (#{child_exception.message})", child_exception, app_type, stderr.empty? ? nil : stderr) elsif status == 'exit' child_exception = unmarshal_exception(channel.read_scalar) stderr = channel.read_scalar exception = AppInitError.new("Application '#{@app_root}' exited during startup", child_exception, app_type, stderr.empty? ? nil : stderr) else exception = nil end if print_exception && exception if print_exception.respond_to?(:puts) print_exception(self.class.to_s, child_exception, print_exception) elsif print_exception.respond_to?(:to_str) filename = print_exception.to_str File.open(filename, 'a') do |f| print_exception(self.class.to_s, child_exception, f) end else print_exception(self.class.to_s, child_exception) end end raise exception if exception end
# File lib/phusion_passenger/utils.rb, line 146 def unmarshal_exception(data) hash = Marshal.load(data) if hash[:is_initialization_error] if hash[:child_exception] child_exception = unmarshal_exception(hash[:child_exception]) else child_exception = nil end exception = Marshal.load(hash[:exception]) exception.child_exception = child_exception return exception else begin return Marshal.load(hash[:exception]) rescue ArgumentError, TypeError return UnknownError.new(hash[:message], hash[:class], hash[:backtrace]) end end end
Generated with the Darkfish Rdoc Generator 2.