Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 # Virtual Me2Me implementation. This script runs and manages the processes | 6 # Virtual Me2Me implementation. This script runs and manages the processes |
| 7 # required for a Virtual Me2Me desktop, which are: X server, X desktop | 7 # required for a Virtual Me2Me desktop, which are: X server, X desktop |
| 8 # session, and Host process. | 8 # session, and Host process. |
| 9 # This script is intended to run continuously as a background daemon | 9 # This script is intended to run continuously as a background daemon |
| 10 # process, running under an ordinary (non-root) user account. | 10 # process, running under an ordinary (non-root) user account. |
| (...skipping 494 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 505 if self.ssh_auth_sockname: | 505 if self.ssh_auth_sockname: |
| 506 args.append("--ssh-auth-sockname=%s" % self.ssh_auth_sockname) | 506 args.append("--ssh-auth-sockname=%s" % self.ssh_auth_sockname) |
| 507 | 507 |
| 508 # Have the host process use SIGUSR1 to signal a successful start. | 508 # Have the host process use SIGUSR1 to signal a successful start. |
| 509 def sigusr1_handler(signum, frame): | 509 def sigusr1_handler(signum, frame): |
| 510 _ = signum, frame | 510 _ = signum, frame |
| 511 logging.info("Host ready to receive connections.") | 511 logging.info("Host ready to receive connections.") |
| 512 self.host_ready = True | 512 self.host_ready = True |
| 513 if (ParentProcessLogger.instance() and | 513 if (ParentProcessLogger.instance() and |
| 514 False not in [desktop.host_ready for desktop in g_desktops]): | 514 False not in [desktop.host_ready for desktop in g_desktops]): |
| 515 ParentProcessLogger.instance().release_parent() | 515 ParentProcessLogger.instance().release_parent(True) |
| 516 | 516 |
| 517 signal.signal(signal.SIGUSR1, sigusr1_handler) | 517 signal.signal(signal.SIGUSR1, sigusr1_handler) |
| 518 args.append("--signal-parent") | 518 args.append("--signal-parent") |
| 519 | 519 |
| 520 self.host_proc = subprocess.Popen(args, env=self.child_env, | 520 self.host_proc = subprocess.Popen(args, env=self.child_env, |
| 521 stdin=subprocess.PIPE) | 521 stdin=subprocess.PIPE) |
| 522 logging.info(args) | 522 logging.info(args) |
| 523 if not self.host_proc.pid: | 523 if not self.host_proc.pid: |
| 524 raise Exception("Could not start Chrome Remote Desktop host") | 524 raise Exception("Could not start Chrome Remote Desktop host") |
| 525 | 525 |
| (...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 675 # Ensure write_pipe is closed on exec, otherwise it will be kept open by | 675 # Ensure write_pipe is closed on exec, otherwise it will be kept open by |
| 676 # child processes (X, host), preventing the read pipe from EOF'ing. | 676 # child processes (X, host), preventing the read pipe from EOF'ing. |
| 677 old_flags = fcntl.fcntl(write_pipe, fcntl.F_GETFD) | 677 old_flags = fcntl.fcntl(write_pipe, fcntl.F_GETFD) |
| 678 fcntl.fcntl(write_pipe, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC) | 678 fcntl.fcntl(write_pipe, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC) |
| 679 self._read_file = os.fdopen(read_pipe, 'r') | 679 self._read_file = os.fdopen(read_pipe, 'r') |
| 680 self._write_file = os.fdopen(write_pipe, 'w') | 680 self._write_file = os.fdopen(write_pipe, 'w') |
| 681 self._logging_handler = None | 681 self._logging_handler = None |
| 682 ParentProcessLogger.__instance = self | 682 ParentProcessLogger.__instance = self |
| 683 | 683 |
| 684 def start_logging(self): | 684 def start_logging(self): |
| 685 """Installs a logging handler that sends log entries to a pipe. | 685 """Installs a logging handler that sends log entries to a pipe, prefixed |
| 686 with the string 'MSG:'. This allows them to be distinguished by the parent | |
| 687 process from commands sent over the same pipe. | |
| 686 | 688 |
| 687 Must be called by the child process. | 689 Must be called by the child process. |
| 688 """ | 690 """ |
| 689 self._read_file.close() | 691 self._read_file.close() |
| 690 self._logging_handler = logging.StreamHandler(self._write_file) | 692 self._logging_handler = logging.StreamHandler(self._write_file) |
| 693 self._logging_handler.setFormatter(logging.Formatter(fmt='MSG:%(message)s')) | |
| 691 logging.getLogger().addHandler(self._logging_handler) | 694 logging.getLogger().addHandler(self._logging_handler) |
| 692 | 695 |
| 693 def release_parent(self): | 696 def release_parent(self, success): |
| 694 """Uninstalls logging handler and closes the pipe, releasing the parent. | 697 """Uninstalls logging handler and closes the pipe, releasing the parent. |
| 695 | 698 |
| 696 Must be called by the child process. | 699 Must be called by the child process. |
| 700 | |
| 701 success: If true, write a "host ready" message to the parent process before | |
| 702 closing the pipe. | |
| 697 """ | 703 """ |
| 698 if self._logging_handler: | 704 if self._logging_handler: |
| 699 logging.getLogger().removeHandler(self._logging_handler) | 705 logging.getLogger().removeHandler(self._logging_handler) |
| 700 self._logging_handler = None | 706 self._logging_handler = None |
| 701 if not self._write_file.closed: | 707 if not self._write_file.closed: |
| 708 if success: | |
| 709 self._write_file.write("READY\n") | |
| 710 self._write_file.flush() | |
| 702 self._write_file.close() | 711 self._write_file.close() |
| 703 | 712 |
| 704 def wait_for_logs(self): | 713 def wait_for_logs(self): |
| 705 """Waits and prints log lines from the daemon until the pipe is closed. | 714 """Waits and prints log lines from the daemon until the pipe is closed. |
| 706 | 715 |
| 707 Must be called by the parent process. | 716 Must be called by the parent process. |
| 717 | |
| 718 Returns: | |
| 719 True if the host started and successfully registered with the directory; | |
| 720 false otherwise. | |
| 708 """ | 721 """ |
| 709 # If Ctrl-C is pressed, inform the user that the daemon is still running. | 722 # If Ctrl-C is pressed, inform the user that the daemon is still running. |
| 710 # This signal will cause the read loop below to stop with an EINTR IOError. | 723 # This signal will cause the read loop below to stop with an EINTR IOError. |
| 711 def sigint_handler(signum, frame): | 724 def sigint_handler(signum, frame): |
| 712 _ = signum, frame | 725 _ = signum, frame |
| 713 print("Interrupted. The daemon is still running in the background.", | 726 print("Interrupted. The daemon is still running in the background.", |
| 714 file=sys.stderr) | 727 file=sys.stderr) |
| 715 | 728 |
| 716 signal.signal(signal.SIGINT, sigint_handler) | 729 signal.signal(signal.SIGINT, sigint_handler) |
| 717 | 730 |
| 718 # Install a fallback timeout to release the parent process, in case the | |
| 719 # daemon never responds (e.g. host crash-looping, daemon killed). | |
| 720 # This signal will cause the read loop below to stop with an EINTR IOError. | |
| 721 def sigalrm_handler(signum, frame): | |
| 722 _ = signum, frame | |
| 723 print("No response from daemon. It may have crashed, or may still be " | |
| 724 "running in the background.", file=sys.stderr) | |
| 725 | |
| 726 signal.signal(signal.SIGALRM, sigalrm_handler) | |
| 727 signal.alarm(30) | |
|
Jamie
2016/02/12 18:32:12
Sergey, do you agree that this change makes sense?
Sergey Ulanov
2016/02/12 22:42:25
This timeout may still be useful when the host pro
Jamie
2016/02/12 23:03:04
I've reinstated it but increased it to 120s to mat
| |
| 728 | |
| 729 self._write_file.close() | 731 self._write_file.close() |
| 730 | 732 |
| 731 # Print lines as they're logged to the pipe until EOF is reached or readline | 733 # Print lines as they're logged to the pipe until EOF is reached or readline |
| 732 # is interrupted by one of the signal handlers above. | 734 # is interrupted by one of the signal handlers above. |
| 735 host_ready = False | |
| 733 try: | 736 try: |
| 734 for line in iter(self._read_file.readline, ''): | 737 for line in iter(self._read_file.readline, ''): |
| 735 sys.stderr.write(line) | 738 if line[:4] == "MSG:": |
| 739 sys.stderr.write(line[4:]) | |
| 740 elif line == "READY\n": | |
| 741 host_ready = True | |
| 742 else: | |
| 743 sys.stderr.write("Unrecognized command: " + line) | |
| 736 except IOError as e: | 744 except IOError as e: |
| 737 if e.errno != errno.EINTR: | 745 if e.errno != errno.EINTR: |
| 738 raise | 746 raise |
| 739 print("Log file: %s" % os.environ[LOG_FILE_ENV_VAR], file=sys.stderr) | 747 print("Log file: %s" % os.environ[LOG_FILE_ENV_VAR], file=sys.stderr) |
| 748 return host_ready | |
| 740 | 749 |
| 741 @staticmethod | 750 @staticmethod |
| 742 def instance(): | 751 def instance(): |
| 743 """Returns the singleton instance, if it exists.""" | 752 """Returns the singleton instance, if it exists.""" |
| 744 return ParentProcessLogger.__instance | 753 return ParentProcessLogger.__instance |
| 745 | 754 |
| 746 | 755 |
| 747 def daemonize(): | 756 def daemonize(): |
| 748 """Background this process and detach from controlling terminal, redirecting | 757 """Background this process and detach from controlling terminal, redirecting |
| 749 stdout/stderr to a log file.""" | 758 stdout/stderr to a log file.""" |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 788 pid = os.fork() | 797 pid = os.fork() |
| 789 | 798 |
| 790 if pid == 0: | 799 if pid == 0: |
| 791 # Grandchild process | 800 # Grandchild process |
| 792 pass | 801 pass |
| 793 else: | 802 else: |
| 794 # Child process | 803 # Child process |
| 795 os._exit(0) # pylint: disable=W0212 | 804 os._exit(0) # pylint: disable=W0212 |
| 796 else: | 805 else: |
| 797 # Parent process | 806 # Parent process |
| 798 parent_logger.wait_for_logs() | 807 if parent_logger.wait_for_logs(): |
| 799 os._exit(0) # pylint: disable=W0212 | 808 os._exit(0) # pylint: disable=W0212 |
| 809 else: | |
| 810 os._exit(1) # pylint: disable=W0212 | |
| 800 | 811 |
| 801 logging.info("Daemon process started in the background, logging to '%s'" % | 812 logging.info("Daemon process started in the background, logging to '%s'" % |
| 802 os.environ[LOG_FILE_ENV_VAR]) | 813 os.environ[LOG_FILE_ENV_VAR]) |
| 803 | 814 |
| 804 os.chdir(HOME_DIR) | 815 os.chdir(HOME_DIR) |
| 805 | 816 |
| 806 parent_logger.start_logging() | 817 parent_logger.start_logging() |
| 807 | 818 |
| 808 # Copy the file-descriptors to create new stdin, stdout and stderr. Note | 819 # Copy the file-descriptors to create new stdin, stdout and stderr. Note |
| 809 # that dup2(oldfd, newfd) closes newfd first, so this will close the current | 820 # that dup2(oldfd, newfd) closes newfd first, so this will close the current |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 835 # process refuses to die for some reason. | 846 # process refuses to die for some reason. |
| 836 psutil_proc.wait(timeout=10) | 847 psutil_proc.wait(timeout=10) |
| 837 except psutil.TimeoutExpired: | 848 except psutil.TimeoutExpired: |
| 838 logging.error("Timed out - sending SIGKILL") | 849 logging.error("Timed out - sending SIGKILL") |
| 839 psutil_proc.kill() | 850 psutil_proc.kill() |
| 840 except psutil.Error: | 851 except psutil.Error: |
| 841 logging.error("Error terminating process") | 852 logging.error("Error terminating process") |
| 842 | 853 |
| 843 g_desktops = [] | 854 g_desktops = [] |
| 844 if ParentProcessLogger.instance(): | 855 if ParentProcessLogger.instance(): |
| 845 ParentProcessLogger.instance().release_parent() | 856 ParentProcessLogger.instance().release_parent(False) |
| 846 | 857 |
| 847 class SignalHandler: | 858 class SignalHandler: |
| 848 """Reload the config file on SIGHUP. Since we pass the configuration to the | 859 """Reload the config file on SIGHUP. Since we pass the configuration to the |
| 849 host processes via stdin, they can't reload it, so terminate them. They will | 860 host processes via stdin, they can't reload it, so terminate them. They will |
| 850 be relaunched automatically with the new config.""" | 861 be relaunched automatically with the new config.""" |
| 851 | 862 |
| 852 def __init__(self, host_config): | 863 def __init__(self, host_config): |
| 853 self.host_config = host_config | 864 self.host_config = host_config |
| 854 | 865 |
| 855 def __call__(self, signum, _stackframe): | 866 def __call__(self, signum, _stackframe): |
| (...skipping 518 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1374 else: | 1385 else: |
| 1375 logging.info("Host exited with status %s." % os.WEXITSTATUS(status)) | 1386 logging.info("Host exited with status %s." % os.WEXITSTATUS(status)) |
| 1376 elif os.WIFSIGNALED(status): | 1387 elif os.WIFSIGNALED(status): |
| 1377 logging.info("Host terminated by signal %s." % os.WTERMSIG(status)) | 1388 logging.info("Host terminated by signal %s." % os.WTERMSIG(status)) |
| 1378 | 1389 |
| 1379 | 1390 |
| 1380 if __name__ == "__main__": | 1391 if __name__ == "__main__": |
| 1381 logging.basicConfig(level=logging.DEBUG, | 1392 logging.basicConfig(level=logging.DEBUG, |
| 1382 format="%(asctime)s:%(levelname)s:%(message)s") | 1393 format="%(asctime)s:%(levelname)s:%(message)s") |
| 1383 sys.exit(main()) | 1394 sys.exit(main()) |
| OLD | NEW |