1# This file is part of Xpra. 2# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org> 3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 4# later version. See the file COPYING for details. 5 6import os 7 8from xpra.util import envbool, typedict 9from xpra.os_util import get_int_uuid 10from xpra.exit_codes import EXIT_MMAP_TOKEN_FAILURE 11from xpra.scripts.config import TRUE_OPTIONS 12from xpra.simple_stats import std_unit 13from xpra.client.mixins.stub_client_mixin import StubClientMixin 14from xpra.log import Logger 15 16log = Logger("mmap") 17 18KEEP_MMAP_FILE = envbool("XPRA_KEEP_MMAP_FILE", False) 19 20 21class MmapClient(StubClientMixin): 22 """ 23 Mixin for adding mmap support to a client 24 """ 25 26 def __init__(self): 27 super().__init__() 28 self.mmap_enabled = False 29 self.mmap = None 30 self.mmap_token = None 31 self.mmap_token_index = 0 32 self.mmap_token_bytes = 0 33 self.mmap_filename = None 34 self.mmap_size = 0 35 self.mmap_group = None 36 self.mmap_tempfile = None 37 self.mmap_delete = False 38 self.supports_mmap = True 39 40 41 def init(self, opts): 42 self.mmap_group = opts.mmap_group 43 if os.path.isabs(opts.mmap): 44 self.mmap_filename = opts.mmap 45 self.supports_mmap = True 46 else: 47 self.supports_mmap = opts.mmap.lower() in TRUE_OPTIONS 48 49 50 def cleanup(self): 51 self.clean_mmap() 52 53 54 def setup_connection(self, conn): 55 if self.supports_mmap: 56 self.init_mmap(self.mmap_filename, self.mmap_group, conn.filename) 57 58 59 def get_root_size(self): 60 #subclasses should provide real values 61 return 1024, 1024 62 63 def parse_server_capabilities(self, c : typedict) -> bool: 64 self.mmap_enabled = self.supports_mmap and self.mmap_enabled and c.boolget("mmap_enabled") 65 log("parse_server_capabilities(..) mmap_enabled=%s", self.mmap_enabled) 66 if self.mmap_enabled: 67 from xpra.net.mmap_pipe import read_mmap_token, DEFAULT_TOKEN_INDEX, DEFAULT_TOKEN_BYTES 68 def iget(attrname, default_value=0): 69 return c.intget("mmap_%s" % attrname) or c.intget("mmap.%s" % attrname) or default_value 70 mmap_token = iget("token") 71 mmap_token_index = iget("token_index", DEFAULT_TOKEN_INDEX) 72 mmap_token_bytes = iget("token_bytes", DEFAULT_TOKEN_BYTES) 73 token = read_mmap_token(self.mmap, mmap_token_index, mmap_token_bytes) 74 if token!=mmap_token: 75 log.error("Error: mmap token verification failed!") 76 log.error(" expected '%#x'", token) 77 log.error(" found '%#x'", mmap_token) 78 self.mmap_enabled = False 79 self.quit(EXIT_MMAP_TOKEN_FAILURE) 80 return 81 log.info("enabled fast mmap transfers using %sB shared memory area", std_unit(self.mmap_size, unit=1024)) 82 #the server will have a handle on the mmap file by now, safe to delete: 83 if not KEEP_MMAP_FILE: 84 self.clean_mmap() 85 return True 86 87 88 def get_info(self): 89 if not self.mmap_enabled: 90 return {} 91 mmap_info = self.get_raw_caps() 92 mmap_info["group"] = self.mmap_group or "" 93 return { 94 "mmap" : mmap_info, 95 } 96 97 def get_caps(self) -> dict: 98 if not self.mmap_enabled: 99 return {} 100 raw_caps = self.get_raw_caps() 101 caps = { 102 "mmap" : raw_caps, 103 } 104 #pre 2.3 servers only use underscore instead of "." prefix for mmap caps: 105 for k,v in raw_caps.items(): 106 caps["mmap_%s" % k] = v 107 return caps 108 109 def get_raw_caps(self): 110 return { 111 "file" : self.mmap_filename, 112 "size" : self.mmap_size, 113 "token" : self.mmap_token, 114 "token_index" : self.mmap_token_index, 115 "token_bytes" : self.mmap_token_bytes, 116 "namespace" : True, #this client understands "mmap.ATTRIBUTE" format 117 } 118 119 def init_mmap(self, mmap_filename, mmap_group, socket_filename): 120 log("init_mmap(%s, %s, %s)", mmap_filename, mmap_group, socket_filename) 121 from xpra.net.mmap_pipe import ( #pylint: disable=import-outside-toplevel 122 init_client_mmap, write_mmap_token, 123 DEFAULT_TOKEN_INDEX, DEFAULT_TOKEN_BYTES, 124 ) 125 #calculate size: 126 root_w, root_h = self.get_root_size() 127 #at least 256MB, or 8 fullscreen RGBX frames: 128 mmap_size = max(512*1024*1024, root_w*root_h*4*8) 129 mmap_size = min(2048*1024*1024, mmap_size) 130 self.mmap_enabled, self.mmap_delete, self.mmap, self.mmap_size, self.mmap_tempfile, self.mmap_filename = \ 131 init_client_mmap(mmap_group, socket_filename, mmap_size, self.mmap_filename) 132 if self.mmap_enabled: 133 self.mmap_token = get_int_uuid() 134 self.mmap_token_bytes = DEFAULT_TOKEN_BYTES 135 self.mmap_token_index = self.mmap_size - DEFAULT_TOKEN_BYTES 136 #self.mmap_token_index = DEFAULT_TOKEN_INDEX*2 137 #write the token twice: 138 # once at the old default offset for older servers, 139 # and at the offset we want to use with new servers 140 for index in (DEFAULT_TOKEN_INDEX, self.mmap_token_index): 141 write_mmap_token(self.mmap, self.mmap_token, index, self.mmap_token_bytes) 142 143 def clean_mmap(self): 144 log("XpraClient.clean_mmap() mmap_filename=%s", self.mmap_filename) 145 if self.mmap_tempfile: 146 try: 147 self.mmap_tempfile.close() 148 except Exception as e: 149 log("clean_mmap error closing file %s: %s", self.mmap_tempfile, e) 150 self.mmap_tempfile = None 151 if self.mmap_delete: 152 #this should be redundant: closing the tempfile should get it deleted 153 if self.mmap_filename and os.path.exists(self.mmap_filename): 154 from xpra.net.mmap_pipe import clean_mmap 155 clean_mmap(self.mmap_filename) 156 self.mmap_filename = None 157