Module | PhusionPassenger::Utils
In: | lib/phusion_passenger/utils/rewindable_input.rb lib/phusion_passenger/utils.rb

Utility functions.

### Methods

assert_valid_app_root assert_valid_directory assert_valid_file assert_valid_groupname assert_valid_username canonicalize_path close_all_io_objects_for_fds lower_privilege marshal_exception passenger_tmpdir passenger_tmpdir passenger_tmpdir= print_exception report_app_init_status safe_fork sanitize_spawn_options setup_bundler_support switch_to_user to_boolean unmarshal_and_raise_errors unmarshal_exception

### Classes and Modules

Class PhusionPassenger::Utils::PseudoIO
Class PhusionPassenger::Utils::RewindableInput

### Protected Class methods

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.

[Source]

         # 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

passenger_tmpdir=(dir)

[Source]

         # File lib/phusion_passenger/utils.rb, line 499
    499:         def self.passenger_tmpdir=(dir)
    500:                 @@passenger_tmpdir = dir
    501:         end

### Protected Instance methods

assert_valid_app_root(app_root)

Assert that `app_root` is a valid Ruby on Rails application root. Raises InvalidPath if that is not the case.

[Source]

        # 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_valid_directory(path)

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

[Source]

        # 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_valid_file(path)

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

[Source]

        # 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_valid_groupname(groupname)

Assert that `groupname` is a valid group name. Raises ArgumentError if that is not the case.

[Source]

        # 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_valid_username(username)

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

[Source]

        # 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

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.

[Source]

        # 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

close_all_io_objects_for_fds(file_descriptors_to_leave_open)

[Source]

         # 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_privilege(filename, lowest_user = "nobody")

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.

[Source]

         # 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

marshal_exception(exception)

[Source]

         # 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

passenger_tmpdir(create = true)

[Source]

         # File lib/phusion_passenger/utils.rb, line 474
    474:         def passenger_tmpdir(create = true)
    475:                 PhusionPassenger::Utils.passenger_tmpdir(create)
    476:         end

print_exception(current_location, exception, destination = STDERR)

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.

[Source]

         # 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

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.

[Source]

         # 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

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.

[Source]

         # 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

sanitize_spawn_options(options)

[Source]

         # 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

setup_bundler_support(options = {})

[Source]

         # 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

switch_to_user(user)

[Source]

         # 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

to_boolean(value)

[Source]

         # File lib/phusion_passenger/utils.rb, line 448
    448:         def to_boolean(value)
    449:                 return !(value.nil? || value == false || value == "false")
    450:         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.

[Source]

         # 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

unmarshal_exception(data)

[Source]

         # 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

[Validate]