2019-05-29 07:17:54 -07:00
// SPDX-License-Identifier: GPL-2.0-only
2013-03-15 12:30:06 -07:00
/*
* 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>
2013-04-24 07:48:52 -07:00
# include <sys/ioctl.h>
2017-08-06 13:12:52 -07:00
# include <sys/stat.h>
2018-03-04 22:17:14 -07:00
# include <sys/sysmacros.h>
2013-04-24 07:48:52 -07:00
# include <fcntl.h>
2013-03-15 12:30:06 -07:00
# include <stdio.h>
2013-04-24 07:48:51 -07:00
# include <mntent.h>
2013-03-15 12:30:06 -07:00
# include <stdlib.h>
# include <unistd.h>
# include <string.h>
# include <ctype.h>
# include <errno.h>
2013-04-24 07:48:52 -07:00
# include <linux/fs.h>
2017-08-06 13:12:52 -07:00
# include <linux/major.h>
2013-03-15 12:30:06 -07:00
# include <linux/hyperv.h>
# include <syslog.h>
2014-10-22 18:07:11 +02:00
# include <getopt.h>
2018-06-05 13:37:56 -07:00
# include <stdbool.h>
# include <dirent.h>
2013-03-15 12:30:06 -07:00
2020-01-25 21:49:41 -08:00
static bool fs_frozen ;
2014-09-25 21:52:04 -07: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 07:48:52 -07:00
{
int ret , fd = open ( dir , O_RDONLY ) ;
if ( fd < 0 )
return 1 ;
2014-09-25 21:52:04 -07:00
2013-04-24 07:48:52 -07:00
ret = ioctl ( fd , cmd , 0 ) ;
2014-09-25 21:52:04 -07: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 :
2019-05-06 16:51:24 +00:00
* 1 ) a user may mount the same partition to different directories
2014-09-25 21:52:04 -07:00
* 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 07:48:52 -07:00
close ( fd ) ;
return ! ! ret ;
}
2018-06-05 13:37:56 -07:00
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 ;
}
2013-03-15 12:30:06 -07:00
static int vss_operate ( int operation )
{
2013-04-24 07:48:51 -07:00
char match [ ] = " /dev/ " ;
FILE * mounts ;
struct mntent * ent ;
2017-08-06 13:12:52 -07:00
struct stat sb ;
2015-03-18 12:29:28 -07:00
char errdir [ 1024 ] = { 0 } ;
2018-06-05 13:37:56 -07:00
char blkdir [ 23 ] ; /* /sys/dev/block/XXX:XXX */
2013-04-24 07:48:52 -07:00
unsigned int cmd ;
2014-11-10 17:37:01 +01:00
int error = 0 , root_seen = 0 , save_errno = 0 ;
2013-03-15 12:30:06 -07:00
switch ( operation ) {
case VSS_OP_FREEZE :
2013-04-24 07:48:52 -07:00
cmd = FIFREEZE ;
2013-03-15 12:30:06 -07:00
break ;
case VSS_OP_THAW :
2013-04-24 07:48:52 -07:00
cmd = FITHAW ;
2013-03-15 12:30:06 -07:00
break ;
2013-04-24 07:48:48 -07:00
default :
return - 1 ;
2013-03-15 12:30:06 -07:00
}
2013-04-24 07:48:51 -07:00
mounts = setmntent ( " /proc/mounts " , " r " ) ;
if ( mounts = = NULL )
2013-04-24 07:48:48 -07:00
return - 1 ;
2013-03-15 12:30:06 -07:00
2013-04-24 07:48:54 -07:00
while ( ( ent = getmntent ( mounts ) ) ) {
2013-04-24 07:48:51 -07:00
if ( strncmp ( ent - > mnt_fsname , match , strlen ( match ) ) )
2013-03-15 12:30:06 -07:00
continue ;
2018-06-05 13:37:56 -07:00
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 ;
}
2014-11-10 17:37:02 +01:00
if ( hasmntopt ( ent , MNTOPT_RO ) ! = NULL )
2013-04-24 07:48:53 -07:00
continue ;
2014-02-12 08:40:22 -08:00
if ( strcmp ( ent - > mnt_type , " vfat " ) = = 0 )
continue ;
2013-04-24 07:48:51 -07:00
if ( strcmp ( ent - > mnt_dir , " / " ) = = 0 ) {
root_seen = 1 ;
continue ;
}
2014-09-25 21:52:04 -07:00
error | = vss_do_freeze ( ent - > mnt_dir , cmd ) ;
2020-01-25 21:49:41 -08:00
if ( operation = = VSS_OP_FREEZE ) {
if ( error )
goto err ;
fs_frozen = true ;
}
2013-03-15 12:30:06 -07:00
}
2015-03-18 12:29:28 -07:00
endmntent ( mounts ) ;
2013-04-24 07:48:51 -07:00
if ( root_seen ) {
2014-09-25 21:52:04 -07:00
error | = vss_do_freeze ( " / " , cmd ) ;
2020-01-25 21:49:41 -08:00
if ( operation = = VSS_OP_FREEZE ) {
if ( error )
goto err ;
fs_frozen = true ;
}
2013-04-24 07:48:51 -07:00
}
2013-03-15 12:30:06 -07:00
2020-01-25 21:49:41 -08:00
if ( operation = = VSS_OP_THAW & & ! error )
fs_frozen = false ;
2014-11-10 17:37:01 +01:00
goto out ;
2014-09-25 21:52:04 -07:00
err :
2014-11-10 17:37:01 +01:00
save_errno = errno ;
2015-03-18 12:29:28 -07:00
if ( ent ) {
strncpy ( errdir , ent - > mnt_dir , sizeof ( errdir ) - 1 ) ;
endmntent ( mounts ) ;
}
2014-09-25 21:52:04 -07:00
vss_operate ( VSS_OP_THAW ) ;
2020-01-25 21:49:41 -08:00
fs_frozen = false ;
2014-11-10 17:37:01 +01:00
/* Call syslog after we thaw all filesystems */
if ( ent )
syslog ( LOG_ERR , " FREEZE of %s failed; error:%d %s " ,
2015-03-18 12:29:28 -07:00
errdir , save_errno , strerror ( save_errno ) ) ;
2014-11-10 17:37:01 +01:00
else
syslog ( LOG_ERR , " FREEZE of / failed; error:%d %s " , save_errno ,
strerror ( save_errno ) ) ;
out :
2013-03-15 12:30:06 -07:00
return error ;
}
2014-10-22 18:07:11 +02: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 12:30:06 -07:00
{
2020-01-25 21:49:41 -08:00
int vss_fd = - 1 , len ;
2013-03-15 12:30:06 -07:00
int error ;
struct pollfd pfd ;
int op ;
2015-04-11 18:07:56 -07:00
struct hv_vss_msg vss_msg [ 1 ] ;
2014-10-22 18:07:11 +02:00
int daemonize = 1 , long_index = 0 , opt ;
2020-01-25 21:49:41 -08:00
int in_handshake ;
2015-04-11 18:07:57 -07:00
__u32 kernel_modver ;
2014-10-22 18:07:11 +02:00
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 ' :
2019-05-06 16:50:58 +00:00
print_usage ( argv ) ;
exit ( 0 ) ;
2014-10-22 18:07:11 +02:00
default :
print_usage ( argv ) ;
exit ( EXIT_FAILURE ) ;
}
}
2013-03-15 12:30:06 -07:00
2014-10-22 18:07:11 +02:00
if ( daemonize & & daemon ( 1 , 0 ) )
2013-04-24 07:48:48 -07:00
return 1 ;
2013-03-15 12:30:06 -07:00
openlog ( " Hyper-V VSS " , 0 , LOG_USER ) ;
syslog ( LOG_INFO , " VSS starting; pid is:%d " , getpid ( ) ) ;
2020-01-25 21:49:41 -08:00
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 ;
2015-04-11 18:07:56 -07:00
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 ) ) ;
2013-06-27 13:52:48 +02:00
exit ( EXIT_FAILURE ) ;
}
2013-03-15 12:30:06 -07:00
/*
* Register ourselves with the kernel .
*/
2015-04-11 18:07:56 -07:00
vss_msg - > vss_hdr . operation = VSS_OP_REGISTER1 ;
2013-03-15 12:30:06 -07:00
2015-04-11 18:07:56 -07:00
len = write ( vss_fd , vss_msg , sizeof ( struct hv_vss_msg ) ) ;
2013-03-15 12:30:06 -07:00
if ( len < 0 ) {
2015-04-11 18:07:56 -07:00
syslog ( LOG_ERR , " registration to kernel failed; error: %d %s " ,
errno , strerror ( errno ) ) ;
close ( vss_fd ) ;
2013-03-15 12:30:06 -07:00
exit ( EXIT_FAILURE ) ;
}
2015-04-11 18:07:56 -07:00
pfd . fd = vss_fd ;
2013-03-15 12:30:06 -07:00
while ( 1 ) {
pfd . events = POLLIN ;
pfd . revents = 0 ;
2013-06-27 13:52:49 +02:00
if ( poll ( & pfd , 1 , - 1 ) < 0 ) {
syslog ( LOG_ERR , " poll failed; error:%d %s " , errno , strerror ( errno ) ) ;
if ( errno = = EINVAL ) {
2015-04-11 18:07:56 -07:00
close ( vss_fd ) ;
2013-06-27 13:52:49 +02:00
exit ( EXIT_FAILURE ) ;
}
else
continue ;
}
2013-03-15 12:30:06 -07:00
2015-04-11 18:07:56 -07:00
len = read ( vss_fd , vss_msg , sizeof ( struct hv_vss_msg ) ) ;
2013-03-15 12:30:06 -07:00
2015-04-11 18:07:57 -07:00
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 ;
}
2015-04-11 18:07:56 -07:00
if ( len ! = sizeof ( struct hv_vss_msg ) ) {
syslog ( LOG_ERR , " read failed; error:%d %s " ,
errno , strerror ( errno ) ) ;
2020-01-25 21:49:41 -08:00
goto reopen_vss_fd ;
2013-04-24 07:48:49 -07:00
}
2013-03-15 12:30:06 -07:00
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-25 21:52:04 -07:00
syslog ( LOG_INFO , " VSS: op=%s: %s \n " ,
op = = VSS_OP_FREEZE ? " FREEZE " : " THAW " ,
error ? " failed " : " succeeded " ) ;
if ( error ) {
2013-03-15 12:30:06 -07:00
error = HV_E_FAIL ;
2014-09-25 21:52:04 -07: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 12:30:06 -07:00
break ;
2016-09-02 05:58:25 -07:00
case VSS_OP_HOT_BACKUP :
syslog ( LOG_INFO , " VSS: op=CHECK HOT BACKUP \n " ) ;
break ;
2013-03-15 12:30:06 -07:00
default :
syslog ( LOG_ERR , " Illegal op:%d \n " , op ) ;
}
2020-01-25 21:49:41 -08:00
/*
* 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 .
*/
2013-03-15 12:30:06 -07:00
vss_msg - > error = error ;
2015-12-14 16:01:58 -08:00
len = write ( vss_fd , vss_msg , sizeof ( struct hv_vss_msg ) ) ;
2015-04-11 18:07:56 -07:00
if ( len ! = sizeof ( struct hv_vss_msg ) ) {
syslog ( LOG_ERR , " write failed; error: %d %s " , errno ,
strerror ( errno ) ) ;
2020-01-25 21:49:41 -08:00
goto reopen_vss_fd ;
2013-03-15 12:30:06 -07:00
}
}
2015-04-11 18:07:56 -07:00
close ( vss_fd ) ;
exit ( 0 ) ;
2013-03-15 12:30:06 -07:00
}