Utility functions.
- assert_valid_app_root
- assert_valid_directory
- assert_valid_file
- assert_valid_groupname
- assert_valid_username
- canonicalize_path
- close_all_io_objects_for_fds
- lower_privilege
- marshal_exception
- passenger_tmpdir
- print_exception
- report_app_init_status
- safe_fork
- sanitize_spawn_options
- switch_to_user
- unmarshal_and_raise_errors
- unmarshal_exception
Assert that app_root is a valid Ruby on Rails application root. Raises InvalidPath if that is not the case.
[ show 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.
[ show 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.
[ show 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.
[ show 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.
[ show 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.
[ show 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
[ show 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.
[ show source ]
# 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
[ show 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
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.
[ show source ]
# 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 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.
[ show source ]
# 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
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.
[ show source ]
# 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
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.
[ show 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: 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
[ show source ]
# 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
[ show source ]
# 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
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:
- AppInitError
- IOError, SystemCallError, SocketError
[ show source ]
# 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
[ show 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