2015-03-01 16:39:31 +01:00
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd .
Copyright 2015 Lennart Poettering
systemd is free software ; you can redistribute it and / or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation ; either version 2.1 of the License , or
( at your option ) any later version .
systemd is distributed in the hope that it will be useful , but
WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
Lesser General Public License for more details .
You should have received a copy of the GNU Lesser General Public License
along with systemd ; If not , see < http : //www.gnu.org/licenses/>.
* * */
# include <sys/prctl.h>
# include <sys/vfs.h>
# include <sys/statvfs.h>
# include <sys/mount.h>
# include "util.h"
2015-04-10 19:10:00 +02:00
# include "process-util.h"
2015-05-04 22:12:46 +02:00
# include "lockfile-util.h"
2015-03-01 16:39:31 +01:00
# include "mkdir.h"
# include "btrfs-util.h"
# include "path-util.h"
2015-05-29 20:14:11 +02:00
# include "signal-util.h"
2015-03-01 16:39:31 +01:00
# include "machine-pool.h"
# define VAR_LIB_MACHINES_SIZE_START (1024UL*1024UL*500UL)
# define VAR_LIB_MACHINES_FREE_MIN (1024UL*1024UL*750UL)
static int check_btrfs ( void ) {
struct statfs sfs ;
if ( statfs ( " /var/lib/machines " , & sfs ) < 0 ) {
if ( errno ! = ENOENT )
return - errno ;
if ( statfs ( " /var/lib " , & sfs ) < 0 )
return - errno ;
}
return F_TYPE_EQUAL ( sfs . f_type , BTRFS_SUPER_MAGIC ) ;
}
2015-03-02 19:21:04 +01:00
static int setup_machine_raw ( uint64_t size , sd_bus_error * error ) {
2015-03-01 16:39:31 +01:00
_cleanup_free_ char * tmp = NULL ;
_cleanup_close_ int fd = - 1 ;
struct statvfs ss ;
pid_t pid = 0 ;
siginfo_t si ;
int r ;
/* We want to be able to make use of btrfs-specific file
* system features , in particular subvolumes , reflinks and
* quota . Hence , if we detect that / var / lib / machines . raw is
* not located on btrfs , let ' s create a loopback file , place a
* btrfs file system into it , and mount it to
* / var / lib / machines . */
fd = open ( " /var/lib/machines.raw " , O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOCTTY ) ;
if ( fd > = 0 ) {
r = fd ;
fd = - 1 ;
return r ;
}
if ( errno ! = ENOENT )
return sd_bus_error_set_errnof ( error , errno , " Failed to open /var/lib/machines.raw: %m " ) ;
2015-06-15 19:09:02 +02:00
r = tempfn_xxxxxx ( " /var/lib/machines.raw " , NULL , & tmp ) ;
2015-03-01 16:39:31 +01:00
if ( r < 0 )
return r ;
( void ) mkdir_p_label ( " /var/lib " , 0755 ) ;
fd = open ( tmp , O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_CLOEXEC , 0600 ) ;
if ( fd < 0 )
return sd_bus_error_set_errnof ( error , errno , " Failed to create /var/lib/machines.raw: %m " ) ;
if ( fstatvfs ( fd , & ss ) < 0 ) {
r = sd_bus_error_set_errnof ( error , errno , " Failed to determine free space on /var/lib/machines.raw: %m " ) ;
goto fail ;
}
if ( ss . f_bsize * ss . f_bavail < VAR_LIB_MACHINES_FREE_MIN ) {
r = sd_bus_error_setf ( error , SD_BUS_ERROR_FAILED , " Not enough free disk space to set up /var/lib/machines. " ) ;
goto fail ;
}
2015-03-02 19:21:04 +01:00
if ( ftruncate ( fd , size ) < 0 ) {
2015-03-01 16:39:31 +01:00
r = sd_bus_error_set_errnof ( error , errno , " Failed to enlarge /var/lib/machines.raw: %m " ) ;
goto fail ;
}
pid = fork ( ) ;
if ( pid < 0 ) {
r = sd_bus_error_set_errnof ( error , errno , " Failed to fork mkfs.btrfs: %m " ) ;
goto fail ;
}
if ( pid = = 0 ) {
/* Child */
2015-05-31 23:55:55 +02:00
( void ) reset_all_signal_handlers ( ) ;
( void ) reset_signal_mask ( ) ;
2015-03-01 16:39:31 +01:00
assert_se ( prctl ( PR_SET_PDEATHSIG , SIGTERM ) = = 0 ) ;
fd = safe_close ( fd ) ;
execlp ( " mkfs.btrfs " , " -Lvar-lib-machines " , tmp , NULL ) ;
if ( errno = = ENOENT )
return 99 ;
_exit ( EXIT_FAILURE ) ;
}
r = wait_for_terminate ( pid , & si ) ;
if ( r < 0 ) {
sd_bus_error_set_errnof ( error , r , " Failed to wait for mkfs.btrfs: %m " ) ;
goto fail ;
}
pid = 0 ;
if ( si . si_code ! = CLD_EXITED ) {
r = sd_bus_error_setf ( error , SD_BUS_ERROR_FAILED , " mkfs.btrfs died abnormally. " ) ;
goto fail ;
}
if ( si . si_status = = 99 ) {
r = sd_bus_error_set_errnof ( error , ENOENT , " Cannot set up /var/lib/machines, mkfs.btrfs is missing " ) ;
goto fail ;
}
if ( si . si_status ! = 0 ) {
r = sd_bus_error_setf ( error , SD_BUS_ERROR_FAILED , " mkfs.btrfs failed with error code %i " , si . si_status ) ;
goto fail ;
}
2015-03-10 18:15:52 +01:00
r = rename_noreplace ( AT_FDCWD , tmp , AT_FDCWD , " /var/lib/machines.raw " ) ;
if ( r < 0 ) {
sd_bus_error_set_errnof ( error , r , " Failed to move /var/lib/machines.raw into place: %m " ) ;
2015-03-01 16:39:31 +01:00
goto fail ;
}
r = fd ;
fd = - 1 ;
return r ;
fail :
2015-03-07 19:40:48 +01:00
unlink_noerrno ( tmp ) ;
2015-03-01 16:39:31 +01:00
if ( pid > 1 )
kill_and_sigcont ( pid , SIGKILL ) ;
return r ;
}
2015-03-02 19:21:04 +01:00
int setup_machine_directory ( uint64_t size , sd_bus_error * error ) {
2015-03-01 16:46:50 +01:00
_cleanup_release_lock_file_ LockFile lock_file = LOCK_FILE_INIT ;
2015-03-01 16:39:31 +01:00
struct loop_info64 info = {
. lo_flags = LO_FLAGS_AUTOCLEAR ,
} ;
_cleanup_close_ int fd = - 1 , control = - 1 , loop = - 1 ;
_cleanup_free_ char * loopdev = NULL ;
char tmpdir [ ] = " /tmp/import-mount.XXXXXX " , * mntdir = NULL ;
bool tmpdir_made = false , mntdir_made = false , mntdir_mounted = false ;
2015-03-03 00:13:12 +01:00
char buf [ FORMAT_BYTES_MAX ] ;
2015-03-01 16:39:31 +01:00
int r , nr = - 1 ;
2015-03-02 19:21:04 +01:00
/* btrfs cannot handle file systems < 16M, hence use this as minimum */
if ( size = = ( uint64_t ) - 1 )
size = VAR_LIB_MACHINES_SIZE_START ;
else if ( size < 16 * 1024 * 1024 )
size = 16 * 1024 * 1024 ;
2015-03-01 16:46:50 +01:00
/* Make sure we only set the directory up once at a time */
r = make_lock_file ( " /run/systemd/machines.lock " , LOCK_EX , & lock_file ) ;
if ( r < 0 )
return r ;
2015-03-01 16:39:31 +01:00
r = check_btrfs ( ) ;
if ( r < 0 )
return sd_bus_error_set_errnof ( error , r , " Failed to determine whether /var/lib/machines is located on btrfs: %m " ) ;
if ( r > 0 ) {
( void ) btrfs_subvol_make_label ( " /var/lib/machines " ) ;
r = btrfs_quota_enable ( " /var/lib/machines " , true ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to enable quota, ignoring: %m " ) ;
return 0 ;
}
2015-05-29 17:13:12 +02:00
if ( path_is_mount_point ( " /var/lib/machines " , AT_SYMLINK_FOLLOW ) > 0 | |
2015-03-01 16:39:31 +01:00
dir_is_empty ( " /var/lib/machines " ) = = 0 )
return sd_bus_error_setf ( error , SD_BUS_ERROR_INVALID_ARGS , " /var/lib/machines is not a btrfs file system. Operation is not supported on legacy file systems. " ) ;
2015-03-02 19:21:04 +01:00
fd = setup_machine_raw ( size , error ) ;
2015-03-01 16:39:31 +01:00
if ( fd < 0 )
return fd ;
control = open ( " /dev/loop-control " , O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK ) ;
if ( control < 0 )
return sd_bus_error_set_errnof ( error , errno , " Failed to open /dev/loop-control: %m " ) ;
nr = ioctl ( control , LOOP_CTL_GET_FREE ) ;
if ( nr < 0 )
return sd_bus_error_set_errnof ( error , errno , " Failed to allocate loop device: %m " ) ;
if ( asprintf ( & loopdev , " /dev/loop%i " , nr ) < 0 ) {
r = - ENOMEM ;
goto fail ;
}
loop = open ( loopdev , O_CLOEXEC | O_RDWR | O_NOCTTY | O_NONBLOCK ) ;
if ( loop < 0 ) {
r = sd_bus_error_set_errnof ( error , errno , " Failed to open loopback device: %m " ) ;
goto fail ;
}
if ( ioctl ( loop , LOOP_SET_FD , fd ) < 0 ) {
r = sd_bus_error_set_errnof ( error , errno , " Failed to bind loopback device: %m " ) ;
goto fail ;
}
if ( ioctl ( loop , LOOP_SET_STATUS64 , & info ) < 0 ) {
r = sd_bus_error_set_errnof ( error , errno , " Failed to enable auto-clear for loopback device: %m " ) ;
goto fail ;
}
/* We need to make sure the new /var/lib/machines directory
* has an access mode of 0700 at the time it is first made
* available . mkfs will create it with 0755 however . Hence ,
* let ' s mount the directory into an inaccessible directory
* below / tmp first , fix the access mode , and move it to the
* public place then . */
if ( ! mkdtemp ( tmpdir ) ) {
r = sd_bus_error_set_errnof ( error , errno , " Failed to create temporary mount parent directory: %m " ) ;
goto fail ;
}
tmpdir_made = true ;
mntdir = strjoina ( tmpdir , " /mnt " ) ;
if ( mkdir ( mntdir , 0700 ) < 0 ) {
r = sd_bus_error_set_errnof ( error , errno , " Failed to create temporary mount directory: %m " ) ;
goto fail ;
}
mntdir_made = true ;
if ( mount ( loopdev , mntdir , " btrfs " , 0 , NULL ) < 0 ) {
r = sd_bus_error_set_errnof ( error , errno , " Failed to mount loopback device: %m " ) ;
goto fail ;
}
mntdir_mounted = true ;
r = btrfs_quota_enable ( mntdir , true ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to enable quota, ignoring: %m " ) ;
if ( chmod ( mntdir , 0700 ) < 0 ) {
r = sd_bus_error_set_errnof ( error , errno , " Failed to fix owner: %m " ) ;
goto fail ;
}
( void ) mkdir_p_label ( " /var/lib/machines " , 0700 ) ;
if ( mount ( mntdir , " /var/lib/machines " , NULL , MS_BIND , NULL ) < 0 ) {
r = sd_bus_error_set_errnof ( error , errno , " Failed to mount directory into right place: %m " ) ;
goto fail ;
}
2015-03-03 00:13:12 +01:00
( void ) syncfs ( fd ) ;
log_info ( " Set up /var/lib/machines as btrfs loopback file system of size %s mounted on /var/lib/machines.raw. " , format_bytes ( buf , sizeof ( buf ) , size ) ) ;
2015-03-01 16:39:31 +01:00
( void ) umount2 ( mntdir , MNT_DETACH ) ;
( void ) rmdir ( mntdir ) ;
( void ) rmdir ( tmpdir ) ;
return 0 ;
fail :
if ( mntdir_mounted )
( void ) umount2 ( mntdir , MNT_DETACH ) ;
if ( mntdir_made )
( void ) rmdir ( mntdir ) ;
if ( tmpdir_made )
( void ) rmdir ( tmpdir ) ;
if ( loop > = 0 ) {
( void ) ioctl ( loop , LOOP_CLR_FD ) ;
loop = safe_close ( loop ) ;
}
if ( control > = 0 & & nr > = 0 )
( void ) ioctl ( control , LOOP_CTL_REMOVE , nr ) ;
return r ;
}
2015-03-03 00:13:12 +01:00
static int sync_path ( const char * p ) {
_cleanup_close_ int fd = - 1 ;
fd = open ( p , O_RDONLY | O_CLOEXEC | O_NOCTTY ) ;
if ( fd < 0 )
return - errno ;
if ( syncfs ( fd ) < 0 )
return - errno ;
return 0 ;
}
int grow_machine_directory ( void ) {
char buf [ FORMAT_BYTES_MAX ] ;
struct statvfs a , b ;
uint64_t old_size , new_size , max_add ;
int r ;
/* Ensure the disk space data is accurate */
sync_path ( " /var/lib/machines " ) ;
sync_path ( " /var/lib/machines.raw " ) ;
if ( statvfs ( " /var/lib/machines.raw " , & a ) < 0 )
return - errno ;
if ( statvfs ( " /var/lib/machines " , & b ) < 0 )
return - errno ;
/* Don't grow if not enough disk space is available on the host */
if ( ( ( uint64_t ) a . f_bavail * ( uint64_t ) a . f_bsize ) < = VAR_LIB_MACHINES_FREE_MIN )
return 0 ;
/* Don't grow if at least 1/3th of the fs is still free */
if ( b . f_bavail > b . f_blocks / 3 )
return 0 ;
/* Calculate how much we are willing to add at maximum */
max_add = ( ( uint64_t ) a . f_bavail * ( uint64_t ) a . f_bsize ) - VAR_LIB_MACHINES_FREE_MIN ;
/* Calculate the old size */
old_size = ( uint64_t ) b . f_blocks * ( uint64_t ) b . f_bsize ;
/* Calculate the new size as three times the size of what is used right now */
new_size = ( ( uint64_t ) b . f_blocks - ( uint64_t ) b . f_bavail ) * ( uint64_t ) b . f_bsize * 3 ;
/* Always, grow at least to the start size */
if ( new_size < VAR_LIB_MACHINES_SIZE_START )
new_size = VAR_LIB_MACHINES_SIZE_START ;
/* If the new size is smaller than the old size, don't grow */
if ( new_size < old_size )
return 0 ;
/* Ensure we never add more than the maximum */
if ( new_size > old_size + max_add )
new_size = old_size + max_add ;
r = btrfs_resize_loopback ( " /var/lib/machines " , new_size , true ) ;
if ( r < = 0 )
return r ;
r = btrfs_quota_limit ( " /var/lib/machines " , new_size ) ;
if ( r < 0 )
return r ;
log_info ( " Grew /var/lib/machines btrfs loopback file system to %s. " , format_bytes ( buf , sizeof ( buf ) , new_size ) ) ;
return 1 ;
}