Utility functions.

Methods
Classes and Modules
Class PhusionPassenger::Utils::PseudoIO
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.

     # File lib/phusion_passenger/utils.rb, line 420
420:         def self.passenger_tmpdir(create = true)
421:                 dir = @@passenger_tmpdir
422:                 if dir.nil? || dir.empty?
423:                         dir = "#{Dir.tmpdir}/passenger.#{Process.pid}"
424:                         @@passenger_tmpdir = dir
425:                 end
426:                 if create && !File.exist?(dir)
427:                         # This is a very minimal implementation of the function
428:                         # passengerCreateTempDir() in Utils.cpp. This implementation
429:                         # is only meant to make the unit tests pass. For production
430:                         # systems one should pre-create the temp directory with
431:                         # passengerCreateTempDir().
432:                         system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", dir)
433:                         system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", "#{dir}/backends")
434:                 end
435:                 return dir
436:         end
passenger_tmpdir=(dir)
     # File lib/phusion_passenger/utils.rb, line 438
438:         def self.passenger_tmpdir=(dir)
439:                 @@passenger_tmpdir = dir
440:         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.

    # File lib/phusion_passenger/utils.rb, line 60
60:         def assert_valid_app_root(app_root)
61:                 assert_valid_directory(app_root)
62:                 assert_valid_file("#{app_root}/config/environment.rb")
63:         end
assert_valid_directory(path)

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

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

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

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

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

    # File lib/phusion_passenger/utils.rb, line 88
88:         def assert_valid_groupname(groupname)
89:                 # If groupname does not exist then getgrnam() will raise an ArgumentError.
90:                 groupname && Etc.getgrnam(groupname)
91:         end
assert_valid_username(username)

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

    # File lib/phusion_passenger/utils.rb, line 81
81:         def assert_valid_username(username)
82:                 # If username does not exist then getpwnam() will raise an ArgumentError.
83:                 username && Etc.getpwnam(username)
84:         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.

    # File lib/phusion_passenger/utils.rb, line 51
51:         def canonicalize_path(path)
52:                 raise ArgumentError, "The 'path' argument may not be nil" if path.nil?
53:                 return Pathname.new(path).realpath.to_s
54:         rescue Errno::ENOENT => e
55:                 raise InvalidAPath, e.message
56:         end
close_all_io_objects_for_fds(file_descriptors_to_leave_open)
     # File lib/phusion_passenger/utils.rb, line 93
 93:         def close_all_io_objects_for_fds(file_descriptors_to_leave_open)
 94:                 ObjectSpace.each_object(IO) do |io|
 95:                         begin
 96:                                 if !file_descriptors_to_leave_open.include?(io.fileno) && !io.closed?
 97:                                         io.close
 98:                                 end
 99:                         rescue
100:                         end
101:                 end
102:         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.

     # File lib/phusion_passenger/utils.rb, line 343
343:         def lower_privilege(filename, lowest_user = "nobody")
344:                 stat = File.lstat(filename)
345:                 begin
346:                         if !switch_to_user(stat.uid)
347:                                 switch_to_user(lowest_user)
348:                         end
349:                 rescue Errno::EPERM
350:                         # No problem if we were unable to switch user.
351:                 end
352:         end
marshal_exception(exception)
     # File lib/phusion_passenger/utils.rb, line 104
104:         def marshal_exception(exception)
105:                 data = {
106:                         :message => exception.message,
107:                         :class => exception.class.to_s,
108:                         :backtrace => exception.backtrace
109:                 }
110:                 if exception.is_a?(InitializationError)
111:                         data[:is_initialization_error] = true
112:                         if exception.child_exception
113:                                 data[:child_exception] = marshal_exception(exception.child_exception)
114:                         end
115:                 else
116:                         begin
117:                                 data[:exception] = Marshal.dump(exception)
118:                         rescue ArgumentError, TypeError
119:                                 e = UnknownError.new(exception.message, exception.class.to_s,
120:                                                         exception.backtrace)
121:                                 data[:exception] = Marshal.dump(e)
122:                         end
123:                 end
124:                 return Marshal.dump(data)
125:         end
passenger_tmpdir(create = true)
     # File lib/phusion_passenger/utils.rb, line 413
