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

Utility functions.

Methods

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 402
402:         def self.passenger_tmpdir(create = true)
403:                 dir = @@passenger_tmpdir
404:                 if dir.nil? || dir.empty?
405:                         dir = "#{Dir.tmpdir}/passenger.#{Process.pid}"
406:                         @@passenger_tmpdir = dir
407:                 end
408:                 if create && !File.exist?(dir)
409:                         # This is a very minimal implementation of the function
410:                         # passengerCreateTempDir() in Utils.cpp. This implementation
411:                         # is only meant to make the unit tests pass. For production
412:                         # systems one should pre-create the temp directory with
413:                         # passengerCreateTempDir().
414:                         system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", dir)
415:                         system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", "#{dir}/backends")
416:                 end
417:                 return dir
418:         end

[Source]

     # File lib/phusion_passenger/utils.rb, line 420
420:         def self.passenger_tmpdir=(dir)
421:                 @@passenger_tmpdir = dir
422:         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 59
59:         def assert_valid_app_root(app_root)
60:                 assert_valid_directory(app_root)
61:                 assert_valid_file("#{app_root}/config/environment.rb")
62:         end

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

[Source]

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

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

[Source]

    # File lib/phusion_passenger/utils.rb, line 72
72:         def assert_valid_file(path)
73:                 if !File.file?(path)
74:                         raise InvalidPath, "'#{path}' is not a valid file."
75:                 end
76:         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 87
87:         def assert_valid_groupname(groupname)
88:                 # If groupname does not exist then getgrnam() will raise an ArgumentError.
89:                 groupname && Etc.getgrnam(groupname)
90:         end

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

[Source]

    # File lib/phusion_passenger/utils.rb, line 80
80:         def assert_valid_username(username)
81:                 # If username does not exist then getpwnam() will raise an ArgumentError.
82:                 username && Etc.getpwnam(username)
83:         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 50
50:         def canonicalize_path(path)
51:                 raise ArgumentError, "The 'path' argument may not be nil" if path.nil?
52:                 return Pathname.new(path).realpath.to_s
53:         rescue Errno::ENOENT => e
54:                 raise InvalidAPath, e.message
55:         end

[Source]

     # File lib/phusion_passenger/utils.rb, line 92
 92:         def close_all_io_objects_for_fds(file_descriptors_to_leave_open)
 93:                 ObjectSpace.each_object(IO) do |io|
 94:                         begin
 95:                                 if !file_descriptors_to_leave_open.include?(io.fileno) && !io.closed?
 96:                                         io.close
 97:                                 end
 98:                         rescue
 99:                         end
100:                 end
101:         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 325
325:         def lower_privilege(filename, lowest_user = "nobody")
326:                 stat = File.lstat(filename)
327:                 begin
328:                         if !switch_to_user(stat.uid)
329:                                 switch_to_user(lowest_user)
330:                         end
331:                 rescue Errno::EPERM
332:                         # No problem if we were unable to switch user.
333:                 end
334:         end

[Source]

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

[Source]

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

Anything written to $stderr and STDERR during execution of the block will be buffered. If write_stderr_contents_to is non-nil, then the buffered stderr data will be written to this object. In this case, write_stderr_contents_to must be an IO-like object. If write_stderr_contents_to is nil, then the stder data will be discarded.

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 220
220:         def report_app_init_status(channel, write_stderr_contents_to = STDERR)
221:                 begin
222:                         old_global_stderr = $stderr
223:                         old_stderr = STDERR
224:                         stderr_output = ""
225:                         tempfile = Tempfile.new('passenger-stderr')
226:                         tempfile.unlink
227:                         Object.send(:remove_const, 'STDERR') rescue nil
228:                         Object.const_set('STDERR', tempfile)
229:                         begin
230:                                 yield
231:                         ensure
232:                                 Object.send(:remove_const, 'STDERR') rescue nil
233:                                 Object.const_set('STDERR', old_stderr)
234:                                 $stderr = old_global_stderr
235:                                 tempfile.rewind
236:                                 stderr_output = tempfile.read
237:                                 tempfile.close rescue nil
238:                                 
239:                                 if write_stderr_contents_to
240:                                         write_stderr_contents_to.write(stderr_output)
241:                                         write_stderr_contents_to.flush
242:                                 end
243:                         end
244:                         channel.write('success')
245:                         return true
246:                 rescue StandardError, ScriptError, NoMemoryError => e
247:                         if ENV['TESTING_PASSENGER'] == '1'
248:                                 print_exception(self.class.to_s, e)
249:                         end
250:                         channel.write('exception')
251:                         channel.write_scalar(marshal_exception(e))
252:                         channel.write_scalar(stderr_output)
253:                         return false
254:                 rescue SystemExit => e
255:                         channel.write('exit')
256:                         channel.write_scalar(marshal_exception(e))
257:                         channel.write_scalar(stderr_output)
258:                         raise
259:                 end
260:         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 174
174:         def safe_fork(current_location = self.class, double_fork = false)
175:                 pid = fork
176:                 if pid.nil?
177:                         begin
178:                                 if double_fork
179:                                         pid2 = fork
180:                                         if pid2.nil?
181:                                                 srand
182:                                                 yield
183:                                         end
184:                                 else
185:                                         srand
186:                                         yield
187:                                 end
188:                         rescue Exception => e
189:                                 print_exception(current_location.to_s, e)
190:                         ensure
191:                                 exit!
192:                         end
193:                 else
194:                         if double_fork
195:                                 Process.waitpid(pid) rescue nil
196:                                 return pid
197:                         else
198:                                 return pid
199:                         end
200:                 end
201:         end

