2013-03-15 23:30:06 +04:00
/*
* 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>
2013-04-24 18:48:52 +04:00
# include <sys/ioctl.h>
# include <fcntl.h>
2013-03-15 23:30:06 +04:00
# include <stdio.h>
2013-04-24 18:48:51 +04:00
# include <mntent.h>
2013-03-15 23:30:06 +04:00
# include <stdlib.h>
# include <unistd.h>
# include <string.h>
# include <ctype.h>
# include <errno.h>
# include <arpa/inet.h>
2013-04-24 18:48:52 +04:00
# include <linux/fs.h>
2013-03-15 23:30:06 +04:00
# include <linux/connector.h>
# include <linux/hyperv.h>
# include <linux/netlink.h>
# include <syslog.h>
2014-10-22 20:07:11 +04:00
# include <getopt.h>
2013-03-15 23:30:06 +04:00
static struct sockaddr_nl addr ;
# ifndef SOL_NETLINK
# define SOL_NETLINK 270
# endif
2014-09-26 08:52:04 +04:00
/* Don't use syslog() in the function since that can cause write to disk */
static int vss_do_freeze ( char * dir , unsigned int cmd )
2013-04-24 18:48:52 +04:00
{
int ret , fd = open ( dir , O_RDONLY ) ;
if ( fd < 0 )
return 1 ;
2014-09-26 08:52:04 +04:00
2013-04-24 18:48:52 +04:00
ret = ioctl ( fd , cmd , 0 ) ;
2014-09-26 08:52:04 +04:00
/*
* 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 differnt 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 ;
}
}
2013-04-24 18:48:52 +04:00
close ( fd ) ;
return ! ! ret ;
}
2013-03-15 23:30:06 +04:00
static int vss_operate ( int operation )
{
2013-04-24 18:48:51 +04:00
char match [ ] = " /dev/ " ;
FILE * mounts ;
struct mntent * ent ;
2013-04-24 18:48:52 +04:00
unsigned int cmd ;
2013-04-24 18:48:51 +04:00
int error = 0 , root_seen = 0 ;
2013-03-15 23:30:06 +04:00
switch ( operation ) {
case VSS_OP_FREEZE :
2013-04-24 18:48:52 +04:00
cmd = FIFREEZE ;
2013-03-15 23:30:06 +04:00
break ;
case VSS_OP_THAW :
2013-04-24 18:48:52 +04:00
cmd = FITHAW ;
2013-03-15 23:30:06 +04:00
break ;
2013-04-24 18:48:48 +04:00
default :
return - 1 ;
2013-03-15 23:30:06 +04:00
}
2013-04-24 18:48:51 +04:00
mounts = setmntent ( " /proc/mounts " , " r " ) ;
if ( mounts = = NULL )
2013-04-24 18:48:48 +04:00
return - 1 ;
2013-03-15 23:30:06 +04:00
2013-04-24 18:48:54 +04:00
while ( ( ent = getmntent ( mounts ) ) ) {
2013-04-24 18:48:51 +04:00
if ( strncmp ( ent - > mnt_fsname , match , strlen ( match ) ) )
2013-03-15 23:30:06 +04:00
continue ;
2013-04-24 18:48:53 +04:00
if ( strcmp ( ent - > mnt_type , " iso9660 " ) = = 0 )
continue ;
2014-02-12 20:40:22 +04:00
if ( strcmp ( ent - > mnt_type , " vfat " ) = = 0 )
continue ;
2013-04-24 18:48:51 +04:00
if ( strcmp ( ent - > mnt_dir , " / " ) = = 0 ) {
root_seen = 1 ;
continue ;
}
2014-09-26 08:52:04 +04:00
error | = vss_do_freeze ( ent - > mnt_dir , cmd ) ;
if ( error & & operation = = VSS_OP_FREEZE )
goto err ;
2013-03-15 23:30:06 +04:00
}
2013-04-24 18:48:51 +04:00
endmntent ( mounts ) ;
2013-03-15 23:30:06 +04:00
2013-04-24 18:48:51 +04:00
if ( root_seen ) {
2014-09-26 08:52:04 +04:00
error | = vss_do_freeze ( " / " , cmd ) ;
if ( error & & operation = = VSS_OP_FREEZE )
goto err ;
2013-04-24 18:48:51 +04:00
}
2013-03-15 23:30:06 +04:00
2014-09-26 08:52:04 +04:00
return error ;
err :
endmntent ( mounts ) ;
vss_operate ( VSS_OP_THAW ) ;
2013-03-15 23:30:06 +04:00
return error ;
}
static int netlink_send ( int fd , struct cn_msg * msg )
{
2013-08-07 17:45:12 +04:00
struct nlmsghdr nlh = { . nlmsg_type = NLMSG_DONE } ;
2013-03-15 23:30:06 +04:00
unsigned int size ;
struct msghdr message ;
struct iovec iov [ 2 ] ;
2013-08-07 17:07:21 +04:00
size = sizeof ( struct cn_msg ) + msg - > len ;
2013-03-15 23:30:06 +04:00
2013-08-07 17:45:12 +04:00
nlh . nlmsg_pid = getpid ( ) ;
nlh . nlmsg_len = NLMSG_LENGTH ( size ) ;
2013-03-15 23:30:06 +04:00
2013-08-07 17:45:12 +04:00
iov [ 0 ] . iov_base = & nlh ;
iov [ 0 ] . iov_len = sizeof ( nlh ) ;
2013-03-15 23:30:06 +04:00
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 ) ;
}
2014-10-22 20:07:11 +04:00
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 [ ] )
2013-03-15 23:30:06 +04:00
{
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 ;
2013-08-01 16:34:26 +04:00
char * vss_recv_buffer ;
size_t vss_recv_buffer_len ;
2014-10-22 20:07:11 +04:00
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 ' :
default :
print_usage ( argv ) ;
exit ( EXIT_FAILURE ) ;
}
}
2013-03-15 23:30:06 +04:00
2014-10-22 20:07:11 +04:00
if ( daemonize & & daemon ( 1 , 0 ) )
2013-04-24 18:48:48 +04:00
return 1 ;
2013-03-15 23:30:06 +04:00
openlog ( " Hyper-V VSS " , 0 , LOG_USER ) ;
syslog ( LOG_INFO , " VSS starting; pid is:%d " , getpid ( ) ) ;
2013-08-06 22:55:38 +04:00
vss_recv_buffer_len = NLMSG_LENGTH ( 0 ) + sizeof ( struct cn_msg ) + sizeof ( struct hv_vss_msg ) ;
2013-08-01 16:34:26 +04:00
vss_recv_buffer = calloc ( 1 , vss_recv_buffer_len ) ;
2013-08-06 22:55:38 +04:00
if ( ! vss_recv_buffer ) {
2013-08-01 16:34:26 +04:00
syslog ( LOG_ERR , " Failed to allocate netlink buffers " ) ;
exit ( EXIT_FAILURE ) ;
}
2013-03-15 23:30:06 +04:00
fd = socket ( AF_NETLINK , SOCK_DGRAM , NETLINK_CONNECTOR ) ;
if ( fd < 0 ) {
2013-06-27 15:52:47 +04:00
syslog ( LOG_ERR , " netlink socket creation failed; error:%d %s " ,
errno , strerror ( errno ) ) ;
2013-03-15 23:30:06 +04:00
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 ) {
2013-06-27 15:52:47 +04:00
syslog ( LOG_ERR , " bind failed; error:%d %s " , errno , strerror ( errno ) ) ;
2013-03-15 23:30:06 +04:00
close ( fd ) ;
exit ( EXIT_FAILURE ) ;
}
nl_group = CN_VSS_IDX ;
2013-06-27 15:52:48 +04:00
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 ) ;
}
2013-03-15 23:30:06 +04:00
/*
* Register ourselves with the kernel .
*/
2013-08-06 22:55:38 +04:00
message = ( struct cn_msg * ) vss_recv_buffer ;
2013-03-15 23:30:06 +04:00
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 ) {
2013-06-27 15:52:47 +04:00
syslog ( LOG_ERR , " netlink_send failed; error:%d %s " , errno , strerror ( errno ) ) ;
2013-03-15 23:30:06 +04:00
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 ;
2013-06-27 15:52:49 +04:00
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 ;
}
2013-03-15 23:30:06 +04:00
2013-08-01 16:34:26 +04:00
len = recvfrom ( fd , vss_recv_buffer , vss_recv_buffer_len , 0 ,
2013-03-15 23:30:06 +04:00
addr_p , & addr_l ) ;
2013-04-24 18:48:49 +04:00
if ( len < 0 ) {
2013-03-15 23:30:06 +04:00
syslog ( LOG_ERR , " recvfrom failed; pid:%u error:%d %s " ,
addr . nl_pid , errno , strerror ( errno ) ) ;
close ( fd ) ;
return - 1 ;
}
2013-04-24 18:48:49 +04:00
if ( addr . nl_pid ) {
2013-04-24 18:48:50 +04:00
syslog ( LOG_WARNING ,
" Received packet from untrusted pid:%u " ,
addr . nl_pid ) ;
2013-04-24 18:48:49 +04:00
continue ;
}
2013-03-15 23:30:06 +04:00
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 ) ;
2014-09-26 08:52:04 +04:00
syslog ( LOG_INFO , " VSS: op=%s: %s \n " ,
op = = VSS_OP_FREEZE ? " FREEZE " : " THAW " ,
error ? " failed " : " succeeded " ) ;
if ( error ) {
2013-03-15 23:30:06 +04:00
error = HV_E_FAIL ;
2014-09-26 08:52:04 +04:00
syslog ( LOG_ERR , " op=%d failed! " , op ) ;
syslog ( LOG_ERR , " report it with these files: " ) ;
syslog ( LOG_ERR , " /etc/fstab and /proc/mounts " ) ;
}
2013-03-15 23:30:06 +04:00
break ;
default :
syslog ( LOG_ERR , " Illegal op:%d \n " , op ) ;
}
vss_msg - > error = error ;
len = netlink_send ( fd , incoming_cn_msg ) ;
if ( len < 0 ) {
2013-06-27 15:52:47 +04:00
syslog ( LOG_ERR , " net_link send failed; error:%d %s " ,
errno , strerror ( errno ) ) ;
2013-03-15 23:30:06 +04:00
exit ( EXIT_FAILURE ) ;
}
}
}