diff --git a/dom0/aux-tools/block_cleaner_daemon.py b/dom0/aux-tools/block_cleaner_daemon.py new file mode 100755 index 00000000..75b549dc --- /dev/null +++ b/dom0/aux-tools/block_cleaner_daemon.py @@ -0,0 +1,53 @@ +#!/usr/bin/python + +import xen.lowlevel.xs +import time +import subprocess + +xs = xen.lowlevel.xs.xs() + +domain_list = [] + +def setup_watches(): + global domain_list + + new_domain_list = xs.ls('', '/local/domain') + for dom in new_domain_list: + if dom not in domain_list: + print "Adding: %s" % dom + xs.watch('/local/domain/%s/backend/vbd' % dom, int(dom)) + for dom in domain_list: + if dom not in new_domain_list: + print "Removing: %s" % dom + xs.unwatch('/local/domain/%s/backend/vbd' % dom, int(dom)) + domain_list = new_domain_list + +def handle_vbd_state(path): + state = xs.read('', path) + if state == '6': + # Closed state; wait a moment to not interrupt reconnect + time.sleep(0.500) + state = xs.read('', path) + if state == '6': + # If still closed, detach device + path_components = path.split('/') + # /local/domain//backend/vbd///... + vm_xid = path_components[6] + vm_dev = path_components[7] + if vm_xid in domain_list: + subprocess.call(['xl', 'block-detach', vm_xid, vm_dev]) + +def main(): + + xs.watch('@introduceDomain', 'reload') + xs.watch('@releaseDomain', 'reload') + setup_watches() + while True: + (path, token) = xs.read_watch() + if token == 'reload': + setup_watches() + else: + if path.endswith('/state'): + handle_vbd_state(path) + +main() diff --git a/dom0/init.d/qubes_core b/dom0/init.d/qubes_core index 76ecf582..1da7e867 100755 --- a/dom0/init.d/qubes_core +++ b/dom0/init.d/qubes_core @@ -37,8 +37,10 @@ start() MEMINFO_DELAY_USEC=100000 /usr/lib/qubes/meminfo-writer $MEM_CHANGE_THRESHOLD_KB $MEMINFO_DELAY_USEC + /usr/lib/qubes/block_cleaner_daemon.py > /var/log/qubes/block_cleaner.log 2>&1 & + # Hide mounted devices from qubes-block list (at first udev run, only / is mounted) - for dev in `xenstore-list /local/domain/0/qubes-block-devices`; do + for dev in `xenstore-list /local/domain/0/qubes-block-devices 2> /dev/null`; do ( eval `udevadm info -q property -n $dev|sed -e 's/\([^=]*\)=\(.*\)/export \1="\2"/'`; /usr/lib/qubes/block_add_change > /dev/null ) diff --git a/dom0/misc/qubes-start.desktop b/dom0/misc/qubes-start.desktop new file mode 100644 index 00000000..63db9f3e --- /dev/null +++ b/dom0/misc/qubes-start.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Exec=qvm-start --quiet --tray %VMNAME% +Icon=%VMDIR%/icon.png +Terminal=false +Name=%VMNAME%: Start +GenericName=%VMNAME%: Start +StartupNotify=false +Categories=System; diff --git a/dom0/misc/vm-template-hvm.conf b/dom0/misc/vm-template-hvm.conf new file mode 100644 index 00000000..aae0f1b4 --- /dev/null +++ b/dom0/misc/vm-template-hvm.conf @@ -0,0 +1,39 @@ +# +# This is a Xen VM config file for Qubes VM +# DO NOT EDIT - autogenerated by qubes tools +# + +name = "{name}" + +builder='hvm' +memory={mem} +viridian=1 +kernel='hvmloader' +stdvga=1 +#acpi=1 +#apic=1 +boot='dca' +device_model='stubdom-dm' +#pae=1 +usbdevice='tablet' +sdl=0 +vnc=0 +localtime = {localtime} +rtc_timeoffset = {timeoffset} +disk = [ {rootdev} + {privatedev} + {otherdevs} + ] +vif = [ {netdev} ] +pci = [ {pcidev} ] +vcpus = {vcpus} + +#tsc_mode = 2 +#xen_extended_power_mgmt=0 + +on_poweroff = 'destroy' +on_reboot = 'destroy' +on_crash = 'destroy' + +# Use of DNS2 as DHCP server IP makes DNS2 not accessible, but DNS1 still should work +device_model_args = [ '-net', 'lwip,client_ip={ip},server_ip={dns2},dns={dns1},gw={gateway},netmask={netmask}' ] diff --git a/dom0/pm-utils/01qubes-sync-vms-clock b/dom0/pm-utils/01qubes-sync-vms-clock index 58344c7f..ccc7eeae 100755 --- a/dom0/pm-utils/01qubes-sync-vms-clock +++ b/dom0/pm-utils/01qubes-sync-vms-clock @@ -8,7 +8,7 @@ sync_qubes_vms_wallclock() DATE=$(date) echo echo "Syncing VMs clock to: $DATE" - qvm-run --all --exclude=`qubes-prefs --get clockvm` -u root "date -s \"$DATE\"" + qvm-run --all -u root "date -s \"$DATE\"" # Then try to sync from the network /usr/bin/qvm-sync-clock & } diff --git a/dom0/qmemman/qmemman.py b/dom0/qmemman/qmemman.py index c35f4e8e..4f3173ec 100755 --- a/dom0/qmemman/qmemman.py +++ b/dom0/qmemman/qmemman.py @@ -53,7 +53,7 @@ class SystemState: self.domdict[id].memory_maximum = self.xs.read('', '/local/domain/%s/memory/static-max' % str(id)) if not self.domdict[id].memory_maximum: self.domdict[id].memory_maximum = self.ALL_PHYS_MEM -# the previous line used to be +# the previous line used to be # self.domdict[id].memory_maximum = domain['maxmem_kb']*1024 # but domain['maxmem_kb'] changes in self.mem_set as well, and this results in # the memory never increasing @@ -109,7 +109,7 @@ class SystemState: for i in self.domdict.keys(): dom = self.domdict[i] if dom.memory_actual is not None and dom.memory_actual + 200*1024 < dom.last_target: - print "Preventing balloon up to", dom.last_target + print "Preventing balloon up to", dom.last_target self.mem_set(i, dom.memory_actual) #perform memory ballooning, across all domains, to add "memsize" to Xen free memory @@ -144,7 +144,7 @@ class SystemState: prev_memory_actual[dom] = self.domdict[dom].memory_actual time.sleep(self.BALOON_DELAY) niter = niter + 1 - + def refresh_meminfo(self, domid, untrusted_meminfo_key): qmemman_algo.refresh_meminfo_for_domain(self.domdict[domid], untrusted_meminfo_key) self.do_balance() @@ -174,7 +174,7 @@ class SystemState: if self.domdict[i].meminfo is not None: print 'dom' , i, 'act/pref', self.domdict[i].memory_actual, qmemman_algo.prefmem(self.domdict[i]) print 'xenfree=', xenfree, 'balance req:', memset_reqs - + def do_balance(self): if os.path.isfile('/var/run/qubes/do-not-membalance'): return @@ -184,7 +184,7 @@ class SystemState: memset_reqs = qmemman_algo.balance(xenfree - self.XEN_FREE_MEM_LEFT, self.domdict) if not self.is_balance_req_significant(memset_reqs, xenfree): return - + self.print_stats(xenfree, memset_reqs) prev_memactual = {} diff --git a/dom0/qubes_rpc/qfile-dom0-unpacker.c b/dom0/qubes_rpc/qfile-dom0-unpacker.c index 757a2c43..4eee29c7 100644 --- a/dom0/qubes_rpc/qfile-dom0-unpacker.c +++ b/dom0/qubes_rpc/qfile-dom0-unpacker.c @@ -52,12 +52,11 @@ void wait_for_child(int statusfd) } } -extern void do_unpack(int); +extern int do_unpack(void); int main(int argc, char ** argv) { char *incoming_dir; - int pipefds[2]; int uid; char *var; long long files_limit = DEFAULT_MAX_UPDATES_FILES; @@ -73,32 +72,15 @@ int main(int argc, char ** argv) if ((var=getenv("UPDATES_MAX_FILES"))) files_limit = atoll(var); - pipe(pipefds); - uid = prepare_creds_return_uid(argv[1]); incoming_dir = argv[2]; mkdir(incoming_dir, 0700); if (chdir(incoming_dir)) gui_fatal("Error chdir to %s", incoming_dir); - switch (fork()) { - case -1: - perror("fork"); - exit(1); - case 0: - if (chroot(incoming_dir)) //impossible - gui_fatal("Error chroot to %s", incoming_dir); - setuid(uid); - close(pipefds[0]); - set_size_limit(bytes_limit, files_limit); - do_unpack(pipefds[1]); - exit(0); - default:; - } - + if (chroot(incoming_dir)) //impossible + gui_fatal("Error chroot to %s", incoming_dir); setuid(uid); - close(pipefds[1]); - wait_for_child(pipefds[0]); - - return 0; + set_size_limit(bytes_limit, files_limit); + return do_unpack(); } diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index 69f9e6f7..cc65e641 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -21,6 +21,7 @@ # import sys +import stat import os import os.path import subprocess @@ -76,7 +77,14 @@ default_memory = 400 default_kernelopts = "" default_kernelopts_pcidevs = "iommu=soft swiotlb=2048" +default_hvm_disk_size = 20*1024*1024*1024 +default_hvm_private_img_size = 2*1024*1024*1024 +default_hvm_memory = 512 + config_template_pv = '/usr/share/qubes/vm-template.conf' +config_template_hvm = '/usr/share/qubes/vm-template-hvm.conf' + +start_appmenu_template = '/usr/share/qubes/qubes-start.desktop' qubes_whitelisted_appmenus = 'whitelisted-appmenus.list' @@ -252,6 +260,7 @@ class QubesVm(object): "services": { "default": {}, "eval": "eval(str(value))" }, "debug": { "default": False }, "default_user": { "default": "user" }, + "qrexec_timeout": { "default": 60, "eval": "int(value)" }, ##### Internal attributes - will be overriden in __init__ regardless of args "appmenus_templates_dir": { "eval": \ 'self.dir_path + "/" + default_appmenus_templates_subdir if self.updateable else ' + \ @@ -263,6 +272,7 @@ class QubesVm(object): "kernels_dir": { 'eval': 'qubes_kernels_base_dir + "/" + self.kernel if self.kernel is not None else ' + \ # for backward compatibility (or another rare case): kernel=None -> kernel in VM dir 'self.dir_path + "/" + default_kernels_subdir' }, + "_start_guid_first": { 'eval': 'False' }, } ### Mark attrs for XML inclusion @@ -271,7 +281,7 @@ class QubesVm(object): 'uses_default_kernel', 'kernel', 'uses_default_kernelopts',\ 'kernelopts', 'services', 'installed_by_rpm',\ 'uses_default_netvm', 'include_in_backups', 'debug',\ - 'default_user' ]: + 'default_user', 'qrexec_timeout' ]: attrs[prop]['save'] = 'str(self.%s)' % prop # Simple paths for prop in ['conf_file', 'root_img', 'volatile_img', 'private_img']: @@ -896,18 +906,24 @@ class QubesVm(object): # If dynamic memory management disabled, set maxmem=mem args['maxmem'] = args['mem'] args['vcpus'] = str(self.vcpus) - args['ip'] = self.ip - args['mac'] = self.mac - args['gateway'] = self.gateway - args['dns1'] = self.gateway - args['dns2'] = self.secondary_dns - args['netmask'] = self.netmask if self.netvm is not None: + args['ip'] = self.ip + args['mac'] = self.mac + args['gateway'] = self.netvm.gateway + args['dns1'] = self.netvm.gateway + args['dns2'] = self.secondary_dns + args['netmask'] = self.netmask args['netdev'] = "'mac={mac},script=/etc/xen/scripts/vif-route-qubes,ip={ip}".format(ip=self.ip, mac=self.mac) if self.netvm.qid != 0: args['netdev'] += ",backend={0}".format(self.netvm.name) args['netdev'] += "'" else: + args['ip'] = '' + args['mac'] = '' + args['gateway'] = '' + args['dns1'] = '' + args['dns2'] = '' + args['netmask'] = '' args['netdev'] = '' args['rootdev'] = self.get_rootdev(source_template=source_template) args['privatedev'] = "'script:file:{dir}/private.img,xvdb,w',".format(dir=self.dir_path) @@ -1346,6 +1362,7 @@ class QubesVm(object): if passio_popen: popen_kwargs={'stdout': subprocess.PIPE} + popen_kwargs['stdin'] = subprocess.PIPE if passio_stderr: popen_kwargs['stderr'] = subprocess.PIPE else: @@ -1422,6 +1439,17 @@ class QubesVm(object): if notify_function is not None: notify_function("error", "ERROR: Cannot start the Qubes Clipboard Notifier!") + def start_qrexec_daemon(self, verbose = False): + if verbose: + print >> sys.stderr, "--> Starting the qrexec daemon..." + xid = self.get_xid() + qrexec_env = os.environ + qrexec_env['QREXEC_STARTUP_TIMEOUT'] = str(self.qrexec_timeout) + retcode = subprocess.call ([qrexec_daemon_path, str(xid), self.default_user], env=qrexec_env) + if (retcode != 0) : + self.force_shutdown() + raise OSError ("ERROR: Cannot execute qrexec_daemon!") + def start(self, debug_console = False, verbose = False, preparing_dvm = False, start_guid = True): if dry_run: return @@ -1501,15 +1529,13 @@ class QubesVm(object): # the successful unpause is some indicator of it qmemman_client.close() - if not preparing_dvm: - if verbose: - print >> sys.stderr, "--> Starting the qrexec daemon..." - retcode = subprocess.call ([qrexec_daemon_path, str(xid)]) - if (retcode != 0) : - self.force_shutdown() - raise OSError ("ERROR: Cannot execute qrexec_daemon!") + if self._start_guid_first and start_guid and not preparing_dvm and os.path.exists('/var/run/shm.id'): + self.start_guid(verbose=verbose) - if start_guid and not preparing_dvm and os.path.exists('/var/run/shm.id'): + if not preparing_dvm: + self.start_qrexec_daemon(verbose=verbose) + + if not self._start_guid_first and start_guid and not preparing_dvm and os.path.exists('/var/run/shm.id'): self.start_guid(verbose=verbose) if preparing_dvm: @@ -2223,6 +2249,253 @@ class QubesAppVm(QubesVm): self.remove_appmenus() super(QubesAppVm, self).remove_from_disk() +class QubesHVm(QubesVm): + """ + A class that represents an HVM. A child of QubesVm. + """ + + # FIXME: logically should inherit after QubesAppVm, but none of its methods + # are useful for HVM + + def _get_attrs_config(self): + attrs = super(QubesHVm, self)._get_attrs_config() + attrs.pop('kernel') + attrs.pop('kernels_dir') + attrs.pop('kernelopts') + attrs.pop('uses_default_kernel') + attrs.pop('uses_default_kernelopts') + attrs['volatile_img']['eval'] = 'None' + attrs['config_file_template']['eval'] = 'config_template_hvm' + attrs['drive'] = { 'save': 'str(self.drive)' } + attrs['maxmem'].pop('save') + attrs['timezone'] = { 'default': 'localtime', 'save': 'str(self.timezone)' } + attrs['qrexec_installed'] = { 'default': False, 'save': 'str(self.qrexec_installed)' } + attrs['_start_guid_first']['eval'] = 'True' + + return attrs + + def __init__(self, **kwargs): + + if "dir_path" not in kwargs or kwargs["dir_path"] is None: + kwargs["dir_path"] = qubes_appvms_dir + "/" + kwargs["name"] + + # only updateable HVM supported + kwargs["updateable"] = True + kwargs["template_vm"] = None + if "memory" not in kwargs or kwargs["memory"] is None: + kwargs["memory"] = default_hvm_memory + + super(QubesHVm, self).__init__(**kwargs) + # HVM doesn't support dynamic memory management + self.maxmem = self.memory + + @property + def type(self): + return "HVM" + + def is_appvm(self): + return True + + def get_clone_attrs(self): + attrs = super(QubesHVm, self).get_clone_attrs() + attrs.remove('kernel') + attrs.remove('uses_default_kernel') + attrs.remove('kernelopts') + attrs.remove('uses_default_kernelopts') + attrs += [ 'timezone' ] + return attrs + + def create_on_disk(self, verbose, source_template = None): + if dry_run: + return + + if verbose: + print >> sys.stderr, "--> Creating directory: {0}".format(self.dir_path) + os.mkdir (self.dir_path) + + if verbose: + print >> sys.stderr, "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path) + os.symlink (self.label.icon_path, self.icon_path) + + if verbose: + print >> sys.stderr, "--> Creating appmenus directory: {0}".format(self.appmenus_templates_dir) + os.mkdir (self.appmenus_templates_dir) + shutil.copy (start_appmenu_template, self.appmenus_templates_dir) + + if not self.internal: + self.create_appmenus (verbose, source_template=source_template) + + self.create_config_file() + + # create empty disk + f_root = open(self.root_img, "w") + f_root.truncate(default_hvm_disk_size) + f_root.close() + + # create empty private.img + f_private = open(self.private_img, "w") + f_private.truncate(default_hvm_private_img_size) + f_root.close() + + def remove_from_disk(self): + if dry_run: + return + + self.remove_appmenus() + super(QubesHVm, self).remove_from_disk() + + def get_disk_utilization_private_img(self): + return 0 + + def get_private_img_sz(self): + return 0 + + def resize_private_img(self, size): + raise NotImplementedError("HVM has no private.img") + + def get_config_params(self, source_template=None): + + params = super(QubesHVm, self).get_config_params(source_template=source_template) + + params['volatiledev'] = '' + if self.drive: + type_mode = ":cdrom,r" + drive_path = self.drive + # leave empty to use standard syntax in case of dom0 + backend_domain = "" + if drive_path.startswith("hd:"): + type_mode = ",w" + drive_path = drive_path[3:] + elif drive_path.startswith("cdrom:"): + type_mode = ":cdrom,r" + drive_path = drive_path[6:] + backend_split = re.match(r"^([a-zA-Z0-9-]*):(.*)", drive_path) + if backend_split: + backend_domain = "," + backend_split.group(1) + drive_path = backend_split.group(2) + + # FIXME: os.stat will work only when backend in dom0... + stat_res = None + if backend_domain == "": + stat_res = os.stat(drive_path) + if stat_res and stat.S_ISBLK(stat_res.st_mode): + params['otherdevs'] = "'phy:%s,xvdc%s%s'," % (drive_path, type_mode, backend_domain) + else: + params['otherdevs'] = "'script:file:%s,xvdc%s%s'," % (drive_path, type_mode, backend_domain) + else: + params['otherdevs'] = '' + + # Disable currently unused private.img - to be enabled when TemplateHVm done + params['privatedev'] = '' + + if self.timezone.lower() == 'localtime': + params['localtime'] = '1' + params['timeoffset'] = '0' + elif self.timezone.isdigit(): + params['localtime'] = '0' + params['timeoffset'] = self.timezone + else: + print >>sys.stderr, "WARNING: invalid 'timezone' value: %s" % self.timezone + params['localtime'] = '0' + params['timeoffset'] = '0' + return params + + def verify_files(self): + if dry_run: + return + + if not os.path.exists (self.dir_path): + raise QubesException ( + "VM directory doesn't exist: {0}".\ + format(self.dir_path)) + + if self.is_updateable() and not os.path.exists (self.root_img): + raise QubesException ( + "VM root image file doesn't exist: {0}".\ + format(self.root_img)) + + if not os.path.exists (self.private_img): + print >>sys.stderr, "WARNING: Creating empty VM private image file: {0}".\ + format(self.private_img) + f_private = open(self.private_img, "w") + f_private.truncate(default_hvm_private_img_size) + f_private.close() + + return True + + def reset_volatile_storage(self, **kwargs): + pass + + @property + def vif(self): + if self.xid < 0: + return None + if self.netvm is None: + return None + return "vif{0}.+".format(self.stubdom_xid) + + def run(self, command, **kwargs): + if self.qrexec_installed: + if 'gui' in kwargs and kwargs['gui']==False: + command = "nogui:" + command + return super(QubesHVm, self).run(command, **kwargs) + else: + raise QubesException("Needs qrexec agent installed in VM to use this function. See also qvm-prefs.") + + @property + def stubdom_xid(self): + if self.xid < 0: + return -1 + + stubdom_xid_str = xs.read('', '/local/domain/%d/image/device-model-domid' % self.xid) + if stubdom_xid_str is not None: + return int(stubdom_xid_str) + else: + return -1 + + def start_guid(self, verbose = True, notify_function = None): + if verbose: + print >> sys.stderr, "--> Starting Qubes GUId..." + + retcode = subprocess.call ([qubes_guid_path, "-d", str(self.stubdom_xid), "-c", self.label.color, "-i", self.label.icon, "-l", str(self.label.index)]) + if (retcode != 0) : + raise QubesException("Cannot start qubes_guid!") + + def start_qrexec_daemon(self, **kwargs): + if self.qrexec_installed: + super(QubesHVm, self).start_qrexec_daemon(**kwargs) + + if kwargs.get('verbose'): + print >> sys.stderr, "--> Waiting for user '%s' login..." % self.default_user + + p = self.run('SYSTEM:QUBESRPC qubes.WaitForSession', passio_popen=True, gui=False, wait=True) + p.communicate(input=self.default_user) + + retcode = subprocess.call([qubes_clipd_path]) + if retcode != 0: + print >> sys.stderr, "ERROR: Cannot start qclipd!" + + def pause(self): + if dry_run: + return + + xc.domain_pause(self.stubdom_xid) + super(QubesHVm, self).pause() + + def unpause(self): + if dry_run: + return + + xc.domain_unpause(self.stubdom_xid) + super(QubesHVm, self).unpause() + + def is_guid_running(self): + xid = self.stubdom_xid + if xid < 0: + return False + if not os.path.exists('/var/run/qubes/guid_running.%d' % xid): + return False + return True class QubesVmCollection(dict): """ @@ -2279,6 +2552,18 @@ class QubesVmCollection(dict): self[vm.qid]=vm return vm + def add_new_hvm(self, name, label = None): + + qid = self.get_new_unused_qid() + vm = QubesHVm (qid=qid, name=name, + netvm = self.get_default_netvm(), + label=label) + + if not self.verify_new_vm (vm): + assert False, "Wrong VM description!" + self[vm.qid]=vm + return vm + def add_new_disposablevm(self, name, template, dispid, label = None): @@ -2574,7 +2859,8 @@ class QubesVmCollection(dict): "installed_by_rpm", "internal", "uses_default_netvm", "label", "memory", "vcpus", "pcidevs", "maxmem", "kernel", "uses_default_kernel", "kernelopts", "uses_default_kernelopts", - "mac", "services", "include_in_backups", "debug", "default_user" ) + "mac", "services", "include_in_backups", "debug", + "default_user", "qrexec_timeout", "qrexec_installed", "drive" ) for attribute in common_attr_list: kwargs[attribute] = element.get(attribute) @@ -2628,13 +2914,20 @@ class QubesVmCollection(dict): if "uses_default_kernelopts" in kwargs: kwargs["uses_default_kernelopts"] = False if kwargs["uses_default_kernelopts"] == "False" else True - if "kernelopts" not in kwargs or kwargs["kernelopts"] == "None": + if "kernelopts" in kwargs and kwargs["kernelopts"] == "None": kwargs.pop("kernelopts") + if "kernelopts" not in kwargs: kwargs["uses_default_kernelopts"] = True if "debug" in kwargs: kwargs["debug"] = True if kwargs["debug"] == "True" else False + if "qrexec_installed" in kwargs: + kwargs["qrexec_installed"] = True if kwargs["qrexec_installed"] == "True" else False + + if "drive" in kwargs and kwargs["drive"] == "None": + kwargs["drive"] = None + return kwargs def set_netvm_dependency(self, element): @@ -2807,6 +3100,20 @@ class QubesVmCollection(dict): os.path.basename(sys.argv[0]), err)) return False + # And HVMs + for element in tree.findall("QubesHVm"): + try: + kwargs = self.parse_xml_element(element) + vm = QubesHVm(**kwargs) + + self[vm.qid] = vm + + self.set_netvm_dependency(element) + except (ValueError, LookupError) as err: + print("{0}: import error (QubesHVm): {1}".format( + os.path.basename(sys.argv[0]), err)) + return False + # Really finally, read in the DisposableVMs for element in tree.findall("QubesDisposableVm"): try: diff --git a/dom0/qvm-core/qubesutils.py b/dom0/qvm-core/qubesutils.py index 6193ba3f..d51dd2cc 100644 --- a/dom0/qvm-core/qubesutils.py +++ b/dom0/qvm-core/qubesutils.py @@ -896,8 +896,8 @@ def backup_restore_print_summary(restore_info, print_callback = print_stdout): + ('}' if vm.is_netvm() else '')"}, "type": {"func": "'Tpl' if vm.is_template() else \ - ('Proxy' if vm.is_proxyvm() else \ - (' Net' if vm.is_netvm() else 'App'))"}, + 'HVM' if vm.type == 'HVM' else \ + vm.type.replace('VM','')"}, "updbl" : {"func": "'Yes' if vm.updateable else ''"}, @@ -1131,10 +1131,14 @@ def backup_restore_do(backup_dir, restore_info, host_collection = None, print_ca new_vm = None try: restore_vm_dir (backup_dir, vm.dir_path, qubes_appvms_dir); - new_vm = host_collection.add_new_appvm(vm.name, template, - conf_file=vm.conf_file, - dir_path=vm.dir_path, - label=vm.label) + if vm.type == "HVM": + new_vm = host_collection.add_new_hvm(vm.name, + label=vm.label) + else: + new_vm = host_collection.add_new_appvm(vm.name, template, + conf_file=vm.conf_file, + dir_path=vm.dir_path, + label=vm.label) except Exception as err: error_callback("ERROR: {0}".format(err)) error_callback("*** Skipping VM: {0}".format(vm.name)) diff --git a/dom0/qvm-tools/qvm-clone b/dom0/qvm-tools/qvm-clone index f3d02d8d..02d24fe9 100755 --- a/dom0/qvm-tools/qvm-clone +++ b/dom0/qvm-tools/qvm-clone @@ -21,7 +21,7 @@ # from qubes.qubes import QubesVmCollection -from qubes.qubes import QubesAppVm, QubesTemplateVm +from qubes.qubes import QubesAppVm, QubesTemplateVm, QubesHVm from qubes.qubes import QubesException from optparse import OptionParser; import sys @@ -72,6 +72,8 @@ def main(): dst_vm = qvm_collection.add_new_appvm(name=dstname, template=src_vm.template, label=src_vm.label, dir_path=options.dir_path) + elif isinstance(src_vm, QubesHVm): + dst_vm = qvm_collection.add_new_hvm(name=dstname, label=src_vm.label) else: print >> sys.stderr, "ERROR: Clone not supported for this type of VM" exit(1) diff --git a/dom0/qvm-tools/qvm-create b/dom0/qvm-tools/qvm-create index 0bc22a8d..d1c4b5ee 100755 --- a/dom0/qvm-tools/qvm-create +++ b/dom0/qvm-tools/qvm-create @@ -38,10 +38,14 @@ def main(): help="Specify the label to use for the new VM (e.g. red, yellow, green, ...)") parser.add_option ("-p", "--proxy", action="store_true", dest="proxyvm", default=False, help="Create ProxyVM") + parser.add_option ("-H", "--hvm", action="store_true", dest="hvm", default=False, + help="Create HVM (implies --standalone)") parser.add_option ("-n", "--net", action="store_true", dest="netvm", default=False, help="Create NetVM") parser.add_option ("-s", "--standalone", action="store_true", dest="standalone", default=False, help="Create standalone VM - independent of template ") + parser.add_option ("-r", "--root", dest="root", default=None, + help="Use provided root.img instead of default/empty one (file will be MOVED)") parser.add_option ("-m", "--mem", dest="mem", default=None, help="Initial memory size (in MB)") parser.add_option ("-c", "--vcpus", dest="vcpus", default=None, @@ -83,6 +87,18 @@ def main(): exit (1) label = QubesVmLabels[options.label] + if options.hvm: + # Only standalone HVMs are supported for now + options.standalone = True + + if not options.standalone and options.root is not None: + print >> sys.stderr, "root.img can be specified only for standalone VMs" + exit (1) + + if options.root is not None and not os.path.exists(options.root): + print >> sys.stderr, "File specified as root.img does not exists" + exit (1) + qvm_collection = QubesVmCollection() qvm_collection.lock_db_for_writing() qvm_collection.load() @@ -91,6 +107,7 @@ def main(): print >> sys.stderr, "A VM with the name '{0}' already exists in the system.".format(vmname) exit(1) + template = None if options.template is not None: template = qvm_collection.get_vm_by_name(options.template) if template is None: @@ -102,7 +119,7 @@ def main(): if (options.verbose): print "--> Using TemplateVM: {0}".format(template.name) - else: + elif not options.hvm: if qvm_collection.get_default_template() is None: print >> sys.stderr, "No default TempleteVM defined!" exit (1) @@ -122,6 +139,8 @@ def main(): vm = qvm_collection.add_new_netvm(vmname, new_vm_template, label = label) elif options.proxyvm: vm = qvm_collection.add_new_proxyvm(vmname, new_vm_template, label = label) + elif options.hvm: + vm = qvm_collection.add_new_hvm(vmname, label = label) else: vm = qvm_collection.add_new_appvm(vmname, new_vm_template, label = label) except QubesException as err: @@ -139,6 +158,9 @@ def main(): try: vm.create_on_disk(verbose=options.verbose, source_template=template) + if options.root: + os.unlink(vm.root_img) + os.rename(options.root, vm.root_img) except (IOError, OSError) as err: print >> sys.stderr, "ERROR: {0}".format(err) diff --git a/dom0/qvm-tools/qvm-prefs b/dom0/qvm-tools/qvm-prefs index 65bd1973..760e41ce 100755 --- a/dom0/qvm-tools/qvm-prefs +++ b/dom0/qvm-tools/qvm-prefs @@ -53,21 +53,23 @@ def do_list(vm): print fmt.format ("root COW img", vm.rootcow_img) if vm.template is not None: print fmt.format ("root img", vm.template.root_img) - if hasattr(vm, 'volatile_img'): + if hasattr(vm, 'volatile_img') and vm.volatile_img is not None: print fmt.format ("root volatile img", vm.volatile_img) - if hasattr(vm, 'private_img'): + if hasattr(vm, 'private_img') and vm.private_img is not None: print fmt.format ("private img", vm.private_img) print fmt.format ("vcpus", str(vm.vcpus)) print fmt.format ("memory", vm.memory) if hasattr(vm, 'maxmem'): print fmt.format ("maxmem", vm.maxmem) print fmt.format ("MAC", "%s%s" % (vm.mac, " (auto)" if vm._mac is None else "")) + if hasattr(vm, 'kernel'): if vm.uses_default_kernel: print fmt.format ("kernel", "%s (default)" % vm.kernel) else: print fmt.format ("kernel", vm.kernel) + if hasattr(vm, 'kernelopts'): if vm.uses_default_kernelopts: print fmt.format ("kernelopts", "%s (default)" % vm.kernelopts) @@ -79,6 +81,18 @@ def do_list(vm): if hasattr(vm, 'default_user'): print fmt.format("default user", str(vm.default_user)) + if hasattr(vm, 'qrexec_installed'): + print fmt.format("qrexec_installed", str(vm.qrexec_installed)) + + if hasattr(vm, 'qrexec_timeout'): + print fmt.format("qrexec timeout", str(vm.qrexec_timeout)) + + if hasattr(vm, 'drive'): + print fmt.format("drive", str(vm.drive)) + + if hasattr(vm, 'timezone'): + print fmt.format("timezone", str(vm.timezone)) + def set_label(vms, vm, args): if len (args) != 1: print >> sys.stderr, "Missing label name argument!" @@ -274,6 +288,16 @@ def set_name(vms, vm, args): vm.set_name(args[0]) return True +def set_drive(vms, vm, args): + if len (args) != 1: + print >> sys.stderr, "Missing new drive content (file/device)!" + return False + + if args[0] == '' or args[0].lower() == 'none': + vm.drive = None + else: + vm.drive = args[0] + return True def set_debug(vms, vm, args): if len (args) != 1: @@ -304,6 +328,34 @@ def set_include_in_backups(vms, vm, args): vm.include_in_backups = bool(eval(args[0].capitalize())) return True +def set_qrexec_installed(vms, vm, args): + if len (args) != 1: + print >> sys.stderr, "Missing value (True/False)!" + return False + + vm.qrexec_installed = bool(eval(args[0].capitalize())) + return True + +def set_qrexec_timeout(vms, vm, args): + if len (args) != 1: + print >> sys.stderr, "Missing timeout value (seconds)!" + return False + + vm.qrexec_timeout = int(args[0]) + return True + +def set_timezone(vms, vm, args): + if len (args) != 1: + print >> sys.stderr, "Missing value ('localtime' or timeoffset in seconds)!" + return False + + if not args[0].isdigit() and args[0].lower() == 'localtime': + print >> sys.stderr, "Invalid timezone value!" + return False + + vm.timezone = args[0] + return True + properties = { "include_in_backups": set_include_in_backups, "pcidevs": set_pcidevs, @@ -316,9 +368,13 @@ properties = { "vcpus" : set_vcpus, "kernelopts": set_kernelopts, "name": set_name, + "drive": set_drive, "mac": set_mac, "debug": set_debug, "default_user": set_default_user, + "qrexec_installed": set_qrexec_installed, + "qrexec_timeout": set_qrexec_timeout, + "timezone": set_timezone, } diff --git a/dom0/qvm-tools/qvm-start b/dom0/qvm-tools/qvm-start index 7ac573e0..bceadff8 100755 --- a/dom0/qvm-tools/qvm-start +++ b/dom0/qvm-tools/qvm-start @@ -78,6 +78,23 @@ def main(): print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(vmname) exit(1) + if bool(options.drive_hd) + bool(options.drive_cdrom) + bool(options.drive) > 1: + print >> sys.stderr, "Only one of --drive, --cdrom, --hddisk can be specified" + exit(1) + + if options.drive_hd: + options.drive = 'hd:' + options.drive_hd + + if options.drive_cdrom: + options.drive = 'cdrom:' + options.drive_cdrom + + if options.drive: + if hasattr(vm, 'drive'): + vm.drive = options.drive + else: + print >> sys.stderr, "This VM does not support attaching drives" + exit (1) + if options.custom_config: vm.conf_file = options.custom_config diff --git a/dom0/qvm-tools/qvm-sync-clock b/dom0/qvm-tools/qvm-sync-clock index fad4a7d0..08a537fa 100755 --- a/dom0/qvm-tools/qvm-sync-clock +++ b/dom0/qvm-tools/qvm-sync-clock @@ -64,7 +64,7 @@ def main(): # Ignore retcode, try even if nm-online failed - user can setup network manually # on-online has timeout 30sec by default - net_vm.run('user:nm-online -x', verbose=verbose, wait=True, ignore_stderr=True) + net_vm.run('DEFAULT:nm-online -x', verbose=verbose, wait=True, ignore_stderr=True) # Sync clock if clock_vm.run('root:QUBESRPC qubes.SyncNtpClock dom0', verbose=verbose, wait=True, ignore_stderr=True) != 0: @@ -72,7 +72,7 @@ def main(): sys.exit(1) # Use the date format based on RFC2822 to avoid localisation issues - p = clock_vm.run('user:date -u -R', verbose=verbose, passio_popen=True, ignore_stderr=True) + p = clock_vm.run('DEFAULT:date -u -R', verbose=verbose, passio_popen=True, ignore_stderr=True) date_out = p.stdout.read(100) date_out = date_out.strip() if not re.match(r'^[A-Za-z]+[,] [0-9][0-9] [A-Za-z]+ [0-9][0-9][0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [+]0000$', date_out): diff --git a/qrexec/qrexec_agent.c b/qrexec/qrexec_agent.c index aa81cb78..66d14045 100644 --- a/qrexec/qrexec_agent.c +++ b/qrexec/qrexec_agent.c @@ -228,8 +228,8 @@ void send_exit_code(int client_id, int status) s_hdr.len = sizeof status; write_all_vchan_ext(&s_hdr, sizeof s_hdr); write_all_vchan_ext(&status, sizeof(status)); - fprintf(stderr, "send exit code for client_id %d pid %d\n", - client_id, client_info[client_id].pid); + fprintf(stderr, "send exit code %d for client_id %d pid %d\n", + status, client_id, client_info[client_id].pid); } @@ -239,8 +239,9 @@ void remove_process(int client_id, int status) int i; if (!client_info[client_id].pid) return; - fork_and_flush_stdin(client_info[client_id].stdin_fd, - &client_info[client_id].buffer); + if (client_info[client_id].stdin_fd >= 0) + fork_and_flush_stdin(client_info[client_id].stdin_fd, + &client_info[client_id].buffer); #if 0 // let's let it die by itself, possibly after it has received buffered stdin kill(client_info[client_id].pid, SIGKILL); @@ -283,7 +284,7 @@ void handle_input(int client_id, int len) char buf[len]; read_all_vchan_ext(buf, len); - if (!client_info[client_id].pid) + if (!client_info[client_id].pid || client_info[client_id].stdin_fd == -1) return; if (len == 0) { @@ -306,7 +307,10 @@ void handle_input(int client_id, int len) client_info[client_id].is_blocked = 1; break; case WRITE_STDIN_ERROR: - remove_process(client_id, 128); + // do not remove process, as it still can write data to stdout + close(client_info[client_id].stdin_fd); + client_info[client_id].stdin_fd = -1; + client_info[client_id].is_blocked = 0; break; default: fprintf(stderr, "unknown write_stdin?\n"); @@ -494,7 +498,11 @@ void flush_client_data_agent(int client_id) } break; case WRITE_STDIN_ERROR: - remove_process(client_id, 128); + // do not remove process, as it still can write data to stdout + info->is_blocked = 0; + close(info->stdin_fd); + info->stdin_fd = -1; + info->is_close_after_flush_needed = 0; break; case WRITE_STDIN_BUFFERED: break; diff --git a/qrexec/qrexec_client.c b/qrexec/qrexec_client.c index 4065e6b0..b4d1835b 100644 --- a/qrexec/qrexec_client.c +++ b/qrexec/qrexec_client.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "qrexec.h" #include "buffer.h" #include "glue.h" @@ -106,12 +107,33 @@ void handle_input(int s) do_exit(1); } if (ret == 0) { + close(local_stdout_fd); local_stdout_fd = -1; shutdown(s, SHUT_WR); + if (local_stdin_fd == -1) { + // if pipe in opposite direction already closed, no need to stay alive + do_exit(0); + } } if (!write_all(s, buf, ret)) { - perror("write daemon"); - do_exit(1); + if (errno == EPIPE) { + // daemon disconnected its end of socket, so no future data will be + // send there; there is no sense to read from child stdout + // + // since AF_UNIX socket is buffered it doesn't mean all data was + // received from the agent + close(local_stdout_fd); + local_stdout_fd = -1; + if (local_stdin_fd == -1) { + // since child does no longer accept data on its stdin, doesn't + // make sense to process the data from the daemon + // + // we don't know real exit VM process code (exiting here, before + // MSG_SERVER_TO_CLIENT_EXIT_CODE message) + do_exit(1); + } + } else + perror("write daemon"); } } @@ -136,11 +158,20 @@ void handle_daemon_data(int s) switch (hdr.type) { case MSG_SERVER_TO_CLIENT_STDOUT: - if (hdr.len == 0) + if (local_stdin_fd == -1) + break; + if (hdr.len == 0) { close(local_stdin_fd); - else if (!write_all(local_stdin_fd, buf, hdr.len)) { - perror("write local stdout"); - do_exit(1); + local_stdin_fd = -1; + } else if (!write_all(local_stdin_fd, buf, hdr.len)) { + if (errno == EPIPE) { + // remote side have closed its stdin, handle data in oposite + // direction (if any) before exit + local_stdin_fd = -1; + } else { + perror("write local stdout"); + do_exit(1); + } } break; case MSG_SERVER_TO_CLIENT_STDERR: diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index 9298b836..7b24e502 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -38,8 +38,10 @@ enum client_flags { CLIENT_INVALID = 0, // table slot not used CLIENT_CMDLINE = 1, // waiting for cmdline from client CLIENT_DATA = 2, // waiting for data from client - CLIENT_DONT_READ = 4, // don't read from the client, the other side pipe is full, or EOF - CLIENT_OUTQ_FULL = 8 // don't write to client, its stdin pipe is full + CLIENT_DONT_READ = 4, // don't read from the client, the other side pipe is full, or EOF (additionally marked with CLIENT_EOF) + CLIENT_OUTQ_FULL = 8, // don't write to client, its stdin pipe is full + CLIENT_EOF = 16, // got EOF + CLIENT_EXITED = 32 // only send remaining data from client and remove from list }; struct _client { @@ -57,6 +59,9 @@ struct _client clients[MAX_CLIENTS]; // data on all qrexec_client connections int max_client_fd = -1; // current max fd of all clients; so that we need not to scan all the "clients" table int qrexec_daemon_unix_socket_fd; // /var/run/qubes/qrexec.xid descriptor +char *default_user = "user"; +char default_user_keyword[] = "DEFAULT:"; +#define default_user_keyword_len_without_colon (sizeof(default_user_keyword)-2) void sigusr1_handler(int x) { @@ -82,7 +87,31 @@ int create_qrexec_socket(int domid, char *domname) return get_server_socket(socket_address); } -#define MAX_STARTUP_TIME 60 +#define MAX_STARTUP_TIME_DEFAULT 60 + +/* ask on qrexec connect timeout */ +int ask_on_connect_timeout(int xid, int timeout) +{ + char text[1024]; + int ret; + snprintf(text, sizeof(text), + "kdialog --title 'Qrexec daemon' --warningyesno " + "'Timeout while trying connecting to qrexec agent (Xen domain ID: %d). Do you want to wait next %d seconds?'", + xid, timeout); + ret = system(text); + ret = WEXITSTATUS(ret); + // fprintf(stderr, "ret=%d\n", ret); + switch (ret) { + case 1: /* NO */ + return 0; + case 0: /*YES */ + return 1; + default: + // this can be the case at system startup (netvm), when Xorg isn't running yet + // so just don't give possibility to extend the timeout + return 0; + } +} /* do the preparatory tasks, needed before entering the main event loop */ void init(int xid) @@ -90,13 +119,23 @@ void init(int xid) char qrexec_error_log_name[256]; int logfd; int i; + pid_t pid; + int startup_timeout = MAX_STARTUP_TIME_DEFAULT; + char *startup_timeout_str = NULL; if (xid <= 0) { fprintf(stderr, "domain id=0?\n"); exit(1); } + startup_timeout_str = getenv("QREXEC_STARTUP_TIMEOUT"); + if (startup_timeout_str) { + startup_timeout = atoi(startup_timeout_str); + if (startup_timeout == 0) + // invalid number + startup_timeout = MAX_STARTUP_TIME_DEFAULT; + } signal(SIGUSR1, sigusr1_handler); - switch (fork()) { + switch (pid=fork()) { case -1: perror("fork"); exit(1); @@ -104,11 +143,16 @@ void init(int xid) break; default: fprintf(stderr, "Waiting for VM's qrexec agent."); - for (i=0;ilen; char buf[len]; + int use_default_user = 0; if (!read_all(fd, buf, len)) { terminate_client_and_flush_data(fd); - return; + return 0; + } + if (!strncmp(buf, default_user_keyword, default_user_keyword_len_without_colon+1)) { + use_default_user = 1; + s_hdr->len -= default_user_keyword_len_without_colon; // -1 because of colon + s_hdr->len += strlen(default_user); } write_all_vchan_ext(s_hdr, sizeof(*s_hdr)); - write_all_vchan_ext(buf, len); + if (use_default_user) { + write_all_vchan_ext(default_user, strlen(default_user)); + write_all_vchan_ext(buf+default_user_keyword_len_without_colon, len-default_user_keyword_len_without_colon); + } else + write_all_vchan_ext(buf, len); + return 1; } void handle_cmdline_message_from_client(int fd) @@ -224,7 +279,10 @@ void handle_cmdline_message_from_client(int fd) s_hdr.client_id = fd; s_hdr.len = hdr.len; - get_cmdline_body_from_client_and_pass_to_agent(fd, &s_hdr); + if (!get_cmdline_body_from_client_and_pass_to_agent(fd, &s_hdr)) + // client disconnected while sending cmdline, above call already + // cleaned up client info + return; clients[fd].state = CLIENT_DATA; set_nonblock(fd); // so that we can detect full queue without blocking if (hdr.type == MSG_CLIENT_TO_SERVER_JUST_EXEC) @@ -262,7 +320,10 @@ void handle_message_from_client(int fd) write_all_vchan_ext(&s_hdr, sizeof(s_hdr)); write_all_vchan_ext(buf, ret); if (ret == 0) // EOF - so don't select() on this client - clients[fd].state |= CLIENT_DONT_READ; + clients[fd].state |= CLIENT_DONT_READ | CLIENT_EOF; + if (clients[fd].state & CLIENT_EXITED) + //client already exited and all data sent - cleanup now + terminate_client_and_flush_data(fd); } /* @@ -278,7 +339,14 @@ void write_buffered_data_to_client(int client_id) clients[client_id].state &= ~CLIENT_OUTQ_FULL; break; case WRITE_STDIN_ERROR: - terminate_client_and_flush_data(client_id); + // do not write to this fd anymore + clients[client_id].state |= CLIENT_EXITED; + if (clients[client_id].state & CLIENT_EOF) + terminate_client_and_flush_data(client_id); + else + // client will be removed when read returns 0 (EOF) + // clear CLIENT_OUTQ_FULL flag to no select on this fd anymore + clients[client_id].state &= ~CLIENT_OUTQ_FULL; break; case WRITE_STDIN_BUFFERED: // no room for all data, don't clear CLIENT_OUTQ_FULL flag break; @@ -301,6 +369,9 @@ void get_packet_data_from_agent_and_pass_to_client(int client_id, struct client_ /* make both the header and data be consecutive in the buffer */ *(struct client_header *) buf = *hdr; read_all_vchan_ext(buf + sizeof(*hdr), len); + if (clients[client_id].state & CLIENT_EXITED) + // ignore data for no longer running client + return; switch (write_stdin (client_id, client_id, buf, len + sizeof(*hdr), @@ -311,7 +382,11 @@ void get_packet_data_from_agent_and_pass_to_client(int client_id, struct client_ clients[client_id].state |= CLIENT_OUTQ_FULL; break; case WRITE_STDIN_ERROR: - terminate_client_and_flush_data(client_id); + // do not write to this fd anymore + clients[client_id].state |= CLIENT_EXITED; + // if already got EOF, remove client + if (clients[client_id].state & CLIENT_EOF) + terminate_client_and_flush_data(client_id); break; default: fprintf(stderr, "unknown write_stdin?\n"); @@ -557,10 +632,12 @@ int main(int argc, char **argv) int max; sigset_t chld_set; - if (argc != 2) { - fprintf(stderr, "usage: %s domainid\n", argv[0]); + if (argc != 2 && argc != 3) { + fprintf(stderr, "usage: %s domainid [default user]\n", argv[0]); exit(1); } + if (argc == 3) + default_user = argv[2]; init(atoi(argv[1])); sigemptyset(&chld_set); sigaddset(&chld_set, SIGCHLD); diff --git a/qrexec/qrexec_policy b/qrexec/qrexec_policy index 69fed14b..69b9cafb 100755 --- a/qrexec/qrexec_policy +++ b/qrexec/qrexec_policy @@ -5,6 +5,7 @@ import os.path import subprocess import xen.lowlevel.xl import qubes.guihelpers +from optparse import OptionParser import fcntl POLICY_FILE_DIR="/etc/qubes_rpc/policy" @@ -126,10 +127,18 @@ def policy_editor(domain, target, exec_index): subprocess.call(["/usr/bin/zenity", "--info", "--text", text]) def main(): - domain=sys.argv[1] - target=sys.argv[2] - exec_index=sys.argv[3] - process_ident=sys.argv[4] + usage = "usage: %prog [options] " + parser = OptionParser (usage) + parser.add_option ("--assume-yes-for-ask", action="store_true", dest="assume_yes_for_ask", default=False, + help="Allow run of service without confirmation if policy say 'ask'") + parser.add_option ("--just-evaluate", action="store_true", dest="just_evaluate", default=False, + help="Do not run the service, only evaluate policy; retcode=0 means 'allow'") + + (options, args) = parser.parse_args () + domain=args[0] + target=args[1] + exec_index=args[2] + process_ident=args[3] policy_list=read_policy_file(exec_index) if policy_list==None: @@ -139,6 +148,9 @@ def main(): policy_list=list() policy_dict=find_policy(policy_list, domain, target) + + if policy_dict["action"] == "ask" and options.assume_yes_for_ask: + policy_dict["action"] = "allow" if policy_dict["action"] == "ask": user_choice = confirm_execution(domain, target, exec_index) @@ -149,14 +161,20 @@ def main(): policy_dict["action"] = "allow" else: policy_dict["action"] = "deny" - + + if options.just_evaluate: + if policy_dict["action"] == "allow": + exit(0) + else: + exit(1) + if policy_dict["action"] == "allow": if policy_dict.has_key("action.target"): target=policy_dict["action.target"] if policy_dict.has_key("action.user"): user=policy_dict["action.user"] else: - user="user" + user="DEFAULT" do_execute(domain, target, user, exec_index, process_ident) print >> sys.stderr, "Rpc denied:", domain, target, exec_index diff --git a/qrexec/write_stdin.c b/qrexec/write_stdin.c index 7ace53b6..939b72ea 100644 --- a/qrexec/write_stdin.c +++ b/qrexec/write_stdin.c @@ -109,18 +109,6 @@ int write_stdin(int fd, int client_id, char *data, int len, } -void set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - -void set_block(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, fl & ~O_NONBLOCK); -} - /* Data feed process has exited, so we need to clear all control structures for the client. However, if we have buffered data for the client (which is rare btw), diff --git a/qubes_rpc/ioall.c b/qubes_rpc/ioall.c index 239f3333..2a81df4d 100644 --- a/qubes_rpc/ioall.c +++ b/qubes_rpc/ioall.c @@ -32,6 +32,17 @@ void perror_wrapper(char * msg) errno=prev; } +void set_nonblock(int fd) +{ + int fl = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, fl | O_NONBLOCK); +} + +void set_block(int fd) +{ + int fl = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, fl & ~O_NONBLOCK); +} int write_all(int fd, void *buf, int size) { @@ -42,7 +53,6 @@ int write_all(int fd, void *buf, int size) if (ret == -1 && errno == EINTR) continue; if (ret <= 0) { - perror_wrapper("write"); return 0; } written += ret; @@ -65,9 +75,14 @@ int read_all(int fd, void *buf, int size) return 0; } if (ret < 0) { - perror_wrapper("read"); + if (errno != EAGAIN) + perror_wrapper("read"); return 0; } + if (got_read == 0) { + // force blocking operation on further reads + set_block(fd); + } got_read += ret; } // fprintf(stderr, "read %d bytes\n", size); diff --git a/qubes_rpc/ioall.h b/qubes_rpc/ioall.h index 1a700c6c..c9913e8f 100644 --- a/qubes_rpc/ioall.h +++ b/qubes_rpc/ioall.h @@ -1,3 +1,5 @@ int write_all(int fd, void *buf, int size); int read_all(int fd, void *buf, int size); int copy_fd_all(int fdout, int fdin); +void set_nonblock(int fd); +void set_block(int fd); diff --git a/qubes_rpc/qfile-agent.c b/qubes_rpc/qfile-agent.c index 4b37738f..0a1256fd 100644 --- a/qubes_rpc/qfile-agent.c +++ b/qubes_rpc/qfile-agent.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "filecopy.h" #include "crc32.h" @@ -49,6 +50,37 @@ void do_notify_progress(long long total, int flag) } } +void wait_for_result() +{ + struct result_header hdr; + + if (!read_all(0, &hdr, sizeof(hdr))) { + if (errno == EAGAIN) { + // no result sent and stdin still open + return; + } else { + // other read error or EOF + exit(1); // hopefully remote has produced error message + } + } + if (hdr.error_code != 0) { + switch (hdr.error_code) { + case EEXIST: + gui_fatal("File copy: not overwriting existing file. Clean incoming dir, and retry copy"); + break; + case EINVAL: + gui_fatal("File copy: Corrupted data from packer"); + break; + default: + gui_fatal("File copy: %s", + strerror(hdr.error_code)); + } + } + if (hdr.crc32 != crc32_sum) { + gui_fatal("File transfer failed: checksum mismatch"); + } +} + void notify_progress(int size, int flag) { static long long total = 0; @@ -56,6 +88,11 @@ void notify_progress(int size, int flag) total += size; if (total > prev_total + PROGRESS_NOTIFY_DELTA || (flag != PROGRESS_FLAG_NORMAL)) { + // check for possible error from qfile-unpacker; if error occured, + // exit() will be called, so don't bother with current state + // (notify_progress can be called as callback from copy_file()) + if (flag == PROGRESS_FLAG_NORMAL) + wait_for_result(); do_notify_progress(total, flag); prev_total = total; } @@ -64,8 +101,11 @@ void notify_progress(int size, int flag) void write_headers(struct file_header *hdr, char *filename) { if (!write_all_with_crc(1, hdr, sizeof(*hdr)) - || !write_all_with_crc(1, filename, hdr->namelen)) + || !write_all_with_crc(1, filename, hdr->namelen)) { + set_block(0); + wait_for_result(); exit(1); + } } int single_file_processor(char *filename, struct stat *st) @@ -89,13 +129,15 @@ int single_file_processor(char *filename, struct stat *st) hdr.filelen = st->st_size; write_headers(&hdr, filename); ret = copy_file(1, fd, hdr.filelen, &crc32_sum); - // if COPY_FILE_WRITE_ERROR, hopefully remote will produce a message if (ret != COPY_FILE_OK) { if (ret != COPY_FILE_WRITE_ERROR) gui_fatal("Copying file %s: %s", filename, copy_file_status_to_str(ret)); - else + else { + set_block(0); + wait_for_result(); exit(1); + } } close(fd); } @@ -109,9 +151,14 @@ int single_file_processor(char *filename, struct stat *st) gui_fatal("readlink %s", filename); hdr.filelen = st->st_size + 1; write_headers(&hdr, filename); - if (!write_all_with_crc(1, name, st->st_size + 1)) + if (!write_all_with_crc(1, name, st->st_size + 1)) { + set_block(0); + wait_for_result(); exit(1); + } } + // check for possible error from qfile-unpacker + wait_for_result(); return 0; } @@ -147,7 +194,6 @@ int do_fs_walk(char *file) void notify_end_and_wait_for_result() { - struct result_header hdr; struct file_header end_hdr; /* nofity end of transfer */ @@ -156,17 +202,8 @@ void notify_end_and_wait_for_result() end_hdr.filelen = 0; write_all_with_crc(1, &end_hdr, sizeof(end_hdr)); - /* wait for result */ - if (!read_all(0, &hdr, sizeof(hdr))) { - exit(1); // hopefully remote has produced error message - } - if (hdr.error_code != 0) { - gui_fatal("Error writing files: %s", - strerror(hdr.error_code)); - } - if (hdr.crc32 != crc32_sum) { - gui_fatal("File transfer failed: checksum mismatch"); - } + set_block(0); + wait_for_result(); } char *get_abs_path(char *cwd, char *pathname) @@ -186,6 +223,8 @@ int main(int argc, char **argv) char *sep; signal(SIGPIPE, SIG_IGN); + // this will allow checking for possible feedback packet in the middle of transfer + set_nonblock(0); notify_progress(0, PROGRESS_FLAG_INIT); crc32_sum = 0; cwd = getcwd(NULL, 0); diff --git a/qubes_rpc/qfile-unpacker.c b/qubes_rpc/qfile-unpacker.c index dd0a510d..402a0092 100644 --- a/qubes_rpc/qfile-unpacker.c +++ b/qubes_rpc/qfile-unpacker.c @@ -29,32 +29,14 @@ int prepare_creds_return_uid(char *username) return pwd->pw_uid; } -void wait_for_child(int statusfd) -{ - int status; - if (read(statusfd, &status, sizeof status)!=sizeof status) - gui_fatal("File copy error: Internal error reading status from unpacker"); - errno = status; - switch (status) { - case LEGAL_EOF: break; - case 0: gui_fatal("File copy: Connection terminated unexpectedly"); break; - case EINVAL: gui_fatal("File copy: Corrupted data from packer"); break; - case EEXIST: gui_fatal("File copy: not overwriting existing file. Clean ~/incoming, and retry copy"); break; - default: gui_fatal("File copy"); - } -} - -extern void do_unpack(int); +extern int do_unpack(void); int main(int argc, char ** argv) { char *incoming_dir; - int pipefds[2]; int uid; char *remote_domain; - pipe(pipefds); - uid = prepare_creds_return_uid("user"); remote_domain = getenv("QREXEC_REMOTE_DOMAIN"); @@ -67,23 +49,8 @@ int main(int argc, char ** argv) mkdir(incoming_dir, 0700); if (chdir(incoming_dir)) gui_fatal("Error chdir to %s", incoming_dir); - switch (fork()) { - case -1: - perror("fork"); - exit(1); - case 0: - if (chroot(incoming_dir)) //impossible - gui_fatal("Error chroot to %s", incoming_dir); - setuid(uid); - close(pipefds[0]); - do_unpack(pipefds[1]); - exit(0); - default:; - } - + if (chroot(incoming_dir)) //impossible + gui_fatal("Error chroot to %s", incoming_dir); setuid(uid); - close(pipefds[1]); - wait_for_child(pipefds[0]); - - return 0; + return do_unpack(); } diff --git a/qubes_rpc/qubes.Filecopy.policy b/qubes_rpc/qubes.Filecopy.policy index 0a0d7352..e0bef3ef 100644 --- a/qubes_rpc/qubes.Filecopy.policy +++ b/qubes_rpc/qubes.Filecopy.policy @@ -3,4 +3,4 @@ ## Please use a single # to start your custom comments -$anyvm $anyvm ask,user=root +$anyvm $anyvm ask diff --git a/qubes_rpc/unpack.c b/qubes_rpc/unpack.c index 580095d0..9c018fa2 100644 --- a/qubes_rpc/unpack.c +++ b/qubes_rpc/unpack.c @@ -35,14 +35,24 @@ int read_all_with_crc(int fd, void *buf, int size) { return ret; } -int global_status_fd; -void do_exit(int code) -{ - int codebuf = code; - write(global_status_fd, &codebuf, sizeof codebuf); - exit(0); +void send_status_and_crc(int code) { + struct result_header hdr; + int saved_errno; + + saved_errno = errno; + hdr.error_code = code; + hdr.crc32 = crc32_sum; + if (!write_all(1, &hdr, sizeof(hdr))) + perror("write status"); + errno = saved_errno; } +void do_exit(int code) +{ + close(0); + send_status_and_crc(code); + exit(code); +} void fix_times_and_perms(struct file_header *untrusted_hdr, char *untrusted_name) @@ -130,20 +140,8 @@ void process_one_file(struct file_header *untrusted_hdr) do_exit(EINVAL); } -void send_status_and_crc() { - struct result_header hdr; - int saved_errno; - - saved_errno = errno; - hdr.error_code = errno; - hdr.crc32 = crc32_sum; - write_all(1, &hdr, sizeof(hdr)); - errno = saved_errno; -} - -void do_unpack(int fd) +int do_unpack() { - global_status_fd = fd; struct file_header untrusted_hdr; /* initialize checksum */ crc32_sum = 0; @@ -158,9 +156,6 @@ void do_unpack(int fd) if (files_limit && total_files > files_limit) do_exit(EDQUOT); } - send_status_and_crc(); - if (errno) - do_exit(errno); - else - do_exit(LEGAL_EOF); + send_status_and_crc(errno); + return errno; } diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index ac2c460f..c08cb709 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -40,6 +40,7 @@ BuildRequires: xen-devel Requires: python, xen-runtime, pciutils, python-inotify, python-daemon, kernel-qubes-dom0 Conflicts: qubes-gui-dom0 < 1.1.13 Requires: xen >= 4.1.0-2 +Requires: xen-hvm Requires: createrepo Requires: gnome-packagekit Requires: cronie @@ -115,6 +116,7 @@ cp qubes_rpc/qubes-receive-updates $RPM_BUILD_ROOT/usr/lib/qubes/ cp ../misc/block_add_change $RPM_BUILD_ROOT/usr/lib/qubes/ cp ../misc/block_remove $RPM_BUILD_ROOT/usr/lib/qubes/ cp ../misc/block_cleanup $RPM_BUILD_ROOT/usr/lib/qubes/ +cp aux-tools/block_cleaner_daemon.py $RPM_BUILD_ROOT/usr/lib/qubes/ cp aux-tools/fix_dir_perms.sh $RPM_BUILD_ROOT/usr/lib/qubes/ mkdir -p $RPM_BUILD_ROOT/etc/qubes_rpc/policy @@ -162,7 +164,9 @@ cp misc/qubes-servicevm.directory.template $RPM_BUILD_ROOT/usr/share/qubes/ cp misc/qubes-dispvm.directory $RPM_BUILD_ROOT/usr/share/qubes/ cp misc/qubes-dispvm-firefox.desktop $RPM_BUILD_ROOT/usr/share/qubes/ cp misc/qubes-appmenu-select.desktop $RPM_BUILD_ROOT/usr/share/qubes/ +cp misc/qubes-start.desktop $RPM_BUILD_ROOT/usr/share/qubes/ cp misc/vm-template.conf $RPM_BUILD_ROOT/usr/share/qubes/ +cp misc/vm-template-hvm.conf $RPM_BUILD_ROOT/usr/share/qubes/ mkdir -p $RPM_BUILD_ROOT/usr/bin cp ../network/qubes_setup_dnat_to_ns $RPM_BUILD_ROOT/usr/lib/qubes @@ -361,6 +365,7 @@ fi /usr/lib/qubes/block_add_change /usr/lib/qubes/block_remove /usr/lib/qubes/block_cleanup +/usr/lib/qubes/block_cleaner_daemon.py* /usr/lib/qubes/fix_dir_perms.sh %attr(4750,root,qubes) /usr/lib/qubes/qfile-dom0-unpacker %attr(770,root,qubes) %dir /var/lib/qubes @@ -378,7 +383,9 @@ fi /usr/share/qubes/qubes-dispvm.directory /usr/share/qubes/qubes-dispvm-firefox.desktop /usr/share/qubes/qubes-appmenu-select.desktop +/usr/share/qubes/qubes-start.desktop /usr/share/qubes/vm-template.conf +/usr/share/qubes/vm-template-hvm.conf /usr/lib/qubes/qubes_setup_dnat_to_ns /usr/lib/qubes/qubes_fix_nm_conf.sh /etc/dhclient.d/qubes_setup_dnat_to_ns.sh diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index dd5e26a9..7b202d37 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -405,7 +405,7 @@ rm -rf $RPM_BUILD_ROOT /usr/lib/qubes/meminfo-writer /usr/lib/qubes/network-manager-prepare-conf-dir /usr/lib/qubes/qfile-agent -/usr/lib/qubes/qfile-unpacker +%attr(4755,root,root) /usr/lib/qubes/qfile-unpacker /usr/lib/qubes/qopen-in-vm /usr/lib/qubes/qrexec_agent /usr/lib/qubes/qrexec_client_vm diff --git a/vchan/Makefile.stubdom b/vchan/Makefile.stubdom new file mode 100644 index 00000000..af716cef --- /dev/null +++ b/vchan/Makefile.stubdom @@ -0,0 +1,36 @@ +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2012 Marek Marczykowski +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +# Assume it is placed as xen-root/tools/vchan + +XEN_ROOT = ../.. +include $(XEN_ROOT)/tools/Rules.mk + +CFLAGS+=-Wall -I$(XEN_ROOT)/tools/libxc -DCONFIG_STUBDOM +all: libvchan.a + +libvchan.a: init.o io.o + $(AR) rc $@ $^ + +clean: + rm -f *.o *so *~ client server node node-select + + diff --git a/vchan/init.c b/vchan/init.c index 30cc2001..09e05ff9 100644 --- a/vchan/init.c +++ b/vchan/init.c @@ -19,41 +19,103 @@ * */ +#ifndef WINNT + +#include +#include +#include #include #include #include #include #include -#include #include #include #include +#ifndef CONFIG_STUBDOM +#include "../u2mfn/u2mfnlib.h" +#else +#include +#endif + +#endif + +#include +#include #include #include "libvchan.h" -#include "../u2mfn/u2mfnlib.h" -static int ring_init(struct libvchan *ctrl) + +static int fill_ctrl(struct libvchan *ctrl, struct vchan_interface *ring, int ring_ref) { - int u2mfn = open("/proc/u2mfn", O_RDONLY); - int mfn; - struct vchan_interface *ring; - ring = (struct vchan_interface *) u2mfn_alloc_kpage (); - - if (ring == MAP_FAILED) + if (!ctrl || !ring) return -1; ctrl->ring = ring; - if (u2mfn_get_last_mfn (&mfn) < 0) - return -1; + ctrl->ring_ref = ring_ref; - ctrl->ring_ref = mfn; - close(u2mfn); ring->cons_in = ring->prod_in = ring->cons_out = ring->prod_out = 0; ring->server_closed = ring->client_closed = 0; ring->debug = 0xaabbccdd; - return 0; + + return 0; } + +#ifdef QREXEC_RING_V2 +static int ring_init(struct libvchan *ctrl) +{ + struct gntmem_handle* h; + grant_ref_t grants[1]; + int result; + struct vchan_interface *ring; + + h = gntmem_open(); + if (h == INVALID_HANDLE_VALUE) + return -1; + + gntmem_set_local_quota(h, 1); + gntmem_set_global_quota(h, 1); + + memset(grants, 0, sizeof(grants)); + ring = gntmem_grant_pages_to_domain(h, 0, 1, grants); + if (!ring) { + gntmem_close(h); + return -1; + } + + return fill_ctrl(ctrl, ring, grants[0]); +} + +#else + +static int ring_init(struct libvchan *ctrl) +{ + int mfn; + struct vchan_interface *ring; +#ifdef CONFIG_STUBDOM + ring = (struct vchan_interface *) memalign(XC_PAGE_SIZE, sizeof(*ring)); + + if (!ring) + return -1; + + + mfn = virtual_to_mfn(ring); +#else + ring = (struct vchan_interface *) u2mfn_alloc_kpage (); + + if (ring == MAP_FAILED) + return -1; + + if (u2mfn_get_last_mfn (&mfn) < 0) + return -1; +#endif + + return fill_ctrl(ctrl, ring, mfn); +} + +#endif + /** creates event channel; creates "ring-ref" and "event-channel" xenstore entries; @@ -66,12 +128,16 @@ static int server_interface_init(struct libvchan *ctrl, int devno) char buf[64]; char ref[16]; #ifdef XENCTRL_HAS_XC_INTERFACE - xc_evtchn *evfd; + xc_evtchn *evfd; #else - int evfd; + EVTCHN evfd; #endif evtchn_port_or_error_t port; +#ifdef WINNT xs = xs_domain_open(); +#else + xs = xs_daemon_open(); +#endif if (!xs) { return ret; } @@ -90,6 +156,14 @@ static int server_interface_init(struct libvchan *ctrl, int devno) if (port < 0) goto fail2; ctrl->evport = port; + ctrl->devno = devno; + +#ifdef QREXEC_RING_V2 + snprintf(buf, sizeof buf, "device/vchan/%d/version", devno); + if (!xs_write(xs, 0, buf, "2", strlen("2"))) + goto fail2; +#endif + snprintf(ref, sizeof ref, "%d", ctrl->ring_ref); snprintf(buf, sizeof buf, "device/vchan/%d/ring-ref", devno); if (!xs_write(xs, 0, buf, ref, strlen(ref))) @@ -98,21 +172,21 @@ static int server_interface_init(struct libvchan *ctrl, int devno) snprintf(buf, sizeof buf, "device/vchan/%d/event-channel", devno); if (!xs_write(xs, 0, buf, ref, strlen(ref))) goto fail2; - // wait for the peer to arrive + // do not block in stubdom and windows - libvchan_server_handle_connected will be + // called on first input +#ifndef ASYNC_INIT + // wait for the peer to arrive if (xc_evtchn_pending(evfd) == -1) goto fail2; xc_evtchn_unmask(ctrl->evfd, ctrl->evport); snprintf(buf, sizeof buf, "device/vchan/%d", devno); xs_rm(xs, 0, buf); +#endif ret = 0; fail2: if (ret) -#ifdef XENCTRL_HAS_XC_INTERFACE xc_evtchn_close(evfd); -#else - close(evfd); -#endif fail: xs_daemon_close(xs); return ret; @@ -129,8 +203,8 @@ static int server_interface_init(struct libvchan *ctrl, int devno) ctrl->rd_ring_size = sizeof(ctrl->ring->buf_##dir2) /** - Run in AppVM (any domain). - Sleeps until the connection is established. + Run in AppVM (any domain). + Sleeps until the connection is established. (unless in stubdom) \param devno something like a well-known port. \returns NULL on failure, handle on success */ @@ -156,6 +230,44 @@ struct libvchan *libvchan_server_init(int devno) return ctrl; } + + +int libvchan_server_handle_connected(struct libvchan *ctrl) +{ + struct xs_handle *xs; + char buf[64]; + int ret = -1; + +#ifdef WINNT + xs = xs_domain_open(); +#else + xs = xs_daemon_open(); +#endif + if (!xs) { + return ret; + } + +#ifndef WINNT + // clear the pending flag + xc_evtchn_pending(ctrl->evfd); +#endif + + snprintf(buf, sizeof buf, "device/vchan/%d", ctrl->devno); + xs_rm(xs, 0, buf); + + ret = 0; + +#if 0 +fail2: + if (ret) + xc_evtchn_close(ctrl->evfd); +#endif + xs_daemon_close(xs); + return ret; +} + +#ifndef WINNT + /** retrieves ring-ref and event-channel numbers from xenstore (if they don't exist, return error, because nobody seems to listen); @@ -167,14 +279,17 @@ static int client_interface_init(struct libvchan *ctrl, int domain, int devno) unsigned int len; struct xs_handle *xs; #ifdef XENCTRL_HAS_XC_INTERFACE - xc_interface *xcfd; + xc_interface *xcfd; + xc_gnttab *xcg; #else int xcfd; + int xcg; #endif char buf[64]; char *ref; + int version; #ifdef XENCTRL_HAS_XC_INTERFACE - xc_evtchn *evfd; + xc_evtchn *evfd; #else int evfd; #endif @@ -183,6 +298,18 @@ static int client_interface_init(struct libvchan *ctrl, int domain, int devno) if (!xs) { return ret; } + + version = 1; + snprintf(buf, sizeof buf, + "/local/domain/%d/device/vchan/%d/version", domain, + devno); + ref = xs_read(xs, 0, buf, &len); + if (ref) { + version = atoi(ref); + free(ref); + } + + snprintf(buf, sizeof buf, "/local/domain/%d/device/vchan/%d/ring-ref", domain, devno); @@ -190,9 +317,9 @@ static int client_interface_init(struct libvchan *ctrl, int domain, int devno) if (!ref) goto fail; ctrl->ring_ref = atoi(ref); + free(ref); if (!ctrl->ring_ref) goto fail; - free(ref); snprintf(buf, sizeof buf, "/local/domain/%d/device/vchan/%d/event-channel", domain, devno); @@ -200,26 +327,39 @@ static int client_interface_init(struct libvchan *ctrl, int domain, int devno) if (!ref) goto fail; remote_port = atoi(ref); + free(ref); if (!remote_port) goto fail; - free(ref); + + switch (version) { + case 1: + #ifdef XENCTRL_HAS_XC_INTERFACE - xcfd = xc_interface_open(NULL, NULL, 0); - if (!xcfd) - goto fail; + xcfd = xc_interface_open(NULL, NULL, 0); + if (!xcfd) + goto fail; #else - xcfd = xc_interface_open(); - if (xcfd < 0) + xcfd = xc_interface_open(); + if (xcfd < 0) + goto fail; +#endif + ctrl->ring = (struct vchan_interface *) + xc_map_foreign_range(xcfd, domain, 4096, + PROT_READ | PROT_WRITE, ctrl->ring_ref); + xc_interface_close(xcfd); + break; + case 2: + xcg = xc_gnttab_open(NULL, 0); + if (xcg < 0) + goto fail; + ctrl->ring = (struct vchan_interface *) + xc_gnttab_map_grant_ref(xcg, domain, ctrl->ring_ref, PROT_READ | PROT_WRITE); + xc_gnttab_close(xcg); + break; + default: goto fail; -#endif - ctrl->ring = (struct vchan_interface *) - xc_map_foreign_range(xcfd, domain, 4096, - PROT_READ | PROT_WRITE, ctrl->ring_ref); -#ifdef XENCTRL_HAS_XC_INTERFACE - xc_interface_close(xcfd); -#else - close(xcfd); -#endif + } + if (ctrl->ring == 0 || ctrl->ring == MAP_FAILED) goto fail; #ifdef XENCTRL_HAS_XC_INTERFACE @@ -235,11 +375,7 @@ static int client_interface_init(struct libvchan *ctrl, int domain, int devno) ctrl->evport = xc_evtchn_bind_interdomain(evfd, domain, remote_port); if (ctrl->evport < 0 || xc_evtchn_notify(evfd, ctrl->evport)) -#ifdef XENCTRL_HAS_XC_INTERFACE xc_evtchn_close(evfd); -#else - close(evfd); -#endif else ret = 0; fail: @@ -250,7 +386,7 @@ static int client_interface_init(struct libvchan *ctrl, int domain, int devno) /** Run on the client side of connection (currently, must be dom0). \returns NULL on failure (e.g. noone listening), handle on success -*/ +*/ struct libvchan *libvchan_client_init(int domain, int devno) { struct libvchan *ctrl = @@ -264,3 +400,14 @@ struct libvchan *libvchan_client_init(int domain, int devno) ctrl->is_server = 0; return ctrl; } + +#else + +// Windows domains can not be dom0 + +struct libvchan *libvchan_client_init(int domain, int devno) +{ + return NULL; +} + +#endif diff --git a/vchan/io.c b/vchan/io.c index 6b16f406..5d32b4e9 100644 --- a/vchan/io.c +++ b/vchan/io.c @@ -20,8 +20,15 @@ */ #include "libvchan.h" + +#ifndef WINNT #include +#include +#include +#endif + #include + /** \return How much data is immediately available for reading */ @@ -64,10 +71,21 @@ int libvchan_is_eof(struct libvchan *ctrl) /** \return -1 return value means peer has closed */ + +#ifdef WINNT int libvchan_wait(struct libvchan *ctrl) { int ret; - ret = xc_evtchn_pending(ctrl->evfd); + + ret = xc_evtchn_pending_with_flush(ctrl->evfd); + // I don't know how to avoid evtchn ring buffer overflow without + // introducing any race condition (in qrexec-agent code). Because of that, + // handle overflow with ring reset - because we just received some events + // (overflow means ring full, so some events was recorded...) the reset + // isn't critical here - always after libvchan_wait we check if there is + // something to read from the vchan + if (ret == -1 && GetLastError() == ERROR_IO_DEVICE) + ret = xc_evtchn_reset(ctrl->evfd); if (ret!=-1 && xc_evtchn_unmask(ctrl->evfd, ctrl->evport)) return -1; if (ret!=-1 && libvchan_is_eof(ctrl)) @@ -75,6 +93,37 @@ int libvchan_wait(struct libvchan *ctrl) return ret; } +#else + +int libvchan_wait(struct libvchan *ctrl) +{ + int ret; +#ifndef CONFIG_STUBDOM + ret = xc_evtchn_pending(ctrl->evfd); +#else + int vchan_fd = libvchan_fd_for_select(ctrl); + fd_set rfds; + + libvchan_prepare_to_select(ctrl); + while ((ret = xc_evtchn_pending(ctrl->evfd)) < 0) { + FD_ZERO(&rfds); + FD_SET(0, &rfds); + FD_SET(vchan_fd, &rfds); + ret = select(vchan_fd + 1, &rfds, NULL, NULL, NULL); + if (ret < 0 && errno != EINTR) { + perror("select"); + return ret; + } + } +#endif + if (ret!=-1 && xc_evtchn_unmask(ctrl->evfd, ctrl->evport)) + return -1; + if (ret!=-1 && libvchan_is_eof(ctrl)) + return -1; + return ret; +} +#endif + /** may sleep (only if no buffer space available); may write less data than requested; @@ -147,7 +196,7 @@ int libvchan_close(struct libvchan *ctrl) } /// The fd to use for select() set -int libvchan_fd_for_select(struct libvchan *ctrl) +EVTCHN libvchan_fd_for_select(struct libvchan *ctrl) { return xc_evtchn_fd(ctrl->evfd); } diff --git a/vchan/libvchan.h b/vchan/libvchan.h index 6a6025fb..6b19b2fa 100644 --- a/vchan/libvchan.h +++ b/vchan/libvchan.h @@ -19,7 +19,29 @@ * */ +#ifndef _LIBVCHAN_H +#define _LIBVCHAN_H + +#ifdef WINNT +#include +#include +#include +typedef HANDLE EVTCHN; +#define snprintf _snprintf +#else /* WINNT */ #include +typedef int EVTCHN; +#endif /* WINNT */ + +/* config vchan features */ +#ifdef WINNT +#define QREXEC_RING_V2 +#define ASYNC_INIT +#endif /* WINNT */ +#ifdef CONFIG_STUBDOM +#define ASYNC_INIT +#endif /* CONFIG_STUBDOM */ + #include typedef uint32_t VCHAN_RING_IDX; @@ -39,11 +61,12 @@ struct libvchan { uint32_t ring_ref; /// descriptor to event channel interface #ifdef XENCTRL_HAS_XC_INTERFACE - xc_evtchn *evfd; + xc_evtchn *evfd; #else - int evfd; + EVTCHN evfd; #endif int evport; + int devno; VCHAN_RING_IDX *wr_cons, *wr_prod, *rd_cons, *rd_prod; char *rd_ring, *wr_ring; int rd_ring_size, wr_ring_size; @@ -54,12 +77,15 @@ struct libvchan *libvchan_server_init(int devno); struct libvchan *libvchan_client_init(int domain, int devno); +int libvchan_server_handle_connected(struct libvchan *ctrl); int libvchan_write(struct libvchan *ctrl, char *data, int size); int libvchan_read(struct libvchan *ctrl, char *data, int size); int libvchan_wait(struct libvchan *ctrl); int libvchan_close(struct libvchan *ctrl); void libvchan_prepare_to_select(struct libvchan *ctrl); -int libvchan_fd_for_select(struct libvchan *ctrl); +EVTCHN libvchan_fd_for_select(struct libvchan *ctrl); int libvchan_is_eof(struct libvchan *ctrl); int libvchan_data_ready(struct libvchan *ctrl); int libvchan_buffer_space(struct libvchan *ctrl); + +#endif /* _LIBVCHAN_H */ diff --git a/vchan/sources b/vchan/sources new file mode 100644 index 00000000..5aea3939 --- /dev/null +++ b/vchan/sources @@ -0,0 +1,22 @@ +TARGETNAME=libvchan +TARGETTYPE=LIBRARY +TARGETPATH=..\win\libs + +#BUILD_PASS0_CONSUMES=gntmem evtchn xenstore +#BUILD_PASS0_PRODUCES=vchan + +USE_MSVCRT=1 + +DLLDEF=libvchan.def + +INCLUDES=$(INCLUDES); \ + ..\win\include;\ + +SOURCES=io.c init.c + +TARGETLIBS=\ + $(SDK_LIB_PATH)\setupapi.lib \ + $(SDK_LIB_PATH)\kernel32.lib + + + diff --git a/version_dom0 b/version_dom0 index c8a481c8..1bb41e63 100644 --- a/version_dom0 +++ b/version_dom0 @@ -1 +1 @@ -1.7.46 +2.0.36