Accepting request 183123 from Virtualization
- update hv_kvp_daemon (merge 0783d72fa from v3.9-rc1) Fix how ifcfg-* file is created - 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 - Fix a bug in IPV6 subnet enumeration (bnc#828714) - Update hv_vss_daemon (bnc#811033) - add hv_vss_daemon (fate#314921) helper to support host initiated backup - build hv_kvp_daemon with -D_GNU_SOURCE to get O_CLOEXEC - 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 OBS-URL: https://build.opensuse.org/request/show/183123 OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/hyper-v?expand=0&rev=10
This commit is contained in:
commit
3d47681214
@ -1,3 +1,52 @@
|
||||
-------------------------------------------------------------------
|
||||
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
|
||||
|
||||
|
@ -27,6 +27,63 @@
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
|
||||
/*
|
||||
* Implementation of host controlled snapshot of the guest.
|
||||
*/
|
||||
|
||||
#define VSS_OP_REGISTER 128
|
||||
|
||||
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 {
|
||||
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));
|
||||
|
||||
/*
|
||||
* An implementation of HyperV key value pair (KVP) functionality for Linux.
|
||||
*
|
||||
|
82
hyper-v.init.vss.sh
Normal file
82
hyper-v.init.vss.sh
Normal file
@ -0,0 +1,82 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# LSB compatible service control script; see http://www.linuxbase.org/spec/
|
||||
#
|
||||
### BEGIN INIT INFO
|
||||
# Provides: hv_vss_daemon
|
||||
# Required-Start: $null
|
||||
# Should-Start: $syslog $remote_fs $time
|
||||
# Required-Stop: $null
|
||||
# Should-Stop: $syslog $remote_fs $time
|
||||
# Default-Start: 3 5
|
||||
# Default-Stop: 0 1 2 6
|
||||
# Short-Description: hv_vss_daemon assists with host initiated backup
|
||||
# Description: Start hv_vss_daemon to allow the host to snapshot this guest
|
||||
### END INIT INFO
|
||||
|
||||
# Check for missing binaries (stale symlinks should not happen)
|
||||
# Note: Special treatment of stop for LSB conformance
|
||||
HV_VSS_BIN=/usr/sbin/hv_vss_daemon
|
||||
test -x $HV_VSS_BIN || { echo "$HV_VSS_BIN not installed";
|
||||
if [ "$1" = "stop" ]; then exit 0;
|
||||
else exit 5; fi; }
|
||||
|
||||
. /etc/rc.status
|
||||
|
||||
# Reset status of this service
|
||||
rc_reset
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
echo -n "Starting Hyper-V VSS daemon "
|
||||
env PATH=/usr/lib/hyper-v/bin:$PATH \
|
||||
startproc $HV_VSS_BIN
|
||||
rc_status -v
|
||||
;;
|
||||
stop)
|
||||
echo -n "Shutting down Hyper-V VSS daemon "
|
||||
killproc -TERM $HV_VSS_BIN
|
||||
rc_status -v
|
||||
;;
|
||||
try-restart|condrestart)
|
||||
if test "$1" = "condrestart"; then
|
||||
echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}"
|
||||
fi
|
||||
$0 status
|
||||
if test $? = 0; then
|
||||
$0 restart
|
||||
else
|
||||
rc_reset # Not running is not a failure.
|
||||
fi
|
||||
# Remember status and be quiet
|
||||
rc_status
|
||||
;;
|
||||
restart)
|
||||
## Stop the service and regardless of whether it was
|
||||
## running or not, start it again.
|
||||
$0 stop
|
||||
$0 start
|
||||
|
||||
# Remember status and be quiet
|
||||
rc_status
|
||||
;;
|
||||
force-reload)
|
||||
echo -n "Reload service Hyper-V VSS daemon "
|
||||
$0 try-restart
|
||||
rc_status
|
||||
;;
|
||||
reload)
|
||||
rc_failed 3
|
||||
rc_status -v
|
||||
;;
|
||||
status)
|
||||
echo -n "Checking for service Hyper-V VSS daemon "
|
||||
checkproc $HV_VSS_BIN
|
||||
rc_status -v
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
rc_exit
|
53
hyper-v.spec
53
hyper-v.spec
@ -1,7 +1,7 @@
|
||||
#
|
||||
# spec file for package hyper-v
|
||||
#
|
||||
# Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany.
|
||||
# Copyright (c) 2013 SUSE LINUX Products GmbH, Nuernberg, Germany.
|
||||
#
|
||||
# All modifications and additions to the file contributed by third parties
|
||||
# remain the property of their copyright owners, unless otherwise agreed
|
||||
@ -17,22 +17,27 @@
|
||||
|
||||
|
||||
%define hv_kvp_daemon hv_kvp_daemon
|
||||
%define hv_vss_daemon hv_vss_daemon
|
||||
|
||||
Name: hyper-v
|
||||
ExclusiveArch: %ix86 x86_64
|
||||
PreReq: %insserv_prereq
|
||||
Requires(pre): coreutils
|
||||
Summary: Microsoft Hyper-V tools
|
||||
License: GPL-2.0
|
||||
Group: System/Kernel
|
||||
Supplements: modalias(dmi*:svn*MicrosoftCorporation*:pn*VirtualMachine*:rn*VirtualMachine*)
|
||||
Supplements: modalias(pci:v00001414d00005353sv*sd*bc*sc*i*)
|
||||
Url: http://www.kernel.org
|
||||
Version: 4
|
||||
# Arbitrary version number
|
||||
Version: 5
|
||||
Release: 0
|
||||
Source5: hyper-v.kvptest.ps1.txt
|
||||
Source9: hyper-v.include.linux.hyperv.h
|
||||
Source10: hyper-v.tools.hv.hv_kvp_daemon.c
|
||||
Source11: hyper-v.init.sh
|
||||
Source12: hyper-v.tools.hv.hv_vss_daemon.c
|
||||
Source13: hyper-v.init.vss.sh
|
||||
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
|
||||
@ -47,23 +52,38 @@ This package contains the Microsoft Hyper-V tools.
|
||||
cp -avL %{S:5} kvptest.ps1.txt
|
||||
cp -vL %{S:9} %{hv_kvp_daemon}.h
|
||||
cp -vL %{S:10} %{hv_kvp_daemon}.c
|
||||
cp -vL %{S:12} %{hv_vss_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
|
||||
gcc \
|
||||
$RPM_OPT_FLAGS \
|
||||
-Wno-unused-variable \
|
||||
-Wno-pointer-sign \
|
||||
-D_GNU_SOURCE \
|
||||
-g \
|
||||
%{hv_kvp_daemon}.c \
|
||||
-include %{hv_kvp_daemon}.h \
|
||||
-DCN_KVP_IDX=0x9 \
|
||||
-DCN_KVP_VAL=0x1 \
|
||||
-o %{hv_kvp_daemon}
|
||||
gcc \
|
||||
$RPM_OPT_FLAGS \
|
||||
-Wno-unused-variable \
|
||||
-Wno-pointer-sign \
|
||||
-D_GNU_SOURCE \
|
||||
-g \
|
||||
%{hv_vss_daemon}.c \
|
||||
-include %{hv_kvp_daemon}.h \
|
||||
-DCN_VSS_IDX=0xa \
|
||||
-DCN_VSS_VAL=0x1 \
|
||||
-o %{hv_vss_daemon}
|
||||
|
||||
%install
|
||||
mkdir -p $RPM_BUILD_ROOT/usr/sbin
|
||||
install -m755 %{hv_kvp_daemon} $RPM_BUILD_ROOT/usr/sbin
|
||||
install -m755 %{hv_vss_daemon} $RPM_BUILD_ROOT/usr/sbin
|
||||
mkdir -p $RPM_BUILD_ROOT/usr/lib/%{name}/bin
|
||||
cp -avL %{S:20} $RPM_BUILD_ROOT/usr/lib/%{name}/bin/hv_get_dhcp_info
|
||||
cp -avL %{S:21} $RPM_BUILD_ROOT/usr/lib/%{name}/bin/hv_get_dns_info
|
||||
@ -72,15 +92,35 @@ chmod 755 $RPM_BUILD_ROOT/usr/lib/%{name}/bin/*
|
||||
mkdir -p $RPM_BUILD_ROOT/etc/init.d
|
||||
install -m755 %{S:11} $RPM_BUILD_ROOT/etc/init.d/%{hv_kvp_daemon}
|
||||
ln -sfvbn ../../etc/init.d/%{hv_kvp_daemon} $RPM_BUILD_ROOT/usr/sbin/rc%{hv_kvp_daemon}
|
||||
install -m755 %{S:13} $RPM_BUILD_ROOT/etc/init.d/%{hv_vss_daemon}
|
||||
ln -sfvbn ../../etc/init.d/%{hv_vss_daemon} $RPM_BUILD_ROOT/usr/sbin/rc%{hv_vss_daemon}
|
||||
|
||||
%files
|
||||
%defattr (-,root,root)
|
||||
%doc kvptest.ps1.txt
|
||||
/etc/init.d/%{hv_kvp_daemon}
|
||||
/usr/sbin/rc%{hv_kvp_daemon}
|
||||
/usr/sbin/%{hv_kvp_daemon}
|
||||
/etc/init.d/*
|
||||
/usr/sbin/*
|
||||
/usr/lib/%{name}
|
||||
|
||||
%pre
|
||||
# hv_kvp_daemon in SLES11 SP2 stored temporary state files in /var/opt
|
||||
# move them to /var/lib and remove old directory, if possible.
|
||||
if test -d /var/opt/hyperv
|
||||
then
|
||||
if mkdir -p -v -m 0755 /var/lib/hyperv && pushd /var/lib/hyperv > /dev/null
|
||||
then
|
||||
for oldfile in /var/opt/hyperv/ifcfg-* /var/opt/hyperv/.kvp_pool_*
|
||||
do
|
||||
if test -e "${oldfile}"
|
||||
then
|
||||
mv -vfb "${oldfile}" . || :
|
||||
fi
|
||||
done
|
||||
popd > /dev/null
|
||||
fi
|
||||
rmdir -v /var/opt/hyperv || :
|
||||
fi
|
||||
|
||||
%post
|
||||
board_vendor=
|
||||
product_name=
|
||||
@ -100,10 +140,13 @@ if test "${board_vendor}" = "Microsoft Corporation" -a "${product_name}" = "Virt
|
||||
then
|
||||
echo "Enabling %{hv_kvp_daemon} on '${product_name}' from '${board_vendor}'"
|
||||
%{insserv_force_if_yast %{hv_kvp_daemon}}
|
||||
echo "Enabling %{hv_vss_daemon} on '${product_name}' from '${board_vendor}'"
|
||||
%{insserv_force_if_yast %{hv_vss_daemon}}
|
||||
fi
|
||||
|
||||
%preun
|
||||
%stop_on_removal %{hv_kvp_daemon}
|
||||
%stop_on_removal %{hv_vss_daemon}
|
||||
|
||||
%postun
|
||||
# no restart on update because the daemon can not be restarted
|
||||
|
@ -97,11 +97,15 @@ static struct utsname uts_buf;
|
||||
* The location of the interface configuration file.
|
||||
*/
|
||||
|
||||
#define KVP_CONFIG_LOC "/var/opt/"
|
||||
#define KVP_CONFIG_LOC "/var/lib/hyperv"
|
||||
|
||||
#define MAX_FILE_NAME 100
|
||||
#define ENTRIES_PER_BLOCK 50
|
||||
|
||||
#ifndef SOL_NETLINK
|
||||
#define SOL_NETLINK 270
|
||||
#endif
|
||||
|
||||
struct kvp_record {
|
||||
char key[HV_KVP_EXCHANGE_MAX_KEY_SIZE];
|
||||
char value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE];
|
||||
@ -123,7 +127,8 @@ static void kvp_acquire_lock(int pool)
|
||||
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", pool);
|
||||
syslog(LOG_ERR, "Failed to acquire the lock pool: %d; error: %d %s", pool,
|
||||
errno, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
@ -134,8 +139,8 @@ static void kvp_release_lock(int pool)
|
||||
fl.l_pid = getpid();
|
||||
|
||||
if (fcntl(kvp_file_info[pool].fd, F_SETLK, &fl) == -1) {
|
||||
perror("fcntl");
|
||||
syslog(LOG_ERR, "Failed to release the lock pool: %d", pool);
|
||||
syslog(LOG_ERR, "Failed to release the lock pool: %d; error: %d %s", pool,
|
||||
errno, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
@ -151,10 +156,11 @@ static void kvp_update_file(int pool)
|
||||
*/
|
||||
kvp_acquire_lock(pool);
|
||||
|
||||
filep = fopen(kvp_file_info[pool].fname, "w");
|
||||
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);
|
||||
syslog(LOG_ERR, "Failed to open file, pool: %d", pool);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@ -182,10 +188,11 @@ static void kvp_update_mem_state(int pool)
|
||||
|
||||
kvp_acquire_lock(pool);
|
||||
|
||||
filep = fopen(kvp_file_info[pool].fname, "r");
|
||||
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);
|
||||
syslog(LOG_ERR, "Failed to open file, pool: %d", pool);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
for (;;) {
|
||||
@ -234,9 +241,10 @@ static int kvp_file_init(void)
|
||||
int i;
|
||||
int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK;
|
||||
|
||||
if (access("/var/opt/hyperv", F_OK)) {
|
||||
if (mkdir("/var/opt/hyperv", S_IRUSR | S_IWUSR | S_IROTH)) {
|
||||
syslog(LOG_ERR, " Failed to create /var/opt/hyperv");
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -245,20 +253,23 @@ static int kvp_file_init(void)
|
||||
fname = kvp_file_info[i].fname;
|
||||
records_read = 0;
|
||||
num_blocks = 1;
|
||||
sprintf(fname, "/var/opt/hyperv/.kvp_pool_%d", i);
|
||||
fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IROTH);
|
||||
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;
|
||||
|
||||
|
||||
filep = fopen(fname, "r");
|
||||
if (!filep)
|
||||
filep = fopen(fname, "re");
|
||||
if (!filep) {
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
record = malloc(alloc_unit * num_blocks);
|
||||
if (record == NULL) {
|
||||
fclose(filep);
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
for (;;) {
|
||||
@ -282,6 +293,7 @@ static int kvp_file_init(void)
|
||||
num_blocks);
|
||||
if (record == NULL) {
|
||||
fclose(filep);
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
@ -299,7 +311,7 @@ static int kvp_file_init(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kvp_key_delete(int pool, __u8 *key, int key_size)
|
||||
static int kvp_key_delete(int pool, const char *key, int key_size)
|
||||
{
|
||||
int i;
|
||||
int j, k;
|
||||
@ -342,7 +354,7 @@ static int kvp_key_delete(int pool, __u8 *key, int key_size)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int kvp_key_add_or_modify(int pool, __u8 *key, int key_size, __u8 *value,
|
||||
static int kvp_key_add_or_modify(int pool, const char *key, int key_size, const char *value,
|
||||
int value_size)
|
||||
{
|
||||
int i;
|
||||
@ -396,7 +408,7 @@ static int kvp_key_add_or_modify(int pool, __u8 *key, int key_size, __u8 *value,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kvp_get_value(int pool, __u8 *key, int key_size, __u8 *value,
|
||||
static int kvp_get_value(int pool, const char *key, int key_size, char *value,
|
||||
int value_size)
|
||||
{
|
||||
int i;
|
||||
@ -428,8 +440,8 @@ static int kvp_get_value(int pool, __u8 *key, int key_size, __u8 *value,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size,
|
||||
__u8 *value, int value_size)
|
||||
static int kvp_pool_enumerate(int pool, int index, char *key, int key_size,
|
||||
char *value, int value_size)
|
||||
{
|
||||
struct kvp_record *record;
|
||||
|
||||
@ -761,7 +773,9 @@ static void kvp_process_ipconfig_file(char *cmd,
|
||||
break;
|
||||
|
||||
x = strchr(p, '\n');
|
||||
*x = '\0';
|
||||
if (x)
|
||||
*x = '\0';
|
||||
|
||||
strcat(config_buf, p);
|
||||
strcat(config_buf, ";");
|
||||
}
|
||||
@ -1012,9 +1026,10 @@ kvp_get_ip_info(int family, char *if_name, int op,
|
||||
|
||||
if (sn_offset == 0)
|
||||
strcpy(sn_str, cidr_mask);
|
||||
else
|
||||
else {
|
||||
strcat((char *)ip_buffer->sub_net, ";");
|
||||
strcat(sn_str, cidr_mask);
|
||||
strcat((char *)ip_buffer->sub_net, ";");
|
||||
}
|
||||
sn_offset += strlen(sn_str) + 1;
|
||||
}
|
||||
|
||||
@ -1162,16 +1177,13 @@ static int process_ip_string(FILE *f, char *ip_string, int type)
|
||||
snprintf(str, sizeof(str), "%s", "DNS");
|
||||
break;
|
||||
}
|
||||
if (i != 0) {
|
||||
if (type != DNS) {
|
||||
snprintf(sub_str, sizeof(sub_str),
|
||||
"_%d", i++);
|
||||
} else {
|
||||
snprintf(sub_str, sizeof(sub_str),
|
||||
"%d", ++i);
|
||||
}
|
||||
} else if (type == DNS) {
|
||||
|
||||
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++);
|
||||
}
|
||||
|
||||
|
||||
@ -1191,17 +1203,13 @@ static int process_ip_string(FILE *f, char *ip_string, int type)
|
||||
snprintf(str, sizeof(str), "%s", "DNS");
|
||||
break;
|
||||
}
|
||||
if ((j != 0) || (type == DNS)) {
|
||||
if (type != DNS) {
|
||||
snprintf(sub_str, sizeof(sub_str),
|
||||
"_%d", j++);
|
||||
} else {
|
||||
snprintf(sub_str, sizeof(sub_str),
|
||||
"%d", ++i);
|
||||
}
|
||||
} else if (type == DNS) {
|
||||
snprintf(sub_str, sizeof(sub_str),
|
||||
"%d", ++i);
|
||||
|
||||
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;
|
||||
@ -1244,18 +1252,19 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
|
||||
* Here is the format of the ip configuration file:
|
||||
*
|
||||
* HWADDR=macaddr
|
||||
* IF_NAME=interface name
|
||||
* DHCP=yes (This is optional; if yes, DHCP is configured)
|
||||
* DEVICE=interface name
|
||||
* BOOTPROTO=<protocol> (where <protocol> is "dhcp" if DHCP is configured
|
||||
* or "none" if no boot-time protocol should be used)
|
||||
*
|
||||
* IPADDR=ipaddr1
|
||||
* IPADDR_1=ipaddr2
|
||||
* IPADDR_x=ipaddry (where y = x + 1)
|
||||
* IPADDR0=ipaddr1
|
||||
* IPADDR1=ipaddr2
|
||||
* IPADDRx=ipaddry (where y = x + 1)
|
||||
*
|
||||
* NETMASK=netmask1
|
||||
* NETMASK_x=netmasky (where y = x + 1)
|
||||
* NETMASK0=netmask1
|
||||
* NETMASKx=netmasky (where y = x + 1)
|
||||
*
|
||||
* GATEWAY=ipaddr1
|
||||
* GATEWAY_x=ipaddry (where y = x + 1)
|
||||
* GATEWAYx=ipaddry (where y = x + 1)
|
||||
*
|
||||
* DNSx=ipaddrx (where first DNS address is tagged as DNS1 etc)
|
||||
*
|
||||
@ -1271,12 +1280,13 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
|
||||
*/
|
||||
|
||||
snprintf(if_file, sizeof(if_file), "%s%s%s", KVP_CONFIG_LOC,
|
||||
"hyperv/ifcfg-", if_name);
|
||||
"/ifcfg-", if_name);
|
||||
|
||||
file = fopen(if_file, "w");
|
||||
|
||||
if (file == NULL) {
|
||||
syslog(LOG_ERR, "Failed to open config file");
|
||||
syslog(LOG_ERR, "Failed to open config file; error: %d %s",
|
||||
errno, strerror(errno));
|
||||
return HV_E_FAIL;
|
||||
}
|
||||
|
||||
@ -1294,12 +1304,12 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
|
||||
if (error)
|
||||
goto setval_error;
|
||||
|
||||
error = kvp_write_file(file, "IF_NAME", "", if_name);
|
||||
error = kvp_write_file(file, "DEVICE", "", if_name);
|
||||
if (error)
|
||||
goto setval_error;
|
||||
|
||||
if (new_val->dhcp_enabled) {
|
||||
error = kvp_write_file(file, "DHCP", "", "yes");
|
||||
error = kvp_write_file(file, "BOOTPROTO", "", "dhcp");
|
||||
if (error)
|
||||
goto setval_error;
|
||||
|
||||
@ -1307,6 +1317,11 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
|
||||
* We are done!.
|
||||
*/
|
||||
goto setval_done;
|
||||
|
||||
} else {
|
||||
error = kvp_write_file(file, "BOOTPROTO", "", "none");
|
||||
if (error)
|
||||
goto setval_error;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1408,7 +1423,7 @@ netlink_send(int fd, struct cn_msg *msg)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int fd, len, sock_opt;
|
||||
int fd, len, nl_group;
|
||||
int error;
|
||||
struct cn_msg *message;
|
||||
struct pollfd pfd;
|
||||
@ -1438,23 +1453,30 @@ int main(void)
|
||||
|
||||
fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
|
||||
if (fd < 0) {
|
||||
syslog(LOG_ERR, "netlink socket creation failed; error:%d", fd);
|
||||
syslog(LOG_ERR, "netlink socket creation failed; error: %d %s", errno,
|
||||
strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
addr.nl_family = AF_NETLINK;
|
||||
addr.nl_pad = 0;
|
||||
addr.nl_pid = 0;
|
||||
addr.nl_groups = CN_KVP_IDX;
|
||||
addr.nl_groups = 0;
|
||||
|
||||
|
||||
error = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
|
||||
if (error < 0) {
|
||||
syslog(LOG_ERR, "bind failed; error:%d", error);
|
||||
syslog(LOG_ERR, "bind failed; error: %d %s", errno, strerror(errno));
|
||||
close(fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
sock_opt = addr.nl_groups;
|
||||
setsockopt(fd, 270, 1, &sock_opt, sizeof(sock_opt));
|
||||
nl_group = CN_KVP_IDX;
|
||||
|
||||
if (setsockopt(fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &nl_group, sizeof(nl_group)) < 0) {
|
||||
syslog(LOG_ERR, "setsockopt failed; error: %d %s", errno, strerror(errno));
|
||||
close(fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Register ourselves with the kernel.
|
||||
*/
|
||||
@ -1469,7 +1491,7 @@ int main(void)
|
||||
|
||||
len = netlink_send(fd, message);
|
||||
if (len < 0) {
|
||||
syslog(LOG_ERR, "netlink_send failed; error:%d", len);
|
||||
syslog(LOG_ERR, "netlink_send failed; error: %d %s", errno, strerror(errno));
|
||||
close(fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
@ -1481,7 +1503,16 @@ int main(void)
|
||||
socklen_t addr_l = sizeof(addr);
|
||||
pfd.events = POLLIN;
|
||||
pfd.revents = 0;
|
||||
poll(&pfd, 1, -1);
|
||||
|
||||
if (poll(&pfd, 1, -1) < 0) {
|
||||
syslog(LOG_ERR, "poll failed; error: %d %s", errno, strerror(errno));
|
||||
if (errno == EINVAL) {
|
||||
close(fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
len = recvfrom(fd, kvp_recv_buffer, sizeof(kvp_recv_buffer), 0,
|
||||
addr_p, &addr_l);
|
||||
@ -1500,6 +1531,10 @@ int main(void)
|
||||
}
|
||||
|
||||
incoming_msg = (struct nlmsghdr *)kvp_recv_buffer;
|
||||
|
||||
if (incoming_msg->nlmsg_type != NLMSG_DONE)
|
||||
continue;
|
||||
|
||||
incoming_cn_msg = (struct cn_msg *)NLMSG_DATA(incoming_msg);
|
||||
hv_msg = (struct hv_kvp_msg *)incoming_cn_msg->data;
|
||||
|
||||
@ -1688,7 +1723,8 @@ kvp_done:
|
||||
|
||||
len = netlink_send(fd, incoming_cn_msg);
|
||||
if (len < 0) {
|
||||
syslog(LOG_ERR, "net_link send failed; error:%d", len);
|
||||
syslog(LOG_ERR, "net_link send failed; error: %d %s", errno,
|
||||
strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
@ -10,18 +10,19 @@
|
||||
# Here is the format of the ip configuration file:
|
||||
#
|
||||
# HWADDR=macaddr
|
||||
# IF_NAME=interface name
|
||||
# DHCP=yes (This is optional; if yes, DHCP is configured)
|
||||
# DEVICE=interface name
|
||||
# BOOTPROTO=<protocol> (where <protocol> is "dhcp" if DHCP is configured
|
||||
# or "none" if no boot-time protocol should be used)
|
||||
#
|
||||
# IPADDR=ipaddr1
|
||||
# IPADDR_1=ipaddr2
|
||||
# IPADDR_x=ipaddry (where y = x + 1)
|
||||
# IPADDR0=ipaddr1
|
||||
# IPADDR1=ipaddr2
|
||||
# IPADDRx=ipaddry (where y = x + 1)
|
||||
#
|
||||
# NETMASK=netmask1
|
||||
# NETMASK_x=netmasky (where y = x + 1)
|
||||
# NETMASK0=netmask1
|
||||
# NETMASKx=netmasky (where y = x + 1)
|
||||
#
|
||||
# GATEWAY=ipaddr1
|
||||
# GATEWAY_x=ipaddry (where y = x + 1)
|
||||
# GATEWAYx=ipaddry (where y = x + 1)
|
||||
#
|
||||
# DNSx=ipaddrx (where first DNS address is tagged as DNS1 etc)
|
||||
#
|
||||
@ -53,8 +54,8 @@ else
|
||||
fi
|
||||
# remove known config variables from environment
|
||||
unset HWADDR
|
||||
unset DHCP
|
||||
unset IF_NAME
|
||||
unset BOOTPROTO
|
||||
unset DEVICE
|
||||
unset ${!IPADDR*}
|
||||
unset ${!NETMASK*}
|
||||
unset ${!GATEWAY*}
|
||||
@ -64,9 +65,9 @@ unset ${!IPV6_DEFAULTGW*}
|
||||
unset ${!DNS*}
|
||||
. "$1"
|
||||
#
|
||||
if test -z "${IF_NAME}"
|
||||
if test -z "${DEVICE}"
|
||||
then
|
||||
echo "Missing IF_NAME= in ${cfg}"
|
||||
echo "Missing DEVICE= in ${cfg}"
|
||||
exit 1
|
||||
fi
|
||||
#
|
||||
@ -91,7 +92,7 @@ fi
|
||||
: # ignore HWADDR, it just repeats the existing MAC value
|
||||
fi
|
||||
#
|
||||
if test "${DHCP}" = "yes"
|
||||
if test "${BOOTPROTO}" = "dhcp"
|
||||
then
|
||||
echo "BOOTPROTO=dhcp"
|
||||
fi
|
||||
@ -145,11 +146,11 @@ fi
|
||||
(
|
||||
if test -n "${GATEWAY}"
|
||||
then
|
||||
echo "default $GATEWAY - $IF_NAME"
|
||||
echo "default $GATEWAY - $DEVICE"
|
||||
fi
|
||||
if test -n "${IPV6_DEFAULTGW}"
|
||||
then
|
||||
echo "default $IPV6_DEFAULTGW - $IF_NAME"
|
||||
echo "default $IPV6_DEFAULTGW - $DEVICE"
|
||||
fi
|
||||
) >> "${t_ifroute}"
|
||||
# Only a single default gateway is supported
|
||||
@ -172,14 +173,14 @@ do
|
||||
fi
|
||||
done
|
||||
#
|
||||
echo "$0: working on network interface ifcfg-${IF_NAME}"
|
||||
cp -fb ${t_ifcfg} "/etc/sysconfig/network/ifcfg-${IF_NAME}"
|
||||
cp -fb ${t_ifroute} "/etc/sysconfig/network/ifroute-${IF_NAME}"
|
||||
echo "$0: working on network interface ifcfg-${DEVICE}"
|
||||
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
|
||||
ifdown "${IF_NAME}"
|
||||
ifup "${IF_NAME}"
|
||||
ifdown "${DEVICE}"
|
||||
ifup "${DEVICE}"
|
||||
) 2>&1 | logger -t "${0##*/}[$PPID / $$]"
|
||||
|
246
hyper-v.tools.hv.hv_vss_daemon.c
Normal file
246
hyper-v.tools.hv.hv_vss_daemon.c
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* An implementation of the host initiated guest snapshot for Hyper-V.
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2013, Microsoft, Inc.
|
||||
* Author : K. Y. Srinivasan <kys@microsoft.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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/types.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 <arpa/inet.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/connector.h>
|
||||
#include <linux/hyperv.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <syslog.h>
|
||||
|
||||
static char vss_recv_buffer[4096];
|
||||
static char vss_send_buffer[4096];
|
||||
static struct sockaddr_nl addr;
|
||||
|
||||
#ifndef SOL_NETLINK
|
||||
#define SOL_NETLINK 270
|
||||
#endif
|
||||
|
||||
|
||||
static int vss_do_freeze(char *dir, unsigned int cmd, char *fs_op)
|
||||
{
|
||||
int ret, fd = open(dir, O_RDONLY);
|
||||
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
ret = ioctl(fd, cmd, 0);
|
||||
syslog(LOG_INFO, "VSS: %s of %s: %s\n", fs_op, dir, strerror(errno));
|
||||
close(fd);
|
||||
return !!ret;
|
||||
}
|
||||
|
||||
static int vss_operate(int operation)
|
||||
{
|
||||
char *fs_op;
|
||||
char match[] = "/dev/";
|
||||
FILE *mounts;
|
||||
struct mntent *ent;
|
||||
unsigned int cmd;
|
||||
int error = 0, root_seen = 0;
|
||||
|
||||
switch (operation) {
|
||||
case VSS_OP_FREEZE:
|
||||
cmd = FIFREEZE;
|
||||
fs_op = "freeze";
|
||||
break;
|
||||
case VSS_OP_THAW:
|
||||
cmd = FITHAW;
|
||||
fs_op = "thaw";
|
||||
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 (strcmp(ent->mnt_dir, "/") == 0) {
|
||||
root_seen = 1;
|
||||
continue;
|
||||
}
|
||||
error |= vss_do_freeze(ent->mnt_dir, cmd, fs_op);
|
||||
}
|
||||
endmntent(mounts);
|
||||
|
||||
if (root_seen) {
|
||||
error |= vss_do_freeze("/", cmd, fs_op);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int netlink_send(int fd, struct cn_msg *msg)
|
||||
{
|
||||
struct nlmsghdr *nlh;
|
||||
unsigned int size;
|
||||
struct msghdr message;
|
||||
char buffer[64];
|
||||
struct iovec iov[2];
|
||||
|
||||
size = NLMSG_SPACE(sizeof(struct cn_msg) + msg->len);
|
||||
|
||||
nlh = (struct nlmsghdr *)buffer;
|
||||
nlh->nlmsg_seq = 0;
|
||||
nlh->nlmsg_pid = getpid();
|
||||
nlh->nlmsg_type = NLMSG_DONE;
|
||||
nlh->nlmsg_len = NLMSG_LENGTH(size - sizeof(*nlh));
|
||||
nlh->nlmsg_flags = 0;
|
||||
|
||||
iov[0].iov_base = nlh;
|
||||
iov[0].iov_len = sizeof(*nlh);
|
||||
|
||||
iov[1].iov_base = msg;
|
||||
iov[1].iov_len = size;
|
||||
|
||||
memset(&message, 0, sizeof(message));
|
||||
message.msg_name = &addr;
|
||||
message.msg_namelen = sizeof(addr);
|
||||
message.msg_iov = iov;
|
||||
message.msg_iovlen = 2;
|
||||
|
||||
return sendmsg(fd, &message, 0);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int fd, len, nl_group;
|
||||
int error;
|
||||
struct cn_msg *message;
|
||||
struct pollfd pfd;
|
||||
struct nlmsghdr *incoming_msg;
|
||||
struct cn_msg *incoming_cn_msg;
|
||||
int op;
|
||||
struct hv_vss_msg *vss_msg;
|
||||
|
||||
if (daemon(1, 0))
|
||||
return 1;
|
||||
|
||||
openlog("Hyper-V VSS", 0, LOG_USER);
|
||||
syslog(LOG_INFO, "VSS starting; pid is:%d", getpid());
|
||||
|
||||
fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
|
||||
if (fd < 0) {
|
||||
syslog(LOG_ERR, "netlink socket creation failed; error:%d", fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
addr.nl_family = AF_NETLINK;
|
||||
addr.nl_pad = 0;
|
||||
addr.nl_pid = 0;
|
||||
addr.nl_groups = 0;
|
||||
|
||||
|
||||
error = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
|
||||
if (error < 0) {
|
||||
syslog(LOG_ERR, "bind failed; error:%d", error);
|
||||
close(fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
nl_group = CN_VSS_IDX;
|
||||
setsockopt(fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &nl_group, sizeof(nl_group));
|
||||
/*
|
||||
* Register ourselves with the kernel.
|
||||
*/
|
||||
message = (struct cn_msg *)vss_send_buffer;
|
||||
message->id.idx = CN_VSS_IDX;
|
||||
message->id.val = CN_VSS_VAL;
|
||||
message->ack = 0;
|
||||
vss_msg = (struct hv_vss_msg *)message->data;
|
||||
vss_msg->vss_hdr.operation = VSS_OP_REGISTER;
|
||||
|
||||
message->len = sizeof(struct hv_vss_msg);
|
||||
|
||||
len = netlink_send(fd, message);
|
||||
if (len < 0) {
|
||||
syslog(LOG_ERR, "netlink_send failed; error:%d", len);
|
||||
close(fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
pfd.fd = fd;
|
||||
|
||||
while (1) {
|
||||
struct sockaddr *addr_p = (struct sockaddr *) &addr;
|
||||
socklen_t addr_l = sizeof(addr);
|
||||
pfd.events = POLLIN;
|
||||
pfd.revents = 0;
|
||||
poll(&pfd, 1, -1);
|
||||
|
||||
len = recvfrom(fd, vss_recv_buffer, sizeof(vss_recv_buffer), 0,
|
||||
addr_p, &addr_l);
|
||||
|
||||
if (len < 0) {
|
||||
syslog(LOG_ERR, "recvfrom failed; pid:%u error:%d %s",
|
||||
addr.nl_pid, errno, strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (addr.nl_pid) {
|
||||
syslog(LOG_WARNING, "Received packet from untrusted pid:%u",
|
||||
addr.nl_pid);
|
||||
continue;
|
||||
}
|
||||
|
||||
incoming_msg = (struct nlmsghdr *)vss_recv_buffer;
|
||||
|
||||
if (incoming_msg->nlmsg_type != NLMSG_DONE)
|
||||
continue;
|
||||
|
||||
incoming_cn_msg = (struct cn_msg *)NLMSG_DATA(incoming_msg);
|
||||
vss_msg = (struct hv_vss_msg *)incoming_cn_msg->data;
|
||||
op = vss_msg->vss_hdr.operation;
|
||||
error = HV_S_OK;
|
||||
|
||||
switch (op) {
|
||||
case VSS_OP_FREEZE:
|
||||
case VSS_OP_THAW:
|
||||
error = vss_operate(op);
|
||||
if (error)
|
||||
error = HV_E_FAIL;
|
||||
break;
|
||||
default:
|
||||
syslog(LOG_ERR, "Illegal op:%d\n", op);
|
||||
}
|
||||
vss_msg->error = error;
|
||||
len = netlink_send(fd, incoming_cn_msg);
|
||||
if (len < 0) {
|
||||
syslog(LOG_ERR, "net_link send failed; error:%d", len);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user