Package sabayon :: Module protosession
[hide private]
[frames] | no frames]

Source Code for Module sabayon.protosession

  1  # 
  2  # Copyright (C) 2005 Red Hat, Inc. 
  3  # 
  4  # This program is free software; you can redistribute it and/or modify 
  5  # it under the terms of the GNU General Public License as published by 
  6  # the Free Software Foundation; either version 2 of the License, or 
  7  # (at your option) any later version. 
  8  # 
  9  # This program is distributed in the hope that it will be useful, 
 10  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 12  # GNU General Public License for more details. 
 13  # 
 14  # You should have received a copy of the GNU General Public License 
 15  # along with this program; if not, write to the Free Software 
 16  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 17  # 
 18   
 19  import os 
 20  import os.path 
 21  import sys 
 22  import socket 
 23  import errno 
 24  import signal 
 25  import select 
 26  import pwd 
 27  import commands 
 28  import binascii 
 29  import struct 
 30  import subprocess 
 31  import tempfile 
 32  import time 
 33  import shutil 
 34  import gobject 
 35  import gtk 
 36  import util 
 37  import usermod 
 38  import errors 
 39  from config import * 
 40  import debuglog 
 41   
42 -def dprint (fmt, *args):
43 debuglog.debug_log (False, debuglog.DEBUG_LOG_DOMAIN_PROTO_SESSION, fmt % args)
44
45 -def mprint (fmt, *args):
46 debuglog.debug_log (True, debuglog.DEBUG_LOG_DOMAIN_PROTO_SESSION, fmt % args)
47
48 -class ProtoSessionError (Exception):
49 pass
50
51 -class SessionStartError (ProtoSessionError):
52 pass
53
54 -class XauthParseError (ProtoSessionError):
55 pass
56 57 # 58 # These functions are run as root in order to prepare to launch the session 59 #
60 -def setup_shell_and_homedir (username):
61 assert os.geteuid () == 0 62 63 pw = pwd.getpwnam (username) 64 65 dprint ("Setting shell for '%s' to '%s'", username, DEFAULT_SHELL) 66 usermod.set_shell (username, DEFAULT_SHELL) 67 68 # Wait for previous sabayon processes to die before proceeding 69 for i in range (1,30): 70 temp_homedir = usermod.create_temporary_homedir (pw.pw_uid, pw.pw_gid) 71 dprint ("Setting temporary home directory for '%s' to '%s' attempt %d", username, temp_homedir, i) 72 retval = usermod.set_homedir (username, temp_homedir) 73 dprint ("retval=%d", retval) 74 if retval == 0: 75 break 76 time.sleep(1) 77 78 return temp_homedir
79
80 -def reset_shell_and_homedir (username, temp_homedir):
81 assert os.geteuid () == 0 82 83 pw = pwd.getpwnam (username) 84 85 dprint ("Unsetting homedir for '%s'", username) 86 usermod.set_homedir (username, "") 87 88 dprint ("Deleting temporary homedir '%s'", temp_homedir) 89 shutil.rmtree (temp_homedir, True) 90 91 dprint ("Resetting shell for '%s' to '%s'", username, NOLOGIN_SHELL) 92 usermod.set_shell (username, NOLOGIN_SHELL)
93
94 -def clobber_user_processes (username):
95 assert os.geteuid () == 0 96 97 pw = pwd.getpwnam (username) 98 99 # FIXME: my, what a big hammer you have! 100 argv = CLOBBER_USER_PROCESSES_ARGV + [ pw.pw_name ] 101 dprint ("Clobbering existing processes running as user '%s': %s", pw.pw_name, argv) 102 subprocess.call (argv)
103
104 -def find_free_display ():
105 def is_display_free (display_number): 106 try: 107 d = gtk.gdk.Display (":%d.0" % display_number) 108 d.close () 109 return False 110 except RuntimeError, e: 111 return True
112 113 display_number = 1 114 while display_number < 100: 115 if is_display_free (display_number): 116 return display_number 117 display_number += 1 118 119 raise ProtoSessionError, _("Unable to find a free X display") 120 121 # 122 # Everything beyond here gets run from sabayon-session 123 # 124
125 -def safe_kill (pid, sig):
126 try: 127 os.kill (pid, sig) 128 except os.error, (err, errstr): 129 if err != errno.ESRCH: 130 raise
131
132 -class ProtoSession (gobject.GObject):
133 __gsignals__ = { 134 "finished" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) 135 } 136
137 - def __init__ (self, profile_file, display_number):
138 gobject.GObject.__init__ (self) 139 140 self.xauth_cookie = None 141 self.profile_file = profile_file 142 self.display_number = display_number 143 self.display_name = ":%s" % display_number 144 145 self.usr1_pipe_r = 0 146 self.usr1_pipe_w = 0 147 148 self.xephyr_pid = 0 149 self.xephyr_child_watch = 0 150 self.xephyr_xauth_file = None 151 152 self.session_pid = 0 153 self.session_child_watch = 0 154 self.session_xauth_file = None 155 156 self.admin_tool_timeout = 0 157 self.admin_tool_pid = 0 158 self.admin_tool_child_watch = 0 159 160 self.pw = pwd.getpwuid (os.getuid ())
161
162 - def __kill_xephyr (self):
163 if self.xephyr_child_watch: 164 gobject.source_remove (self.xephyr_child_watch) 165 self.xephyr_child_watch = 0 166 167 if self.xephyr_pid: 168 safe_kill (self.xephyr_pid, signal.SIGTERM) 169 self.xephyr_pid = 0
170
171 - def __kill_session (self):
172 if self.session_child_watch: 173 gobject.source_remove (self.session_child_watch) 174 self.session_child_watch = 0 175 176 if self.session_pid: 177 safe_kill (self.session_pid, signal.SIGTERM) 178 self.session_pid = 0
179
180 - def __kill_admin_tool (self):
181 if self.admin_tool_timeout: 182 gobject.source_remove (self.admin_tool_timeout) 183 self.admin_tool_timeout = 0 184 185 if self.admin_tool_child_watch: 186 gobject.source_remove (self.admin_tool_child_watch) 187 self.admin_tool_child_watch = 0 188 189 if self.admin_tool_pid: 190 safe_kill (self.admin_tool_pid, signal.SIGTERM) 191 self.admin_tool_pid = 0
192
193 - def __del__ (self):
194 if self.xephyr_xauth_file: 195 os.remove (self.xephyr_xauth_file) 196 if self.session_xauth_file: 197 os.remove (self.session_xauth_file) 198 if self.usr1_pipe_r: 199 os.close (self.usr1_pipe_r) 200 if self.usr1_pipe_w: 201 os.close (self.usr1_pipe_w) 202 self.force_quit ()
203 204 # 205 # This USR1 malarky is that if you set the signal handler for 206 # SIGUSR1 to SIG_IGN, then the Xserver will send its parent 207 # a SIGUSR1 when its ready to start accepting connections. 208 #
209 - def __sigusr1_handler (self, sig, frame):
210 self.got_usr1_signal = True 211 os.write (self.usr1_pipe_w, "Y")
212
213 - def __xephyr_child_watch_handler (self, pid, status):
214 dprint ("Xephyr died") 215 216 self.xephyr_pid = 0 217 self.xephyr_child_watch = 0 218 219 self.emit ("finished"); 220 221 self.force_quit () 222 223 # If we're waiting for USR1, quit main loop 224 if self.main_loop: 225 self.main_loop.quit () 226 227 return False
228
229 - def __usr1_pipe_watch_handler (self, source, condition):
230 dprint ("Got USR1 signal, quitting mainloop") 231 self.main_loop.quit () 232 return True
233
234 - def __usr1_timeout_handler (self):
235 dprint ("Timed out waiting for USR1, quitting mainloop") 236 self.main_loop.quit () 237 return True
238 239 # Write out a temporary Xauthority file which contains the same magic 240 # cookie as the parent display so that we can pass that using -auth 241 # to Xephyr. 242 # 243 # Xauthority is a binary file format: 244 # 245 # 2 bytes Family value (second byte is as in protocol HOST) 246 # 2 bytes address length (always MSB first) 247 # A bytes host address (as in protocol HOST) 248 # 2 bytes display "number" length (always MSB first) 249 # S bytes display "number" string 250 # 2 bytes name length (always MSB first) 251 # N bytes authorization name string 252 # 2 bytes data length (always MSB first) 253 # D bytes authorization data string 254 #
255 - def __write_temp_xauth_file (self, wildcard_addr):
256 if self.xauth_cookie == None: 257 self.xauth_cookie = util.random_string (16) 258 259 xauth_name = "MIT-MAGIC-COOKIE-1" 260 xauth_data = self.xauth_cookie 261 262 if wildcard_addr: 263 family = 0xffff # FamilyWild 264 display_addr = "" 265 else: 266 family = 0x0100 # FamilyLocal 267 display_addr = socket.gethostname () 268 269 display_num_str = self.display_number 270 display_num_len = len (display_num_str) 271 272 display_addr_len = len (display_addr) 273 xauth_name_len = len (xauth_name) 274 xauth_data_len = len (xauth_data) 275 276 pack_format = ">hh%dsh%dsh%dsh%d" % (display_addr_len, display_num_len, xauth_name_len, xauth_data_len) 277 278 blob = struct.pack (pack_format, 279 family, 280 display_addr_len, display_addr, 281 display_num_len, display_num_str, 282 xauth_name_len, xauth_name, 283 xauth_data_len) + xauth_data 284 285 (fd, temp_xauth_file) = tempfile.mkstemp (prefix = ".xauth-") 286 os.write (fd, blob) 287 os.close (fd) 288 289 dprint ("Wrote temporary xauth file to %s" % temp_xauth_file) 290 291 return temp_xauth_file
292 293 # 294 # Need to hold open the first X connection until the session dies 295 #
296 - def __open_x_connection (self, display_name, xauth_file):
297 (pipe_r, pipe_w) = os.pipe () 298 pid = os.fork () 299 if pid == 0: # Child process 300 os.close (pipe_r) 301 new_environ = os.environ.copy () 302 new_environ["DISPLAY"] = display_name 303 new_environ["XAUTHORITY"] = xauth_file 304 argv = [ "python", "-c", 305 "import gtk, os, sys; os.write (int (sys.argv[1]), 'Y'); gtk.main ()" , 306 str (pipe_w) ] 307 os.execvpe (argv[0], argv, new_environ) 308 os.close (pipe_w) 309 while True: 310 try: 311 select.select ([pipe_r], [], []) 312 break 313 except select.error, (err, errstr): 314 if err != errno.EINTR: 315 raise 316 os.close (pipe_r)
317 318 # 319 # FIXME: we have a re-entrancy issue here - if while we're 320 # runing the mainloop we re-enter, then we'll install another 321 # SIGUSR1 handler and everything will break 322 #
323 - def __start_xephyr (self, parent_window):
324 dprint ("Starting Xephyr %s" % self.display_name) 325 326 self.xephyr_xauth_file = self.__write_temp_xauth_file (True) 327 328 (self.usr1_pipe_r, self.usr1_pipe_w) = os.pipe () 329 self.got_usr1_signal = False 330 signal.signal (signal.SIGUSR1, self.__sigusr1_handler) 331 332 self.xephyr_pid = os.fork () 333 if self.xephyr_pid == 0: # Child process 334 signal.signal (signal.SIGUSR1, signal.SIG_IGN) 335 336 argv = XEPHYR_ARGV + \ 337 [ "-auth", self.xephyr_xauth_file ] 338 if parent_window: 339 argv += [ "-parent", parent_window ] 340 argv += [ self.display_name ] 341 342 dprint ("Child process launching %s" % argv) 343 344 os.execv (argv[0], argv) 345 346 # Shouldn't ever reach here 347 sys.stderr.write ("Failed to launch Xephyr") 348 os._exit (1) 349 350 self.main_loop = gobject.MainLoop () 351 352 self.xephyr_child_watch = gobject.child_watch_add (self.xephyr_pid, 353 self.__xephyr_child_watch_handler) 354 io_watch = gobject.io_add_watch (self.usr1_pipe_r, 355 gobject.IO_IN | gobject.IO_PRI, 356 self.__usr1_pipe_watch_handler) 357 timeout = gobject.timeout_add_seconds (XEPHYR_USR1_TIMEOUT, 358 self.__usr1_timeout_handler) 359 360 dprint ("Waiting on child process (%d)" % self.xephyr_pid) 361 362 self.main_loop.run () 363 self.main_loop = None 364 365 signal.signal (signal.SIGUSR1, signal.SIG_IGN) 366 367 gobject.source_remove (timeout) 368 gobject.source_remove (io_watch) 369 370 os.close (self.usr1_pipe_r) 371 self.usr1_pipe_r = 0 372 os.close (self.usr1_pipe_w) 373 self.usr1_pipe_w = 0 374 375 if not self.got_usr1_signal: 376 if self.xephyr_child_watch: 377 gobject.source_remove (self.xephyr_child_watch) 378 self.xephyr_child_watch = 0 379 380 if self.xephyr_pid: 381 safe_kill (self.xephyr_pid, signal.SIGTERM) 382 self.xephyr_pid = 0 383 raise SessionStartError, _("Failed to start Xephyr: timed out waiting for USR1 signal") 384 else: 385 raise SessionStartError, _("Failed to start Xephyr: died during startup")
386
387 - def __session_child_watch_handler (self, pid, status):
388 dprint ("Session died") 389 390 self.session_pid = 0 391 self.session_child_watch = 0 392 393 self.force_quit () 394 395 self.emit ("finished") 396 397 return False
398
399 - def __start_session (self):
400 dprint ("Starting session") 401 402 self.session_xauth_file = self.__write_temp_xauth_file (False) 403 404 self.__open_x_connection (self.display_name, self.session_xauth_file) 405 406 self.session_pid = os.fork () 407 if self.session_pid == 0: # Child process 408 new_environ = os.environ.copy () 409 410 new_environ["DISPLAY"] = self.display_name 411 new_environ["XAUTHORITY"] = self.session_xauth_file 412 413 self.session_xauth_file = None 414 415 # Disable sabayon-xinitrc.sh 416 new_environ["DISABLE_SABAYON_XINITRC"] = "yes" 417 418 # Don't allow running Sabayon in the session 419 new_environ["SABAYON_SESSION_RUNNING"] = "yes" 420 421 # Disable Xscreensaver locking 422 new_environ["RUNNING_UNDER_GDM"] = "yes" 423 424 # Don't let the child session's gvfs use FUSE, so old 425 # gvfs-fuse-daemons that crash won't make FUSE turn 426 # /tmp/blahblah/.gvfs unreadable --- this screws up 427 # resetting and erasing the temporary home directory. 428 new_environ["GVFS_DISABLE_FUSE"] = "yes" 429 430 dprint ("Child process env: %s" % new_environ) 431 432 # Start the session 433 dprint ("Executing %s" % SESSION_ARGV) 434 os.execve (SESSION_ARGV[0], SESSION_ARGV, new_environ) 435 436 # Shouldn't ever happen 437 sys.stderr.write ("Failed to launch Xsession") 438 os._exit (1) 439 440 self.session_child_watch = gobject.child_watch_add (self.session_pid, 441 self.__session_child_watch_handler)
442 443
444 - def apply_profile (self):
445 argv = APPLY_TOOL_ARGV + [ "-s", self.profile_file, 446 ("--admin-log-config=%s" % util.get_admin_log_config_filename ()), 447 ("--readable-log-config=%s" % util.get_readable_log_config_filename ()) ] 448 mprint ("Running sabayon-apply: %s" % argv) 449 450 try: 451 pipe = subprocess.Popen (argv, 452 # stderr = subprocess.PIPE, 453 env = os.environ) 454 except Exception, e: 455 raise errors.FatalApplyErrorException (("Could not create the 'sabayon-apply' process: %s\n" + 456 "The command used is '%s'\n" + 457 "Child traceback:\n%s") % 458 (e.message, 459 " ".join (argv), 460 e.child_traceback)) 461 462 return_code = pipe.wait () 463 # stderr_str = pipe.stderr.read () 464 465 # print "<BEGIN SABAYON-APPLY STDERR>\n%s\n<END SABAYON-APPLY STDERR>" % stderr_str 466 467 if return_code == util.EXIT_CODE_NORMAL: 468 mprint ("Finished running sabayon-apply with normal exit status") 469 pass 470 elif return_code == util.EXIT_CODE_RECOVERABLE: 471 mprint ("Finished running sabayon-apply with RECOVERABLE exit status") 472 # mprint ("========== BEGIN SABAYON-APPLY LOG (RECOVERABLE) ==========\n" 473 # "%s\n" 474 # "========== END SABAYON-APPLY LOG (RECOVERABLE) ==========", 475 # stderr_str) 476 raise errors.RecoverableApplyErrorException (_("There was a recoverable error while applying " 477 "the user profile from file '%s'.") % self.profile_file) 478 else: 479 # return_code == util.EXIT_CODE_FATAL or something else 480 mprint ("Finished running sabayon-apply with FATAL exit status") 481 # mprint ("========== BEGIN SABAYON-APPLY LOG (FATAL) ==========\n" 482 # "%s\n" 483 # "========== END SABAYON-APPLY LOG (FATAL) ==========", 484 # stderr_str) 485 raise errors.FatalApplyErrorException (_("There was a fatal error while applying the " 486 "user profile from '%s'.") % self.profile_file)
487
488 - def start (self, parent_window):
489 # Get an X server going 490 self.__start_xephyr (parent_window) 491 492 # Start the session 493 self.__start_session ()
494
495 - def force_quit (self):
496 self.__kill_session () 497 self.__kill_xephyr ()
498 499 gobject.type_register (ProtoSession) 500