| 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 | 731 # Install a fallback timeout to release the parent process, in case the |
| 719 # daemon never responds (e.g. host crash-looping, daemon killed). | 732 # 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. | 733 # This signal will cause the read loop below to stop with an EINTR IOError. |
| 734 # |
| 735 # The value of 120s is chosen to match the heartbeat retry timeout in |
| 736 # hearbeat_sender.cc. |
| 721 def sigalrm_handler(signum, frame): | 737 def sigalrm_handler(signum, frame): |
| 722 _ = signum, frame | 738 _ = signum, frame |
| 723 print("No response from daemon. It may have crashed, or may still be " | 739 print("No response from daemon. It may have crashed, or may still be " |
| 724 "running in the background.", file=sys.stderr) | 740 "running in the background.", file=sys.stderr) |
| 725 | 741 |
| 726 signal.signal(signal.SIGALRM, sigalrm_handler) | 742 signal.signal(signal.SIGALRM, sigalrm_handler) |
| 727 signal.alarm(30) | 743 signal.alarm(120) |
| 728 | 744 |
| 729 self._write_file.close() | 745 self._write_file.close() |
| 730 | 746 |
| 731 # Print lines as they're logged to the pipe until EOF is reached or readline | 747 # 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. | 748 # is interrupted by one of the signal handlers above. |
| 749 host_ready = False |
| 733 try: | 750 try: |
| 734 for line in iter(self._read_file.readline, ''): | 751 for line in iter(self._read_file.readline, ''): |
| 735 sys.stderr.write(line) | 752 if line[:4] == "MSG:": |
| 753 sys.stderr.write(line[4:]) |
| 754 elif line == "READY\n": |
| 755 host_ready = True |
| 756 else: |
| 757 sys.stderr.write("Unrecognized command: " + line) |
| 736 except IOError as e: | 758 except IOError as e: |
| 737 if e.errno != errno.EINTR: | 759 if e.errno != errno.EINTR: |
| 738 raise | 760 raise |
| 739 print("Log file: %s" % os.environ[LOG_FILE_ENV_VAR], file=sys.stderr) | 761 print("Log file: %s" % os.environ[LOG_FILE_ENV_VAR], file=sys.stderr) |
| 762 return host_ready |
| 740 | 763 |
| 741 @staticmethod | 764 @staticmethod |
| 742 def instance(): | 765 def instance(): |
| 743 """Returns the singleton instance, if it exists.""" | 766 """Returns the singleton instance, if it exists.""" |
| 744 return ParentProcessLogger.__instance | 767 return ParentProcessLogger.__instance |
| 745 | 768 |
| 746 | 769 |
| 747 def daemonize(): | 770 def daemonize(): |
| 748 """Background this process and detach from controlling terminal, redirecting | 771 """Background this process and detach from controlling terminal, redirecting |
| 749 stdout/stderr to a log file.""" | 772 stdout/stderr to a log file.""" |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 788 pid = os.fork() | 811 pid = os.fork() |
| 789 | 812 |
| 790 if pid == 0: | 813 if pid == 0: |
| 791 # Grandchild process | 814 # Grandchild process |
| 792 pass | 815 pass |
| 793 else: | 816 else: |
| 794 # Child process | 817 # Child process |
| 795 os._exit(0) # pylint: disable=W0212 | 818 os._exit(0) # pylint: disable=W0212 |
| 796 else: | 819 else: |
| 797 # Parent process | 820 # Parent process |
| 798 parent_logger.wait_for_logs() | 821 if parent_logger.wait_for_logs(): |
| 799 os._exit(0) # pylint: disable=W0212 | 822 os._exit(0) # pylint: disable=W0212 |
| 823 else: |
| 824 os._exit(1) # pylint: disable=W0212 |
| 800 | 825 |
| 801 logging.info("Daemon process started in the background, logging to '%s'" % | 826 logging.info("Daemon process started in the background, logging to '%s'" % |
| 802 os.environ[LOG_FILE_ENV_VAR]) | 827 os.environ[LOG_FILE_ENV_VAR]) |
| 803 | 828 |
| 804 os.chdir(HOME_DIR) | 829 os.chdir(HOME_DIR) |
| 805 | 830 |
| 806 parent_logger.start_logging() | 831 parent_logger.start_logging() |
| 807 | 832 |
| 808 # Copy the file-descriptors to create new stdin, stdout and stderr. Note | 833 # 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 | 834 # 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. | 860 # process refuses to die for some reason. |
| 836 psutil_proc.wait(timeout=10) | 861 psutil_proc.wait(timeout=10) |
| 837 except psutil.TimeoutExpired: | 862 except psutil.TimeoutExpired: |
| 838 logging.error("Timed out - sending SIGKILL") | 863 logging.error("Timed out - sending SIGKILL") |
| 839 psutil_proc.kill() | 864 psutil_proc.kill() |
| 840 except psutil.Error: | 865 except psutil.Error: |
| 841 logging.error("Error terminating process") | 866 logging.error("Error terminating process") |
| 842 | 867 |
| 843 g_desktops = [] | 868 g_desktops = [] |
| 844 if ParentProcessLogger.instance(): | 869 if ParentProcessLogger.instance(): |
| 845 ParentProcessLogger.instance().release_parent() | 870 ParentProcessLogger.instance().release_parent(False) |
| 846 | 871 |
| 847 class SignalHandler: | 872 class SignalHandler: |
| 848 """Reload the config file on SIGHUP. Since we pass the configuration to the | 873 """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 | 874 host processes via stdin, they can't reload it, so terminate them. They will |
| 850 be relaunched automatically with the new config.""" | 875 be relaunched automatically with the new config.""" |
| 851 | 876 |
| 852 def __init__(self, host_config): | 877 def __init__(self, host_config): |
| 853 self.host_config = host_config | 878 self.host_config = host_config |
| 854 | 879 |
| 855 def __call__(self, signum, _stackframe): | 880 def __call__(self, signum, _stackframe): |
| (...skipping 518 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1374 else: | 1399 else: |
| 1375 logging.info("Host exited with status %s." % os.WEXITSTATUS(status)) | 1400 logging.info("Host exited with status %s." % os.WEXITSTATUS(status)) |
| 1376 elif os.WIFSIGNALED(status): | 1401 elif os.WIFSIGNALED(status): |
| 1377 logging.info("Host terminated by signal %s." % os.WTERMSIG(status)) | 1402 logging.info("Host terminated by signal %s." % os.WTERMSIG(status)) |
| 1378 | 1403 |
| 1379 | 1404 |
| 1380 if __name__ == "__main__": | 1405 if __name__ == "__main__": |
| 1381 logging.basicConfig(level=logging.DEBUG, | 1406 logging.basicConfig(level=logging.DEBUG, |
| 1382 format="%(asctime)s:%(levelname)s:%(message)s") | 1407 format="%(asctime)s:%(levelname)s:%(message)s") |
| 1383 sys.exit(main()) | 1408 sys.exit(main()) |
| OLD | NEW |