413:         def passenger_tmpdir(create = true)
414:                 PhusionPassenger::Utils.passenger_tmpdir(create)
415:         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.

     # File lib/phusion_passenger/utils.rb, line 158
158:         def print_exception(current_location, exception, destination = STDERR)
159:                 if !exception.is_a?(SystemExit)
160:                         destination.puts(exception.backtrace_string(current_location))
161:                         destination.flush if destination.respond_to?(:flush)
162:                 end
163:         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.

     # File lib/phusion_passenger/utils.rb, line 242
242:         def report_app_init_status(channel, sink = STDERR)
243:                 begin
244:                         old_global_stderr = $stderr
245:                         old_stderr = STDERR
246:                         stderr_output = ""
247:                         
248:                         pseudo_stderr = PseudoIO.new(sink)
249:                         Object.send(:remove_const, 'STDERR') rescue nil
250:                         Object.const_set('STDERR', pseudo_stderr)
251:                         $stderr = pseudo_stderr
252:                         
253:                         begin
254:                                 yield
255:                         ensure
256:                                 Object.send(:remove_const, 'STDERR') rescue nil
257:                                 Object.const_set('STDERR', old_stderr)
258:                                 $stderr = old_global_stderr
259:                                 stderr_output = pseudo_stderr.done!
260:                         end
261:                         
262:                         channel.write('success')
263:                         return true
264:                 rescue StandardError, ScriptError, NoMemoryError => e
265:                         if ENV['TESTING_PASSENGER'] == '1'
266:                                 print_exception(self.class.to_s, e)
267:                         end
268:                         channel.write('exception')
269:                         channel.write_scalar(marshal_exception(e))
270:                         channel.write_scalar(stderr_output)
271:                         return false
272:                 rescue SystemExit => e
273:                         channel.write('exit')
274:                         channel.write_scalar(marshal_exception(e))
275:                         channel.write_scalar(stderr_output)
276:                         raise
277:                 end
278:         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.

     # File lib/phusion_passenger/utils.rb, line 175
175:         def safe_fork(current_location = self.class, double_fork = false)
176:                 pid = fork
177:                 if pid.nil?
178:                         begin
179:                                 if double_fork
180:                                         pid2 = fork
181:                                         if pid2.nil?
182:                                                 srand
183:                                                 yield
184:                                         end
185:                                 else
186:                                         srand
187:                                         yield
188:                                 end
189:                         rescue Exception => e
190:                                 print_exception(current_location.to_s, e)
191:                         ensure
192:                                 exit!
193:                         end
194:                 else
195:                         if double_fork
196:                                 Process.waitpid(pid) rescue nil
197:                                 return pid
198:                         else
199:                                 return pid
200:                         end
201:                 end
202:         end
sanitize_spawn_options(options)
     # File lib/phusion_passenger/utils.rb, line 391
391:         def sanitize_spawn_options(options)
392:                 defaults = {
393:                         "lower_privilege" => true,
394:                         "lowest_user"     => "nobody",
395:                         "environment"     => "production",
396:                         "app_type"        => "rails",
397:                         "spawn_method"    => "smart-lv2",
398:                         "framework_spawner_timeout" => -1,
399:                         "app_spawner_timeout"       => -1,
400:                         "print_exceptions" => true
401:                 }
402:                 options = defaults.merge(options)
403:                 options["lower_privilege"]           = to_boolean(options["lower_privilege"])
404:                 options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i
405:                 options["app_spawner_timeout"]       = options["app_spawner_timeout"].to_i
406:                 # Force this to be a boolean for easy use with Utils#unmarshal_and_raise_errors.
407:                 options["print_exceptions"]          = to_boolean(options["print_exceptions"])
408:                 return options
409:         end
switch_to_user(user)
     # File lib/phusion_passenger/utils.rb, line 354
