From a23ad958765274a36b09674c00c14b1259b0ca8a8846088246daeaea61d8f1ae Mon Sep 17 00:00:00 2001 From: Olaf Hering <ohering@suse.com> Date: Tue, 7 Jan 2025 15:01:01 +0000 Subject: [PATCH] - update route parsing in kvp daemon - reduce resource usage in hv_kvp_daemon (175c71c2) - reduce resouce usage in hv_get_dns_info helper (a4d024fe) - hv_kvp_daemon: Pass NIC name to hv_get_dns_info as well (07dfa6e8) - terminate fcopy daemon if read from uio fails (a9640fcd) - change permissions of NetworkManager configuration file (91ae69c7) - Fix a complier warning in the fcopy uio daemon (cb1b78f1) - remove obsolete kvptest.ps1.txt which failed since a decade - remove obsolete rpm postinstall code for SLE11SP2 OBS-URL: https://build.opensuse.org/package/show/Virtualization/hyper-v?expand=0&rev=162 --- .gitattributes | 23 + .gitignore | 1 + hyper-v.changes | 553 +++++++ hyper-v.compare-with-upstream.sh | 6 + hyper-v.include.linux.hyperv.h | 411 +++++ hyper-v.kvp.gethostname.patch | 149 ++ hyper-v.kvptest.ps1.txt | 49 + hyper-v.lsvmbus.py | 112 ++ hyper-v.spec | 269 ++++ hyper-v.tools.hv.hv_fcopy_daemon.c | 266 ++++ hyper-v.tools.hv.hv_fcopy_uio_daemon.c | 497 ++++++ hyper-v.tools.hv.hv_get_dhcp_info.sh | 28 + hyper-v.tools.hv.hv_get_dns_info.sh | 17 + hyper-v.tools.hv.hv_kvp_daemon.c | 1947 ++++++++++++++++++++++++ hyper-v.tools.hv.hv_set_ifconfig.sh | 247 +++ hyper-v.tools.hv.hv_vss_daemon.c | 355 +++++ hyper-v.tools.hv.vmbus_bufring.c | 318 ++++ hyper-v.tools.hv.vmbus_bufring.h | 158 ++ 18 files changed, 5406 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 hyper-v.changes create mode 100644 hyper-v.compare-with-upstream.sh create mode 100644 hyper-v.include.linux.hyperv.h create mode 100644 hyper-v.kvp.gethostname.patch create mode 100644 hyper-v.kvptest.ps1.txt create mode 100644 hyper-v.lsvmbus.py create mode 100644 hyper-v.spec create mode 100644 hyper-v.tools.hv.hv_fcopy_daemon.c create mode 100644 hyper-v.tools.hv.hv_fcopy_uio_daemon.c create mode 100644 hyper-v.tools.hv.hv_get_dhcp_info.sh create mode 100644 hyper-v.tools.hv.hv_get_dns_info.sh create mode 100644 hyper-v.tools.hv.hv_kvp_daemon.c create mode 100644 hyper-v.tools.hv.hv_set_ifconfig.sh create mode 100644 hyper-v.tools.hv.hv_vss_daemon.c create mode 100644 hyper-v.tools.hv.vmbus_bufring.c create mode 100644 hyper-v.tools.hv.vmbus_bufring.h diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9b03811 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,23 @@ +## Default LFS +*.7z filter=lfs diff=lfs merge=lfs -text +*.bsp filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.gem filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.jar filter=lfs diff=lfs merge=lfs -text +*.lz filter=lfs diff=lfs merge=lfs -text +*.lzma filter=lfs diff=lfs merge=lfs -text +*.obscpio filter=lfs diff=lfs merge=lfs -text +*.oxt filter=lfs diff=lfs merge=lfs -text +*.pdf filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.rpm filter=lfs diff=lfs merge=lfs -text +*.tbz filter=lfs diff=lfs merge=lfs -text +*.tbz2 filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.txz filter=lfs diff=lfs merge=lfs -text +*.whl filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57affb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.osc diff --git a/hyper-v.changes b/hyper-v.changes new file mode 100644 index 0000000..2488990 --- /dev/null +++ b/hyper-v.changes @@ -0,0 +1,553 @@ +------------------------------------------------------------------- +Wed Jan 1 01:01:01 UTC 2025 - ohering@suse.de + +- update route parsing in kvp daemon +- reduce resource usage in hv_kvp_daemon (175c71c2) +- reduce resouce usage in hv_get_dns_info helper (a4d024fe) +- hv_kvp_daemon: Pass NIC name to hv_get_dns_info as well (07dfa6e8) +- terminate fcopy daemon if read from uio fails (a9640fcd) +- change permissions of NetworkManager configuration file (91ae69c7) +- Fix a complier warning in the fcopy uio daemon (cb1b78f1) +- remove obsolete kvptest.ps1.txt which failed since a decade +- remove obsolete rpm postinstall code for SLE11SP2 + +------------------------------------------------------------------- +Tue Oct 22 22:22:22 UTC 2024 - ohering@suse.de + +- Add memory allocation check in hv_fcopy_start (94e86b17) +- suppress the invalid warning for packed member alignment (207e03b0) +- Add new fcopy application based on uio driver (82b0945c) +- Add vmbus_bufring (45bab4d7) +- kvp: Handle IPv4 and Ipv6 combination for keyfile format (f971f6dd) +- kvp: Some small fixes for handling NM keyfiles (c3803203) +- kvp: Support for keyfile based connection profile (42999c90) +- kvp: remove unnecessary (void*) conversions (22589542) +- Remove an extraneous "the" (f15f39fa) +- change http to https in hv_kvp_daemon.c (fa52a4b2) +- replace the copy of include/linux/hyperv.h with include/uapi/linux/hyperv.h (6de74d10) +- merge individual udev rules files into a single rules file +- package only files, not directories already owned by filesystem.rpm +- remove braces from rpm spec macros +- remove obsolete Group tag +- replace RPM_BUILD_ROOT with buildroot +- use a meaningful name for the UAPI include file +- use a meaningful variable name for ifcfg in hv_set_ifconfig.sh + +------------------------------------------------------------------- +Tue Apr 23 10:01:10 UTC 2024 - pgajdos@suse.com + +- remove dependency on /usr/bin/python3 using + %python3_fix_shebang macro, [bsc#1212476] + +------------------------------------------------------------------- +Thu Feb 22 07:56:09 UTC 2024 - Dominique Leuenberger <dimstar@opensuse.org> + +- Use %patch -P N instead of deprecated %patchN. + +------------------------------------------------------------------- +Thu Aug 20 12:12:12 UTC 2020 - ohering@suse.de + +- Allow daemon restart again (bsc#1116957) + Since some time the kernel moved from netlink sockets to char + devices, which is supposed to handle a connection reopen for the + kernel<->daemon interface + +------------------------------------------------------------------- +Wed Jul 22 12:12:12 UTC 2020 - ohering@suse.de + +- Remove dependency to network-online.target now that gethostname + is used in kvp_daemon (bsc#1174443, bsc#1174444) +- Reopen the devices if read() or write() returns errors (9fc3c01a) +- Use either python2 or python3 for lsvmbus (bsc#1093910) +- Remove sysv init scripts + +------------------------------------------------------------------- +Wed Jul 22 09:12:44 UTC 2020 - Guillaume GARDET <guillaume.gardet@opensuse.org> + +- Enable build on aarch64 + +------------------------------------------------------------------- +Thu Feb 20 16:16:16 UTC 2020 - ohering@suse.de + +- Revert previous non-upstream change for async name resolution + Just use gethostname via hyper-v.kvp.gethostname.patch (bsc#1100758) + +------------------------------------------------------------------- +Fri Nov 29 16:41:25 UTC 2019 - ohering@suse.de + +- Update lsvmbus interpreter from python(1) to python3(1) again + because only SLE12 lacked proper python3 support (bsc#1093910) + +------------------------------------------------------------------- +Wed Nov 27 09:08:07 UTC 2019 - ohering@suse.de + +- async name resolution in kvp_daemon (bsc#1100758) +- kvp: eliminate 'may be used uninitialized' warning (89eb4d8d) +- fix typos in toolchain (2d35c660) +- fixed Python pep8/flake8 warnings for lsvmbus (5912e791) +- Replace GPLv2 boilerplate/reference with SPDX (43aa3132) +- Fix a warning of buffer overflow with gcc 8.0.1 (4fcba780) + +------------------------------------------------------------------- +Tue Oct 9 09:27:45 UTC 2018 - ohering@suse.de + +- fcopy: set 'error' in case an unknown operation was requested (c2d68afb) + +------------------------------------------------------------------- +Mon Oct 8 06:48:04 UTC 2018 - ohering@suse.de + +- Update lsvmbus interpreter from python3(1) to python(1) + because SLE12 lacks python3 support (bsc#1093910) + +------------------------------------------------------------------- +Tue Sep 25 08:33:36 UTC 2018 - ohering@suse.de + +- vss: fix loop device detection (07136793) +- Fix IP reporting by KVP daemon with SRIOV (4ba63412) +- Fix a bug in the key delete code (86503bd3) +- fix compiler warnings about major/target_fname (1330fc35) +- PRIVATE hyper-v.compare-with-upstream.sh + +------------------------------------------------------------------- +Thu Aug 2 08:46:52 UTC 2018 - schwab@suse.de + +- hyper-v.tools.hv.hv_vss_daemon.c: Include <sys/sysmacros.h> for major + +------------------------------------------------------------------- +Tue May 22 06:56:57 UTC 2018 - ohering@suse.de + +- Update lsvmbus interpreter from env(1) to python3(1) (bsc#1093910) + +------------------------------------------------------------------- +Mon Jan 8 10:45:35 UTC 2018 - ohering@suse.de + +- update buffer handling in hv_fcopy_daemon +- remove unnecessary header files and netlink related code +- Avoid reading past allocated blocks from KVP file (bnc#1087658) +- fix snprintf warning in kvp_daemon +- properly handle long paths +- kvp: configurable external scripts path +- vss: Thaw the filesystem and continue if freeze call has timed out +- vss: Skip freezing filesystems backed by loop + +------------------------------------------------------------------- +Wed Jun 28 12:04:38 UTC 2017 - ohering@suse.de + +- Check VSS daemon is listening before a hot backup (bnc#1029693) +- fix a compile warning in snprintf +- kvp: ensure kvp device fd is closed on exec +- lsvmbus: add pci pass-through UUID + +------------------------------------------------------------------- +Wed May 4 13:29:54 UTC 2016 - ohering@suse.de + +- add Conflicts with kernel < 4.2 (fate#320485) +- vss: fix the write()'s argument: error -> vss_msg +- remove repeated HV_FCOPY string +- report ENOSPC errors in hv_fcopy_daemon +- fcopy: full handshake support +- vss: full handshake support +- vss: use misc char device to communicate with kernel +- kvp: use misc char device to communicate with kernel + +------------------------------------------------------------------- +Fri Sep 18 10:11:01 UTC 2015 - ohering@suse.de + +- add a python script lsvmbus to list VMBus devices (fate#315887) + +------------------------------------------------------------------- +Thu Apr 30 11:25:47 UTC 2015 - ohering@suse.de + +- hv: hypervvssd: call endmntent before call setmntent again +- remove unused bytes_written from kvp_update_file() +- address compiler warnings for hv_kvp_daemon.c +- kvp_daemon: make IPv6-only-injection work +- address compiler warnings for hv_fcopy_daemon.c +- do not add redundant '/' in hv_start_fcopy() + +------------------------------------------------------------------- +Thu Apr 16 15:23:18 UTC 2015 - ohering@suse.de + +- Add runlevel script for fcopy daemon in SLE11 (bnc#926534) + +------------------------------------------------------------------- +Mon Jan 12 09:37:40 UTC 2015 - ohering@suse.de + +- Check return value of setsockopt call +- Improve error logging in VSS daemon. +- Check return value of poll call +- Properly pack the data for file copy functionality +- make struct hv_do_fcopy match Hyper-V host messages + +------------------------------------------------------------------- +Wed Dec 17 09:06:33 UTC 2014 - ohering@suse.de + +- Start hv_kvp_daemon after network-online.target (bnc#910353) +- ignore ENOBUFS and ENOMEM in the KVP daemon +- vssdaemon: skip all filesystems mounted readonly (bnc#909864) +- vssdaemon: report freeze errors + +------------------------------------------------------------------- +Tue Nov 25 17:46:30 UTC 2014 - ohering@suse.de + +- introduce -n/--no-daemon option (fate#317533) + +------------------------------------------------------------------- +Sun Nov 09 04:39:00 UTC 2014 - Led <ledest@gmail.com> + +- fix bashisms in pre/post scripts + +------------------------------------------------------------------- +Tue Sep 30 15:41:02 UTC 2014 - ohering@suse.de + +- vssdaemon: ignore the EBUSY on multiple freezing the same + partition (bnc#899204) + +------------------------------------------------------------------- +Tue Jul 1 17:32:30 CEST 2014 - ohering@suse.de + +- fix file overwriting of hv_fcopy_daemon + +------------------------------------------------------------------- +Mon May 26 15:50:21 CEST 2014 - ohering@suse.de + +- copy mask2pfxlen from /etc/sysconfig/network/scripts/functions + to hv_set_ifconfig (bnc#879256) + +------------------------------------------------------------------- +Wed May 21 12:05:31 CEST 2014 - ohering@suse.de + +- Start hyper-v helpers manually with helper script in inst-sys + +------------------------------------------------------------------- +Thu Apr 10 19:19:04 CEST 2014 - ohering@suse.de + +- Handle the case when the target file exists correctly in hv_fcopy_daemon +- vssdaemon: Ignore VFAT mounts during the Freeze operation + +------------------------------------------------------------------- +Sun Mar 2 18:11:31 CET 2014 - ohering@suse.de + +- Package /usr/lib/udev to fix build error + +------------------------------------------------------------------- +Wed Feb 12 10:32:54 CET 2014 - ohering@suse.de + +- Update hv_fcopy_daemon, kernel name changed to /dev/vmbus/hv_fcopy + +------------------------------------------------------------------- +Thu Jan 23 17:00:54 CET 2014 - ohering@suse.de + +- Start daemons on demand via udev rules because the guest services + are optional. +- If systemd is used daemons live in libdir, they are not usercallable apps + +------------------------------------------------------------------- +Thu Jan 16 11:09:34 CET 2014 - ohering@suse.de + +- Add hv_fcopy_daemon for post 13.1 releases (fate#315887) + hyper-v.tools.hv.hv_fcopy_daemon.c + +------------------------------------------------------------------- +Wed Dec 18 14:46:37 CET 2013 - ohering@suse.de + +- Use native systemd services for post 13.1 releases + +------------------------------------------------------------------- +Sun Sep 8 10:36:29 CEST 2013 - ohering@suse.de + +- Update Supplements: modalias(dmi:*) + +------------------------------------------------------------------- +Mon Aug 12 16:56:08 CEST 2013 - ohering@suse.de + +- Skip restart_on_update with old hyper-v.rpms while old + kernel is running (bnc#770763) + +------------------------------------------------------------------- +Wed Aug 7 19:04:35 CEST 2013 - ohering@suse.de + +- cache FQDN in kvp_daemon to avoid timeouts (bnc#828714) +- use full nlmsghdr in netlink_send +- correct payload size in netlink_send +- use single send+recv buffer +- log errors to syslog in kvp_set_ip_info +- check return value of system in hv_kvp_daemon +- in kvp_set_ip_info free mac_addr right after usage +- check return value of daemon to fix compiler warning. + +------------------------------------------------------------------- +Thu Aug 1 14:21:57 CEST 2013 - ohering@suse.de + +- Fix send/recv buffer allocation (bnc#828714) + +------------------------------------------------------------------- +Wed Jul 24 10:18:34 CEST 2013 - ohering@suse.de + +- set BOOTPROTO=static if any IPADDR/IP6ADDR was passed to + hv_set_ifconfig and dhcp is disabled + +------------------------------------------------------------------- +Mon Jul 15 16:16:06 CEST 2013 - ohering@suse.de + +- update hv_kvp_daemon (merge 0783d72fa from v3.9-rc1) + Fix how ifcfg-* file is created + +------------------------------------------------------------------- +Mon Jul 15 15:24:00 CEST 2013 - ohering@suse.de + +- update hv_kvp_daemon (changes up to 3.11-rc1): + Improve error logging in KVP daemon. + Fix file descriptor leaks + Check retrun value of strchr call + Check return value of poll call + Check return value of setsockopt call + daemon should check type of received Netlink msg + daemon setsockopt should use options macros + daemon should subscribe only to CN_KVP_IDX group + +------------------------------------------------------------------- +Mon Jul 15 12:04:05 CEST 2013 - ohering@suse.de + +- Fix a bug in IPV6 subnet enumeration (bnc#828714) + +------------------------------------------------------------------- +Tue Mar 26 18:03:47 CET 2013 - ohering@suse.de + +- Update hv_vss_daemon (bnc#811033) + +------------------------------------------------------------------- +Fri Mar 22 17:23:36 CET 2013 - ohering@suse.de + +- add hv_vss_daemon (fate#314921) + helper to support host initiated backup + +------------------------------------------------------------------- +Fri Mar 22 16:56:57 CET 2013 - ohering@suse.de + +- build hv_kvp_daemon with -D_GNU_SOURCE to get O_CLOEXEC + +------------------------------------------------------------------- +Fri Mar 22 16:19:38 CET 2013 - ohering@suse.de + +- update hv_kvp_daemon + Use CLOEXEC when opening kvp_pool files + Fix permissions of created directory and files + Fix /var subdirectory (move state files from /var/opt to /var/lib) + Fix string types + +------------------------------------------------------------------- +Tue Nov 27 11:19:32 CET 2012 - ohering@suse.de + +- update hv_set_ifconfig, use single index for static ipv4/ipv6 [bnc#790469] + +------------------------------------------------------------------- +Sun Nov 25 17:50:40 CET 2012 - ohering@suse.de + +- update hv_set_ifconfig further to work with our ifcfg [bnc#790469] + +------------------------------------------------------------------- +Thu Nov 22 18:14:12 CET 2012 - ohering@suse.de + +- update hv_get_dhcp_info to work with our ifcfg [bnc#790469] +- remove cat usage from hv_get_dns_info +- add quoting to hv_set_ifconfig to make it more robust + +------------------------------------------------------------------- +Mon Nov 12 17:18:25 CET 2012 - ohering@suse.de + +- remove code to build kmp, it was not enabled because the + drivers are now in kernel since a long time. [bnc#676890] + +------------------------------------------------------------------- +Thu Nov 8 14:30:05 CET 2012 - ohering@suse.de + +- Netlink source address validation allows DoS [bnc#791605, CVE-2012-5532] + bugfix for recvfrom check from bnc#761200 + +------------------------------------------------------------------- +Fri Oct 26 17:13:40 CEST 2012 - ohering@suse.de + +- update hv_set_ifconfig to work with our ifcfg + +------------------------------------------------------------------- +Sat Oct 13 11:40:30 CEST 2012 - ohering@suse.de + +- update hv_kvp_daemon + Return the full kernel version + Don't return loopback addresses + +------------------------------------------------------------------- +Thu Oct 4 15:14:05 CEST 2012 - ohering@suse.de + +- bump to version 4 +- update kv_kvp_daemon to 3.7-rc1 state [fate#314441] + support KVP IP Injection, helper scripts go to /usr/lib/hyper-v/bin: + hv_get_dhcp_info, hv_get_dns_info, hv_set_ifconfig +- remove usage of absolute paths in runlevel script + +------------------------------------------------------------------- +Tue Sep 4 14:55:38 CEST 2012 - ohering@suse.de + +- remove restart_on_update in postun section + the daemon can not be restarted at this point, and the new daemon + may not be 100 percent compatible with the currently running + kernel [bnc#770763] + +------------------------------------------------------------------- +Wed May 16 20:44:36 CEST 2012 - ohering@suse.de + +- check origin of netlink messages, use recvfrom() [bnc#761200, CVE-2012-2669] + +------------------------------------------------------------------- +Wed May 2 12:13:03 CEST 2012 - ohering@suse.de + +- update insserv part, see comments in OBS submit request #112701 + +------------------------------------------------------------------- +Tue Mar 27 08:50:43 CEST 2012 - ohering@suse.de + +- add kvptest.ps1.txt to docs, a PowerShell script to verify KVP + +------------------------------------------------------------------- +Mon Mar 26 19:05:48 CEST 2012 - ohering@suse.de + +- update hv_kvp_daemon to 3.4-rc1 state + use a copy linux/hyperv.h to compile the daemon + +------------------------------------------------------------------- +Thu Dec 8 17:46:21 CET 2011 - ohering@suse.de + +- do not package modprobe.conf rule in main package + prevents loading of ata_piix which is required for cdrom access + +------------------------------------------------------------------- +Fri Nov 18 21:49:57 CET 2011 - ohering@suse.de + +- add Supplements to install package if dmi matches [bnc#731198] + +------------------------------------------------------------------- +Thu Sep 1 18:51:03 CEST 2011 - ohering@suse.de + +- disable hyper-v-kmp + hv_storvsc handles now IDE and SCSI disks and ata_piix is + compiled into the kernel in SLE12, so the modprobe.conf rules + have no meaning anymore + +------------------------------------------------------------------- +Tue Jul 26 21:24:25 CEST 2011 - ohering@suse.de + +- update hv_kvp_daemon: Cleanup kvp_get_domain_name(). If + getaddrinfo() fails, deal with it properly (this can happen if no + IP address has been assigned). Also, don't specify a specific + service in the call to getaddrinfo() to make this code as generic + as possible. + +------------------------------------------------------------------- +Sun Jul 24 17:22:41 CEST 2011 - ohering@suse.de + +- update hv_kvp_daemon: The current win7 host does not like it when + we return the complete kernel release information. Conform to + what the host expects. + +------------------------------------------------------------------- +Fri Jun 17 15:41:33 CEST 2011 - ohering@suse.de + +- update preun, stop_on_removal requires an argument + +------------------------------------------------------------------- +Fri Jun 17 11:09:30 CEST 2011 - ohering@suse.de + +- remove get_release_number.sh, which was added by accident +- bump version number to 2 + +------------------------------------------------------------------- +Fri Jun 17 10:58:42 CEST 2011 - ohering@suse.de + +- catch errors from daemon() + +------------------------------------------------------------------- +Fri Jun 17 10:33:50 CEST 2011 - ohering@suse.de + +- update postun to run restart_on_update +- use rpmmacro for daemon name + +------------------------------------------------------------------- +Fri Jun 17 08:10:12 CEST 2011 - ohering@suse.de + +- update postin to work in / again when running insserv macro + +------------------------------------------------------------------- +Thu Jun 16 15:33:49 CEST 2011 - ohering@suse.de + +- Update version number to 1 so kernel.rpm can obsolete + the old kmp packages + +------------------------------------------------------------------- +Thu Jun 16 11:12:06 CEST 2011 - ohering@suse.de + +- enable hv_kvp_daemon if run in a hv guest [fate#312213] + +------------------------------------------------------------------- +Thu Apr 21 17:18:20 CEST 2011 - ohering@suse.de + +- add hv_kvp_daemon to provide system infos to hypervisor [bnc#685189] + this enables the hyper-v main package + +------------------------------------------------------------------- +Sat Apr 16 15:13:36 CEST 2011 - ohering@suse.de + +- Require at least kernel-default 2.6.32.27 for an empty KMP. + +------------------------------------------------------------------- +Fri Apr 15 16:50:25 CEST 2011 - ohering@suse.de + +- actually keep the KMP packages with just the modprobe.conf file + +------------------------------------------------------------------- +Thu Mar 31 11:24:03 CEST 2011 - ohering@suse.de + +- make building of kernel modules optional [bnc#676890] + the hv*.ko drivers exist in two places, kernel-default and this + KMP package. Both can get out of sync, then mkinitrd will use + (the possible outdated) drivers from this KMP package. + disable building drivers per default, keep only the + hyperv_pvdrivers.conf +- mark hyperv_pvdrivers.conf as config to preserve local + modifications done by the sysadmin + +------------------------------------------------------------------- +Thu Feb 24 12:09:25 CET 2011 - meissner@suse.de + +- also add hv_timesource to Modules.supported [bnc#650748] + +------------------------------------------------------------------- +Tue Jan 4 10:28:21 CET 2011 - meissner@suse.de + +- add hv_utils to Modules.supported [bnc#650748] + +------------------------------------------------------------------- +Wed Apr 28 11:40:33 CEST 2010 - kukuk@suse.de + +- Next update of hyperv_pvdrivers.conf [bnc#600212] + +------------------------------------------------------------------- +Tue Apr 27 13:20:55 CEST 2010 - kukuk@suse.de + +- hyperv_pvdrivers.conf: fix syntax error + +------------------------------------------------------------------- +Tue Apr 27 07:10:05 CEST 2010 - ksrinivasan@novell.com + +- Update hyperv_pvdrivers.conf config file + +------------------------------------------------------------------- +Mon Apr 26 18:15:40 CEST 2010 - kukuk@suse.de + +- Add hyperv_pvdrivers.conf modprobe config file + +------------------------------------------------------------------- +Fri Mar 5 13:40:56 CET 2010 - mmarek@suse.cz + +- Packaged drivers/staging/hv as a standalone KMP (bnc#585651). + diff --git a/hyper-v.compare-with-upstream.sh b/hyper-v.compare-with-upstream.sh new file mode 100644 index 0000000..b3486d6 --- /dev/null +++ b/hyper-v.compare-with-upstream.sh @@ -0,0 +1,6 @@ +d=~/work/src/kernel/kerncvs.kernel.master +diff -u hyper-v.tools.hv.hv_vss_daemon.c ${d}/tools/hv/hv_vss_daemon.c +diff -u hyper-v.tools.hv.hv_kvp_daemon.c ${d}/tools/hv/hv_kvp_daemon.c +diff -u hyper-v.tools.hv.hv_fcopy_daemon.c ${d}/tools/hv/hv_fcopy_daemon.c +diff -u hyper-v.include.linux.hyperv.h ${d}/include/uapi/linux/hyperv.h +diff -u hyper-v.lsvmbus.py ${d}/tools/hv/lsvmbus diff --git a/hyper-v.include.linux.hyperv.h b/hyper-v.include.linux.hyperv.h new file mode 100644 index 0000000..d08235b --- /dev/null +++ b/hyper-v.include.linux.hyperv.h @@ -0,0 +1,411 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * + * Copyright (c) 2011, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * K. Y. Srinivasan <kys@microsoft.com> + * + */ + +#ifndef _UAPI_HYPERV_H +#define _UAPI_HYPERV_H + +#include <linux/types.h> + +/* + * Framework version for util services. + */ +#define UTIL_FW_MINOR 0 + +#define UTIL_WS2K8_FW_MAJOR 1 +#define UTIL_WS2K8_FW_VERSION (UTIL_WS2K8_FW_MAJOR << 16 | UTIL_FW_MINOR) + +#define UTIL_FW_MAJOR 3 +#define UTIL_FW_VERSION (UTIL_FW_MAJOR << 16 | UTIL_FW_MINOR) + + +/* + * Implementation of host controlled snapshot of the guest. + */ + +#define VSS_OP_REGISTER 128 + +/* + Daemon code with full handshake support. + */ +#define VSS_OP_REGISTER1 129 + +enum hv_vss_op { + VSS_OP_CREATE = 0, + VSS_OP_DELETE, + VSS_OP_HOT_BACKUP, + VSS_OP_GET_DM_INFO, + VSS_OP_BU_COMPLETE, + /* + * Following operations are only supported with IC version >= 5.0 + */ + VSS_OP_FREEZE, /* Freeze the file systems in the VM */ + VSS_OP_THAW, /* Unfreeze the file systems */ + VSS_OP_AUTO_RECOVER, + VSS_OP_COUNT /* Number of operations, must be last */ +}; + + +/* + * Header for all VSS messages. + */ +struct hv_vss_hdr { + __u8 operation; + __u8 reserved[7]; +} __attribute__((packed)); + + +/* + * Flag values for the hv_vss_check_feature. Linux supports only + * one value. + */ +#define VSS_HBU_NO_AUTO_RECOVERY 0x00000005 + +struct hv_vss_check_feature { + __u32 flags; +} __attribute__((packed)); + +struct hv_vss_check_dm_info { + __u32 flags; +} __attribute__((packed)); + +/* + * struct hv_vss_msg encodes the fields that the Linux VSS + * driver accesses. However, FREEZE messages from Hyper-V contain + * additional LUN information that Linux doesn't use and are not + * represented in struct hv_vss_msg. A received FREEZE message may + * be as large as 6,260 bytes, so the driver must allocate at least + * that much space, not sizeof(struct hv_vss_msg). Other messages + * such as AUTO_RECOVER may be as large as 12,500 bytes. However, + * because the Linux VSS driver responds that it doesn't support + * auto-recovery, it should not receive such messages. + */ +struct hv_vss_msg { + union { + struct hv_vss_hdr vss_hdr; + int error; + }; + union { + struct hv_vss_check_feature vss_cf; + struct hv_vss_check_dm_info dm_info; + }; +} __attribute__((packed)); + +/* + * Implementation of a host to guest copy facility. + */ + +#define FCOPY_VERSION_0 0 +#define FCOPY_VERSION_1 1 +#define FCOPY_CURRENT_VERSION FCOPY_VERSION_1 +#define W_MAX_PATH 260 + +enum hv_fcopy_op { + START_FILE_COPY = 0, + WRITE_TO_FILE, + COMPLETE_FCOPY, + CANCEL_FCOPY, +}; + +struct hv_fcopy_hdr { + __u32 operation; + __u8 service_id0[16]; /* currently unused */ + __u8 service_id1[16]; /* currently unused */ +} __attribute__((packed)); + +#define OVER_WRITE 0x1 +#define CREATE_PATH 0x2 + +struct hv_start_fcopy { + struct hv_fcopy_hdr hdr; + __u16 file_name[W_MAX_PATH]; + __u16 path_name[W_MAX_PATH]; + __u32 copy_flags; + __u64 file_size; +} __attribute__((packed)); + +/* + * The file is chunked into fragments. + */ +#define DATA_FRAGMENT (6 * 1024) + +struct hv_do_fcopy { + struct hv_fcopy_hdr hdr; + __u32 pad; + __u64 offset; + __u32 size; + __u8 data[DATA_FRAGMENT]; +} __attribute__((packed)); + +/* + * An implementation of HyperV key value pair (KVP) functionality for Linux. + * + * + * Copyright (C) 2010, Novell, Inc. + * Author : K. Y. Srinivasan <ksrinivasan@novell.com> + * + */ + +/* + * Maximum value size - used for both key names and value data, and includes + * any applicable NULL terminators. + * + * Note: This limit is somewhat arbitrary, but falls easily within what is + * supported for all native guests (back to Win 2000) and what is reasonable + * for the IC KVP exchange functionality. Note that Windows Me/98/95 are + * limited to 255 character key names. + * + * MSDN recommends not storing data values larger than 2048 bytes in the + * registry. + * + * Note: This value is used in defining the KVP exchange message - this value + * cannot be modified without affecting the message size and compatibility. + */ + +/* + * bytes, including any null terminators + */ +#define HV_KVP_EXCHANGE_MAX_VALUE_SIZE (2048) + + +/* + * Maximum key size - the registry limit for the length of an entry name + * is 256 characters, including the null terminator + */ + +#define HV_KVP_EXCHANGE_MAX_KEY_SIZE (512) + +/* + * In Linux, we implement the KVP functionality in two components: + * 1) The kernel component which is packaged as part of the hv_utils driver + * is responsible for communicating with the host and responsible for + * implementing the host/guest protocol. 2) A user level daemon that is + * responsible for data gathering. + * + * Host/Guest Protocol: The host iterates over an index and expects the guest + * to assign a key name to the index and also return the value corresponding to + * the key. The host will have atmost one KVP transaction outstanding at any + * given point in time. The host side iteration stops when the guest returns + * an error. Microsoft has specified the following mapping of key names to + * host specified index: + * + * Index Key Name + * 0 FullyQualifiedDomainName + * 1 IntegrationServicesVersion + * 2 NetworkAddressIPv4 + * 3 NetworkAddressIPv6 + * 4 OSBuildNumber + * 5 OSName + * 6 OSMajorVersion + * 7 OSMinorVersion + * 8 OSVersion + * 9 ProcessorArchitecture + * + * The Windows host expects the Key Name and Key Value to be encoded in utf16. + * + * Guest Kernel/KVP Daemon Protocol: As noted earlier, we implement all of the + * data gathering functionality in a user mode daemon. The user level daemon + * is also responsible for binding the key name to the index as well. The + * kernel and user-level daemon communicate using a connector channel. + * + * The user mode component first registers with the + * kernel component. Subsequently, the kernel component requests, data + * for the specified keys. In response to this message the user mode component + * fills in the value corresponding to the specified key. We overload the + * sequence field in the cn_msg header to define our KVP message types. + * + * + * The kernel component simply acts as a conduit for communication between the + * Windows host and the user-level daemon. The kernel component passes up the + * index received from the Host to the user-level daemon. If the index is + * valid (supported), the corresponding key as well as its + * value (both are strings) is returned. If the index is invalid + * (not supported), a NULL key string is returned. + */ + + +/* + * Registry value types. + */ + +#define REG_SZ 1 +#define REG_U32 4 +#define REG_U64 8 + +/* + * As we look at expanding the KVP functionality to include + * IP injection functionality, we need to maintain binary + * compatibility with older daemons. + * + * The KVP opcodes are defined by the host and it was unfortunate + * that I chose to treat the registration operation as part of the + * KVP operations defined by the host. + * Here is the level of compatibility + * (between the user level daemon and the kernel KVP driver) that we + * will implement: + * + * An older daemon will always be supported on a newer driver. + * A given user level daemon will require a minimal version of the + * kernel driver. + * If we cannot handle the version differences, we will fail gracefully + * (this can happen when we have a user level daemon that is more + * advanced than the KVP driver. + * + * We will use values used in this handshake for determining if we have + * workable user level daemon and the kernel driver. We begin by taking the + * registration opcode out of the KVP opcode namespace. We will however, + * maintain compatibility with the existing user-level daemon code. + */ + +/* + * Daemon code not supporting IP injection (legacy daemon). + */ + +#define KVP_OP_REGISTER 4 + +/* + * Daemon code supporting IP injection. + * The KVP opcode field is used to communicate the + * registration information; so define a namespace that + * will be distinct from the host defined KVP opcode. + */ + +#define KVP_OP_REGISTER1 100 + +enum hv_kvp_exchg_op { + KVP_OP_GET = 0, + KVP_OP_SET, + KVP_OP_DELETE, + KVP_OP_ENUMERATE, + KVP_OP_GET_IP_INFO, + KVP_OP_SET_IP_INFO, + KVP_OP_COUNT /* Number of operations, must be last. */ +}; + +enum hv_kvp_exchg_pool { + KVP_POOL_EXTERNAL = 0, + KVP_POOL_GUEST, + KVP_POOL_AUTO, + KVP_POOL_AUTO_EXTERNAL, + KVP_POOL_AUTO_INTERNAL, + KVP_POOL_COUNT /* Number of pools, must be last. */ +}; + +/* + * Some Hyper-V status codes. + */ + +#define HV_S_OK 0x00000000 +#define HV_E_FAIL 0x80004005 +#define HV_S_CONT 0x80070103 +#define HV_ERROR_NOT_SUPPORTED 0x80070032 +#define HV_ERROR_MACHINE_LOCKED 0x800704F7 +#define HV_ERROR_DEVICE_NOT_CONNECTED 0x8007048F +#define HV_INVALIDARG 0x80070057 +#define HV_GUID_NOTFOUND 0x80041002 +#define HV_ERROR_ALREADY_EXISTS 0x80070050 +#define HV_ERROR_DISK_FULL 0x80070070 + +#define ADDR_FAMILY_NONE 0x00 +#define ADDR_FAMILY_IPV4 0x01 +#define ADDR_FAMILY_IPV6 0x02 + +#define MAX_ADAPTER_ID_SIZE 128 +#define MAX_IP_ADDR_SIZE 1024 +#define MAX_GATEWAY_SIZE 512 + + +struct hv_kvp_ipaddr_value { + __u16 adapter_id[MAX_ADAPTER_ID_SIZE]; + __u8 addr_family; + __u8 dhcp_enabled; + __u16 ip_addr[MAX_IP_ADDR_SIZE]; + __u16 sub_net[MAX_IP_ADDR_SIZE]; + __u16 gate_way[MAX_GATEWAY_SIZE]; + __u16 dns_addr[MAX_IP_ADDR_SIZE]; +} __attribute__((packed)); + + +struct hv_kvp_hdr { + __u8 operation; + __u8 pool; + __u16 pad; +} __attribute__((packed)); + +struct hv_kvp_exchg_msg_value { + __u32 value_type; + __u32 key_size; + __u32 value_size; + __u8 key[HV_KVP_EXCHANGE_MAX_KEY_SIZE]; + union { + __u8 value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE]; + __u32 value_u32; + __u64 value_u64; + }; +} __attribute__((packed)); + +struct hv_kvp_msg_enumerate { + __u32 index; + struct hv_kvp_exchg_msg_value data; +} __attribute__((packed)); + +struct hv_kvp_msg_get { + struct hv_kvp_exchg_msg_value data; +}; + +struct hv_kvp_msg_set { + struct hv_kvp_exchg_msg_value data; +}; + +struct hv_kvp_msg_delete { + __u32 key_size; + __u8 key[HV_KVP_EXCHANGE_MAX_KEY_SIZE]; +}; + +struct hv_kvp_register { + __u8 version[HV_KVP_EXCHANGE_MAX_KEY_SIZE]; +}; + +struct hv_kvp_msg { + union { + struct hv_kvp_hdr kvp_hdr; + int error; + }; + union { + struct hv_kvp_msg_get kvp_get; + struct hv_kvp_msg_set kvp_set; + struct hv_kvp_msg_delete kvp_delete; + struct hv_kvp_msg_enumerate kvp_enum_data; + struct hv_kvp_ipaddr_value kvp_ip_val; + struct hv_kvp_register kvp_register; + } body; +} __attribute__((packed)); + +struct hv_kvp_ip_msg { + __u8 operation; + __u8 pool; + struct hv_kvp_ipaddr_value kvp_ip_val; +} __attribute__((packed)); + +#endif /* _UAPI_HYPERV_H */ diff --git a/hyper-v.kvp.gethostname.patch b/hyper-v.kvp.gethostname.patch new file mode 100644 index 0000000..4a2597b --- /dev/null +++ b/hyper-v.kvp.gethostname.patch @@ -0,0 +1,149 @@ +--- a/hyper-v.tools.hv.hv_kvp_daemon.c ++++ b/hyper-v.tools.hv.hv_kvp_daemon.c +@@ -24,6 +24,7 @@ + + #include <sys/poll.h> + #include <sys/utsname.h> ++#include <stdbool.h> + #include <stdio.h> + #include <stdlib.h> + #include <unistd.h> +@@ -677,6 +678,83 @@ static void kvp_process_ipconfig_file(ch + pclose(file); + } + ++static bool kvp_verify_ip_address(void *address_string) ++{ ++ char verify_buf[sizeof(struct in6_addr)]; ++ ++ if (inet_pton(AF_INET, address_string, verify_buf) == 1) ++ return true; ++ if (inet_pton(AF_INET6, address_string, verify_buf) == 1) ++ return true; ++ return false; ++} ++ ++static void kvp_extract_routes(void **output, size_t *output_len, char *line) ++{ ++ static const char needle[] = "via "; ++ char *match, *haystack = line; ++ ++ while ((match = strstr(haystack, needle))) { ++ char *address, *end; ++ ++ /* Address starts after needle. */ ++ address = match + strlen(needle); ++ ++ /* The char following address is a space or end of line. */ ++ end = strpbrk(address, " \t\\"); ++ if (!end) ++ end = address + strlen(address) + 1; ++ ++ /* Enough room for address and semicolon. */ ++ if (*output_len >= (end - address) + 1) { ++ memcpy(*output, address, end - address); ++ /* Terminate string for verification. */ ++ memcpy(*output + (end - address), "", 1); ++ if (kvp_verify_ip_address(*output)) { ++ /* Advance output buffer. */ ++ *output += end - address; ++ *output_len -= end - address; ++ ++ /* Each address needs a trailing semicolon. */ ++ memcpy(*output, ";", 1); ++ *output += 1; ++ *output_len -= 1; ++ } ++ } ++ haystack = end; ++ } ++} ++ ++static void kvp_get_gateway(void *buffer, size_t buffer_len) ++{ ++ static const char needle[] = "default "; ++ FILE *f; ++ void *output = buffer; ++ char *line = NULL; ++ size_t alloc_size = 0, output_len = buffer_len - 1; ++ ssize_t num_chars; ++ ++ /* Show route information in a single line, for each address family */ ++ f = popen("ip --oneline -4 route show;exec ip --oneline -6 route show", "r"); ++ while ((num_chars = getline(&line, &alloc_size, f)) > 0) { ++ /* Skip short lines. */ ++ if (num_chars <= strlen(needle)) ++ continue; ++ /* Skip lines without default route. */ ++ if (memcmp(line, needle, strlen(needle))) ++ continue; ++ /* Remove trailing newline to simplify further parsing. */ ++ if (line[num_chars - 1] == '\n') ++ line[num_chars - 1] = '\0'; ++ /* Search routes after match. */ ++ kvp_extract_routes(&output, &output_len, line + strlen(needle)); ++ } ++ /* Convert buffer into C-String. */ ++ memcpy(output, "", 1); ++ free(line); ++ pclose(f); ++} ++ + static void kvp_get_ipconfig_info(char *if_name, + struct hv_kvp_ipaddr_value *buffer) + { +@@ -684,32 +762,7 @@ static void kvp_get_ipconfig_info(char * + char dhcp_info[128]; + char *p; + FILE *file; +- +- /* +- * Get the address of default gateway (ipv4). +- */ +- sprintf(cmd, "%s %s", "ip route show dev", if_name); +- strcat(cmd, " | awk '/default/ {print $3 }'"); +- +- /* +- * Execute the command to gather gateway info. +- */ +- kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way, +- (MAX_GATEWAY_SIZE * 2), INET_ADDRSTRLEN, 0); +- +- /* +- * Get the address of default gateway (ipv6). +- */ +- sprintf(cmd, "%s %s", "ip -f inet6 route show dev", if_name); +- strcat(cmd, " | awk '/default/ {print $3 }'"); +- +- /* +- * Execute the command to gather gateway info (ipv6). +- */ +- kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way, +- (MAX_GATEWAY_SIZE * 2), INET6_ADDRSTRLEN, 1); +- +- ++ kvp_get_gateway(buffer->gate_way, sizeof(buffer->gate_way)); + /* + * Gather the DNS state. + * Since there is no standard way to get this information +@@ -1335,6 +1335,7 @@ kvp_get_domain_name(char *buffer, int le + struct addrinfo hints, *info ; + int error = 0; + ++ return; + gethostname(buffer, length); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; /*Get only ipv4 addrinfo. */ +@@ -1572,6 +1573,12 @@ int main(int argc, char *argv[]) + + switch (hv_msg->body.kvp_enum_data.index) { + case FullyQualifiedDomainName: ++ /* ++ * The API is undocumented. ++ * The Host can not possibly care about DNS within the guest network ++ * The time it takes to get the hostname is much shorter than a DNS lookup. ++ */ ++ gethostname(full_domain_name, sizeof(full_domain_name)); + strcpy(key_value, full_domain_name); + strcpy(key_name, "FullyQualifiedDomainName"); + break; diff --git a/hyper-v.kvptest.ps1.txt b/hyper-v.kvptest.ps1.txt new file mode 100644 index 0000000..e058cfe --- /dev/null +++ b/hyper-v.kvptest.ps1.txt @@ -0,0 +1,49 @@ +# Windows PowerShell script to test Key Value Pair functionality +# +# http://blogs.msdn.com/b/virtual_pc_guy/archive/2008/11/18/hyper-v-script-looking-at-kvp-guestintrinsicexchangeitems.aspx +# +# Per default execution of scripts is disabled. +# http://technet.microsoft.com/en-us/library/ee176949.aspx +# The command 'Set-ExecutionPolicy RemoteSigned' will enable it. +# +# Filter for parsing XML data +filter Import-CimXml +{ + # Create new XML object from input + $CimXml = [Xml]$_ + $CimObj = New-Object -TypeName System.Object + + # Iterate over the data and pull out just the value name and data for each entry + foreach ($CimProperty in $CimXml.SelectNodes("/INSTANCE/PROPERTY[@NAME='Name']")) + { + $CimObj | Add-Member -MemberType NoteProperty -Name $CimProperty.NAME -Value $CimProperty.VALUE + } + + foreach ($CimProperty in $CimXml.SelectNodes("/INSTANCE/PROPERTY[@NAME='Data']")) + { + $CimObj | Add-Member -MemberType NoteProperty -Name $CimProperty.NAME -Value $CimProperty.VALUE + } + + # Display output + $CimObj +} + +# Prompt for the Hyper-V Server to use +$HyperVServer = Read-Host "Specify the Hyper-V Server to use (enter '.' for the local computer)" + +# Prompt for the virtual machine to use +$VMName = Read-Host "Specify the name of the virtual machine" + +# Get the virtual machine object +$query = "Select * From Msvm_ComputerSystem Where ElementName='" + $VMName + "'" +$Vm = gwmi -namespace root\virtualization -query $query -computername $HyperVServer + +# Get the KVP Object +$query = "Associators of {$Vm} Where AssocClass=Msvm_SystemDevice ResultClass=Msvm_KvpExchangeComponent" +$Kvp = gwmi -namespace root\virtualization -query $query -computername $HyperVServer + +Write-Host +Write-Host "Guest KVP information for" $VMName + +# Filter the results +$Kvp.GuestIntrinsicExchangeItems | Import-CimXml diff --git a/hyper-v.lsvmbus.py b/hyper-v.lsvmbus.py new file mode 100644 index 0000000..efd3233 --- /dev/null +++ b/hyper-v.lsvmbus.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0 + +import os +from optparse import OptionParser + +help_msg = "print verbose messages. Try -vv, -vvv for more verbose messages" +parser = OptionParser() +parser.add_option( + "-v", "--verbose", dest="verbose", help=help_msg, action="count") + +(options, args) = parser.parse_args() + +verbose = 0 +if options.verbose is not None: + verbose = options.verbose + +vmbus_sys_path = '/sys/bus/vmbus/devices' +if not os.path.isdir(vmbus_sys_path): + print("%s doesn't exist: exiting..." % vmbus_sys_path) + exit(-1) + +vmbus_dev_dict = { + '{0e0b6031-5213-4934-818b-38d90ced39db}': '[Operating system shutdown]', + '{9527e630-d0ae-497b-adce-e80ab0175caf}': '[Time Synchronization]', + '{57164f39-9115-4e78-ab55-382f3bd5422d}': '[Heartbeat]', + '{a9a0f4e7-5a45-4d96-b827-8a841e8c03e6}': '[Data Exchange]', + '{35fa2e29-ea23-4236-96ae-3a6ebacba440}': '[Backup (volume checkpoint)]', + '{34d14be3-dee4-41c8-9ae7-6b174977c192}': '[Guest services]', + '{525074dc-8985-46e2-8057-a307dc18a502}': '[Dynamic Memory]', + '{cfa8b69e-5b4a-4cc0-b98b-8ba1a1f3f95a}': 'Synthetic mouse', + '{f912ad6d-2b17-48ea-bd65-f927a61c7684}': 'Synthetic keyboard', + '{da0a7802-e377-4aac-8e77-0558eb1073f8}': 'Synthetic framebuffer adapter', + '{f8615163-df3e-46c5-913f-f2d2f965ed0e}': 'Synthetic network adapter', + '{32412632-86cb-44a2-9b5c-50d1417354f5}': 'Synthetic IDE Controller', + '{ba6163d9-04a1-4d29-b605-72e2ffb1dc7f}': 'Synthetic SCSI Controller', + '{2f9bcc4a-0069-4af3-b76b-6fd0be528cda}': 'Synthetic fiber channel adapter', + '{8c2eaf3d-32a7-4b09-ab99-bd1f1c86b501}': 'Synthetic RDMA adapter', + '{44c4f61d-4444-4400-9d52-802e27ede19f}': 'PCI Express pass-through', + '{276aacf4-ac15-426c-98dd-7521ad3f01fe}': '[Reserved system device]', + '{f8e65716-3cb3-4a06-9a60-1889c5cccab5}': '[Reserved system device]', + '{3375baf4-9e15-4b30-b765-67acb10d607b}': '[Reserved system device]', +} + + +def get_vmbus_dev_attr(dev_name, attr): + try: + f = open('%s/%s/%s' % (vmbus_sys_path, dev_name, attr), 'r') + lines = f.readlines() + f.close() + except IOError: + lines = [] + + return lines + + +class VMBus_Dev: + pass + + +vmbus_dev_list = [] + +for f in os.listdir(vmbus_sys_path): + vmbus_id = get_vmbus_dev_attr(f, 'id')[0].strip() + class_id = get_vmbus_dev_attr(f, 'class_id')[0].strip() + device_id = get_vmbus_dev_attr(f, 'device_id')[0].strip() + dev_desc = vmbus_dev_dict.get(class_id, 'Unknown') + + chn_vp_mapping = get_vmbus_dev_attr(f, 'channel_vp_mapping') + chn_vp_mapping = [c.strip() for c in chn_vp_mapping] + chn_vp_mapping = sorted( + chn_vp_mapping, key=lambda c: int(c.split(':')[0])) + + chn_vp_mapping = [ + '\tRel_ID=%s, target_cpu=%s' % + (c.split(':')[0], c.split(':')[1]) for c in chn_vp_mapping + ] + d = VMBus_Dev() + d.sysfs_path = '%s/%s' % (vmbus_sys_path, f) + d.vmbus_id = vmbus_id + d.class_id = class_id + d.device_id = device_id + d.dev_desc = dev_desc + d.chn_vp_mapping = '\n'.join(chn_vp_mapping) + if d.chn_vp_mapping: + d.chn_vp_mapping += '\n' + + vmbus_dev_list.append(d) + + +vmbus_dev_list = sorted(vmbus_dev_list, key=lambda d: int(d.vmbus_id)) + +format0 = '%2s: %s' +format1 = '%2s: Class_ID = %s - %s\n%s' +format2 = '%2s: Class_ID = %s - %s\n\tDevice_ID = %s\n\tSysfs path: %s\n%s' + +for d in vmbus_dev_list: + if verbose == 0: + print(('VMBUS ID ' + format0) % (d.vmbus_id, d.dev_desc)) + elif verbose == 1: + print( + ('VMBUS ID ' + format1) % + (d.vmbus_id, d.class_id, d.dev_desc, d.chn_vp_mapping) + ) + else: + print( + ('VMBUS ID ' + format2) % + ( + d.vmbus_id, d.class_id, d.dev_desc, + d.device_id, d.sysfs_path, d.chn_vp_mapping + ) + ) diff --git a/hyper-v.spec b/hyper-v.spec new file mode 100644 index 0000000..fc98fcc --- /dev/null +++ b/hyper-v.spec @@ -0,0 +1,269 @@ +# +# spec file for package hyper-v +# +# Copyright (c) 2024 SUSE LLC +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via https://bugs.opensuse.org/ +# + + +%define hv_kvp_daemon hv_kvp_daemon +%define hv_vss_daemon hv_vss_daemon +%define hv_fcopy_daemon hv_fcopy_daemon +%define vmbus_bufring vmbus_bufring +%define hv_fcopy_uio_daemon hv_fcopy_uio_daemon +%define include_uapi_linux_hyperv include_uapi_linux_hyperv +%define chardev_kvp vmbus/hv_kvp +%define chardev_vss vmbus/hv_vss +%define chardev_fcopy vmbus/hv_fcopy +%define helper_dir /usr/lib/%name + +Name: hyper-v +ExclusiveArch: %ix86 x86_64 aarch64 +%{?systemd_requires} +BuildRequires: python-rpm-macros +BuildRequires: pkgconfig(systemd) +# Due to usage of char device instead of netlink +Conflicts: kernel < 4.2 +Summary: Microsoft Hyper-V tools +License: GPL-2.0-only +Supplements: modalias(dmi:*svnMicrosoftCorporation*pnVirtualMachine*rnVirtualMachine*) +Supplements: modalias(pci:v00001414d00005353sv*sd*bc*sc*i*) +URL: http://www.kernel.org +# Arbitrary version number +Version: 9 +Release: 0 +Source0: hyper-v.lsvmbus.py +Source7: hyper-v.compare-with-upstream.sh +Source8: hyper-v.tools.hv.vmbus_bufring.h +Source9: hyper-v.include.linux.hyperv.h +Source10: hyper-v.tools.hv.hv_kvp_daemon.c +Source11: hyper-v.tools.hv.vmbus_bufring.c +Source12: hyper-v.tools.hv.hv_vss_daemon.c +Source13: hyper-v.tools.hv.hv_fcopy_uio_daemon.c +Source14: hyper-v.tools.hv.hv_fcopy_daemon.c +Source20: hyper-v.tools.hv.hv_get_dhcp_info.sh +Source21: hyper-v.tools.hv.hv_get_dns_info.sh +Source22: hyper-v.tools.hv.hv_set_ifconfig.sh + +Patch0: hyper-v.kvp.gethostname.patch + +%description +This package contains the Microsoft Hyper-V tools. + +%prep +%setup -Tc +cp -vL %{S:8} %vmbus_bufring.h +cp -vL %{S:9} %include_uapi_linux_hyperv.h +cp -vL %{S:10} . +cp -vL %{S:11} %vmbus_bufring.c +cp -vL %{S:12} %hv_vss_daemon.c +cp -vL %{S:13} %hv_fcopy_uio_daemon.c +cp -vL %{S:14} %hv_fcopy_daemon.c +%patch -P 0 -p1 +mv `basename %{S:10}` %hv_kvp_daemon.c + +%build +sed -i~ '/#include <linux.hyperv.h>/d' %hv_kvp_daemon.c +sed -i~ '/#include <linux.hyperv.h>/d' %hv_vss_daemon.c +sed -i~ '/#include <linux.hyperv.h>/d' %hv_fcopy_uio_daemon.c +sed -i~ '/#include <linux.hyperv.h>/d' %hv_fcopy_daemon.c +gcc \ + $RPM_OPT_FLAGS \ + -Wno-unused-variable \ + -Wno-pointer-sign \ + -D_GNU_SOURCE \ + -g \ + %hv_kvp_daemon.c \ + -include %include_uapi_linux_hyperv.h \ + -DCN_KVP_IDX=0x9 \ + -DCN_KVP_VAL=0x1 \ + -DKVP_SCRIPTS_PATH= \ + -pthread \ + -o %hv_kvp_daemon +gcc \ + $RPM_OPT_FLAGS \ + -Wno-unused-variable \ + -Wno-pointer-sign \ + -D_GNU_SOURCE \ + -g \ + %hv_vss_daemon.c \ + -include %include_uapi_linux_hyperv.h \ + -DCN_VSS_IDX=0xa \ + -DCN_VSS_VAL=0x1 \ + -o %hv_vss_daemon +gcc \ + $RPM_OPT_FLAGS \ + -Wno-unused-variable \ + -Wno-pointer-sign \ + -D_GNU_SOURCE \ + -g \ + %hv_fcopy_daemon.c \ + -include %include_uapi_linux_hyperv.h \ + -o %hv_fcopy_daemon + +%ifarch %ix86 x86_64 +gcc \ + $RPM_OPT_FLAGS \ + -Wno-unused-variable \ + -Wno-pointer-sign \ + -Wno-address-of-packed-member \ + -D_GNU_SOURCE \ + -g \ + %vmbus_bufring.c \ + %hv_fcopy_uio_daemon.c \ + -include %include_uapi_linux_hyperv.h \ + -o %hv_fcopy_uio_daemon +%endif + +%install +# It is not a callable app anyway, so move it out of the way +bindir=%helper_dir/bin +mkdir -p %buildroot${bindir} +mkdir -p %buildroot%_sbindir +install -m755 %hv_kvp_daemon %buildroot${bindir} +install -m755 %hv_vss_daemon %buildroot${bindir} +install -m755 %hv_fcopy_daemon %buildroot${bindir} +%ifarch %ix86 x86_64 +install -m755 %hv_fcopy_uio_daemon %buildroot${bindir} +%endif +cp -avL %{S:0} %buildroot%_sbindir/lsvmbus +chmod 0755 %buildroot%_sbindir/lsvmbus +cp -avL %{S:20} %buildroot${bindir}/hv_get_dhcp_info +cp -avL %{S:21} %buildroot${bindir}/hv_get_dns_info +cp -avL %{S:22} %buildroot${bindir}/hv_set_ifconfig +chmod 755 %buildroot${bindir}/* +d=%buildroot%_unitdir +mkdir -vp ${d} +# +tee ${d}/%hv_kvp_daemon.service <<EOF +# started via %_udevrulesdir/%name.rules +[Unit] +Description=Hyper-V KVP Daemon +After=local-fs.target +ConditionVirtualization=microsoft +ConditionPathExists=/dev/%chardev_kvp + +[Service] +Environment="PATH=${bindir}:/usr/sbin:/usr/bin:/sbin:/bin" +ExecStart=${bindir}/%hv_kvp_daemon --no-daemon +Restart=on-failure + +[Install] +WantedBy=default.target +EOF +# +tee ${d}/%hv_vss_daemon.service <<EOF +# started via %_udevrulesdir/%name.rules +[Unit] +Description=Hyper-V VSS Daemon +ConditionVirtualization=microsoft +ConditionPathExists=/dev/%chardev_vss + +[Service] +ExecStart=${bindir}/%hv_vss_daemon --no-daemon +Restart=on-failure + +[Install] +WantedBy=default.target +EOF +# +tee ${d}/%hv_fcopy_daemon.service <<EOF +# started via %_udevrulesdir/%name.rules +[Unit] +Description=Hyper-V host to guest file copy daemon +ConditionVirtualization=microsoft +ConditionPathExists=/dev/%chardev_fcopy + +[Service] +ExecStart=${bindir}/%hv_fcopy_daemon --no-daemon +Restart=on-failure + +[Install] +WantedBy=default.target +EOF +# +tee ${d}/%hv_fcopy_uio_daemon.service <<EOF +# started via %_udevrulesdir/%name.rules +[Unit] +Description=Hyper-V host to guest UIO file copy daemon +ConditionVirtualization=microsoft +ConditionPathExists=/sys/bus/vmbus/drivers/uio_hv_generic/new_id +ConditionPathExists=!/dev/%chardev_fcopy + +[Service] +ExecStartPre=/bin/sh -c 'set -e;echo 34d14be3-dee4-41c8-9ae7-6b174977c192 > /sys/bus/vmbus/drivers/uio_hv_generic/new_id' +ExecStart=${bindir}/%hv_fcopy_uio_daemon --no-daemon +Restart=on-failure + +[Install] +WantedBy=default.target +EOF +# +# +# +d=%buildroot%_udevrulesdir +mkdir -vp ${d} +tee ${d}/%name.rules <<EOF +ACTION=="add", KERNEL=="%chardev_kvp", TAG+="systemd", ENV{SYSTEMD_WANTS}+="%hv_kvp_daemon.service" +ACTION=="add", KERNEL=="%chardev_vss", TAG+="systemd", ENV{SYSTEMD_WANTS}+="%hv_vss_daemon.service" +ACTION=="add", KERNEL=="%chardev_fcopy", TAG+="systemd", ENV{SYSTEMD_WANTS}+="%hv_fcopy_daemon.service" +ACTION=="add", SUBSYSTEM=="vmbus", ATTRS{class_id}=="{34d14be3-dee4-41c8-9ae7-6b174977c192}", RUN{builtin}+="kmod load uio_hv_generic", TAG+="systemd", ENV{SYSTEMD_WANTS}+="%hv_fcopy_uio_daemon.service" +EOF +# +# +helper=inst_sys.sh +tee %buildroot${bindir}/${helper} <<'_EOF_' +#!/bin/bash +# Starting daemons via RUN== in udev rules is not supported. +# In inst-sys systemd is not used, so start all daemons manually. +bindir=%helper_dir/bin +declare -a helpers +test -c /dev/%chardev_kvp && helpers+=(%hv_kvp_daemon) +test -c /dev/%chardev_vss && helpers+=(%hv_vss_daemon) +if test -c /dev/%chardev_fcopy +then + helpers+=(%hv_fcopy_daemon) +else + modprobe -v uio_hv_generic + if test -f /sys/bus/vmbus/drivers/uio_hv_generic/new_id + then + echo '34d14be3-dee4-41c8-9ae7-6b174977c192' > /sys/bus/vmbus/drivers/uio_hv_generic/new_id + helpers+=(%hv_fcopy_uio_daemon) + fi +fi +if test -d /sys/bus/vmbus/devices +then + export PATH=${bindir}:$PATH + echo -n "Starting hyper-v helpers:" + for i in "${helpers[@]}" + do + if mkdir /run/$i + then + echo -n " $i" + $i < /dev/null & + fi + done + echo " ... done" +fi +_EOF_ +chmod 755 %buildroot${bindir}/${helper} +# +%?python3_fix_shebang + +%files +%_unitdir/* +%_udevrulesdir/* +%_sbindir/* +%helper_dir + +%changelog diff --git a/hyper-v.tools.hv.hv_fcopy_daemon.c b/hyper-v.tools.hv.hv_fcopy_daemon.c new file mode 100644 index 0000000..11628d1 --- /dev/null +++ b/hyper-v.tools.hv.hv_fcopy_daemon.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * An implementation of host to guest copy functionality for Linux. + * + * Copyright (C) 2014, Microsoft, Inc. + * + * Author : K. Y. Srinivasan <kys@microsoft.com> + */ + + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <linux/hyperv.h> +#include <linux/limits.h> +#include <syslog.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <getopt.h> + +static int target_fd; +static char target_fname[PATH_MAX]; +static unsigned long long filesize; + +static int hv_start_fcopy(struct hv_start_fcopy *smsg) +{ + int error = HV_E_FAIL; + char *q, *p; + + filesize = 0; + p = (char *)smsg->path_name; + snprintf(target_fname, sizeof(target_fname), "%s/%s", + (char *)smsg->path_name, (char *)smsg->file_name); + + syslog(LOG_INFO, "Target file name: %s", target_fname); + /* + * Check to see if the path is already in place; if not, + * create if required. + */ + while ((q = strchr(p, '/')) != NULL) { + if (q == p) { + p++; + continue; + } + *q = '\0'; + if (access((char *)smsg->path_name, F_OK)) { + if (smsg->copy_flags & CREATE_PATH) { + if (mkdir((char *)smsg->path_name, 0755)) { + syslog(LOG_ERR, "Failed to create %s", + (char *)smsg->path_name); + goto done; + } + } else { + syslog(LOG_ERR, "Invalid path: %s", + (char *)smsg->path_name); + goto done; + } + } + p = q + 1; + *q = '/'; + } + + if (!access(target_fname, F_OK)) { + syslog(LOG_INFO, "File: %s exists", target_fname); + if (!(smsg->copy_flags & OVER_WRITE)) { + error = HV_ERROR_ALREADY_EXISTS; + goto done; + } + } + + target_fd = open(target_fname, + O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0744); + if (target_fd == -1) { + syslog(LOG_INFO, "Open Failed: %s", strerror(errno)); + goto done; + } + + error = 0; +done: + if (error) + target_fname[0] = '\0'; + return error; +} + +static int hv_copy_data(struct hv_do_fcopy *cpmsg) +{ + ssize_t bytes_written; + int ret = 0; + + bytes_written = pwrite(target_fd, cpmsg->data, cpmsg->size, + cpmsg->offset); + + filesize += cpmsg->size; + if (bytes_written != cpmsg->size) { + switch (errno) { + case ENOSPC: + ret = HV_ERROR_DISK_FULL; + break; + default: + ret = HV_E_FAIL; + break; + } + syslog(LOG_ERR, "pwrite failed to write %llu bytes: %ld (%s)", + filesize, (long)bytes_written, strerror(errno)); + } + + return ret; +} + +/* + * Reset target_fname to "" in the two below functions for hibernation: if + * the fcopy operation is aborted by hibernation, the daemon should remove the + * partially-copied file; to achieve this, the hv_utils driver always fakes a + * CANCEL_FCOPY message upon suspend, and later when the VM resumes back, + * the daemon calls hv_copy_cancel() to remove the file; if a file is copied + * successfully before suspend, hv_copy_finished() must reset target_fname to + * avoid that the file can be incorrectly removed upon resume, since the faked + * CANCEL_FCOPY message is spurious in this case. + */ +static int hv_copy_finished(void) +{ + close(target_fd); + target_fname[0] = '\0'; + return 0; +} +static int hv_copy_cancel(void) +{ + close(target_fd); + if (strlen(target_fname) > 0) { + unlink(target_fname); + target_fname[0] = '\0'; + } + return 0; + +} + +void print_usage(char *argv[]) +{ + fprintf(stderr, "Usage: %s [options]\n" + "Options are:\n" + " -n, --no-daemon stay in foreground, don't daemonize\n" + " -h, --help print this help\n", argv[0]); +} + +int main(int argc, char *argv[]) +{ + int fcopy_fd = -1; + int error; + int daemonize = 1, long_index = 0, opt; + int version = FCOPY_CURRENT_VERSION; + union { + struct hv_fcopy_hdr hdr; + struct hv_start_fcopy start; + struct hv_do_fcopy copy; + __u32 kernel_modver; + } buffer = { }; + int in_handshake; + + static struct option long_options[] = { + {"help", no_argument, 0, 'h' }, + {"no-daemon", no_argument, 0, 'n' }, + {0, 0, 0, 0 } + }; + + while ((opt = getopt_long(argc, argv, "hn", long_options, + &long_index)) != -1) { + switch (opt) { + case 'n': + daemonize = 0; + break; + case 'h': + default: + print_usage(argv); + exit(EXIT_FAILURE); + } + } + + if (daemonize && daemon(1, 0)) { + syslog(LOG_ERR, "daemon() failed; error: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + + openlog("HV_FCOPY", 0, LOG_USER); + syslog(LOG_INFO, "starting; pid is:%d", getpid()); + +reopen_fcopy_fd: + if (fcopy_fd != -1) + close(fcopy_fd); + /* Remove any possible partially-copied file on error */ + hv_copy_cancel(); + in_handshake = 1; + fcopy_fd = open("/dev/vmbus/hv_fcopy", O_RDWR); + + if (fcopy_fd < 0) { + syslog(LOG_ERR, "open /dev/vmbus/hv_fcopy failed; error: %d %s", + errno, strerror(errno)); + exit(EXIT_FAILURE); + } + + /* + * Register with the kernel. + */ + if ((write(fcopy_fd, &version, sizeof(int))) != sizeof(int)) { + syslog(LOG_ERR, "Registration failed: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + + while (1) { + /* + * In this loop we process fcopy messages after the + * handshake is complete. + */ + ssize_t len; + + len = pread(fcopy_fd, &buffer, sizeof(buffer), 0); + if (len < 0) { + syslog(LOG_ERR, "pread failed: %s", strerror(errno)); + goto reopen_fcopy_fd; + } + + if (in_handshake) { + if (len != sizeof(buffer.kernel_modver)) { + syslog(LOG_ERR, "invalid version negotiation"); + exit(EXIT_FAILURE); + } + in_handshake = 0; + syslog(LOG_INFO, "kernel module version: %u", + buffer.kernel_modver); + continue; + } + + switch (buffer.hdr.operation) { + case START_FILE_COPY: + error = hv_start_fcopy(&buffer.start); + break; + case WRITE_TO_FILE: + error = hv_copy_data(&buffer.copy); + break; + case COMPLETE_FCOPY: + error = hv_copy_finished(); + break; + case CANCEL_FCOPY: + error = hv_copy_cancel(); + break; + + default: + error = HV_E_FAIL; + syslog(LOG_ERR, "Unknown operation: %d", + buffer.hdr.operation); + + } + + /* + * pwrite() may return an error due to the faked CANCEL_FCOPY + * message upon hibernation. Ignore the error by resetting the + * dev file, i.e. closing and re-opening it. + */ + if (pwrite(fcopy_fd, &error, sizeof(int), 0) != sizeof(int)) { + syslog(LOG_ERR, "pwrite failed: %s", strerror(errno)); + goto reopen_fcopy_fd; + } + } +} diff --git a/hyper-v.tools.hv.hv_fcopy_uio_daemon.c b/hyper-v.tools.hv.hv_fcopy_uio_daemon.c new file mode 100644 index 0000000..cf19a75 --- /dev/null +++ b/hyper-v.tools.hv.hv_fcopy_uio_daemon.c @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * An implementation of host to guest copy functionality for Linux. + * + * Copyright (C) 2023, Microsoft, Inc. + * + * Author : K. Y. Srinivasan <kys@microsoft.com> + * Author : Saurabh Sengar <ssengar@microsoft.com> + * + */ + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <locale.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <wchar.h> +#include <sys/stat.h> +#include <linux/hyperv.h> +#include <linux/limits.h> +#include "vmbus_bufring.h" + +#define ICMSGTYPE_NEGOTIATE 0 +#define ICMSGTYPE_FCOPY 7 + +#define WIN8_SRV_MAJOR 1 +#define WIN8_SRV_MINOR 1 +#define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR) + +#define FCOPY_UIO "/sys/bus/vmbus/devices/eb765408-105f-49b6-b4aa-c123b64d17d4/uio" + +#define FCOPY_VER_COUNT 1 +static const int fcopy_versions[] = { + WIN8_SRV_VERSION +}; + +#define FW_VER_COUNT 1 +static const int fw_versions[] = { + UTIL_FW_VERSION +}; + +#define HV_RING_SIZE 0x4000 /* 16KB ring buffer size */ + +static unsigned char desc[HV_RING_SIZE]; + +static int target_fd; +static char target_fname[PATH_MAX]; +static unsigned long long filesize; + +static int hv_fcopy_create_file(char *file_name, char *path_name, __u32 flags) +{ + int error = HV_E_FAIL; + char *q, *p; + + filesize = 0; + p = path_name; + snprintf(target_fname, sizeof(target_fname), "%s/%s", + path_name, file_name); + + /* + * Check to see if the path is already in place; if not, + * create if required. + */ + while ((q = strchr(p, '/')) != NULL) { + if (q == p) { + p++; + continue; + } + *q = '\0'; + if (access(path_name, F_OK)) { + if (flags & CREATE_PATH) { + if (mkdir(path_name, 0755)) { + syslog(LOG_ERR, "Failed to create %s", + path_name); + goto done; + } + } else { + syslog(LOG_ERR, "Invalid path: %s", path_name); + goto done; + } + } + p = q + 1; + *q = '/'; + } + + if (!access(target_fname, F_OK)) { + syslog(LOG_INFO, "File: %s exists", target_fname); + if (!(flags & OVER_WRITE)) { + error = HV_ERROR_ALREADY_EXISTS; + goto done; + } + } + + target_fd = open(target_fname, + O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0744); + if (target_fd == -1) { + syslog(LOG_INFO, "Open Failed: %s", strerror(errno)); + goto done; + } + + error = 0; +done: + if (error) + target_fname[0] = '\0'; + return error; +} + +/* copy the data into the file */ +static int hv_copy_data(struct hv_do_fcopy *cpmsg) +{ + ssize_t len; + int ret = 0; + + len = pwrite(target_fd, cpmsg->data, cpmsg->size, cpmsg->offset); + + filesize += cpmsg->size; + if (len != cpmsg->size) { + switch (errno) { + case ENOSPC: + ret = HV_ERROR_DISK_FULL; + break; + default: + ret = HV_E_FAIL; + break; + } + syslog(LOG_ERR, "pwrite failed to write %llu bytes: %ld (%s)", + filesize, (long)len, strerror(errno)); + } + + return ret; +} + +static int hv_copy_finished(void) +{ + close(target_fd); + target_fname[0] = '\0'; + + return 0; +} + +static void print_usage(char *argv[]) +{ + fprintf(stderr, "Usage: %s [options]\n" + "Options are:\n" + " -n, --no-daemon stay in foreground, don't daemonize\n" + " -h, --help print this help\n", argv[0]); +} + +static bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp, unsigned char *buf, + unsigned int buflen, const int *fw_version, int fw_vercnt, + const int *srv_version, int srv_vercnt, + int *nego_fw_version, int *nego_srv_version) +{ + int icframe_major, icframe_minor; + int icmsg_major, icmsg_minor; + int fw_major, fw_minor; + int srv_major, srv_minor; + int i, j; + bool found_match = false; + struct icmsg_negotiate *negop; + + /* Check that there's enough space for icframe_vercnt, icmsg_vercnt */ + if (buflen < ICMSG_HDR + offsetof(struct icmsg_negotiate, reserved)) { + syslog(LOG_ERR, "Invalid icmsg negotiate"); + return false; + } + + icmsghdrp->icmsgsize = 0x10; + negop = (struct icmsg_negotiate *)&buf[ICMSG_HDR]; + + icframe_major = negop->icframe_vercnt; + icframe_minor = 0; + + icmsg_major = negop->icmsg_vercnt; + icmsg_minor = 0; + + /* Validate negop packet */ + if (icframe_major > IC_VERSION_NEGOTIATION_MAX_VER_COUNT || + icmsg_major > IC_VERSION_NEGOTIATION_MAX_VER_COUNT || + ICMSG_NEGOTIATE_PKT_SIZE(icframe_major, icmsg_major) > buflen) { + syslog(LOG_ERR, "Invalid icmsg negotiate - icframe_major: %u, icmsg_major: %u\n", + icframe_major, icmsg_major); + goto fw_error; + } + + /* + * Select the framework version number we will + * support. + */ + + for (i = 0; i < fw_vercnt; i++) { + fw_major = (fw_version[i] >> 16); + fw_minor = (fw_version[i] & 0xFFFF); + + for (j = 0; j < negop->icframe_vercnt; j++) { + if (negop->icversion_data[j].major == fw_major && + negop->icversion_data[j].minor == fw_minor) { + icframe_major = negop->icversion_data[j].major; + icframe_minor = negop->icversion_data[j].minor; + found_match = true; + break; + } + } + + if (found_match) + break; + } + + if (!found_match) + goto fw_error; + + found_match = false; + + for (i = 0; i < srv_vercnt; i++) { + srv_major = (srv_version[i] >> 16); + srv_minor = (srv_version[i] & 0xFFFF); + + for (j = negop->icframe_vercnt; + (j < negop->icframe_vercnt + negop->icmsg_vercnt); + j++) { + if (negop->icversion_data[j].major == srv_major && + negop->icversion_data[j].minor == srv_minor) { + icmsg_major = negop->icversion_data[j].major; + icmsg_minor = negop->icversion_data[j].minor; + found_match = true; + break; + } + } + + if (found_match) + break; + } + + /* + * Respond with the framework and service + * version numbers we can support. + */ +fw_error: + if (!found_match) { + negop->icframe_vercnt = 0; + negop->icmsg_vercnt = 0; + } else { + negop->icframe_vercnt = 1; + negop->icmsg_vercnt = 1; + } + + if (nego_fw_version) + *nego_fw_version = (icframe_major << 16) | icframe_minor; + + if (nego_srv_version) + *nego_srv_version = (icmsg_major << 16) | icmsg_minor; + + negop->icversion_data[0].major = icframe_major; + negop->icversion_data[0].minor = icframe_minor; + negop->icversion_data[1].major = icmsg_major; + negop->icversion_data[1].minor = icmsg_minor; + + return found_match; +} + +static void wcstoutf8(char *dest, const __u16 *src, size_t dest_size) +{ + size_t len = 0; + + while (len < dest_size) { + if (src[len] < 0x80) + dest[len++] = (char)(*src++); + else + dest[len++] = 'X'; + } + + dest[len] = '\0'; +} + +static int hv_fcopy_start(struct hv_start_fcopy *smsg_in) +{ + setlocale(LC_ALL, "en_US.utf8"); + size_t file_size, path_size; + char *file_name, *path_name; + char *in_file_name = (char *)smsg_in->file_name; + char *in_path_name = (char *)smsg_in->path_name; + + file_size = wcstombs(NULL, (const wchar_t *restrict)in_file_name, 0) + 1; + path_size = wcstombs(NULL, (const wchar_t *restrict)in_path_name, 0) + 1; + + file_name = (char *)malloc(file_size * sizeof(char)); + path_name = (char *)malloc(path_size * sizeof(char)); + + if (!file_name || !path_name) { + free(file_name); + free(path_name); + syslog(LOG_ERR, "Can't allocate memory for file name and/or path name"); + return HV_E_FAIL; + } + + wcstoutf8(file_name, (__u16 *)in_file_name, file_size); + wcstoutf8(path_name, (__u16 *)in_path_name, path_size); + + return hv_fcopy_create_file(file_name, path_name, smsg_in->copy_flags); +} + +static int hv_fcopy_send_data(struct hv_fcopy_hdr *fcopy_msg, int recvlen) +{ + int operation = fcopy_msg->operation; + + /* + * The strings sent from the host are encoded in + * utf16; convert it to utf8 strings. + * The host assures us that the utf16 strings will not exceed + * the max lengths specified. We will however, reserve room + * for the string terminating character - in the utf16s_utf8s() + * function we limit the size of the buffer where the converted + * string is placed to W_MAX_PATH -1 to guarantee + * that the strings can be properly terminated! + */ + + switch (operation) { + case START_FILE_COPY: + return hv_fcopy_start((struct hv_start_fcopy *)fcopy_msg); + case WRITE_TO_FILE: + return hv_copy_data((struct hv_do_fcopy *)fcopy_msg); + case COMPLETE_FCOPY: + return hv_copy_finished(); + } + + return HV_E_FAIL; +} + +/* process the packet recv from host */ +static int fcopy_pkt_process(struct vmbus_br *txbr) +{ + int ret, offset, pktlen; + int fcopy_srv_version; + const struct vmbus_chanpkt_hdr *pkt; + struct hv_fcopy_hdr *fcopy_msg; + struct icmsg_hdr *icmsghdr; + + pkt = (const struct vmbus_chanpkt_hdr *)desc; + offset = pkt->hlen << 3; + pktlen = (pkt->tlen << 3) - offset; + icmsghdr = (struct icmsg_hdr *)&desc[offset + sizeof(struct vmbuspipe_hdr)]; + icmsghdr->status = HV_E_FAIL; + + if (icmsghdr->icmsgtype == ICMSGTYPE_NEGOTIATE) { + if (vmbus_prep_negotiate_resp(icmsghdr, desc + offset, pktlen, fw_versions, + FW_VER_COUNT, fcopy_versions, FCOPY_VER_COUNT, + NULL, &fcopy_srv_version)) { + syslog(LOG_INFO, "FCopy IC version %d.%d", + fcopy_srv_version >> 16, fcopy_srv_version & 0xFFFF); + icmsghdr->status = 0; + } + } else if (icmsghdr->icmsgtype == ICMSGTYPE_FCOPY) { + /* Ensure recvlen is big enough to contain hv_fcopy_hdr */ + if (pktlen < ICMSG_HDR + sizeof(struct hv_fcopy_hdr)) { + syslog(LOG_ERR, "Invalid Fcopy hdr. Packet length too small: %u", + pktlen); + return -ENOBUFS; + } + + fcopy_msg = (struct hv_fcopy_hdr *)&desc[offset + ICMSG_HDR]; + icmsghdr->status = hv_fcopy_send_data(fcopy_msg, pktlen); + } + + icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE; + ret = rte_vmbus_chan_send(txbr, 0x6, desc + offset, pktlen, 0); + if (ret) { + syslog(LOG_ERR, "Write to ringbuffer failed err: %d", ret); + return ret; + } + + return 0; +} + +static void fcopy_get_first_folder(char *path, char *chan_no) +{ + DIR *dir = opendir(path); + struct dirent *entry; + + if (!dir) { + syslog(LOG_ERR, "Failed to open directory (errno=%s).\n", strerror(errno)); + return; + } + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type == DT_DIR && strcmp(entry->d_name, ".") != 0 && + strcmp(entry->d_name, "..") != 0) { + strcpy(chan_no, entry->d_name); + break; + } + } + + closedir(dir); +} + +int main(int argc, char *argv[]) +{ + int fcopy_fd = -1, tmp = 1; + int daemonize = 1, long_index = 0, opt, ret = -EINVAL; + struct vmbus_br txbr, rxbr; + void *ring; + uint32_t len = HV_RING_SIZE; + char uio_name[NAME_MAX] = {0}; + char uio_dev_path[PATH_MAX] = {0}; + + static struct option long_options[] = { + {"help", no_argument, 0, 'h' }, + {"no-daemon", no_argument, 0, 'n' }, + {0, 0, 0, 0 } + }; + + while ((opt = getopt_long(argc, argv, "hn", long_options, + &long_index)) != -1) { + switch (opt) { + case 'n': + daemonize = 0; + break; + case 'h': + default: + print_usage(argv); + goto exit; + } + } + + if (daemonize && daemon(1, 0)) { + syslog(LOG_ERR, "daemon() failed; error: %s", strerror(errno)); + goto exit; + } + + openlog("HV_UIO_FCOPY", 0, LOG_USER); + syslog(LOG_INFO, "starting; pid is:%d", getpid()); + + fcopy_get_first_folder(FCOPY_UIO, uio_name); + snprintf(uio_dev_path, sizeof(uio_dev_path), "/dev/%s", uio_name); + fcopy_fd = open(uio_dev_path, O_RDWR); + + if (fcopy_fd < 0) { + syslog(LOG_ERR, "open %s failed; error: %d %s", + uio_dev_path, errno, strerror(errno)); + ret = fcopy_fd; + goto exit; + } + + ring = vmbus_uio_map(&fcopy_fd, HV_RING_SIZE); + if (!ring) { + ret = errno; + syslog(LOG_ERR, "mmap ringbuffer failed; error: %d %s", ret, strerror(ret)); + goto close; + } + vmbus_br_setup(&txbr, ring, HV_RING_SIZE); + vmbus_br_setup(&rxbr, (char *)ring + HV_RING_SIZE, HV_RING_SIZE); + + rxbr.vbr->imask = 0; + + while (1) { + /* + * In this loop we process fcopy messages after the + * handshake is complete. + */ + ret = pread(fcopy_fd, &tmp, sizeof(int), 0); + if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + syslog(LOG_ERR, "pread failed: %s", strerror(errno)); + goto close; + } + + len = HV_RING_SIZE; + ret = rte_vmbus_chan_recv_raw(&rxbr, desc, &len); + if (unlikely(ret <= 0)) { + /* This indicates a failure to communicate (or worse) */ + syslog(LOG_ERR, "VMBus channel recv error: %d", ret); + } else { + ret = fcopy_pkt_process(&txbr); + if (ret < 0) + goto close; + + /* Signal host */ + if ((write(fcopy_fd, &tmp, sizeof(int))) != sizeof(int)) { + ret = errno; + syslog(LOG_ERR, "Signal to host failed: %s\n", strerror(ret)); + goto close; + } + } + } +close: + close(fcopy_fd); +exit: + return ret; +} diff --git a/hyper-v.tools.hv.hv_get_dhcp_info.sh b/hyper-v.tools.hv.hv_get_dhcp_info.sh new file mode 100644 index 0000000..a6d81e9 --- /dev/null +++ b/hyper-v.tools.hv.hv_get_dhcp_info.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# This script retrieves the DHCP state of a given interface. +# In the interest of keeping the KVP daemon code free of distro specific +# information; the kvp daemon code invokes this external script to gather +# DHCP setting for the specific interface. +# +# Input: Name of the interface +# +# Output: The script prints the string "Enabled" to stdout to indicate +# that DHCP is enabled on the interface. If DHCP is not enabled, +# the script prints the string "Disabled" to stdout. +# +# Each Distro is expected to implement this script in a distro specific +# fashion. For instance, on Distros that ship with Network Manager enabled, +# this script can be based on the Network Manager APIs for retrieving DHCP +# information. + +if_file="/etc/sysconfig/network/ifcfg-$1" + +dhcp=$(grep -- '^BOOTPROTO=.*dhcp' "$if_file" 2>/dev/null) + +if [ "$dhcp" != "" ]; +then +echo "Enabled" +else +echo "Disabled" +fi diff --git a/hyper-v.tools.hv.hv_get_dns_info.sh b/hyper-v.tools.hv.hv_get_dns_info.sh new file mode 100644 index 0000000..db17856 --- /dev/null +++ b/hyper-v.tools.hv.hv_get_dns_info.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# This script parses /etc/resolv.conf to retrive DNS information. +# In the interest of keeping the KVP daemon code free of distro specific +# information; the kvp daemon code invokes this external script to gather +# DNS information. +# This script is expected to print the nameserver values to stdout. +# Each Distro is expected to implement this script in a distro specific +# fashion. For instance on Distros that ship with Network Manager enabled, +# this script can be based on the Network Manager APIs for retrieving DNS +# entries. + +if test -f /etc/resolv.conf +then + exec awk -- '/^nameserver/ { print $2 }' /etc/resolv.conf +fi +exit 0 diff --git a/hyper-v.tools.hv.hv_kvp_daemon.c b/hyper-v.tools.hv.hv_kvp_daemon.c new file mode 100644 index 0000000..8c4ed6d --- /dev/null +++ b/hyper-v.tools.hv.hv_kvp_daemon.c @@ -0,0 +1,1947 @@ +/* + * An implementation of key value pair (KVP) functionality for Linux. + * + * + * Copyright (C) 2010, Novell, Inc. + * Author : K. Y. Srinivasan <ksrinivasan@novell.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * 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, GOOD TITLE or + * NON INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + + +#include <sys/poll.h> +#include <sys/utsname.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <arpa/inet.h> +#include <linux/hyperv.h> +#include <ifaddrs.h> +#include <netdb.h> +#include <syslog.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <net/if.h> +#include <limits.h> +#include <getopt.h> + +/* + * KVP protocol: The user mode component first registers with the + * kernel component. Subsequently, the kernel component requests, data + * for the specified keys. In response to this message the user mode component + * fills in the value corresponding to the specified key. We overload the + * sequence field in the cn_msg header to define our KVP message types. + * + * We use this infrastructure for also supporting queries from user mode + * application for state that may be maintained in the KVP kernel component. + * + */ + + +enum key_index { + FullyQualifiedDomainName = 0, + IntegrationServicesVersion, /*This key is serviced in the kernel*/ + NetworkAddressIPv4, + NetworkAddressIPv6, + OSBuildNumber, + OSName, + OSMajorVersion, + OSMinorVersion, + OSVersion, + ProcessorArchitecture +}; + + +enum { + IPADDR = 0, + NETMASK, + GATEWAY, + DNS +}; + +enum { + IPV4 = 1, + IPV6, + IP_TYPE_MAX +}; + +static int in_hand_shake; + +static char *os_name = ""; +static char *os_major = ""; +static char *os_minor = ""; +static char *processor_arch; +static char *os_build; +static char *os_version; +static char *lic_version = "Unknown version"; +static char full_domain_name[HV_KVP_EXCHANGE_MAX_VALUE_SIZE]; +static struct utsname uts_buf; + +/* + * The location of the interface configuration file. + */ + +#define KVP_CONFIG_LOC "/var/lib/hyperv" + +#ifndef KVP_SCRIPTS_PATH +#define KVP_SCRIPTS_PATH "/usr/libexec/hypervkvpd/" +#endif + +#define KVP_NET_DIR "/sys/class/net/" + +#define MAX_FILE_NAME 100 +#define ENTRIES_PER_BLOCK 50 +/* + * Change this entry if the number of addresses increases in future + */ +#define MAX_IP_ENTRIES 64 +#define OUTSTR_BUF_SIZE ((INET6_ADDRSTRLEN + 1) * MAX_IP_ENTRIES) + +struct kvp_record { + char key[HV_KVP_EXCHANGE_MAX_KEY_SIZE]; + char value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE]; +}; + +struct kvp_file_state { + int fd; + int num_blocks; + struct kvp_record *records; + int num_records; + char fname[MAX_FILE_NAME]; +}; + +static struct kvp_file_state kvp_file_info[KVP_POOL_COUNT]; + +static void kvp_acquire_lock(int pool) +{ + struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0}; + fl.l_pid = getpid(); + + if (fcntl(kvp_file_info[pool].fd, F_SETLKW, &fl) == -1) { + syslog(LOG_ERR, "Failed to acquire the lock pool: %d; error: %d %s", pool, + errno, strerror(errno)); + exit(EXIT_FAILURE); + } +} + +static void kvp_release_lock(int pool) +{ + struct flock fl = {F_UNLCK, SEEK_SET, 0, 0, 0}; + fl.l_pid = getpid(); + + if (fcntl(kvp_file_info[pool].fd, F_SETLK, &fl) == -1) { + syslog(LOG_ERR, "Failed to release the lock pool: %d; error: %d %s", pool, + errno, strerror(errno)); + exit(EXIT_FAILURE); + } +} + +static void kvp_update_file(int pool) +{ + FILE *filep; + + /* + * We are going to write our in-memory registry out to + * disk; acquire the lock first. + */ + kvp_acquire_lock(pool); + + filep = fopen(kvp_file_info[pool].fname, "we"); + if (!filep) { + syslog(LOG_ERR, "Failed to open file, pool: %d; error: %d %s", pool, + errno, strerror(errno)); + kvp_release_lock(pool); + exit(EXIT_FAILURE); + } + + fwrite(kvp_file_info[pool].records, sizeof(struct kvp_record), + kvp_file_info[pool].num_records, filep); + + if (ferror(filep) || fclose(filep)) { + kvp_release_lock(pool); + syslog(LOG_ERR, "Failed to write file, pool: %d", pool); + exit(EXIT_FAILURE); + } + + kvp_release_lock(pool); +} + +static void kvp_update_mem_state(int pool) +{ + FILE *filep; + size_t records_read = 0; + struct kvp_record *record = kvp_file_info[pool].records; + struct kvp_record *readp; + int num_blocks = kvp_file_info[pool].num_blocks; + int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK; + + kvp_acquire_lock(pool); + + filep = fopen(kvp_file_info[pool].fname, "re"); + if (!filep) { + syslog(LOG_ERR, "Failed to open file, pool: %d; error: %d %s", pool, + errno, strerror(errno)); + kvp_release_lock(pool); + exit(EXIT_FAILURE); + } + for (;;) { + readp = &record[records_read]; + records_read += fread(readp, sizeof(struct kvp_record), + ENTRIES_PER_BLOCK * num_blocks - records_read, + filep); + + if (ferror(filep)) { + syslog(LOG_ERR, + "Failed to read file, pool: %d; error: %d %s", + pool, errno, strerror(errno)); + kvp_release_lock(pool); + exit(EXIT_FAILURE); + } + + if (!feof(filep)) { + /* + * We have more data to read. + */ + num_blocks++; + record = realloc(record, alloc_unit * num_blocks); + + if (record == NULL) { + syslog(LOG_ERR, "malloc failed"); + kvp_release_lock(pool); + exit(EXIT_FAILURE); + } + continue; + } + break; + } + + kvp_file_info[pool].num_blocks = num_blocks; + kvp_file_info[pool].records = record; + kvp_file_info[pool].num_records = records_read; + + fclose(filep); + kvp_release_lock(pool); +} + +static int kvp_file_init(void) +{ + int fd; + char *fname; + int i; + int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK; + + if (access(KVP_CONFIG_LOC, F_OK)) { + if (mkdir(KVP_CONFIG_LOC, 0755 /* rwxr-xr-x */)) { + syslog(LOG_ERR, "Failed to create '%s'; error: %d %s", KVP_CONFIG_LOC, + errno, strerror(errno)); + exit(EXIT_FAILURE); + } + } + + for (i = 0; i < KVP_POOL_COUNT; i++) { + fname = kvp_file_info[i].fname; + sprintf(fname, "%s/.kvp_pool_%d", KVP_CONFIG_LOC, i); + fd = open(fname, O_RDWR | O_CREAT | O_CLOEXEC, 0644 /* rw-r--r-- */); + + if (fd == -1) + return 1; + + kvp_file_info[i].fd = fd; + kvp_file_info[i].num_blocks = 1; + kvp_file_info[i].records = malloc(alloc_unit); + if (kvp_file_info[i].records == NULL) + return 1; + kvp_file_info[i].num_records = 0; + kvp_update_mem_state(i); + } + + return 0; +} + +static int kvp_key_delete(int pool, const __u8 *key, int key_size) +{ + int i; + int j, k; + int num_records; + struct kvp_record *record; + + /* + * First update the in-memory state. + */ + kvp_update_mem_state(pool); + + num_records = kvp_file_info[pool].num_records; + record = kvp_file_info[pool].records; + + for (i = 0; i < num_records; i++) { + if (memcmp(key, record[i].key, key_size)) + continue; + /* + * Found a match; just move the remaining + * entries up. + */ + if (i == (num_records - 1)) { + kvp_file_info[pool].num_records--; + kvp_update_file(pool); + return 0; + } + + j = i; + k = j + 1; + for (; k < num_records; k++) { + strcpy(record[j].key, record[k].key); + strcpy(record[j].value, record[k].value); + j++; + } + + kvp_file_info[pool].num_records--; + kvp_update_file(pool); + return 0; + } + return 1; +} + +static int kvp_key_add_or_modify(int pool, const __u8 *key, int key_size, + const __u8 *value, int value_size) +{ + int i; + int num_records; + struct kvp_record *record; + int num_blocks; + + if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) || + (value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) + return 1; + + /* + * First update the in-memory state. + */ + kvp_update_mem_state(pool); + + num_records = kvp_file_info[pool].num_records; + record = kvp_file_info[pool].records; + num_blocks = kvp_file_info[pool].num_blocks; + + for (i = 0; i < num_records; i++) { + if (memcmp(key, record[i].key, key_size)) + continue; + /* + * Found a match; just update the value - + * this is the modify case. + */ + memcpy(record[i].value, value, value_size); + kvp_update_file(pool); + return 0; + } + + /* + * Need to add a new entry; + */ + if (num_records == (ENTRIES_PER_BLOCK * num_blocks)) { + /* Need to allocate a larger array for reg entries. */ + record = realloc(record, sizeof(struct kvp_record) * + ENTRIES_PER_BLOCK * (num_blocks + 1)); + + if (record == NULL) + return 1; + kvp_file_info[pool].num_blocks++; + + } + memcpy(record[i].value, value, value_size); + memcpy(record[i].key, key, key_size); + kvp_file_info[pool].records = record; + kvp_file_info[pool].num_records++; + kvp_update_file(pool); + return 0; +} + +static int kvp_get_value(int pool, const __u8 *key, int key_size, __u8 *value, + int value_size) +{ + int i; + int num_records; + struct kvp_record *record; + + if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) || + (value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) + return 1; + + /* + * First update the in-memory state. + */ + kvp_update_mem_state(pool); + + num_records = kvp_file_info[pool].num_records; + record = kvp_file_info[pool].records; + + for (i = 0; i < num_records; i++) { + if (memcmp(key, record[i].key, key_size)) + continue; + /* + * Found a match; just copy the value out. + */ + memcpy(value, record[i].value, value_size); + return 0; + } + + return 1; +} + +static int kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size, + __u8 *value, int value_size) +{ + struct kvp_record *record; + + /* + * First update our in-memory database. + */ + kvp_update_mem_state(pool); + record = kvp_file_info[pool].records; + + if (index >= kvp_file_info[pool].num_records) { + return 1; + } + + memcpy(key, record[index].key, key_size); + memcpy(value, record[index].value, value_size); + return 0; +} + + +void kvp_get_os_info(void) +{ + FILE *file; + char *p, buf[512]; + + uname(&uts_buf); + os_version = uts_buf.release; + os_build = strdup(uts_buf.release); + + os_name = uts_buf.sysname; + processor_arch = uts_buf.machine; + + /* + * The current windows host (win7) expects the build + * string to be of the form: x.y.z + * Strip additional information we may have. + */ + p = strchr(os_version, '-'); + if (p) + *p = '\0'; + + /* + * Parse the /etc/os-release file if present: + * https://www.freedesktop.org/software/systemd/man/os-release.html + */ + file = fopen("/etc/os-release", "r"); + if (file != NULL) { + while (fgets(buf, sizeof(buf), file)) { + char *value, *q; + + /* Ignore comments */ + if (buf[0] == '#') + continue; + + /* Split into name=value */ + p = strchr(buf, '='); + if (!p) + continue; + *p++ = 0; + + /* Remove quotes and newline; un-escape */ + value = p; + q = p; + while (*p) { + if (*p == '\\') { + ++p; + if (!*p) + break; + *q++ = *p++; + } else if (*p == '\'' || *p == '"' || + *p == '\n') { + ++p; + } else { + *q++ = *p++; + } + } + *q = 0; + + if (!strcmp(buf, "NAME")) { + p = strdup(value); + if (!p) + break; + os_name = p; + } else if (!strcmp(buf, "VERSION_ID")) { + p = strdup(value); + if (!p) + break; + os_major = p; + } + } + fclose(file); + return; + } + + /* Fallback for older RH/SUSE releases */ + file = fopen("/etc/SuSE-release", "r"); + if (file != NULL) + goto kvp_osinfo_found; + file = fopen("/etc/redhat-release", "r"); + if (file != NULL) + goto kvp_osinfo_found; + + /* + * We don't have information about the os. + */ + return; + +kvp_osinfo_found: + /* up to three lines */ + p = fgets(buf, sizeof(buf), file); + if (p) { + p = strchr(buf, '\n'); + if (p) + *p = '\0'; + p = strdup(buf); + if (!p) + goto done; + os_name = p; + + /* second line */ + p = fgets(buf, sizeof(buf), file); + if (p) { + p = strchr(buf, '\n'); + if (p) + *p = '\0'; + p = strdup(buf); + if (!p) + goto done; + os_major = p; + + /* third line */ + p = fgets(buf, sizeof(buf), file); + if (p) { + p = strchr(buf, '\n'); + if (p) + *p = '\0'; + p = strdup(buf); + if (p) + os_minor = p; + } + } + } + +done: + fclose(file); + return; +} + + + +/* + * Retrieve an interface name corresponding to the specified guid. + * If there is a match, the function returns a pointer + * to the interface name and if not, a NULL is returned. + * If a match is found, the caller is responsible for + * freeing the memory. + */ + +static char *kvp_get_if_name(char *guid) +{ + DIR *dir; + struct dirent *entry; + FILE *file; + char *p, *x; + char *if_name = NULL; + char buf[256]; + char dev_id[PATH_MAX]; + + dir = opendir(KVP_NET_DIR); + if (dir == NULL) + return NULL; + + while ((entry = readdir(dir)) != NULL) { + /* + * Set the state for the next pass. + */ + snprintf(dev_id, sizeof(dev_id), "%s%s/device/device_id", + KVP_NET_DIR, entry->d_name); + + file = fopen(dev_id, "r"); + if (file == NULL) + continue; + + p = fgets(buf, sizeof(buf), file); + if (p) { + x = strchr(p, '\n'); + if (x) + *x = '\0'; + + if (!strcmp(p, guid)) { + /* + * Found the guid match; return the interface + * name. The caller will free the memory. + */ + if_name = strdup(entry->d_name); + fclose(file); + break; + } + } + fclose(file); + } + + closedir(dir); + return if_name; +} + +/* + * Retrieve the MAC address given the interface name. + */ + +static char *kvp_if_name_to_mac(char *if_name) +{ + FILE *file; + char *p, *x; + char buf[256]; + char addr_file[PATH_MAX]; + unsigned int i; + char *mac_addr = NULL; + + snprintf(addr_file, sizeof(addr_file), "%s%s%s", KVP_NET_DIR, + if_name, "/address"); + + file = fopen(addr_file, "r"); + if (file == NULL) + return NULL; + + p = fgets(buf, sizeof(buf), file); + if (p) { + x = strchr(p, '\n'); + if (x) + *x = '\0'; + for (i = 0; i < strlen(p); i++) + p[i] = toupper(p[i]); + mac_addr = strdup(p); + } + + fclose(file); + return mac_addr; +} + +static void kvp_process_ipconfig_file(char *cmd, + char *config_buf, unsigned int len, + int element_size, int offset) +{ + char buf[256]; + char *p; + char *x; + FILE *file; + + /* + * First execute the command. + */ + file = popen(cmd, "r"); + if (file == NULL) + return; + + if (offset == 0) + memset(config_buf, 0, len); + while ((p = fgets(buf, sizeof(buf), file)) != NULL) { + if (len < strlen(config_buf) + element_size + 1) + break; + + x = strchr(p, '\n'); + if (x) + *x = '\0'; + + strcat(config_buf, p); + strcat(config_buf, ";"); + } + pclose(file); +} + +static void kvp_get_ipconfig_info(char *if_name, + struct hv_kvp_ipaddr_value *buffer) +{ + char cmd[512]; + char dhcp_info[128]; + char *p; + FILE *file; + + /* + * Get the address of default gateway (ipv4). + */ + sprintf(cmd, "%s %s", "ip route show dev", if_name); + strcat(cmd, " | awk '/default/ {print $3 }'"); + + /* + * Execute the command to gather gateway info. + */ + kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way, + (MAX_GATEWAY_SIZE * 2), INET_ADDRSTRLEN, 0); + + /* + * Get the address of default gateway (ipv6). + */ + sprintf(cmd, "%s %s", "ip -f inet6 route show dev", if_name); + strcat(cmd, " | awk '/default/ {print $3 }'"); + + /* + * Execute the command to gather gateway info (ipv6). + */ + kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way, + (MAX_GATEWAY_SIZE * 2), INET6_ADDRSTRLEN, 1); + + + /* + * Gather the DNS state. + * Since there is no standard way to get this information + * across various distributions of interest; we just invoke + * an external script that needs to be ported across distros + * of interest. + * + * Following is the expected format of the information from the script: + * + * ipaddr1 (nameserver1) + * ipaddr2 (nameserver2) + * . + * . + */ + + sprintf(cmd, "exec %s %s", KVP_SCRIPTS_PATH "hv_get_dns_info", if_name); + + /* + * Execute the command to gather DNS info. + */ + kvp_process_ipconfig_file(cmd, (char *)buffer->dns_addr, + (MAX_IP_ADDR_SIZE * 2), INET_ADDRSTRLEN, 0); + + /* + * Gather the DHCP state. + * We will gather this state by invoking an external script. + * The parameter to the script is the interface name. + * Here is the expected output: + * + * Enabled: DHCP enabled. + */ + + sprintf(cmd, "exec %s %s", KVP_SCRIPTS_PATH "hv_get_dhcp_info", if_name); + + file = popen(cmd, "r"); + if (file == NULL) + return; + + p = fgets(dhcp_info, sizeof(dhcp_info), file); + if (p == NULL) { + pclose(file); + return; + } + + if (!strncmp(p, "Enabled", 7)) + buffer->dhcp_enabled = 1; + else + buffer->dhcp_enabled = 0; + + pclose(file); +} + + +static unsigned int hweight32(unsigned int *w) +{ + unsigned int res = *w - ((*w >> 1) & 0x55555555); + res = (res & 0x33333333) + ((res >> 2) & 0x33333333); + res = (res + (res >> 4)) & 0x0F0F0F0F; + res = res + (res >> 8); + return (res + (res >> 16)) & 0x000000FF; +} + +static int kvp_process_ip_address(void *addrp, + int family, char *buffer, + int length, int *offset) +{ + struct sockaddr_in *addr; + struct sockaddr_in6 *addr6; + int addr_length; + char tmp[50]; + const char *str; + + if (family == AF_INET) { + addr = addrp; + str = inet_ntop(family, &addr->sin_addr, tmp, 50); + addr_length = INET_ADDRSTRLEN; + } else { + addr6 = addrp; + str = inet_ntop(family, &addr6->sin6_addr.s6_addr, tmp, 50); + addr_length = INET6_ADDRSTRLEN; + } + + if ((length - *offset) < addr_length + 2) + return HV_E_FAIL; + if (str == NULL) { + strcpy(buffer, "inet_ntop failed\n"); + return HV_E_FAIL; + } + if (*offset == 0) + strcpy(buffer, tmp); + else { + strcat(buffer, ";"); + strcat(buffer, tmp); + } + + *offset += strlen(str) + 1; + + return 0; +} + +static int +kvp_get_ip_info(int family, char *if_name, int op, + void *out_buffer, unsigned int length) +{ + struct ifaddrs *ifap; + struct ifaddrs *curp; + int offset = 0; + int sn_offset = 0; + int error = 0; + char *buffer; + struct hv_kvp_ipaddr_value *ip_buffer = NULL; + char cidr_mask[5]; /* /xyz */ + int weight; + int i; + unsigned int *w; + char *sn_str; + struct sockaddr_in6 *addr6; + + if (op == KVP_OP_ENUMERATE) { + buffer = out_buffer; + } else { + ip_buffer = out_buffer; + buffer = (char *)ip_buffer->ip_addr; + ip_buffer->addr_family = 0; + } + /* + * On entry into this function, the buffer is capable of holding the + * maximum key value. + */ + + if (getifaddrs(&ifap)) { + strcpy(buffer, "getifaddrs failed\n"); + return HV_E_FAIL; + } + + curp = ifap; + while (curp != NULL) { + if (curp->ifa_addr == NULL) { + curp = curp->ifa_next; + continue; + } + + if ((if_name != NULL) && + (strncmp(curp->ifa_name, if_name, strlen(if_name)))) { + /* + * We want info about a specific interface; + * just continue. + */ + curp = curp->ifa_next; + continue; + } + + /* + * We only support two address families: AF_INET and AF_INET6. + * If a family value of 0 is specified, we collect both + * supported address families; if not we gather info on + * the specified address family. + */ + if ((((family != 0) && + (curp->ifa_addr->sa_family != family))) || + (curp->ifa_flags & IFF_LOOPBACK)) { + curp = curp->ifa_next; + continue; + } + if ((curp->ifa_addr->sa_family != AF_INET) && + (curp->ifa_addr->sa_family != AF_INET6)) { + curp = curp->ifa_next; + continue; + } + + if (op == KVP_OP_GET_IP_INFO) { + /* + * Gather info other than the IP address. + * IP address info will be gathered later. + */ + if (curp->ifa_addr->sa_family == AF_INET) { + ip_buffer->addr_family |= ADDR_FAMILY_IPV4; + /* + * Get subnet info. + */ + error = kvp_process_ip_address( + curp->ifa_netmask, + AF_INET, + (char *) + ip_buffer->sub_net, + length, + &sn_offset); + if (error) + goto gather_ipaddr; + } else { + ip_buffer->addr_family |= ADDR_FAMILY_IPV6; + + /* + * Get subnet info in CIDR format. + */ + weight = 0; + sn_str = (char *)ip_buffer->sub_net; + addr6 = (struct sockaddr_in6 *) + curp->ifa_netmask; + w = addr6->sin6_addr.s6_addr32; + + for (i = 0; i < 4; i++) + weight += hweight32(&w[i]); + + sprintf(cidr_mask, "/%d", weight); + if (length < sn_offset + strlen(cidr_mask) + 1) + goto gather_ipaddr; + + if (sn_offset == 0) + strcpy(sn_str, cidr_mask); + else { + strcat((char *)ip_buffer->sub_net, ";"); + strcat(sn_str, cidr_mask); + } + sn_offset += strlen(sn_str) + 1; + } + + /* + * Collect other ip related configuration info. + */ + + kvp_get_ipconfig_info(if_name, ip_buffer); + } + +gather_ipaddr: + error = kvp_process_ip_address(curp->ifa_addr, + curp->ifa_addr->sa_family, + buffer, + length, &offset); + if (error) + goto getaddr_done; + + curp = curp->ifa_next; + } + +getaddr_done: + freeifaddrs(ifap); + return error; +} + +/* + * Retrieve the IP given the MAC address. + */ +static int kvp_mac_to_ip(struct hv_kvp_ipaddr_value *kvp_ip_val) +{ + char *mac = (char *)kvp_ip_val->adapter_id; + DIR *dir; + struct dirent *entry; + FILE *file; + char *p, *x; + char *if_name = NULL; + char buf[256]; + char dev_id[PATH_MAX]; + unsigned int i; + int error = HV_E_FAIL; + + dir = opendir(KVP_NET_DIR); + if (dir == NULL) + return HV_E_FAIL; + + while ((entry = readdir(dir)) != NULL) { + /* + * Set the state for the next pass. + */ + snprintf(dev_id, sizeof(dev_id), "%s%s/address", KVP_NET_DIR, + entry->d_name); + + file = fopen(dev_id, "r"); + if (file == NULL) + continue; + + p = fgets(buf, sizeof(buf), file); + fclose(file); + if (!p) + continue; + + x = strchr(p, '\n'); + if (x) + *x = '\0'; + + for (i = 0; i < strlen(p); i++) + p[i] = toupper(p[i]); + + if (strcmp(p, mac)) + continue; + + /* + * Found the MAC match. + * A NIC (e.g. VF) matching the MAC, but without IP, is skipped. + */ + if_name = entry->d_name; + if (!if_name) + continue; + + error = kvp_get_ip_info(0, if_name, KVP_OP_GET_IP_INFO, + kvp_ip_val, MAX_IP_ADDR_SIZE * 2); + + if (!error && strlen((char *)kvp_ip_val->ip_addr)) + break; + } + + closedir(dir); + return error; +} + +static int expand_ipv6(char *addr, int type) +{ + int ret; + struct in6_addr v6_addr; + + ret = inet_pton(AF_INET6, addr, &v6_addr); + + if (ret != 1) { + if (type == NETMASK) + return 1; + return 0; + } + + sprintf(addr, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:%02x%02x", + (int)v6_addr.s6_addr[0], (int)v6_addr.s6_addr[1], + (int)v6_addr.s6_addr[2], (int)v6_addr.s6_addr[3], + (int)v6_addr.s6_addr[4], (int)v6_addr.s6_addr[5], + (int)v6_addr.s6_addr[6], (int)v6_addr.s6_addr[7], + (int)v6_addr.s6_addr[8], (int)v6_addr.s6_addr[9], + (int)v6_addr.s6_addr[10], (int)v6_addr.s6_addr[11], + (int)v6_addr.s6_addr[12], (int)v6_addr.s6_addr[13], + (int)v6_addr.s6_addr[14], (int)v6_addr.s6_addr[15]); + + return 1; + +} + +static int is_ipv4(char *addr) +{ + int ret; + struct in_addr ipv4_addr; + + ret = inet_pton(AF_INET, addr, &ipv4_addr); + + if (ret == 1) + return 1; + return 0; +} + +static int parse_ip_val_buffer(char *in_buf, int *offset, + char *out_buf, int out_len) +{ + char *x; + char *start; + + /* + * in_buf has sequence of characters that are separated by + * the character ';'. The last sequence does not have the + * terminating ";" character. + */ + start = in_buf + *offset; + + x = strchr(start, ';'); + if (x) + *x = 0; + else + x = start + strlen(start); + + if (strlen(start) != 0) { + int i = 0; + /* + * Get rid of leading spaces. + */ + while (start[i] == ' ') + i++; + + if ((x - start) <= out_len) { + strcpy(out_buf, (start + i)); + *offset += (x - start) + 1; + return 1; + } + } + return 0; +} + +static int kvp_write_file(FILE *f, char *s1, char *s2, char *s3) +{ + int ret; + + ret = fprintf(f, "%s%s%s%s\n", s1, s2, "=", s3); + + if (ret < 0) + return HV_E_FAIL; + + return 0; +} + + +static int process_ip_string(FILE *f, char *ip_string, int type) +{ + int error = 0; + char addr[INET6_ADDRSTRLEN]; + int i = 0; + int j = 0; + char str[256]; + char sub_str[13]; + int offset = 0; + + memset(addr, 0, sizeof(addr)); + + while (parse_ip_val_buffer(ip_string, &offset, addr, + (MAX_IP_ADDR_SIZE * 2))) { + + sub_str[0] = 0; + if (is_ipv4(addr)) { + switch (type) { + case IPADDR: + snprintf(str, sizeof(str), "%s", "IPADDR"); + break; + case NETMASK: + snprintf(str, sizeof(str), "%s", "NETMASK"); + break; + case GATEWAY: + snprintf(str, sizeof(str), "%s", "GATEWAY"); + break; + case DNS: + snprintf(str, sizeof(str), "%s", "DNS"); + break; + } + + if (type == DNS) { + snprintf(sub_str, sizeof(sub_str), "%d", ++i); + } else if (type == GATEWAY && i == 0) { + ++i; + } else { + snprintf(sub_str, sizeof(sub_str), "%d", i++); + } + + + } else if (expand_ipv6(addr, type)) { + switch (type) { + case IPADDR: + snprintf(str, sizeof(str), "%s", "IPV6ADDR"); + break; + case NETMASK: + snprintf(str, sizeof(str), "%s", "IPV6NETMASK"); + break; + case GATEWAY: + snprintf(str, sizeof(str), "%s", + "IPV6_DEFAULTGW"); + break; + case DNS: + snprintf(str, sizeof(str), "%s", "DNS"); + break; + } + + if (type == DNS) { + snprintf(sub_str, sizeof(sub_str), "%d", ++i); + } else if (j == 0) { + ++j; + } else { + snprintf(sub_str, sizeof(sub_str), "_%d", j++); + } + } else { + return HV_INVALIDARG; + } + + error = kvp_write_file(f, str, sub_str, addr); + if (error) + return error; + memset(addr, 0, sizeof(addr)); + } + + return 0; +} + +int ip_version_check(const char *input_addr) +{ + struct in6_addr addr; + + if (inet_pton(AF_INET, input_addr, &addr)) + return IPV4; + else if (inet_pton(AF_INET6, input_addr, &addr)) + return IPV6; + + return -EINVAL; +} + +/* + * Only IPv4 subnet strings needs to be converted to plen + * For IPv6 the subnet is already privided in plen format + */ +static int kvp_subnet_to_plen(char *subnet_addr_str) +{ + int plen = 0; + struct in_addr subnet_addr4; + + /* + * Convert subnet address to binary representation + */ + if (inet_pton(AF_INET, subnet_addr_str, &subnet_addr4) == 1) { + uint32_t subnet_mask = ntohl(subnet_addr4.s_addr); + + while (subnet_mask & 0x80000000) { + plen++; + subnet_mask <<= 1; + } + } else { + return -1; + } + + return plen; +} + +static int process_dns_gateway_nm(FILE *f, char *ip_string, int type, + int ip_sec) +{ + char addr[INET6_ADDRSTRLEN], *output_str; + int ip_offset = 0, error = 0, ip_ver; + char *param_name; + + if (type == DNS) + param_name = "dns"; + else if (type == GATEWAY) + param_name = "gateway"; + else + return -EINVAL; + + output_str = (char *)calloc(OUTSTR_BUF_SIZE, sizeof(char)); + if (!output_str) + return -ENOMEM; + + while (1) { + memset(addr, 0, sizeof(addr)); + + if (!parse_ip_val_buffer(ip_string, &ip_offset, addr, + (MAX_IP_ADDR_SIZE * 2))) + break; + + ip_ver = ip_version_check(addr); + if (ip_ver < 0) + continue; + + if ((ip_ver == IPV4 && ip_sec == IPV4) || + (ip_ver == IPV6 && ip_sec == IPV6)) { + /* + * do a bound check to avoid out-of bound writes + */ + if ((OUTSTR_BUF_SIZE - strlen(output_str)) > + (strlen(addr) + 1)) { + strncat(output_str, addr, + OUTSTR_BUF_SIZE - + strlen(output_str) - 1); + strncat(output_str, ",", + OUTSTR_BUF_SIZE - + strlen(output_str) - 1); + } + } else { + continue; + } + } + + if (strlen(output_str)) { + /* + * This is to get rid of that extra comma character + * in the end of the string + */ + output_str[strlen(output_str) - 1] = '\0'; + error = fprintf(f, "%s=%s\n", param_name, output_str); + } + + free(output_str); + return error; +} + +static int process_ip_string_nm(FILE *f, char *ip_string, char *subnet, + int ip_sec) +{ + char addr[INET6_ADDRSTRLEN]; + char subnet_addr[INET6_ADDRSTRLEN]; + int error = 0, i = 0; + int ip_offset = 0, subnet_offset = 0; + int plen, ip_ver; + + memset(addr, 0, sizeof(addr)); + memset(subnet_addr, 0, sizeof(subnet_addr)); + + while (parse_ip_val_buffer(ip_string, &ip_offset, addr, + (MAX_IP_ADDR_SIZE * 2)) && + parse_ip_val_buffer(subnet, + &subnet_offset, + subnet_addr, + (MAX_IP_ADDR_SIZE * + 2))) { + ip_ver = ip_version_check(addr); + if (ip_ver < 0) + continue; + + if (ip_ver == IPV4 && ip_sec == IPV4) + plen = kvp_subnet_to_plen((char *)subnet_addr); + else if (ip_ver == IPV6 && ip_sec == IPV6) + plen = atoi(subnet_addr); + else + continue; + + if (plen < 0) + return plen; + + error = fprintf(f, "address%d=%s/%d\n", ++i, (char *)addr, + plen); + if (error < 0) + return error; + + memset(addr, 0, sizeof(addr)); + memset(subnet_addr, 0, sizeof(subnet_addr)); + } + + return error; +} + +static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) +{ + int error = 0, ip_ver; + char if_filename[PATH_MAX]; + char nm_filename[PATH_MAX]; + FILE *ifcfg_file, *nmfile; + char cmd[PATH_MAX]; + char *mac_addr; + int str_len; + + /* + * Set the configuration for the specified interface with + * the information provided. Since there is no standard + * way to configure an interface, we will have an external + * script that does the job of configuring the interface and + * flushing the configuration. + * + * The parameters passed to this external script are: + * 1. A configuration file that has the specified configuration. + * + * We will embed the name of the interface in the configuration + * file: ifcfg-ethx (where ethx is the interface name). + * + * The information provided here may be more than what is needed + * in a given distro to configure the interface and so are free + * ignore information that may not be relevant. + * + * Here is the ifcfg format of the ip configuration file: + * + * HWADDR=macaddr + * DEVICE=interface name + * BOOTPROTO=<protocol> (where <protocol> is "dhcp" if DHCP is configured + * or "none" if no boot-time protocol should be used) + * + * IPADDR0=ipaddr1 + * IPADDR1=ipaddr2 + * IPADDRx=ipaddry (where y = x + 1) + * + * NETMASK0=netmask1 + * NETMASKx=netmasky (where y = x + 1) + * + * GATEWAY=ipaddr1 + * GATEWAYx=ipaddry (where y = x + 1) + * + * DNSx=ipaddrx (where first DNS address is tagged as DNS1 etc) + * + * IPV6 addresses will be tagged as IPV6ADDR, IPV6 gateway will be + * tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as + * IPV6NETMASK. + * + * Here is the keyfile format of the ip configuration file: + * + * [ethernet] + * mac-address=macaddr + * [connection] + * interface-name=interface name + * + * [ipv4] + * method=<protocol> (where <protocol> is "auto" if DHCP is configured + * or "manual" if no boot-time protocol should be used) + * + * address1=ipaddr1/plen + * address2=ipaddr2/plen + * + * gateway=gateway1;gateway2 + * + * dns=dns1;dns2 + * + * [ipv6] + * address1=ipaddr1/plen + * address2=ipaddr2/plen + * + * gateway=gateway1;gateway2 + * + * dns=dns1;dns2 + * + * The host can specify multiple ipv4 and ipv6 addresses to be + * configured for the interface. Furthermore, the configuration + * needs to be persistent. A subsequent GET call on the interface + * is expected to return the configuration that is set via the SET + * call. + */ + + /* + * We are populating both ifcfg and nmconnection files + */ + snprintf(if_filename, sizeof(if_filename), "%s%s%s", KVP_CONFIG_LOC, + "/ifcfg-", if_name); + + ifcfg_file = fopen(if_filename, "w"); + + if (!ifcfg_file) { + syslog(LOG_ERR, "Failed to open config file; error: %d %s", + errno, strerror(errno)); + return HV_E_FAIL; + } + + snprintf(nm_filename, sizeof(nm_filename), "%s%s%s%s", KVP_CONFIG_LOC, + "/", if_name, ".nmconnection"); + + nmfile = fopen(nm_filename, "w"); + + if (!nmfile) { + syslog(LOG_ERR, "Failed to open config file; error: %d %s", + errno, strerror(errno)); + fclose(ifcfg_file); + return HV_E_FAIL; + } + + /* + * First write out the MAC address. + */ + + mac_addr = kvp_if_name_to_mac(if_name); + if (mac_addr == NULL) { + error = HV_E_FAIL; + goto setval_error; + } + + error = kvp_write_file(ifcfg_file, "HWADDR", "", mac_addr); + if (error < 0) + goto setmac_error; + + error = kvp_write_file(ifcfg_file, "DEVICE", "", if_name); + if (error < 0) + goto setmac_error; + + error = fprintf(nmfile, "\n[connection]\n"); + if (error < 0) + goto setmac_error; + + error = kvp_write_file(nmfile, "interface-name", "", if_name); + if (error) + goto setmac_error; + + error = fprintf(nmfile, "\n[ethernet]\n"); + if (error < 0) + goto setmac_error; + + error = kvp_write_file(nmfile, "mac-address", "", mac_addr); + if (error) + goto setmac_error; + + free(mac_addr); + + /* + * The dhcp_enabled flag is only for IPv4. In the case the host only + * injects an IPv6 address, the flag is true, but we still need to + * proceed to parse and pass the IPv6 information to the + * disto-specific script hv_set_ifconfig. + */ + + /* + * First populate the ifcfg file format + */ + if (new_val->dhcp_enabled) { + error = kvp_write_file(ifcfg_file, "BOOTPROTO", "", "dhcp"); + if (error) + goto setval_error; + } else { + error = kvp_write_file(ifcfg_file, "BOOTPROTO", "", "none"); + if (error) + goto setval_error; + } + + error = process_ip_string(ifcfg_file, (char *)new_val->ip_addr, + IPADDR); + if (error) + goto setval_error; + + error = process_ip_string(ifcfg_file, (char *)new_val->sub_net, + NETMASK); + if (error) + goto setval_error; + + error = process_ip_string(ifcfg_file, (char *)new_val->gate_way, + GATEWAY); + if (error) + goto setval_error; + + error = process_ip_string(ifcfg_file, (char *)new_val->dns_addr, DNS); + if (error) + goto setval_error; + + /* + * Now we populate the keyfile format + * + * The keyfile format expects the IPv6 and IPv4 configuration in + * different sections. Therefore we iterate through the list twice, + * once to populate the IPv4 section and the next time for IPv6 + */ + ip_ver = IPV4; + do { + if (ip_ver == IPV4) { + error = fprintf(nmfile, "\n[ipv4]\n"); + if (error < 0) + goto setval_error; + } else { + error = fprintf(nmfile, "\n[ipv6]\n"); + if (error < 0) + goto setval_error; + } + + /* + * Write the configuration for ipaddress, netmask, gateway and + * name services + */ + error = process_ip_string_nm(nmfile, (char *)new_val->ip_addr, + (char *)new_val->sub_net, + ip_ver); + if (error < 0) + goto setval_error; + + /* + * As dhcp_enabled is only valid for ipv4, we do not set dhcp + * methods for ipv6 based on dhcp_enabled flag. + * + * For ipv4, set method to manual only when dhcp_enabled is + * false and specific ipv4 addresses are configured. If neither + * dhcp_enabled is true and no ipv4 addresses are configured, + * set method to 'disabled'. + * + * For ipv6, set method to manual when we configure ipv6 + * addresses. Otherwise set method to 'auto' so that SLAAC from + * RA may be used. + */ + if (ip_ver == IPV4) { + if (new_val->dhcp_enabled) { + error = kvp_write_file(nmfile, "method", "", + "auto"); + if (error < 0) + goto setval_error; + } else if (error) { + error = kvp_write_file(nmfile, "method", "", + "manual"); + if (error < 0) + goto setval_error; + } else { + error = kvp_write_file(nmfile, "method", "", + "disabled"); + if (error < 0) + goto setval_error; + } + } else if (ip_ver == IPV6) { + if (error) { + error = kvp_write_file(nmfile, "method", "", + "manual"); + if (error < 0) + goto setval_error; + } else { + error = kvp_write_file(nmfile, "method", "", + "auto"); + if (error < 0) + goto setval_error; + } + } + + error = process_dns_gateway_nm(nmfile, + (char *)new_val->gate_way, + GATEWAY, ip_ver); + if (error < 0) + goto setval_error; + + error = process_dns_gateway_nm(nmfile, + (char *)new_val->dns_addr, DNS, + ip_ver); + if (error < 0) + goto setval_error; + + ip_ver++; + } while (ip_ver < IP_TYPE_MAX); + + fclose(nmfile); + fclose(ifcfg_file); + + /* + * Now that we have populated the configuration file, + * invoke the external script to do its magic. + */ + + str_len = snprintf(cmd, sizeof(cmd), "exec %s %s %s", + KVP_SCRIPTS_PATH "hv_set_ifconfig", + if_filename, nm_filename); + /* + * This is a little overcautious, but it's necessary to suppress some + * false warnings from gcc 8.0.1. + */ + if (str_len <= 0 || (unsigned int)str_len >= sizeof(cmd)) { + syslog(LOG_ERR, "Cmd '%s' (len=%d) may be too long", + cmd, str_len); + return HV_E_FAIL; + } + + if (system(cmd)) { + syslog(LOG_ERR, "Failed to execute cmd '%s'; error: %d %s", + cmd, errno, strerror(errno)); + return HV_E_FAIL; + } + return 0; + +setmac_error: + free(mac_addr); +setval_error: + syslog(LOG_ERR, "Failed to write config file. error: %d %s", + errno, strerror(errno)); + fclose(ifcfg_file); + fclose(nmfile); + return error; +} + + +static void +kvp_get_domain_name(char *buffer, int length) +{ + struct addrinfo hints, *info ; + int error = 0; + + gethostname(buffer, length); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; /*Get only ipv4 addrinfo. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + + error = getaddrinfo(buffer, NULL, &hints, &info); + if (error != 0) { + snprintf(buffer, length, "getaddrinfo failed: 0x%x %s", + error, gai_strerror(error)); + return; + } + snprintf(buffer, length, "%s", info->ai_canonname); + freeaddrinfo(info); +} + +void print_usage(char *argv[]) +{ + fprintf(stderr, "Usage: %s [options]\n" + "Options are:\n" + " -n, --no-daemon stay in foreground, don't daemonize\n" + " -h, --help print this help\n", argv[0]); +} + +int main(int argc, char *argv[]) +{ + int kvp_fd = -1, len; + int error; + struct pollfd pfd; + char *p; + struct hv_kvp_msg hv_msg[1]; + char *key_value; + char *key_name; + int op; + int pool; + char *if_name; + struct hv_kvp_ipaddr_value *kvp_ip_val; + int daemonize = 1, long_index = 0, opt; + + static struct option long_options[] = { + {"help", no_argument, 0, 'h' }, + {"no-daemon", no_argument, 0, 'n' }, + {0, 0, 0, 0 } + }; + + while ((opt = getopt_long(argc, argv, "hn", long_options, + &long_index)) != -1) { + switch (opt) { + case 'n': + daemonize = 0; + break; + case 'h': + print_usage(argv); + exit(0); + default: + print_usage(argv); + exit(EXIT_FAILURE); + } + } + + if (daemonize && daemon(1, 0)) + return 1; + + openlog("KVP", 0, LOG_USER); + syslog(LOG_INFO, "KVP starting; pid is:%d", getpid()); + + /* + * Retrieve OS release information. + */ + kvp_get_os_info(); + /* + * Cache Fully Qualified Domain Name because getaddrinfo takes an + * unpredictable amount of time to finish. + */ + kvp_get_domain_name(full_domain_name, sizeof(full_domain_name)); + + if (kvp_file_init()) { + syslog(LOG_ERR, "Failed to initialize the pools"); + exit(EXIT_FAILURE); + } + +reopen_kvp_fd: + if (kvp_fd != -1) + close(kvp_fd); + in_hand_shake = 1; + kvp_fd = open("/dev/vmbus/hv_kvp", O_RDWR | O_CLOEXEC); + + if (kvp_fd < 0) { + syslog(LOG_ERR, "open /dev/vmbus/hv_kvp failed; error: %d %s", + errno, strerror(errno)); + exit(EXIT_FAILURE); + } + + /* + * Register ourselves with the kernel. + */ + hv_msg->kvp_hdr.operation = KVP_OP_REGISTER1; + len = write(kvp_fd, hv_msg, sizeof(struct hv_kvp_msg)); + if (len != sizeof(struct hv_kvp_msg)) { + syslog(LOG_ERR, "registration to kernel failed; error: %d %s", + errno, strerror(errno)); + close(kvp_fd); + exit(EXIT_FAILURE); + } + + pfd.fd = kvp_fd; + + while (1) { + pfd.events = POLLIN; + pfd.revents = 0; + + if (poll(&pfd, 1, -1) < 0) { + syslog(LOG_ERR, "poll failed; error: %d %s", errno, strerror(errno)); + if (errno == EINVAL) { + close(kvp_fd); + exit(EXIT_FAILURE); + } + else + continue; + } + + len = read(kvp_fd, hv_msg, sizeof(struct hv_kvp_msg)); + + if (len != sizeof(struct hv_kvp_msg)) { + syslog(LOG_ERR, "read failed; error:%d %s", + errno, strerror(errno)); + goto reopen_kvp_fd; + } + + /* + * We will use the KVP header information to pass back + * the error from this daemon. So, first copy the state + * and set the error code to success. + */ + op = hv_msg->kvp_hdr.operation; + pool = hv_msg->kvp_hdr.pool; + hv_msg->error = HV_S_OK; + + if ((in_hand_shake) && (op == KVP_OP_REGISTER1)) { + /* + * Driver is registering with us; stash away the version + * information. + */ + in_hand_shake = 0; + p = (char *)hv_msg->body.kvp_register.version; + lic_version = malloc(strlen(p) + 1); + if (lic_version) { + strcpy(lic_version, p); + syslog(LOG_INFO, "KVP LIC Version: %s", + lic_version); + } else { + syslog(LOG_ERR, "malloc failed"); + } + continue; + } + + switch (op) { + case KVP_OP_GET_IP_INFO: + kvp_ip_val = &hv_msg->body.kvp_ip_val; + + error = kvp_mac_to_ip(kvp_ip_val); + + if (error) + hv_msg->error = error; + + break; + + case KVP_OP_SET_IP_INFO: + kvp_ip_val = &hv_msg->body.kvp_ip_val; + if_name = kvp_get_if_name( + (char *)kvp_ip_val->adapter_id); + if (if_name == NULL) { + /* + * We could not map the guid to an + * interface name; return error. + */ + hv_msg->error = HV_GUID_NOTFOUND; + break; + } + error = kvp_set_ip_info(if_name, kvp_ip_val); + if (error) + hv_msg->error = error; + + free(if_name); + break; + + case KVP_OP_SET: + if (kvp_key_add_or_modify(pool, + hv_msg->body.kvp_set.data.key, + hv_msg->body.kvp_set.data.key_size, + hv_msg->body.kvp_set.data.value, + hv_msg->body.kvp_set.data.value_size)) + hv_msg->error = HV_S_CONT; + break; + + case KVP_OP_GET: + if (kvp_get_value(pool, + hv_msg->body.kvp_set.data.key, + hv_msg->body.kvp_set.data.key_size, + hv_msg->body.kvp_set.data.value, + hv_msg->body.kvp_set.data.value_size)) + hv_msg->error = HV_S_CONT; + break; + + case KVP_OP_DELETE: + if (kvp_key_delete(pool, + hv_msg->body.kvp_delete.key, + hv_msg->body.kvp_delete.key_size)) + hv_msg->error = HV_S_CONT; + break; + + default: + break; + } + + if (op != KVP_OP_ENUMERATE) + goto kvp_done; + + /* + * If the pool is KVP_POOL_AUTO, dynamically generate + * both the key and the value; if not read from the + * appropriate pool. + */ + if (pool != KVP_POOL_AUTO) { + if (kvp_pool_enumerate(pool, + hv_msg->body.kvp_enum_data.index, + hv_msg->body.kvp_enum_data.data.key, + HV_KVP_EXCHANGE_MAX_KEY_SIZE, + hv_msg->body.kvp_enum_data.data.value, + HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) + hv_msg->error = HV_S_CONT; + goto kvp_done; + } + + key_name = (char *)hv_msg->body.kvp_enum_data.data.key; + key_value = (char *)hv_msg->body.kvp_enum_data.data.value; + + switch (hv_msg->body.kvp_enum_data.index) { + case FullyQualifiedDomainName: + strcpy(key_value, full_domain_name); + strcpy(key_name, "FullyQualifiedDomainName"); + break; + case IntegrationServicesVersion: + strcpy(key_name, "IntegrationServicesVersion"); + strcpy(key_value, lic_version); + break; + case NetworkAddressIPv4: + kvp_get_ip_info(AF_INET, NULL, KVP_OP_ENUMERATE, + key_value, HV_KVP_EXCHANGE_MAX_VALUE_SIZE); + strcpy(key_name, "NetworkAddressIPv4"); + break; + case NetworkAddressIPv6: + kvp_get_ip_info(AF_INET6, NULL, KVP_OP_ENUMERATE, + key_value, HV_KVP_EXCHANGE_MAX_VALUE_SIZE); + strcpy(key_name, "NetworkAddressIPv6"); + break; + case OSBuildNumber: + strcpy(key_value, os_build); + strcpy(key_name, "OSBuildNumber"); + break; + case OSName: + strcpy(key_value, os_name); + strcpy(key_name, "OSName"); + break; + case OSMajorVersion: + strcpy(key_value, os_major); + strcpy(key_name, "OSMajorVersion"); + break; + case OSMinorVersion: + strcpy(key_value, os_minor); + strcpy(key_name, "OSMinorVersion"); + break; + case OSVersion: + strcpy(key_value, os_version); + strcpy(key_name, "OSVersion"); + break; + case ProcessorArchitecture: + strcpy(key_value, processor_arch); + strcpy(key_name, "ProcessorArchitecture"); + break; + default: + hv_msg->error = HV_S_CONT; + break; + } + + /* + * Send the value back to the kernel. Note: the write() may + * return an error due to hibernation; we can ignore the error + * by resetting the dev file, i.e. closing and re-opening it. + */ +kvp_done: + len = write(kvp_fd, hv_msg, sizeof(struct hv_kvp_msg)); + if (len != sizeof(struct hv_kvp_msg)) { + syslog(LOG_ERR, "write failed; error: %d %s", errno, + strerror(errno)); + goto reopen_kvp_fd; + } + } + + close(kvp_fd); + exit(0); +} diff --git a/hyper-v.tools.hv.hv_set_ifconfig.sh b/hyper-v.tools.hv.hv_set_ifconfig.sh new file mode 100644 index 0000000..11efe3d --- /dev/null +++ b/hyper-v.tools.hv.hv_set_ifconfig.sh @@ -0,0 +1,247 @@ +#!/bin/bash +# +# In the interest of keeping the KVP daemon code free of distro specific +# information; the kvp daemon code invokes this external script to configure +# the interface. +# +# The only argument to this script is the configuration file that is to +# be used to configure the interface. +# +# Here is the ifcfg format of the ip configuration file: +# +# HWADDR=macaddr +# DEVICE=interface name +# BOOTPROTO=<protocol> (where <protocol> is "dhcp" if DHCP is configured +# or "none" if no boot-time protocol should be used) +# +# IPADDR0=ipaddr1 +# IPADDR1=ipaddr2 +# IPADDRx=ipaddry (where y = x + 1) +# +# NETMASK0=netmask1 +# NETMASKx=netmasky (where y = x + 1) +# +# GATEWAY=ipaddr1 +# GATEWAYx=ipaddry (where y = x + 1) +# +# DNSx=ipaddrx (where first DNS address is tagged as DNS1 etc) +# +# IPV6 addresses will be tagged as IPV6ADDR, IPV6 gateway will be +# tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as +# IPV6NETMASK. +# +# Here is the keyfile format of the ip configuration file: +# +# [ethernet] +# mac-address=macaddr +# [connection] +# interface-name=interface name +# +# [ipv4] +# method=<protocol> (where <protocol> is "auto" if DHCP is configured +# or "manual" if no boot-time protocol should be used) +# +# address1=ipaddr1/plen +# address2=ipaddr2/plen +# +# gateway=gateway1;gateway2 +# +# dns=dns1; +# +# [ipv6] +# address1=ipaddr1/plen +# address2=ipaddr2/plen +# +# gateway=gateway1;gateway2 +# +# dns=dns1;dns2 +# +# The host can specify multiple ipv4 and ipv6 addresses to be +# configured for the interface. Furthermore, the configuration +# needs to be persistent. A subsequent GET call on the interface +# is expected to return the configuration that is set via the SET +# call. +# +if_cfg=$1 +nm_cfg=$2 +if ! test -f "${if_cfg}" +then + : expect configuration datafile as first argument + exit 1 +fi +# send subshell output to syslog +( +# copied from /etc/sysconfig/network/scripts/functions +mask2pfxlen() { + local i octet width=0 mask=(${1//./ }) + test ${#mask[@]} -eq 4 || return 1 + for octet in 0 1 2 3 + do + test "${mask[octet]}" -ge 0 -a "${mask[octet]}" -le 255 || return 1 + for i in 128 192 224 240 248 252 254 255 + do + test ${mask[octet]} -ge $i && ((width++)) + done + done + echo $width + return 0 +} + +pfxlen2mask() { + test -n "$1" || return 1 + local o i n=0 adr=() len=$(($1)) + for o in 0 1 2 3 + do + adr[$o]=0 + for i in 128 64 32 16 8 4 2 1 + do + ((n++ < len)) && ((adr[$o] = ${adr[$o]} + $i)) + done + done + echo ${adr[0]}.${adr[1]}.${adr[2]}.${adr[3]} + return 0 +} + +# remove known config variables from environment +unset HWADDR +unset BOOTPROTO +unset DEVICE +unset ${!IPADDR*} +unset ${!NETMASK*} +unset ${!GATEWAY*} +unset ${!IPV6ADDR*} +unset ${!IPV6NETMASK*} +unset ${!IPV6_DEFAULTGW*} +unset ${!DNS*} +. "${if_cfg}" +# +if test -z "${DEVICE}" +then + echo "Missing DEVICE= in ${if_cfg}" + exit 1 +fi +# +t_ifcfg=`mktemp` +t_ifroute=`mktemp` +_exit() { + rm -f "${t_ifcfg}" "${t_ifroute}" +} +trap _exit EXIT +# +if test -z "${t_ifcfg}" || test -z "${t_ifroute}" +then + exit 1 +fi +# +# Create ifcfg-* file +( + echo "STARTMODE=auto" + # + if test -n "${HWADDR}" + then + : # ignore HWADDR, it just repeats the existing MAC value + fi + # + if test "${BOOTPROTO}" = "dhcp" + then + echo "BOOTPROTO=dhcp" + elif test -n "${!IPADDR*}${!IPV6ADDR*}" + then + echo "BOOTPROTO=static" + fi + # single index for all ipv4 and ipv6 adresses in final ifcfg file + i=0 + idx="" + # loop through all ipv4 adresses + for var in ${!IPADDR*} + do + index=${var#IPADDR} + pfx= + # find corresponding NETMASK variable + eval nm=\$NETMASK${index} + # if specified, calculate prefix + if test -n "${nm}" + then + pfx=`mask2pfxlen "${nm}" 2>/dev/null` + fi + # if not specified, force prefix + if test -z "${pfx}" + then + pfx="32" + fi + # construct actual value + eval val=\$IPADDR${index} + # write config variable + echo "IPADDR${idx}='${val}/${pfx}'" + idx="_$((++i))" + done + # loop through all ipv6 adresses + for var in ${!IPV6ADDR*} + do + index=${var#IPV6ADDR} + # find corresponding IPV6NETMASK variable + eval pfx=\$IPV6NETMASK${index} + # if not specified, force prefix + if test -z "${pfx}" + then + pfx=128 + fi + # construct actual value + eval val=\$IPV6ADDR${index} + # write config variable + echo "IPADDR${idx}='${val}/${pfx}'" + idx="_$((++i))" + done + +) >> "${t_ifcfg}" + +# Create ifroute-* file +( + if test -n "${GATEWAY}" + then + echo "default $GATEWAY - $DEVICE" + fi + if test -n "${IPV6_DEFAULTGW}" + then + echo "default $IPV6_DEFAULTGW - $DEVICE" + fi +) >> "${t_ifroute}" +# Only a single default gateway is supported +unset GATEWAY IPV6_DEFAULTGW +if test -n "${!GATEWAY*}${!IPV6_DEFAULTGW*}" +then + echo "WARNING: multiple gateways not supported: ${!GATEWAY*} ${!IPV6_DEFAULTGW*}" +fi + +# collect DNS info +_DNS_= +for var in ${!DNS*} +do + eval val=\$${var} + if test -n "${_DNS_}" + then + _DNS_="${_DNS_} ${val}" + else + _DNS_=${val} + fi +done +# +echo "$0: working on network interface ifcfg-${DEVICE}" +mkdir -vp "/etc/sysconfig/network" +cp -fb ${t_ifcfg} "/etc/sysconfig/network/ifcfg-${DEVICE}" +cp -fb ${t_ifroute} "/etc/sysconfig/network/ifroute-${DEVICE}" +if test -w /etc/sysconfig/network/config +then + sed -i "s@^NETCONFIG_DNS_STATIC_SERVERS=.*@NETCONFIG_DNS_STATIC_SERVERS='$_DNS_'@" /etc/sysconfig/network/config + netconfig update -m dns +fi + +echo "$0: working on network interface ifcfg-${DEVICE}" +nm_filename="${nm_cfg##*/}" +mkdir -vp "/etc/NetworkManager/system-connections" +umask 0177 +sed '/\[connection\]/a autoconnect=true' "${nm_cfg}" > "/etc/NetworkManager/system-connections/${nm_filename}" + +ifdown "${DEVICE}" +ifup "${DEVICE}" +) 2>&1 | logger -t "${0##*/}[$PPID / $$]" diff --git a/hyper-v.tools.hv.hv_vss_daemon.c b/hyper-v.tools.hv.hv_vss_daemon.c new file mode 100644 index 0000000..62b9a9e --- /dev/null +++ b/hyper-v.tools.hv.hv_vss_daemon.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * An implementation of the host initiated guest snapshot for Hyper-V. + * + * Copyright (C) 2013, Microsoft, Inc. + * Author : K. Y. Srinivasan <kys@microsoft.com> + */ + + +#include <sys/types.h> +#include <sys/poll.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <fcntl.h> +#include <stdio.h> +#include <mntent.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <linux/fs.h> +#include <linux/major.h> +#include <linux/hyperv.h> +#include <syslog.h> +#include <getopt.h> +#include <stdbool.h> +#include <dirent.h> + +static bool fs_frozen; + +/* Don't use syslog() in the function since that can cause write to disk */ +static int vss_do_freeze(char *dir, unsigned int cmd) +{ + int ret, fd = open(dir, O_RDONLY); + + if (fd < 0) + return 1; + + ret = ioctl(fd, cmd, 0); + + /* + * If a partition is mounted more than once, only the first + * FREEZE/THAW can succeed and the later ones will get + * EBUSY/EINVAL respectively: there could be 2 cases: + * 1) a user may mount the same partition to different directories + * by mistake or on purpose; + * 2) The subvolume of btrfs appears to have the same partition + * mounted more than once. + */ + if (ret) { + if ((cmd == FIFREEZE && errno == EBUSY) || + (cmd == FITHAW && errno == EINVAL)) { + close(fd); + return 0; + } + } + + close(fd); + return !!ret; +} + +static bool is_dev_loop(const char *blkname) +{ + char *buffer; + DIR *dir; + struct dirent *entry; + bool ret = false; + + buffer = malloc(PATH_MAX); + if (!buffer) { + syslog(LOG_ERR, "Can't allocate memory!"); + exit(1); + } + + snprintf(buffer, PATH_MAX, "%s/loop", blkname); + if (!access(buffer, R_OK | X_OK)) { + ret = true; + goto free_buffer; + } else if (errno != ENOENT) { + syslog(LOG_ERR, "Can't access: %s; error:%d %s!", + buffer, errno, strerror(errno)); + } + + snprintf(buffer, PATH_MAX, "%s/slaves", blkname); + dir = opendir(buffer); + if (!dir) { + if (errno != ENOENT) + syslog(LOG_ERR, "Can't opendir: %s; error:%d %s!", + buffer, errno, strerror(errno)); + goto free_buffer; + } + + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) + continue; + + snprintf(buffer, PATH_MAX, "%s/slaves/%s", blkname, + entry->d_name); + if (is_dev_loop(buffer)) { + ret = true; + break; + } + } + closedir(dir); +free_buffer: + free(buffer); + return ret; +} + +static int vss_operate(int operation) +{ + char match[] = "/dev/"; + FILE *mounts; + struct mntent *ent; + struct stat sb; + char errdir[1024] = {0}; + char blkdir[23]; /* /sys/dev/block/XXX:XXX */ + unsigned int cmd; + int error = 0, root_seen = 0, save_errno = 0; + + switch (operation) { + case VSS_OP_FREEZE: + cmd = FIFREEZE; + break; + case VSS_OP_THAW: + cmd = FITHAW; + break; + default: + return -1; + } + + mounts = setmntent("/proc/mounts", "r"); + if (mounts == NULL) + return -1; + + while ((ent = getmntent(mounts))) { + if (strncmp(ent->mnt_fsname, match, strlen(match))) + continue; + if (stat(ent->mnt_fsname, &sb)) { + syslog(LOG_ERR, "Can't stat: %s; error:%d %s!", + ent->mnt_fsname, errno, strerror(errno)); + } else { + sprintf(blkdir, "/sys/dev/block/%d:%d", + major(sb.st_rdev), minor(sb.st_rdev)); + if (is_dev_loop(blkdir)) + continue; + } + if (hasmntopt(ent, MNTOPT_RO) != NULL) + continue; + if (strcmp(ent->mnt_type, "vfat") == 0) + continue; + if (strcmp(ent->mnt_dir, "/") == 0) { + root_seen = 1; + continue; + } + error |= vss_do_freeze(ent->mnt_dir, cmd); + if (operation == VSS_OP_FREEZE) { + if (error) + goto err; + fs_frozen = true; + } + } + + endmntent(mounts); + + if (root_seen) { + error |= vss_do_freeze("/", cmd); + if (operation == VSS_OP_FREEZE) { + if (error) + goto err; + fs_frozen = true; + } + } + + if (operation == VSS_OP_THAW && !error) + fs_frozen = false; + + goto out; +err: + save_errno = errno; + if (ent) { + strncpy(errdir, ent->mnt_dir, sizeof(errdir)-1); + endmntent(mounts); + } + vss_operate(VSS_OP_THAW); + fs_frozen = false; + /* Call syslog after we thaw all filesystems */ + if (ent) + syslog(LOG_ERR, "FREEZE of %s failed; error:%d %s", + errdir, save_errno, strerror(save_errno)); + else + syslog(LOG_ERR, "FREEZE of / failed; error:%d %s", save_errno, + strerror(save_errno)); +out: + return error; +} + +void print_usage(char *argv[]) +{ + fprintf(stderr, "Usage: %s [options]\n" + "Options are:\n" + " -n, --no-daemon stay in foreground, don't daemonize\n" + " -h, --help print this help\n", argv[0]); +} + +int main(int argc, char *argv[]) +{ + int vss_fd = -1, len; + int error; + struct pollfd pfd; + int op; + struct hv_vss_msg vss_msg[1]; + int daemonize = 1, long_index = 0, opt; + int in_handshake; + __u32 kernel_modver; + + static struct option long_options[] = { + {"help", no_argument, 0, 'h' }, + {"no-daemon", no_argument, 0, 'n' }, + {0, 0, 0, 0 } + }; + + while ((opt = getopt_long(argc, argv, "hn", long_options, + &long_index)) != -1) { + switch (opt) { + case 'n': + daemonize = 0; + break; + case 'h': + print_usage(argv); + exit(0); + default: + print_usage(argv); + exit(EXIT_FAILURE); + } + } + + if (daemonize && daemon(1, 0)) + return 1; + + openlog("Hyper-V VSS", 0, LOG_USER); + syslog(LOG_INFO, "VSS starting; pid is:%d", getpid()); + +reopen_vss_fd: + if (vss_fd != -1) + close(vss_fd); + if (fs_frozen) { + if (vss_operate(VSS_OP_THAW) || fs_frozen) { + syslog(LOG_ERR, "failed to thaw file system: err=%d", + errno); + exit(EXIT_FAILURE); + } + } + + in_handshake = 1; + vss_fd = open("/dev/vmbus/hv_vss", O_RDWR); + if (vss_fd < 0) { + syslog(LOG_ERR, "open /dev/vmbus/hv_vss failed; error: %d %s", + errno, strerror(errno)); + exit(EXIT_FAILURE); + } + /* + * Register ourselves with the kernel. + */ + vss_msg->vss_hdr.operation = VSS_OP_REGISTER1; + + len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); + if (len < 0) { + syslog(LOG_ERR, "registration to kernel failed; error: %d %s", + errno, strerror(errno)); + close(vss_fd); + exit(EXIT_FAILURE); + } + + pfd.fd = vss_fd; + + while (1) { + pfd.events = POLLIN; + pfd.revents = 0; + + if (poll(&pfd, 1, -1) < 0) { + syslog(LOG_ERR, "poll failed; error:%d %s", errno, strerror(errno)); + if (errno == EINVAL) { + close(vss_fd); + exit(EXIT_FAILURE); + } + else + continue; + } + + len = read(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); + + if (in_handshake) { + if (len != sizeof(kernel_modver)) { + syslog(LOG_ERR, "invalid version negotiation"); + exit(EXIT_FAILURE); + } + kernel_modver = *(__u32 *)vss_msg; + in_handshake = 0; + syslog(LOG_INFO, "VSS: kernel module version: %d", + kernel_modver); + continue; + } + + if (len != sizeof(struct hv_vss_msg)) { + syslog(LOG_ERR, "read failed; error:%d %s", + errno, strerror(errno)); + goto reopen_vss_fd; + } + + op = vss_msg->vss_hdr.operation; + error = HV_S_OK; + + switch (op) { + case VSS_OP_FREEZE: + case VSS_OP_THAW: + error = vss_operate(op); + syslog(LOG_INFO, "VSS: op=%s: %s\n", + op == VSS_OP_FREEZE ? "FREEZE" : "THAW", + error ? "failed" : "succeeded"); + + if (error) { + error = HV_E_FAIL; + syslog(LOG_ERR, "op=%d failed!", op); + syslog(LOG_ERR, "report it with these files:"); + syslog(LOG_ERR, "/etc/fstab and /proc/mounts"); + } + break; + case VSS_OP_HOT_BACKUP: + syslog(LOG_INFO, "VSS: op=CHECK HOT BACKUP\n"); + break; + default: + syslog(LOG_ERR, "Illegal op:%d\n", op); + } + + /* + * The write() may return an error due to the faked VSS_OP_THAW + * message upon hibernation. Ignore the error by resetting the + * dev file, i.e. closing and re-opening it. + */ + vss_msg->error = error; + len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); + if (len != sizeof(struct hv_vss_msg)) { + syslog(LOG_ERR, "write failed; error: %d %s", errno, + strerror(errno)); + goto reopen_vss_fd; + } + } + + close(vss_fd); + exit(0); +} diff --git a/hyper-v.tools.hv.vmbus_bufring.c b/hyper-v.tools.hv.vmbus_bufring.c new file mode 100644 index 0000000..faf5945 --- /dev/null +++ b/hyper-v.tools.hv.vmbus_bufring.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2009-2012,2016,2023 Microsoft Corp. + * Copyright (c) 2012 NetApp Inc. + * Copyright (c) 2012 Citrix Inc. + * All rights reserved. + */ + +#include <errno.h> +#include <fcntl.h> +#include <emmintrin.h> +#include <linux/limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/uio.h> +#include <unistd.h> +#include "vmbus_bufring.h" + +/** + * Compiler barrier. + * + * Guarantees that operation reordering does not occur at compile time + * for operations directly before and after the barrier. + */ +#define rte_compiler_barrier() ({ asm volatile ("" : : : "memory"); }) + +#define VMBUS_RQST_ERROR 0xFFFFFFFFFFFFFFFF +#define ALIGN(val, align) ((typeof(val))((val) & (~((typeof(val))((align) - 1))))) + +void *vmbus_uio_map(int *fd, int size) +{ + void *map; + + map = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0); + if (map == MAP_FAILED) + return NULL; + + return map; +} + +/* Increase bufring index by inc with wraparound */ +static inline uint32_t vmbus_br_idxinc(uint32_t idx, uint32_t inc, uint32_t sz) +{ + idx += inc; + if (idx >= sz) + idx -= sz; + + return idx; +} + +void vmbus_br_setup(struct vmbus_br *br, void *buf, unsigned int blen) +{ + br->vbr = buf; + br->windex = br->vbr->windex; + br->dsize = blen - sizeof(struct vmbus_bufring); +} + +static inline __always_inline void +rte_smp_mb(void) +{ + asm volatile("lock addl $0, -128(%%rsp); " ::: "memory"); +} + +static inline int +rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src) +{ + uint8_t res; + + asm volatile("lock ; " + "cmpxchgl %[src], %[dst];" + "sete %[res];" + : [res] "=a" (res), /* output */ + [dst] "=m" (*dst) + : [src] "r" (src), /* input */ + "a" (exp), + "m" (*dst) + : "memory"); /* no-clobber list */ + return res; +} + +static inline uint32_t +vmbus_txbr_copyto(const struct vmbus_br *tbr, uint32_t windex, + const void *src0, uint32_t cplen) +{ + uint8_t *br_data = tbr->vbr->data; + uint32_t br_dsize = tbr->dsize; + const uint8_t *src = src0; + + /* XXX use double mapping like Linux kernel? */ + if (cplen > br_dsize - windex) { + uint32_t fraglen = br_dsize - windex; + + /* Wrap-around detected */ + memcpy(br_data + windex, src, fraglen); + memcpy(br_data, src + fraglen, cplen - fraglen); + } else { + memcpy(br_data + windex, src, cplen); + } + + return vmbus_br_idxinc(windex, cplen, br_dsize); +} + +/* + * Write scattered channel packet to TX bufring. + * + * The offset of this channel packet is written as a 64bits value + * immediately after this channel packet. + * + * The write goes through three stages: + * 1. Reserve space in ring buffer for the new data. + * Writer atomically moves priv_write_index. + * 2. Copy the new data into the ring. + * 3. Update the tail of the ring (visible to host) that indicates + * next read location. Writer updates write_index + */ +static int +vmbus_txbr_write(struct vmbus_br *tbr, const struct iovec iov[], int iovlen) +{ + struct vmbus_bufring *vbr = tbr->vbr; + uint32_t ring_size = tbr->dsize; + uint32_t old_windex, next_windex, windex, total; + uint64_t save_windex; + int i; + + total = 0; + for (i = 0; i < iovlen; i++) + total += iov[i].iov_len; + total += sizeof(save_windex); + + /* Reserve space in ring */ + do { + uint32_t avail; + + /* Get current free location */ + old_windex = tbr->windex; + + /* Prevent compiler reordering this with calculation */ + rte_compiler_barrier(); + + avail = vmbus_br_availwrite(tbr, old_windex); + + /* If not enough space in ring, then tell caller. */ + if (avail <= total) + return -EAGAIN; + + next_windex = vmbus_br_idxinc(old_windex, total, ring_size); + + /* Atomic update of next write_index for other threads */ + } while (!rte_atomic32_cmpset(&tbr->windex, old_windex, next_windex)); + + /* Space from old..new is now reserved */ + windex = old_windex; + for (i = 0; i < iovlen; i++) + windex = vmbus_txbr_copyto(tbr, windex, iov[i].iov_base, iov[i].iov_len); + + /* Set the offset of the current channel packet. */ + save_windex = ((uint64_t)old_windex) << 32; + windex = vmbus_txbr_copyto(tbr, windex, &save_windex, + sizeof(save_windex)); + + /* The region reserved should match region used */ + if (windex != next_windex) + return -EINVAL; + + /* Ensure that data is available before updating host index */ + rte_compiler_barrier(); + + /* Checkin for our reservation. wait for our turn to update host */ + while (!rte_atomic32_cmpset(&vbr->windex, old_windex, next_windex)) + _mm_pause(); + + return 0; +} + +int rte_vmbus_chan_send(struct vmbus_br *txbr, uint16_t type, void *data, + uint32_t dlen, uint32_t flags) +{ + struct vmbus_chanpkt pkt; + unsigned int pktlen, pad_pktlen; + const uint32_t hlen = sizeof(pkt); + uint64_t pad = 0; + struct iovec iov[3]; + int error; + + pktlen = hlen + dlen; + pad_pktlen = ALIGN(pktlen, sizeof(uint64_t)); + + pkt.hdr.type = type; + pkt.hdr.flags = flags; + pkt.hdr.hlen = hlen >> VMBUS_CHANPKT_SIZE_SHIFT; + pkt.hdr.tlen = pad_pktlen >> VMBUS_CHANPKT_SIZE_SHIFT; + pkt.hdr.xactid = VMBUS_RQST_ERROR; + + iov[0].iov_base = &pkt; + iov[0].iov_len = hlen; + iov[1].iov_base = data; + iov[1].iov_len = dlen; + iov[2].iov_base = &pad; + iov[2].iov_len = pad_pktlen - pktlen; + + error = vmbus_txbr_write(txbr, iov, 3); + + return error; +} + +static inline uint32_t +vmbus_rxbr_copyfrom(const struct vmbus_br *rbr, uint32_t rindex, + void *dst0, size_t cplen) +{ + const uint8_t *br_data = rbr->vbr->data; + uint32_t br_dsize = rbr->dsize; + uint8_t *dst = dst0; + + if (cplen > br_dsize - rindex) { + uint32_t fraglen = br_dsize - rindex; + + /* Wrap-around detected. */ + memcpy(dst, br_data + rindex, fraglen); + memcpy(dst + fraglen, br_data, cplen - fraglen); + } else { + memcpy(dst, br_data + rindex, cplen); + } + + return vmbus_br_idxinc(rindex, cplen, br_dsize); +} + +/* Copy data from receive ring but don't change index */ +static int +vmbus_rxbr_peek(const struct vmbus_br *rbr, void *data, size_t dlen) +{ + uint32_t avail; + + /* + * The requested data and the 64bits channel packet + * offset should be there at least. + */ + avail = vmbus_br_availread(rbr); + if (avail < dlen + sizeof(uint64_t)) + return -EAGAIN; + + vmbus_rxbr_copyfrom(rbr, rbr->vbr->rindex, data, dlen); + return 0; +} + +/* + * Copy data from receive ring and change index + * NOTE: + * We assume (dlen + skip) == sizeof(channel packet). + */ +static int +vmbus_rxbr_read(struct vmbus_br *rbr, void *data, size_t dlen, size_t skip) +{ + struct vmbus_bufring *vbr = rbr->vbr; + uint32_t br_dsize = rbr->dsize; + uint32_t rindex; + + if (vmbus_br_availread(rbr) < dlen + skip + sizeof(uint64_t)) + return -EAGAIN; + + /* Record where host was when we started read (for debug) */ + rbr->windex = rbr->vbr->windex; + + /* + * Copy channel packet from RX bufring. + */ + rindex = vmbus_br_idxinc(rbr->vbr->rindex, skip, br_dsize); + rindex = vmbus_rxbr_copyfrom(rbr, rindex, data, dlen); + + /* + * Discard this channel packet's 64bits offset, which is useless to us. + */ + rindex = vmbus_br_idxinc(rindex, sizeof(uint64_t), br_dsize); + + /* Update the read index _after_ the channel packet is fetched. */ + rte_compiler_barrier(); + + vbr->rindex = rindex; + + return 0; +} + +int rte_vmbus_chan_recv_raw(struct vmbus_br *rxbr, + void *data, uint32_t *len) +{ + struct vmbus_chanpkt_hdr pkt; + uint32_t dlen, bufferlen = *len; + int error; + + error = vmbus_rxbr_peek(rxbr, &pkt, sizeof(pkt)); + if (error) + return error; + + if (unlikely(pkt.hlen < VMBUS_CHANPKT_HLEN_MIN)) + /* XXX this channel is dead actually. */ + return -EIO; + + if (unlikely(pkt.hlen > pkt.tlen)) + return -EIO; + + /* Length are in quad words */ + dlen = pkt.tlen << VMBUS_CHANPKT_SIZE_SHIFT; + *len = dlen; + + /* If caller buffer is not large enough */ + if (unlikely(dlen > bufferlen)) + return -ENOBUFS; + + /* Read data and skip packet header */ + error = vmbus_rxbr_read(rxbr, data, dlen, 0); + if (error) + return error; + + /* Return the number of bytes read */ + return dlen + sizeof(uint64_t); +} diff --git a/hyper-v.tools.hv.vmbus_bufring.h b/hyper-v.tools.hv.vmbus_bufring.h new file mode 100644 index 0000000..ff6673b --- /dev/null +++ b/hyper-v.tools.hv.vmbus_bufring.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#ifndef _VMBUS_BUF_H_ +#define _VMBUS_BUF_H_ + +#include <stdbool.h> +#include <stdint.h> + +#define __packed __attribute__((__packed__)) +#define unlikely(x) __builtin_expect(!!(x), 0) + +#define ICMSGHDRFLAG_TRANSACTION 1 +#define ICMSGHDRFLAG_REQUEST 2 +#define ICMSGHDRFLAG_RESPONSE 4 + +#define IC_VERSION_NEGOTIATION_MAX_VER_COUNT 100 +#define ICMSG_HDR (sizeof(struct vmbuspipe_hdr) + sizeof(struct icmsg_hdr)) +#define ICMSG_NEGOTIATE_PKT_SIZE(icframe_vercnt, icmsg_vercnt) \ + (ICMSG_HDR + sizeof(struct icmsg_negotiate) + \ + (((icframe_vercnt) + (icmsg_vercnt)) * sizeof(struct ic_version))) + +/* + * Channel packets + */ + +/* Channel packet flags */ +#define VMBUS_CHANPKT_TYPE_INBAND 0x0006 +#define VMBUS_CHANPKT_TYPE_RXBUF 0x0007 +#define VMBUS_CHANPKT_TYPE_GPA 0x0009 +#define VMBUS_CHANPKT_TYPE_COMP 0x000b + +#define VMBUS_CHANPKT_FLAG_NONE 0 +#define VMBUS_CHANPKT_FLAG_RC 0x0001 /* report completion */ + +#define VMBUS_CHANPKT_SIZE_SHIFT 3 +#define VMBUS_CHANPKT_SIZE_ALIGN BIT(VMBUS_CHANPKT_SIZE_SHIFT) +#define VMBUS_CHANPKT_HLEN_MIN \ + (sizeof(struct vmbus_chanpkt_hdr) >> VMBUS_CHANPKT_SIZE_SHIFT) + +/* + * Buffer ring + */ +struct vmbus_bufring { + volatile uint32_t windex; + volatile uint32_t rindex; + + /* + * Interrupt mask {0,1} + * + * For TX bufring, host set this to 1, when it is processing + * the TX bufring, so that we can safely skip the TX event + * notification to host. + * + * For RX bufring, once this is set to 1 by us, host will not + * further dispatch interrupts to us, even if there are data + * pending on the RX bufring. This effectively disables the + * interrupt of the channel to which this RX bufring is attached. + */ + volatile uint32_t imask; + + /* + * Win8 uses some of the reserved bits to implement + * interrupt driven flow management. On the send side + * we can request that the receiver interrupt the sender + * when the ring transitions from being full to being able + * to handle a message of size "pending_send_sz". + * + * Add necessary state for this enhancement. + */ + volatile uint32_t pending_send; + uint32_t reserved1[12]; + + union { + struct { + uint32_t feat_pending_send_sz:1; + }; + uint32_t value; + } feature_bits; + + /* Pad it to rte_mem_page_size() so that data starts on page boundary */ + uint8_t reserved2[4028]; + + /* + * Ring data starts here + RingDataStartOffset + * !!! DO NOT place any fields below this !!! + */ + uint8_t data[]; +} __packed; + +struct vmbus_br { + struct vmbus_bufring *vbr; + uint32_t dsize; + uint32_t windex; /* next available location */ +}; + +struct vmbus_chanpkt_hdr { + uint16_t type; /* VMBUS_CHANPKT_TYPE_ */ + uint16_t hlen; /* header len, in 8 bytes */ + uint16_t tlen; /* total len, in 8 bytes */ + uint16_t flags; /* VMBUS_CHANPKT_FLAG_ */ + uint64_t xactid; +} __packed; + +struct vmbus_chanpkt { + struct vmbus_chanpkt_hdr hdr; +} __packed; + +struct vmbuspipe_hdr { + unsigned int flags; + unsigned int msgsize; +} __packed; + +struct ic_version { + unsigned short major; + unsigned short minor; +} __packed; + +struct icmsg_negotiate { + unsigned short icframe_vercnt; + unsigned short icmsg_vercnt; + unsigned int reserved; + struct ic_version icversion_data[]; /* any size array */ +} __packed; + +struct icmsg_hdr { + struct ic_version icverframe; + unsigned short icmsgtype; + struct ic_version icvermsg; + unsigned short icmsgsize; + unsigned int status; + unsigned char ictransaction_id; + unsigned char icflags; + unsigned char reserved[2]; +} __packed; + +int rte_vmbus_chan_recv_raw(struct vmbus_br *rxbr, void *data, uint32_t *len); +int rte_vmbus_chan_send(struct vmbus_br *txbr, uint16_t type, void *data, + uint32_t dlen, uint32_t flags); +void vmbus_br_setup(struct vmbus_br *br, void *buf, unsigned int blen); +void *vmbus_uio_map(int *fd, int size); + +/* Amount of space available for write */ +static inline uint32_t vmbus_br_availwrite(const struct vmbus_br *br, uint32_t windex) +{ + uint32_t rindex = br->vbr->rindex; + + if (windex >= rindex) + return br->dsize - (windex - rindex); + else + return rindex - windex; +} + +static inline uint32_t vmbus_br_availread(const struct vmbus_br *br) +{ + return br->dsize - vmbus_br_availwrite(br, br->vbr->windex); +} + +#endif /* !_VMBUS_BUF_H_ */