Utility functions.
- assert_valid_app_root
- assert_valid_directory
- assert_valid_file
- assert_valid_groupname
- assert_valid_username
- close_all_io_objects_for_fds
- lower_privilege
- marshal_exception
- normalize_path
- 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 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 that path is a directory. Raises InvalidPath if it isn‘t.
[ show source ]
# 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 that path is a file. Raises InvalidPath if it isn‘t.
[ show source ]
# 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 that groupname is a valid group name. Raises ArgumentError if that is not the case.
[ show source ]
# 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 that username is a valid username. Raises ArgumentError if that is not the case.
[ show source ]
# 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
[ show source ]
# 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 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 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
[ show source ]
# 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
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.
[ show source ]
# 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
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 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 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 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
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 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
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 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
[ show source ]
# 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
[ show source ]
# 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
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 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
[ show source ]
# 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