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

Utility functions.

Methods

- assert_valid_app_root
- assert_valid_directory
- assert_valid_file
- assert_valid_groupname
- assert_valid_username
- close_all_io_objects_for_fds
- lower_privilege
- marshal_exception
- normalize_path
- passenger_tmpdir
- print_exception
- report_app_init_status
- safe_fork
- sanitize_spawn_options
- switch_to_user
- unmarshal_and_raise_errors
- unmarshal_exception

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.

[ show source ]

        # File lib/phusion_passenger/utils.rb, line 54
    54:         def assert_valid_app_root(app_root)
    55:                 assert_valid_directory(app_root)
    56:                 assert_valid_file("#{app_root}/config/environment.rb")
    57:         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 60
    60:         def assert_valid_directory(path)
    61:                 if !File.directory?(path)
    62:                         raise InvalidPath, "'#{path}' is not a valid directory."
    63:                 end
    64:         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 67
    67:         def assert_valid_file(path)
    68:                 if !File.file?(path)
    69:                         raise InvalidPath, "'#{path}' is not a valid file."
    70:                 end
    71:         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 82
    82:         def assert_valid_groupname(groupname)
    83:                 # If groupname does not exist then getgrnam() will raise an ArgumentError.
    84:                 groupname && Etc.getgrnam(groupname)
    85:         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 75
    75:         def assert_valid_username(username)
    76:                 # If username does not exist then getpwnam() will raise an ArgumentError.
    77:                 username && Etc.getpwnam(username)
    78:         end

close_all_io_objects_for_fds(file_descriptors_to_leave_open)

[ show source ]

        # File lib/phusion_passenger/utils.rb, line 87
    87:         def close_all_io_objects_for_fds(file_descriptors_to_leave_open)
    88:                 ObjectSpace.each_object(IO) do |io|
    89:                         begin
    90:                                 if !file_descriptors_to_leave_open.include?(io.fileno) && !io.closed?
    91:                                         io.close
    92:                                 end
    93:                         rescue
    94:                         end
    95:                 end
    96:         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.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 274
    274:         def lower_privilege(filename, lowest_user = "nobody")
    275:                 stat = File.lstat(filename)
    276:                 begin
    277:                         if !switch_to_user(stat.uid)
    278:                                 switch_to_user(lowest_user)
    279:                         end
    280:                 rescue Errno::EPERM
    281:                         # No problem if we were unable to switch user.
    282:                 end
    283:         end

marshal_exception(exception)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 98
     98:         def marshal_exception(exception)
     99:                 data = {
    100:                         :message => exception.message,
    101:                         :class => exception.class.to_s,
    102:                         :backtrace => exception.backtrace
    103:                 }
    104:                 if exception.is_a?(InitializationError)
    105:                         data[:is_initialization_error] = true
    106:                         if exception.child_exception
    107:                                 data[:child_exception] = marshal_exception(exception.child_exception)
    108:                         end
    109:                 else
    110:                         begin
    111:                                 data[:exception] = Marshal.dump(exception)
    112:                         rescue ArgumentError, TypeError
    113:                                 e = UnknownError.new(exception.message, exception.class.to_s,
    114:                                                         exception.backtrace)
    115:                                 data[:exception] = Marshal.dump(e)
    116:                         end
    117:                 end
    118:                 return Marshal.dump(data)
    119:         end

normalize_path(path)

Return the absolute version of `path`. This path is guaranteed to to be "normal", i.e. it doesn‘t contain stuff like ".." or "/", and it correctly respects 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 45
    45:         def normalize_path(path)
    46:                 raise ArgumentError, "The 'path' argument may not be nil" if path.nil?
    47:                 return Pathname.new(path).realpath.to_s
    48:         rescue Errno::ENOENT => e
    49:                 raise InvalidAPath, e.message
    50:         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.rb, line 338
    338:         def passenger_tmpdir(create = true)
    339:                 dir = ENV['PASSENGER_INSTANCE_TEMP_DIR']
    340:                 if dir.nil? || dir.empty?
    341:                         dir = "#{Dir.tmpdir}/passenger.#{Process.pid}"
    342:                         ENV['PASSENGER_INSTANCE_TEMP_DIR'] = dir
    343:                 end
    344:                 if create && !File.exist?(dir)
    345:                         # This is a very minimal implementation of the function
    346:                         # passengerCreateTempDir() in Utils.cpp. This implementation
    347:                         # is only meant to make the unit tests pass. For production
    348:                         # systems one should pre-create the temp directory with
    349:                         # passengerCreateTempDir().
    350:                         system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", dir)
    351:                         system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", "#{dir}/backends")
    352:                 end
    353:                 return dir
    354:         end

print_exception(current_location, exception)

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 152
    152:         def print_exception(current_location, exception)
    153:                 if !exception.is_a?(SystemExit)
    154:                         STDERR.puts(exception.backtrace_string(current_location))
    155:                         STDERR.flush
    156:                 end
    157:         end