[Source]

     # File lib/phusion_passenger/utils.rb, line 373
373:         def sanitize_spawn_options(options)
374:                 defaults = {
375:                         "lower_privilege" => true,
376:                         "lowest_user"     => "nobody",
377:                         "environment"     => "production",
378:                         "app_type"        => "rails",
379:                         "spawn_method"    => "smart-lv2",
380:                         "framework_spawner_timeout" => -1,
381:                         "app_spawner_timeout"       => -1,
382:                         "print_exceptions" => true
383:                 }
384:                 options = defaults.merge(options)
385:                 options["lower_privilege"]           = to_boolean(options["lower_privilege"])
386:                 options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i
387:                 options["app_spawner_timeout"]       = options["app_spawner_timeout"].to_i
388:                 # Force this to be a boolean for easy use with Utils#unmarshal_and_raise_errors.
389:                 options["print_exceptions"]          = to_boolean(options["print_exceptions"])
390:                 return options
391:         end

[Source]

     # File lib/phusion_passenger/utils.rb, line 336
336:         def switch_to_user(user)
337:                 begin
338:                         if user.is_a?(String)
339:                                 pw = Etc.getpwnam(user)
340:                                 username = user
341:                                 uid = pw.uid
342:                                 gid = pw.gid
343:                         else
344:                                 pw = Etc.getpwuid(user)
345:                                 username = pw.name
346:                                 uid = user
347:                                 gid = pw.gid
348:                         end
349:                 rescue
350:                         return false
351:                 end
352:                 if uid == 0
353:                         return false
354:                 else
355:                         # Some systems are broken. initgroups can fail because of
356:                         # all kinds of stupid reasons. So we ignore any errors
357:                         # raised by initgroups.
358:                         begin
359:                                 Process.groups = Process.initgroups(username, gid)
360:                         rescue
361:                         end
362:                         Process::Sys.setgid(gid)
363:                         Process::Sys.setuid(uid)
364:                         ENV['HOME'] = pw.dir
365:                         return true
366:                 end
367:         end

[Source]

     # File lib/phusion_passenger/utils.rb, line 369
369:         def to_boolean(value)
370:                 return !(value.nil? || value == false || value == "false")
371:         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 284
284:         def unmarshal_and_raise_errors(channel, print_exception = nil, app_type = "rails")
285:                 args = channel.read
286:                 if args.nil?
287:                         raise EOFError, "Unexpected end-of-file detected."
288:                 end
289:                 status = args[0]
290:                 if status == 'exception'
291:                         child_exception = unmarshal_exception(channel.read_scalar)
292:                         stderr = channel.read_scalar
293:                         exception = AppInitError.new(
294:                                 "Application '#{@app_root}' raised an exception: " <<
295:                                 "#{child_exception.class} (#{child_exception.message})",
296:                                 child_exception,
297:                                 app_type,
298:                                 stderr.empty? ? nil : stderr)
299:                 elsif status == 'exit'
300:                         child_exception = unmarshal_exception(channel.read_scalar)
301:                         stderr = channel.read_scalar
302:                         exception = AppInitError.new("Application '#{@app_root}' exited during startup",
303:                                 child_exception, app_type, stderr.empty? ? nil : stderr)
304:                 else
305:                         exception = nil
306:                 end
307:                 
308:                 if print_exception && exception
309:                         if print_exception.respond_to?(:puts)
310:                                 print_exception(self.class.to_s, child_exception, print_exception)
311:                         elsif print_exception.respond_to?(:to_str)
312:                                 filename = print_exception.to_str
313:                                 File.open(filename, 'a') do |f|
314:                                         print_exception(self.class.to_s, child_exception, f)
315:                                 end
316:                         else
317:                                 print_exception(self.class.to_s, child_exception)
318:                         end
319:                 end
320:                 raise exception if exception
321:         end

[Source]

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

[Validate]