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 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.

    # 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.

    # 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.

    # 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.

    # 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)
    # 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.

     # 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)
     # 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.

    # 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.

     # 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.

     # 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.

     # 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.

     # 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)
     # 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)
     # 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:

     # 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)
     # 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