354:         def switch_to_user(user)
355:                 begin
356:                         if user.is_a?(String)
357:                                 pw = Etc.getpwnam(user)
358:                                 username = user
359:                                 uid = pw.uid
360:                                 gid = pw.gid
361:                         else
362:                                 pw = Etc.getpwuid(user)
363:                                 username = pw.name
364:                                 uid = user
365:                                 gid = pw.gid
366:                         end
367:                 rescue
368:                         return false
369:                 end
370:                 if uid == 0
371:                         return false
372:                 else
373:                         # Some systems are broken. initgroups can fail because of
374:                         # all kinds of stupid reasons. So we ignore any errors
375:                         # raised by initgroups.
376:                         begin
377:                                 Process.groups = Process.initgroups(username, gid)
378:                         rescue
379:                         end
380:                         Process::Sys.setgid(gid)
381:                         Process::Sys.setuid(uid)
382:                         ENV['HOME'] = pw.dir
383:                         return true
384:                 end
385:         end
to_boolean(value)
     # File lib/phusion_passenger/utils.rb, line 387
387:         def to_boolean(value)
388:                 return !(value.nil? || value == false || value == "false")
389:         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.
     # File lib/phusion_passenger/utils.rb, line 302
302:         def unmarshal_and_raise_errors(channel, print_exception = nil, app_type = "rails")
303:                 args = channel.read
304:                 if args.nil?
305:                         raise EOFError, "Unexpected end-of-file detected."
306:                 end
307:                 status = args[0]
308:                 if status == 'exception'
309:                         child_exception = unmarshal_exception(channel.read_scalar)
310:                         stderr = channel.read_scalar
311:                         exception = AppInitError.new(
312:                                 "Application '#{@app_root}' raised an exception: " <<
313:                                 "#{child_exception.class} (#{child_exception.message})",
314:                                 child_exception,
315:                                 app_type,
316:                                 stderr.empty? ? nil : stderr)
317:                 elsif status == 'exit'
318:                         child_exception = unmarshal_exception(channel.read_scalar)
319:                         stderr = channel.read_scalar
320:                         exception = AppInitError.new("Application '#{@app_root}' exited during startup",
321:                                 child_exception, app_type, stderr.empty? ? nil : stderr)
322:                 else
323:                         exception = nil
324:                 end
325:                 
326:                 if print_exception && exception
327:                         if print_exception.respond_to?(:puts)
328:                                 print_exception(self.class.to_s, child_exception, print_exception)
329:                         elsif print_exception.respond_to?(:to_str)
330:                                 filename = print_exception.to_str
331:                                 File.open(filename, 'a') do |f|
332:                                         print_exception(self.class.to_s, child_exception, f)
333:                                 end
334:                         else
335:                                 print_exception(self.class.to_s, child_exception)
336:                         end
337:                 end
338:                 raise exception if exception
339:         end
unmarshal_exception(data)
     # File lib/phusion_passenger/utils.rb, line 127
127:         def unmarshal_exception(data)
128:                 hash = Marshal.load(data)
129:                 if hash[:is_initialization_error]
130:                         if hash[:child_exception]
131:                                 child_exception = unmarshal_exception(hash[:child_exception])
132:                         else
133:                                 child_exception = nil
134:                         end
135:                         
136:                         case hash[:class]
137:                         when AppInitError.to_s
138:                                 exception_class = AppInitError
139:                         when FrameworkInitError.to_s
140:                                 exception_class = FrameworkInitError
141:                         else
142:                                 exception_class = InitializationError
143:                         end
144:                         return exception_class.new(hash[:message], child_exception)
145:                 else
146:                         begin
147:                                 return Marshal.load(hash[:exception])
148:                         rescue ArgumentError, TypeError
149:                                 return UnknownError.new(hash[:message], hash[:class], hash[:backtrace])
150:                         end
151:                 end
152:         end