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 |