report_app_init_status(channel) {|| ...}

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. Returns whether the block succeeded. Exceptions are not propagated, except for SystemExit.

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 201
    201:         def report_app_init_status(channel)
    202:                 begin
    203:                         old_global_stderr = $stderr
    204:                         old_stderr = STDERR
    205:                         stderr_output = ""
    206:                         tempfile = Tempfile.new('passenger-stderr')
    207:                         tempfile.unlink
    208:                         Object.send(:remove_const, 'STDERR') rescue nil
    209:                         Object.const_set('STDERR', tempfile)
    210:                         begin
    211:                                 yield
    212:                         ensure
    213:                                 Object.send(:remove_const, 'STDERR') rescue nil
    214:                                 Object.const_set('STDERR', old_stderr)
    215:                                 $stderr = old_global_stderr
    216:                                 if tempfile
    217:                                         tempfile.rewind
    218:                                         stderr_output = tempfile.read
    219:                                         tempfile.close rescue nil
    220:                                 end
    221:                         end
    222:                         channel.write('success')
    223:                         return true
    224:                 rescue StandardError, ScriptError, NoMemoryError => e
    225:                         if ENV['TESTING_PASSENGER'] == '1'
    226:                                 print_exception(self.class.to_s, e)
    227:                         end
    228:                         channel.write('exception')
    229:                         channel.write_scalar(marshal_exception(e))
    230:                         channel.write_scalar(stderr_output)
    231:                         return false
    232:                 rescue SystemExit => e
    233:                         channel.write('exit')
    234:                         channel.write_scalar(marshal_exception(e))
    235:                         channel.write_scalar(stderr_output)
    236:                         raise
    237:                 end
    238:         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 169
    169:         def safe_fork(current_location = self.class, double_fork = false)
    170:                 pid = fork
    171:                 if pid.nil?
    172:                         begin
    173:                                 if double_fork
    174:                                         pid2 = fork
    175:                                         if pid2.nil?
    176:                                                 yield
    177:                                         end
    178:                                 else
    179:                                         yield
    180:                                 end
    181:                         rescue Exception => e
    182:                                 print_exception(current_location.to_s, e)
    183:                         ensure
    184:                                 exit!
    185:                         end
    186:                 else
    187:                         if double_fork
    188:                                 Process.waitpid(pid) rescue nil
    189:                                 return pid
    190:                         else
    191:                                 return pid
    192:                         end
    193:                 end
    194:         end

sanitize_spawn_options(options)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 318
    318:         def sanitize_spawn_options(options)
    319:                 defaults = {
    320:                         "lower_privilege" => true,
    321:                         "lowest_user"     => "nobody",
    322:                         "environment"     => "production",
    323:                         "app_type"        => "rails",
    324:                         "spawn_method"    => "smart-lv2",
    325:                         "framework_spawner_timeout" => -1,
    326:                         "app_spawner_timeout"       => -1
    327:                 }
    328:                 options = defaults.merge(options)
    329:                 options["lower_privilege"]           = options["lower_privilege"].to_s == "true"
    330:                 options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i
    331:                 options["app_spawner_timeout"]       = options["app_spawner_timeout"].to_i
    332:                 return options
    333:         end

switch_to_user(user)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 285
    285:         def switch_to_user(user)
    286:                 begin
    287:                         if user.is_a?(String)
    288:                                 pw = Etc.getpwnam(user)
    289:                                 username = user
    290:                                 uid = pw.uid
    291:                                 gid = pw.gid
    292:                         else
    293:                                 pw = Etc.getpwuid(user)
    294:                                 username = pw.name
    295:                                 uid = user
    296:                                 gid = pw.gid
    297:                         end
    298:                 rescue
    299:                         return false
    300:                 end
    301:                 if uid == 0
    302:                         return false
    303:                 else
    304:                         # Some systems are broken. initgroups can fail because of
    305:                         # all kinds of stupid reasons. So we ignore any errors
    306:                         # raised by initgroups.
    307:                         begin
    308:                                 Process.groups = Process.initgroups(username, gid)
    309:                         rescue
    310:                         end
    311:                         Process::Sys.setgid(gid)
    312:                         Process::Sys.setuid(uid)
    313:                         ENV['HOME'] = pw.dir
    314:                         return true
    315:                 end
    316:         end

unmarshal_and_raise_errors(channel, 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.

Raises:

- AppInitError
- IOError, SystemCallError, SocketError

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 248
    248:         def unmarshal_and_raise_errors(channel, app_type = "rails")
    249:                 args = channel.read
    250:                 if args.nil?
    251:                         raise EOFError, "Unexpected end-of-file detected."
    252:                 end
    253:                 status = args[0]
    254:                 if status == 'exception'
    255:                         child_exception = unmarshal_exception(channel.read_scalar)
    256:                         stderr = channel.read_scalar
    257:                         #print_exception(self.class.to_s, child_exception)
    258:                         raise AppInitError.new(
    259:                                 "Application '#{@app_root}' raised an exception: " <<
    260:                                 "#{child_exception.class} (#{child_exception.message})",
    261:                                 child_exception,
    262:                                 app_type,
    263:                                 stderr.empty? ? nil : stderr)
    264:                 elsif status == 'exit'
    265:                         child_exception = unmarshal_exception(channel.read_scalar)
    266:                         stderr = channel.read_scalar
    267:                         raise AppInitError.new("Application '#{@app_root}' exited during startup",
    268:                                 child_exception, app_type, stderr.empty? ? nil : stderr)
    269:                 end
    270:         end

unmarshal_exception(data)

[ show source ]

         # File lib/phusion_passenger/utils.rb, line 121
    121:         def unmarshal_exception(data)
    122:                 hash = Marshal.load(data)
    123:                 if hash[:is_initialization_error]
    124:                         if hash[:child_exception]
    125:                                 child_exception = unmarshal_exception(hash[:child_exception])
    126:                         else
    127:                                 child_exception = nil
    128:                         end
    129:
    130:                         case hash[:class]
    131:                         when AppInitError.to_s
    132:                                 exception_class = AppInitError
    133:                         when FrameworkInitError.to_s
    134:                                 exception_class = FrameworkInitError
    135:                         else
    136:                                 exception_class = InitializationError
    137:                         end
    138:                         return exception_class.new(hash[:message], child_exception)
    139:                 else
    140:                         begin
    141:                                 return Marshal.load(hash[:exception])
    142:                         rescue ArgumentError, TypeError
    143:                                 return UnknownError.new(hash[:message], hash[:class], hash[:backtrace])
    144:                         end
    145:                 end
    146:         end