Module | PhusionPassenger::Utils |
In: |
lib/phusion_passenger/utils.rb
|
Utility functions.
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 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
# File lib/phusion_passenger/utils.rb, line 420 420: def self.passenger_tmpdir=(dir) 421: @@passenger_tmpdir = dir 422: end
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 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 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 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 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
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
# 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.
# 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
# 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
# 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.
# 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.
# 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.
# 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
# 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
# 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
# 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:
Raises:
# 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
# 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