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

Utility functions.

Methods

Classes and Modules

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

Protected Class methods

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

[Source]

     # File lib/phusion_passenger/utils.rb, line 439
439:         def self.passenger_tmpdir=(dir)
440:                 @@passenger_tmpdir = dir
441:         end

Protected Instance methods

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 61
61:         def assert_valid_app_root(app_root)
62:                 assert_valid_directory(app_root)
63:                 assert_valid_file("#{app_root}/config/environment.rb")
64:         end

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

[Source]

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

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

[Source]

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

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

[Source]

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

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

[Source]

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

[Source]

    # File lib/phusion_passenger/utils.rb, line 52
52:         def canonicalize_path(path)
53:                 raise ArgumentError, "The 'path' argument may not be nil" if path.nil?
54:                 return Pathname.new(path).realpath.to_s
55:         rescue Errno::ENOENT => e
56:                 raise InvalidAPath, e.message
57:         end

[Source]

     # File lib/phusion_passenger/utils.rb, line 94
 94:         def close_all_io_objects_for_fds(file_descriptors_to_leave_open)
 95:                 ObjectSpace.each_object(IO) do |io|
 96:                         begin
 97:                                 if !file_descriptors_to_leave_open.include?(io.fileno) && !io.closed?
 98:                                         io.close
 99:                                 end
100:                         rescue
101:                         end
102:                 end
103:         end

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 344
344:         def lower_privilege(filename, lowest_user = "nobody")
345:                 stat = File.lstat(filename)
346:                 begin
347:                         if !switch_to_user(stat.uid)
348:                                 switch_to_user(lowest_user)
349:                         end
350:                 rescue Errno::EPERM
351:                         # No problem if we were unable to switch user.
352:                 end
353:         end

[Source]

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

[Source]

     # File lib/phusion_passenger/utils.rb, line 414
414:         def passenger_tmpdir(create = true)
415:                 PhusionPassenger::Utils.passenger_tmpdir(create)
416:         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.

[Source]

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

[Source]

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

[Source]

     # File lib/phusion_passenger/utils.rb, line 176
176:         def safe_fork(current_location = self.class, double_fork = false)
177:                 pid = fork
178:                 if pid.nil?
179:                         begin
180:                                 if double_fork
181:                                         pid2 = fork
182:                                         if pid2.nil?
183:                                                 srand
184:                                                 yield
185:                                         end
186:                                 else
187:                                         srand
188:                                         yield
189:                                 end
190:                         rescue Exception => e
191:                                 print_exception(current_location.to_s, e)
192:                         ensure
193:                                 exit!
194:                         end
195:                 else
196:                         if double_fork
197:                                 Process.waitpid(pid) rescue nil
198:                                 return pid
199:                         else
200:                                 return pid
201:                         end
202:                 end
203:         end

[Source]

     # File lib/phusion_passenger/utils.rb, line 392
392:         def sanitize_spawn_options(options)
393:                 defaults = {
394:                         "lower_privilege" => true,
395:                         "lowest_user"     => "nobody",
396:                         "environment"     => "production",
397:                         "app_type"        => "rails",
398:                         "spawn_method"    => "smart-lv2",
399:                         "framework_spawner_timeout" => -1,
400:                         "app_spawner_timeout"       => -1,
401:                         "print_exceptions" => true
402:                 }
403:                 options = defaults.merge(options)
404:                 options["lower_privilege"]           = to_boolean(options["lower_privilege"])
405:                 options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i
406:                 options["app_spawner_timeout"]       = options["app_spawner_timeout"].to_i
407:                 # Force this to be a boolean for easy use with Utils#unmarshal_and_raise_errors.
408:                 options["print_exceptions"]          = to_boolean(options["print_exceptions"])
409:                 return options
410:         end

[Source]

     # File lib/phusion_passenger/utils.rb, line 355
355:         def switch_to_user(user)
356:                 begin
357:                         if user.is_a?(String)
358:                                 pw = Etc.getpwnam(user)
359:                                 username = user
360:                                 uid = pw.uid
361:                                 gid = pw.gid
362:                         else
363:                                 pw = Etc.getpwuid(user)
364:                                 username = pw.name
365:                                 uid = user
366:                                 gid = pw.gid
367:                         end
368:                 rescue
369:                         return false
370:                 end
371:                 if uid == 0
372:                         return false
373:                 else
374:                         # Some systems are broken. initgroups can fail because of
375:                         # all kinds of stupid reasons. So we ignore any errors
376:                         # raised by initgroups.
377:                         begin
378:                                 Process.groups = Process.initgroups(username, gid)
379:                         rescue
380:                         end
381:                         Process::Sys.setgid(gid)
382:                         Process::Sys.setuid(uid)
383:                         ENV['HOME'] = pw.dir
384:                         return true
385:                 end
386:         end

[Source]

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

[Source]

     # File lib/phusion_passenger/utils.rb, line 303
303:         def unmarshal_and_raise_errors(channel, print_exception = nil, app_type = "rails")
304:                 args = channel.read
305:                 if args.nil?
306:                         raise EOFError, "Unexpected end-of-file detected."
307:                 end
308:                 status = args[0]
309:                 if status == 'exception'
310:                         child_exception = unmarshal_exception(channel.read_scalar)
311:                         stderr = channel.read_scalar
312:                         exception = AppInitError.new(
313:                                 "Application '#{@app_root}' raised an exception: " <<
314:                                 "#{child_exception.class} (#{child_exception.message})",
315:                                 child_exception,
316:                                 app_type,
317:                                 stderr.empty? ? nil : stderr)
318:                 elsif status == 'exit'
319:                         child_exception = unmarshal_exception(channel.read_scalar)
320:                         stderr = channel.read_scalar
321:                         exception = AppInitError.new("Application '#{@app_root}' exited during startup",
322:                                 child_exception, app_type, stderr.empty? ? nil : stderr)
323:                 else
324:                         exception = nil
325:                 end
326:                 
327:                 if print_exception && exception
328:                         if print_exception.respond_to?(:puts)
329:                                 print_exception(self.class.to_s, child_exception, print_exception)
330:                         elsif print_exception.respond_to?(:to_str)
331:                                 filename = print_exception.to_str
332:                                 File.open(filename, 'a') do |f|
333:                                         print_exception(self.class.to_s, child_exception, f)
334:                                 end
335:                         else
336:                                 print_exception(self.class.to_s, child_exception)
337:                         end
338:                 end
339:                 raise exception if exception
340:         end

[Source]

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

[Validate]