Utility functions.

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

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

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

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

    # 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_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 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_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 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
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 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
close_all_io_objects_for_fds(file_descriptors_to_leave_open)
     # 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_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 279
279:         def lower_privilege(filename, lowest_user = "nobody")
280:                 stat = File.lstat(filename)
281:                 begin
282:                         if !switch_to_user(stat.uid)
283:                                 switch_to_user(lowest_user)
284:                         end
285:                 rescue Errno::EPERM
286:                         # No problem if we were unable to switch user.
287:                 end
288:         end
marshal_exception(exception)
     # 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
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 343
343:         def passenger_tmpdir(create = true)
344:                 dir = ENV['PASSENGER_INSTANCE_TEMP_DIR']
345:                 if dir.nil? || dir.empty?
346:                         dir = "#{Dir.tmpdir}/passenger.#{Process.pid}"
347:                         ENV['PASSENGER_INSTANCE_TEMP_DIR'] = dir
348:                 end
349:                 if create && !File.exist?(dir)
350:                         # This is a very minimal implementation of the function
351:                         # passengerCreateTempDir() in Utils.cpp. This implementation
352:                         # is only meant to make the unit tests pass. For production
353:                         # systems one should pre-create the temp directory with
354:                         # passengerCreateTempDir().
355:                         system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", dir)
356:                         system("mkdir", "-p", "-m", "u=wxs,g=wx,o=wx", "#{dir}/backends")
357:                 end
358:                 return dir
359:         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.

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

     # File lib/phusion_passenger/utils.rb, line 206
206:         def report_app_init_status(channel)
207:                 begin
208:                         old_global_stderr = $stderr
209:                         old_stderr = STDERR
210:                         stderr_output = ""
211:                         tempfile = Tempfile.new('passenger-stderr')
212:                         tempfile.unlink
213:                         Object.send(:remove_const, 'STDERR') rescue nil
214:                         Object.const_set('STDERR', tempfile)
215:                         begin
216:                                 yield
217:                         ensure
218:                                 Object.send(:remove_const, 'STDERR') rescue nil
219:                                 Object.const_set('STDERR', old_stderr)
220:                                 $stderr = old_global_stderr
221:                                 if tempfile
222:                                         tempfile.rewind
223:                                         stderr_output = tempfile.read
224:                                         tempfile.close rescue nil
225:                                 end
226:                         end
227:                         channel.write('success')
228:                         return true
229:                 rescue StandardError, ScriptError, NoMemoryError => e
230:                         if ENV['TESTING_PASSENGER'] == '1'
231:                                 print_exception(self.class.to_s, e)
232:                         end
233:                         channel.write('exception')
234:                         channel.write_scalar(marshal_exception(e))
235:                         channel.write_scalar(stderr_output)
236:                         return false
237:                 rescue SystemExit => e
238:                         channel.write('exit')
239:                         channel.write_scalar(marshal_exception(e))
240:                         channel.write_scalar(stderr_output)
241:                         raise
242:                 end
243:         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 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:                                                 yield
182:                                         end
183:                                 else
184:                                         yield
185:                                 end
186:                         rescue Exception => e
187:                                 print_exception(current_location.to_s, e)
188:                         ensure
189:                                 exit!
190:                         end
191:                 else
192:                         if double_fork
193:                                 Process.waitpid(pid) rescue nil
194:                                 return pid
195:                         else
196:                                 return pid
197:                         end
198:                 end
199:         end
sanitize_spawn_options(options)
     # File lib/phusion_passenger/utils.rb, line 323
323:         def sanitize_spawn_options(options)
324:                 defaults = {
325:                         "lower_privilege" => true,
326:                         "lowest_user"     => "nobody",
327:                         "environment"     => "production",
328:                         "app_type"        => "rails",
329:                         "spawn_method"    => "smart-lv2",
330:                         "framework_spawner_timeout" => -1,
331:                         "app_spawner_timeout"       => -1
332:                 }
333:                 options = defaults.merge(options)
334:                 options["lower_privilege"]           = options["lower_privilege"].to_s == "true"
335:                 options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i
336:                 options["app_spawner_timeout"]       = options["app_spawner_timeout"].to_i
337:                 return options
338:         end
switch_to_user(user)
     # File lib/phusion_passenger/utils.rb, line 290
290:         def switch_to_user(user)
291:                 begin
292:                         if user.is_a?(String)
293:                                 pw = Etc.getpwnam(user)
294:                                 username = user
295:                                 uid = pw.uid
296:                                 gid = pw.gid
297:                         else
298:                                 pw = Etc.getpwuid(user)
299:                                 username = pw.name
300:                                 uid = user
301:                                 gid = pw.gid
302:                         end
303:                 rescue
304:                         return false
305:                 end
306:                 if uid == 0
307:                         return false
308:                 else
309:                         # Some systems are broken. initgroups can fail because of
310:                         # all kinds of stupid reasons. So we ignore any errors
311:                         # raised by initgroups.
312:                         begin
313:                                 Process.groups = Process.initgroups(username, gid)
314:                         rescue
315:                         end
316:                         Process::Sys.setgid(gid)
317:                         Process::Sys.setuid(uid)
318:                         ENV['HOME'] = pw.dir
319:                         return true
320:                 end
321:         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:

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