2014-12-12 05:15:58 +03:00
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd .
Copyright 2014 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 <stdlib.h>
# include <sys/vfs.h>
# include <sys/stat.h>
# ifdef HAVE_LINUX_BTRFS_H
# include <linux/btrfs.h>
# endif
# include "missing.h"
# include "util.h"
# include "path-util.h"
# include "macro.h"
# include "copy.h"
2014-12-27 20:46:36 +03:00
# include "selinux-util.h"
# include "smack-util.h"
2015-02-26 02:40:43 +03:00
# include "fileio.h"
2014-12-25 05:19:19 +03:00
# include "btrfs-ctree.h"
2014-12-12 05:15:58 +03:00
# include "btrfs-util.h"
static int validate_subvolume_name ( const char * name ) {
if ( ! filename_is_valid ( name ) )
return - EINVAL ;
if ( strlen ( name ) > BTRFS_SUBVOL_NAME_MAX )
return - E2BIG ;
return 0 ;
}
static int open_parent ( const char * path , int flags ) {
_cleanup_free_ char * parent = NULL ;
int r , fd ;
assert ( path ) ;
r = path_get_parent ( path , & parent ) ;
if ( r < 0 )
return r ;
fd = open ( parent , flags ) ;
if ( fd < 0 )
return - errno ;
return fd ;
}
static int extract_subvolume_name ( const char * path , const char * * subvolume ) {
const char * fn ;
int r ;
assert ( path ) ;
assert ( subvolume ) ;
fn = basename ( path ) ;
r = validate_subvolume_name ( fn ) ;
if ( r < 0 )
return r ;
* subvolume = fn ;
return 0 ;
}
int btrfs_is_snapshot ( int fd ) {
struct stat st ;
struct statfs sfs ;
2014-12-19 20:42:50 +03:00
/* On btrfs subvolumes always have the inode 256 */
if ( fstat ( fd , & st ) < 0 )
2014-12-12 05:15:58 +03:00
return - errno ;
2014-12-19 20:42:50 +03:00
if ( ! S_ISDIR ( st . st_mode ) | | st . st_ino ! = 256 )
2014-12-12 05:15:58 +03:00
return 0 ;
2014-12-19 20:42:50 +03:00
if ( fstatfs ( fd , & sfs ) < 0 )
2014-12-12 05:15:58 +03:00
return - errno ;
2014-12-19 20:42:50 +03:00
return F_TYPE_EQUAL ( sfs . f_type , BTRFS_SUPER_MAGIC ) ;
2014-12-12 05:15:58 +03:00
}
int btrfs_subvol_make ( const char * path ) {
struct btrfs_ioctl_vol_args args = { } ;
_cleanup_close_ int fd = - 1 ;
const char * subvolume ;
int r ;
assert ( path ) ;
r = extract_subvolume_name ( path , & subvolume ) ;
if ( r < 0 )
return r ;
fd = open_parent ( path , O_RDONLY | O_NOCTTY | O_CLOEXEC | O_DIRECTORY ) ;
if ( fd < 0 )
return fd ;
strncpy ( args . name , subvolume , sizeof ( args . name ) - 1 ) ;
if ( ioctl ( fd , BTRFS_IOC_SUBVOL_CREATE , & args ) < 0 )
return - errno ;
return 0 ;
}
2014-12-27 20:46:36 +03:00
int btrfs_subvol_make_label ( const char * path ) {
int r ;
assert ( path ) ;
r = mac_selinux_create_file_prepare ( path , S_IFDIR ) ;
if ( r < 0 )
return r ;
r = btrfs_subvol_make ( path ) ;
mac_selinux_create_file_clear ( ) ;
if ( r < 0 )
return r ;
return mac_smack_fix ( path , false , false ) ;
}
2015-01-20 05:00:07 +03:00
int btrfs_subvol_set_read_only_fd ( int fd , bool b ) {
2014-12-12 05:15:58 +03:00
uint64_t flags , nflags ;
2015-01-20 05:00:07 +03:00
struct stat st ;
2014-12-12 05:15:58 +03:00
2015-01-20 05:00:07 +03:00
assert ( fd > = 0 ) ;
if ( fstat ( fd , & st ) < 0 )
2014-12-12 05:15:58 +03:00
return - errno ;
2015-01-20 05:00:07 +03:00
if ( ! S_ISDIR ( st . st_mode ) | | st . st_ino ! = 256 )
return - EINVAL ;
2014-12-12 05:15:58 +03:00
if ( ioctl ( fd , BTRFS_IOC_SUBVOL_GETFLAGS , & flags ) < 0 )
return - errno ;
if ( b )
nflags = flags | BTRFS_SUBVOL_RDONLY ;
else
nflags = flags & ~ BTRFS_SUBVOL_RDONLY ;
if ( flags = = nflags )
return 0 ;
if ( ioctl ( fd , BTRFS_IOC_SUBVOL_SETFLAGS , & nflags ) < 0 )
return - errno ;
return 0 ;
}
2015-01-20 05:00:07 +03:00
int btrfs_subvol_set_read_only ( const char * path , bool b ) {
_cleanup_close_ int fd = - 1 ;
fd = open ( path , O_RDONLY | O_NOCTTY | O_CLOEXEC | O_DIRECTORY ) ;
if ( fd < 0 )
return - errno ;
return btrfs_subvol_set_read_only_fd ( fd , b ) ;
}
2014-12-25 05:19:19 +03:00
int btrfs_subvol_get_read_only_fd ( int fd ) {
2014-12-19 20:42:50 +03:00
uint64_t flags ;
if ( ioctl ( fd , BTRFS_IOC_SUBVOL_GETFLAGS , & flags ) < 0 )
return - errno ;
return ! ! ( flags & BTRFS_SUBVOL_RDONLY ) ;
}
2014-12-12 05:15:58 +03:00
int btrfs_reflink ( int infd , int outfd ) {
int r ;
assert ( infd > = 0 ) ;
assert ( outfd > = 0 ) ;
r = ioctl ( outfd , BTRFS_IOC_CLONE , infd ) ;
if ( r < 0 )
return - errno ;
return 0 ;
}
2015-01-17 20:11:45 +03:00
int btrfs_clone_range ( int infd , uint64_t in_offset , int outfd , uint64_t out_offset , uint64_t sz ) {
struct btrfs_ioctl_clone_range_args args = {
. src_fd = infd ,
. src_offset = in_offset ,
. src_length = sz ,
. dest_offset = out_offset ,
} ;
int r ;
assert ( infd > = 0 ) ;
assert ( outfd > = 0 ) ;
assert ( sz > 0 ) ;
r = ioctl ( outfd , BTRFS_IOC_CLONE_RANGE , & args ) ;
if ( r < 0 )
return - errno ;
return 0 ;
}
2015-02-26 02:40:43 +03:00
int btrfs_get_block_device_fd ( int fd , dev_t * dev ) {
2014-12-12 05:15:58 +03:00
struct btrfs_ioctl_fs_info_args fsi = { } ;
uint64_t id ;
2015-02-26 02:40:43 +03:00
assert ( fd > = 0 ) ;
2014-12-12 05:15:58 +03:00
assert ( dev ) ;
if ( ioctl ( fd , BTRFS_IOC_FS_INFO , & fsi ) < 0 )
return - errno ;
/* We won't do this for btrfs RAID */
if ( fsi . num_devices ! = 1 )
return 0 ;
for ( id = 1 ; id < = fsi . max_id ; id + + ) {
struct btrfs_ioctl_dev_info_args di = {
. devid = id ,
} ;
struct stat st ;
if ( ioctl ( fd , BTRFS_IOC_DEV_INFO , & di ) < 0 ) {
if ( errno = = ENODEV )
continue ;
return - errno ;
}
if ( stat ( ( char * ) di . path , & st ) < 0 )
return - errno ;
if ( ! S_ISBLK ( st . st_mode ) )
return - ENODEV ;
if ( major ( st . st_rdev ) = = 0 )
return - ENODEV ;
* dev = st . st_rdev ;
return 1 ;
}
return - ENODEV ;
}
2014-12-25 05:19:19 +03:00
2015-02-26 02:40:43 +03:00
int btrfs_get_block_device ( const char * path , dev_t * dev ) {
_cleanup_close_ int fd = - 1 ;
assert ( path ) ;
assert ( dev ) ;
fd = open ( path , O_RDONLY | O_NOCTTY | O_CLOEXEC ) ;
if ( fd < 0 )
return - errno ;
return btrfs_get_block_device_fd ( fd , dev ) ;
}
2014-12-25 05:19:19 +03:00
int btrfs_subvol_get_id_fd ( int fd , uint64_t * ret ) {
struct btrfs_ioctl_ino_lookup_args args = {
. objectid = BTRFS_FIRST_FREE_OBJECTID
} ;
assert ( fd > = 0 ) ;
assert ( ret ) ;
if ( ioctl ( fd , BTRFS_IOC_INO_LOOKUP , & args ) < 0 )
return - errno ;
* ret = args . treeid ;
return 0 ;
}
2015-01-07 16:43:10 +03:00
static bool btrfs_ioctl_search_args_inc ( struct btrfs_ioctl_search_args * args ) {
assert ( args ) ;
/* the objectid, type, offset together make up the btrfs key,
* which is considered a single 136 byte integer when
* comparing . This call increases the counter by one , dealing
* with the overflow between the overflows */
if ( args - > key . min_offset < ( uint64_t ) - 1 ) {
args - > key . min_offset + + ;
return true ;
}
if ( args - > key . min_type < ( uint8_t ) - 1 ) {
args - > key . min_type + + ;
args - > key . min_offset = 0 ;
return true ;
}
if ( args - > key . min_objectid < ( uint64_t ) - 1 ) {
args - > key . min_objectid + + ;
args - > key . min_offset = 0 ;
args - > key . min_type = 0 ;
return true ;
}
return 0 ;
}
static void btrfs_ioctl_search_args_set ( struct btrfs_ioctl_search_args * args , const struct btrfs_ioctl_search_header * h ) {
assert ( args ) ;
assert ( h ) ;
args - > key . min_objectid = h - > objectid ;
args - > key . min_type = h - > type ;
args - > key . min_offset = h - > offset ;
}
static int btrfs_ioctl_search_args_compare ( const struct btrfs_ioctl_search_args * args ) {
assert ( args ) ;
/* Compare min and max */
if ( args - > key . min_objectid < args - > key . max_objectid )
return - 1 ;
if ( args - > key . min_objectid > args - > key . max_objectid )
return 1 ;
if ( args - > key . min_type < args - > key . max_type )
return - 1 ;
if ( args - > key . min_type > args - > key . max_type )
return 1 ;
if ( args - > key . min_offset < args - > key . max_offset )
return - 1 ;
if ( args - > key . min_offset > args - > key . max_offset )
return 1 ;
return 0 ;
}
# define FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) \
for ( ( i ) = 0 , \
( sh ) = ( const struct btrfs_ioctl_search_header * ) ( args ) . buf ; \
( i ) < ( args ) . key . nr_items ; \
( i ) + + , \
( sh ) = ( const struct btrfs_ioctl_search_header * ) ( ( uint8_t * ) ( sh ) + sizeof ( struct btrfs_ioctl_search_header ) + ( sh ) - > len ) )
# define BTRFS_IOCTL_SEARCH_HEADER_BODY(sh) \
( ( void * ) ( ( uint8_t * ) sh + sizeof ( struct btrfs_ioctl_search_header ) ) )
2014-12-25 05:19:19 +03:00
int btrfs_subvol_get_info_fd ( int fd , BtrfsSubvolInfo * ret ) {
struct btrfs_ioctl_search_args args = {
/* Tree of tree roots */
2014-12-28 04:05:28 +03:00
. key . tree_id = BTRFS_ROOT_TREE_OBJECTID ,
2014-12-25 05:19:19 +03:00
/* Look precisely for the subvolume items */
. key . min_type = BTRFS_ROOT_ITEM_KEY ,
. key . max_type = BTRFS_ROOT_ITEM_KEY ,
. key . min_offset = 0 ,
. key . max_offset = ( uint64_t ) - 1 ,
2015-01-07 16:43:10 +03:00
/* No restrictions on the other components */
2014-12-25 05:19:19 +03:00
. key . min_transid = 0 ,
. key . max_transid = ( uint64_t ) - 1 ,
} ;
uint64_t subvol_id ;
2014-12-28 04:05:28 +03:00
bool found = false ;
2014-12-25 05:19:19 +03:00
int r ;
assert ( fd > = 0 ) ;
assert ( ret ) ;
r = btrfs_subvol_get_id_fd ( fd , & subvol_id ) ;
if ( r < 0 )
return r ;
args . key . min_objectid = args . key . max_objectid = subvol_id ;
2015-01-07 16:43:10 +03:00
while ( btrfs_ioctl_search_args_compare ( & args ) < = 0 ) {
2014-12-28 04:05:28 +03:00
const struct btrfs_ioctl_search_header * sh ;
unsigned i ;
args . key . nr_items = 256 ;
if ( ioctl ( fd , BTRFS_IOC_TREE_SEARCH , & args ) < 0 )
return - errno ;
if ( args . key . nr_items < = 0 )
break ;
2014-12-25 05:19:19 +03:00
2015-01-07 16:43:10 +03:00
FOREACH_BTRFS_IOCTL_SEARCH_HEADER ( i , sh , args ) {
2014-12-25 05:19:19 +03:00
2014-12-28 04:05:28 +03:00
const struct btrfs_root_item * ri ;
2015-01-07 16:43:10 +03:00
/* Make sure we start the next search at least from this entry */
btrfs_ioctl_search_args_set ( & args , sh ) ;
2014-12-28 04:05:28 +03:00
if ( sh - > objectid ! = subvol_id )
continue ;
if ( sh - > type ! = BTRFS_ROOT_ITEM_KEY )
continue ;
2015-01-07 16:43:10 +03:00
/* Older versions of the struct lacked the otime setting */
2014-12-28 04:05:28 +03:00
if ( sh - > len < offsetof ( struct btrfs_root_item , otime ) + sizeof ( struct btrfs_timespec ) )
continue ;
2014-12-25 05:19:19 +03:00
2015-01-07 16:43:10 +03:00
ri = BTRFS_IOCTL_SEARCH_HEADER_BODY ( sh ) ;
2014-12-25 05:19:19 +03:00
2014-12-28 04:05:28 +03:00
ret - > otime = ( usec_t ) le64toh ( ri - > otime . sec ) * USEC_PER_SEC +
( usec_t ) le32toh ( ri - > otime . nsec ) / NSEC_PER_USEC ;
2014-12-25 05:19:19 +03:00
2014-12-28 04:05:28 +03:00
ret - > subvol_id = subvol_id ;
ret - > read_only = ! ! ( le64toh ( ri - > flags ) & BTRFS_ROOT_SUBVOL_RDONLY ) ;
2014-12-25 05:19:19 +03:00
2014-12-28 04:05:28 +03:00
assert_cc ( sizeof ( ri - > uuid ) = = sizeof ( ret - > uuid ) ) ;
memcpy ( & ret - > uuid , ri - > uuid , sizeof ( ret - > uuid ) ) ;
memcpy ( & ret - > parent_uuid , ri - > parent_uuid , sizeof ( ret - > parent_uuid ) ) ;
found = true ;
goto finish ;
}
2015-01-07 16:43:10 +03:00
/* Increase search key by one, to read the next item, if we can. */
if ( ! btrfs_ioctl_search_args_inc ( & args ) )
2014-12-28 04:05:28 +03:00
break ;
}
finish :
if ( ! found )
return - ENODATA ;
return 0 ;
}
int btrfs_subvol_get_quota_fd ( int fd , BtrfsQuotaInfo * ret ) {
struct btrfs_ioctl_search_args args = {
/* Tree of quota items */
. key . tree_id = BTRFS_QUOTA_TREE_OBJECTID ,
2015-01-07 16:43:10 +03:00
/* The object ID is always 0 */
. key . min_objectid = 0 ,
. key . max_objectid = 0 ,
2014-12-28 04:05:28 +03:00
/* Look precisely for the quota items */
. key . min_type = BTRFS_QGROUP_STATUS_KEY ,
. key . max_type = BTRFS_QGROUP_LIMIT_KEY ,
/* No restrictions on the other components */
. key . min_transid = 0 ,
. key . max_transid = ( uint64_t ) - 1 ,
} ;
uint64_t subvol_id ;
bool found_info = false , found_limit = false ;
int r ;
assert ( fd > = 0 ) ;
assert ( ret ) ;
r = btrfs_subvol_get_id_fd ( fd , & subvol_id ) ;
if ( r < 0 )
return r ;
args . key . min_offset = args . key . max_offset = subvol_id ;
2015-01-07 16:43:10 +03:00
while ( btrfs_ioctl_search_args_compare ( & args ) < = 0 ) {
2014-12-28 04:05:28 +03:00
const struct btrfs_ioctl_search_header * sh ;
unsigned i ;
args . key . nr_items = 256 ;
if ( ioctl ( fd , BTRFS_IOC_TREE_SEARCH , & args ) < 0 )
return - errno ;
if ( args . key . nr_items < = 0 )
break ;
2015-01-07 16:43:10 +03:00
FOREACH_BTRFS_IOCTL_SEARCH_HEADER ( i , sh , args ) {
2014-12-28 04:05:28 +03:00
2015-01-07 16:43:10 +03:00
/* Make sure we start the next search at least from this entry */
btrfs_ioctl_search_args_set ( & args , sh ) ;
2014-12-28 04:05:28 +03:00
if ( sh - > objectid ! = 0 )
continue ;
if ( sh - > offset ! = subvol_id )
continue ;
if ( sh - > type = = BTRFS_QGROUP_INFO_KEY ) {
2015-01-07 16:43:10 +03:00
const struct btrfs_qgroup_info_item * qii = BTRFS_IOCTL_SEARCH_HEADER_BODY ( sh ) ;
2014-12-28 04:05:28 +03:00
2015-03-10 17:55:58 +03:00
ret - > referenced = le64toh ( qii - > rfer ) ;
2014-12-28 04:05:28 +03:00
ret - > exclusive = le64toh ( qii - > excl ) ;
found_info = true ;
} else if ( sh - > type = = BTRFS_QGROUP_LIMIT_KEY ) {
2015-01-07 16:43:10 +03:00
const struct btrfs_qgroup_limit_item * qli = BTRFS_IOCTL_SEARCH_HEADER_BODY ( sh ) ;
2014-12-28 04:05:28 +03:00
2015-03-10 17:55:58 +03:00
ret - > referenced_max = le64toh ( qli - > max_rfer ) ;
2014-12-28 04:05:28 +03:00
ret - > exclusive_max = le64toh ( qli - > max_excl ) ;
2015-03-10 17:55:58 +03:00
if ( ret - > referenced_max = = 0 )
ret - > referenced_max = ( uint64_t ) - 1 ;
2014-12-28 04:05:28 +03:00
if ( ret - > exclusive_max = = 0 )
ret - > exclusive_max = ( uint64_t ) - 1 ;
found_limit = true ;
}
if ( found_info & & found_limit )
goto finish ;
}
2015-01-07 16:43:10 +03:00
/* Increase search key by one, to read the next item, if we can. */
if ( ! btrfs_ioctl_search_args_inc ( & args ) )
2014-12-28 04:05:28 +03:00
break ;
}
finish :
if ( ! found_limit & & ! found_info )
return - ENODATA ;
if ( ! found_info ) {
2015-03-10 17:55:58 +03:00
ret - > referenced = ( uint64_t ) - 1 ;
2014-12-28 04:05:28 +03:00
ret - > exclusive = ( uint64_t ) - 1 ;
}
if ( ! found_limit ) {
2015-03-10 17:55:58 +03:00
ret - > referenced_max = ( uint64_t ) - 1 ;
2014-12-28 04:05:28 +03:00
ret - > exclusive_max = ( uint64_t ) - 1 ;
}
2014-12-25 05:19:19 +03:00
return 0 ;
}
2015-01-06 21:51:03 +03:00
int btrfs_defrag_fd ( int fd ) {
assert ( fd > = 0 ) ;
if ( ioctl ( fd , BTRFS_IOC_DEFRAG , NULL ) < 0 )
return - errno ;
return 0 ;
}
int btrfs_defrag ( const char * p ) {
_cleanup_close_ int fd = - 1 ;
fd = open ( p , O_RDWR | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW ) ;
if ( fd < 0 )
return - errno ;
return btrfs_defrag_fd ( fd ) ;
}
2015-02-24 20:43:37 +03:00
int btrfs_quota_enable_fd ( int fd , bool b ) {
struct btrfs_ioctl_quota_ctl_args args = {
. cmd = b ? BTRFS_QUOTA_CTL_ENABLE : BTRFS_QUOTA_CTL_DISABLE ,
} ;
assert ( fd > = 0 ) ;
if ( ioctl ( fd , BTRFS_IOC_QUOTA_CTL , & args ) < 0 )
return - errno ;
return 0 ;
}
int btrfs_quota_enable ( const char * path , bool b ) {
_cleanup_close_ int fd = - 1 ;
fd = open ( path , O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW ) ;
if ( fd < 0 )
return - errno ;
return btrfs_quota_enable_fd ( fd , b ) ;
}
2015-02-25 01:50:37 +03:00
2015-03-10 17:55:58 +03:00
int btrfs_quota_limit_fd ( int fd , uint64_t referenced_max ) {
2015-02-25 01:50:37 +03:00
struct btrfs_ioctl_qgroup_limit_args args = {
. lim . max_rfer =
2015-03-10 17:55:58 +03:00
referenced_max = = ( uint64_t ) - 1 ? 0 :
referenced_max = = 0 ? 1 : referenced_max ,
2015-02-25 01:50:37 +03:00
. lim . flags = BTRFS_QGROUP_LIMIT_MAX_RFER ,
} ;
assert ( fd > = 0 ) ;
if ( ioctl ( fd , BTRFS_IOC_QGROUP_LIMIT , & args ) < 0 )
return - errno ;
return 0 ;
}
2015-03-10 17:55:58 +03:00
int btrfs_quota_limit ( const char * path , uint64_t referenced_max ) {
2015-02-25 01:50:37 +03:00
_cleanup_close_ int fd = - 1 ;
fd = open ( path , O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW ) ;
if ( fd < 0 )
return - errno ;
2015-03-10 17:55:58 +03:00
return btrfs_quota_limit_fd ( fd , referenced_max ) ;
2015-02-25 01:50:37 +03:00
}
2015-02-26 02:40:43 +03:00
2015-03-03 02:13:12 +03:00
int btrfs_resize_loopback_fd ( int fd , uint64_t new_size , bool grow_only ) {
2015-02-26 02:40:43 +03:00
struct btrfs_ioctl_vol_args args = { } ;
_cleanup_free_ char * p = NULL , * loop = NULL , * backing = NULL ;
_cleanup_close_ int loop_fd = - 1 , backing_fd = - 1 ;
struct stat st ;
2015-03-27 14:02:49 +03:00
dev_t dev = 0 ;
2015-02-26 02:40:43 +03:00
int r ;
/* btrfs cannot handle file systems < 16M, hence use this as minimum */
if ( new_size < 16 * 1024 * 1024 )
new_size = 16 * 1024 * 1024 ;
r = btrfs_get_block_device_fd ( fd , & dev ) ;
if ( r < 0 )
return r ;
if ( r = = 0 )
return - ENODEV ;
if ( asprintf ( & p , " /sys/dev/block/%u:%u/loop/backing_file " , major ( dev ) , minor ( dev ) ) < 0 )
return - ENOMEM ;
r = read_one_line_file ( p , & backing ) ;
2015-03-03 02:13:12 +03:00
if ( r = = - ENOENT )
return - ENODEV ;
2015-02-26 02:40:43 +03:00
if ( r < 0 )
return r ;
if ( isempty ( backing ) | | ! path_is_absolute ( backing ) )
return - ENODEV ;
backing_fd = open ( backing , O_RDWR | O_CLOEXEC | O_NOCTTY ) ;
if ( backing_fd < 0 )
return - errno ;
if ( fstat ( backing_fd , & st ) < 0 )
return - errno ;
if ( ! S_ISREG ( st . st_mode ) )
return - ENODEV ;
if ( new_size = = ( uint64_t ) st . st_size )
return 0 ;
2015-03-03 02:13:12 +03:00
if ( grow_only & & new_size < ( uint64_t ) st . st_size )
return - EINVAL ;
2015-02-26 02:40:43 +03:00
if ( asprintf ( & loop , " /dev/block/%u:%u " , major ( dev ) , minor ( dev ) ) < 0 )
return - ENOMEM ;
loop_fd = open ( loop , O_RDWR | O_CLOEXEC | O_NOCTTY ) ;
if ( loop_fd < 0 )
return - errno ;
if ( snprintf ( args . name , sizeof ( args . name ) , " % " PRIu64 , new_size ) > = ( int ) sizeof ( args . name ) )
return - EINVAL ;
if ( new_size < ( uint64_t ) st . st_size ) {
/* Decrease size: first decrease btrfs size, then shorten loopback */
if ( ioctl ( fd , BTRFS_IOC_RESIZE , & args ) < 0 )
return - errno ;
}
if ( ftruncate ( backing_fd , new_size ) < 0 )
return - errno ;
if ( ioctl ( loop_fd , LOOP_SET_CAPACITY , 0 ) < 0 )
return - errno ;
if ( new_size > ( uint64_t ) st . st_size ) {
/* Increase size: first enlarge loopback, then increase btrfs size */
if ( ioctl ( fd , BTRFS_IOC_RESIZE , & args ) < 0 )
return - errno ;
}
2015-03-03 02:13:12 +03:00
/* Make sure the free disk space is correctly updated for both file systems */
( void ) fsync ( fd ) ;
( void ) fsync ( backing_fd ) ;
return 1 ;
2015-02-26 02:40:43 +03:00
}
2015-03-03 02:13:12 +03:00
int btrfs_resize_loopback ( const char * p , uint64_t new_size , bool grow_only ) {
2015-02-26 02:40:43 +03:00
_cleanup_close_ int fd = - 1 ;
fd = open ( p , O_RDONLY | O_NOCTTY | O_CLOEXEC ) ;
if ( fd < 0 )
return - errno ;
2015-03-03 02:13:12 +03:00
return btrfs_resize_loopback_fd ( fd , new_size , grow_only ) ;
2015-02-26 02:40:43 +03:00
}
2015-04-06 11:57:17 +03:00
static int subvol_remove_children ( int fd , const char * subvolume , uint64_t subvol_id , bool recursive ) {
struct btrfs_ioctl_search_args args = {
. key . tree_id = BTRFS_ROOT_TREE_OBJECTID ,
. key . min_objectid = BTRFS_FIRST_FREE_OBJECTID ,
. key . max_objectid = BTRFS_LAST_FREE_OBJECTID ,
. key . min_type = BTRFS_ROOT_BACKREF_KEY ,
. key . max_type = BTRFS_ROOT_BACKREF_KEY ,
. key . min_transid = 0 ,
. key . max_transid = ( uint64_t ) - 1 ,
} ;
struct btrfs_ioctl_vol_args vol_args = { } ;
_cleanup_close_ int subvol_fd = - 1 ;
int r ;
assert ( fd > = 0 ) ;
assert ( subvolume ) ;
/* First, try to remove the subvolume. If it happens to be
* already empty , this will just work . */
strncpy ( vol_args . name , subvolume , sizeof ( vol_args . name ) - 1 ) ;
if ( ioctl ( fd , BTRFS_IOC_SNAP_DESTROY , & vol_args ) > = 0 )
return 0 ;
if ( ! recursive | | errno ! = ENOTEMPTY )
return - errno ;
/* OK, the subvolume is not empty, let's look for child
* subvolumes , and remove them , first */
subvol_fd = openat ( fd , subvolume , O_RDONLY | O_NOCTTY | O_CLOEXEC | O_DIRECTORY ) ;
if ( subvol_fd < 0 )
return - errno ;
if ( subvol_id = = 0 ) {
r = btrfs_subvol_get_id_fd ( subvol_fd , & subvol_id ) ;
if ( r < 0 )
return r ;
}
args . key . min_offset = args . key . max_offset = subvol_id ;
while ( btrfs_ioctl_search_args_compare ( & args ) < = 0 ) {
const struct btrfs_ioctl_search_header * sh ;
unsigned i ;
args . key . nr_items = 256 ;
if ( ioctl ( fd , BTRFS_IOC_TREE_SEARCH , & args ) < 0 )
return - errno ;
if ( args . key . nr_items < = 0 )
break ;
FOREACH_BTRFS_IOCTL_SEARCH_HEADER ( i , sh , args ) {
_cleanup_free_ char * p = NULL ;
const struct btrfs_root_ref * ref ;
struct btrfs_ioctl_ino_lookup_args ino_args ;
btrfs_ioctl_search_args_set ( & args , sh ) ;
if ( sh - > type ! = BTRFS_ROOT_BACKREF_KEY )
continue ;
if ( sh - > offset ! = subvol_id )
continue ;
ref = BTRFS_IOCTL_SEARCH_HEADER_BODY ( sh ) ;
p = strndup ( ( char * ) ref + sizeof ( struct btrfs_root_ref ) , le64toh ( ref - > name_len ) ) ;
if ( ! p )
return - ENOMEM ;
zero ( ino_args ) ;
ino_args . treeid = subvol_id ;
2015-04-06 15:55:45 +03:00
ino_args . objectid = htole64 ( ref - > dirid ) ;
2015-04-06 11:57:17 +03:00
if ( ioctl ( fd , BTRFS_IOC_INO_LOOKUP , & ino_args ) < 0 )
return - errno ;
if ( isempty ( ino_args . name ) )
/* Subvolume is in the top-level
* directory of the subvolume . */
r = subvol_remove_children ( subvol_fd , p , sh - > objectid , recursive ) ;
else {
_cleanup_close_ int child_fd = - 1 ;
/* Subvolume is somewhere further down,
* hence we need to open the
* containing directory first */
child_fd = openat ( subvol_fd , ino_args . name , O_RDONLY | O_NOCTTY | O_CLOEXEC | O_DIRECTORY ) ;
if ( child_fd < 0 )
return - errno ;
r = subvol_remove_children ( child_fd , p , sh - > objectid , recursive ) ;
}
if ( r < 0 )
return r ;
}
/* Increase search key by one, to read the next item, if we can. */
if ( ! btrfs_ioctl_search_args_inc ( & args ) )
break ;
}
/* OK, the child subvolumes should all be gone now, let's try
* again to remove the subvolume */
if ( ioctl ( fd , BTRFS_IOC_SNAP_DESTROY , & vol_args ) < 0 )
return - errno ;
return 0 ;
}
int btrfs_subvol_remove ( const char * path , bool recursive ) {
_cleanup_close_ int fd = - 1 ;
const char * subvolume ;
int r ;
assert ( path ) ;
r = extract_subvolume_name ( path , & subvolume ) ;
if ( r < 0 )
return r ;
fd = open_parent ( path , O_RDONLY | O_NOCTTY | O_CLOEXEC | O_DIRECTORY ) ;
if ( fd < 0 )
return fd ;
return subvol_remove_children ( fd , subvolume , 0 , recursive ) ;
}
int btrfs_subvol_remove_fd ( int fd , const char * subvolume , bool recursive ) {
return subvol_remove_children ( fd , subvolume , 0 , recursive ) ;
}
2015-04-06 16:26:59 +03:00
static int subvol_snapshot_children ( int old_fd , int new_fd , const char * subvolume , uint64_t subvol_id , BtrfsSnapshotFlags flags ) {
struct btrfs_ioctl_search_args args = {
. key . tree_id = BTRFS_ROOT_TREE_OBJECTID ,
. key . min_objectid = BTRFS_FIRST_FREE_OBJECTID ,
. key . max_objectid = BTRFS_LAST_FREE_OBJECTID ,
. key . min_type = BTRFS_ROOT_BACKREF_KEY ,
. key . max_type = BTRFS_ROOT_BACKREF_KEY ,
. key . min_transid = 0 ,
. key . max_transid = ( uint64_t ) - 1 ,
} ;
struct btrfs_ioctl_vol_args_v2 vol_args = {
. flags = flags & BTRFS_SNAPSHOT_READ_ONLY ? BTRFS_SUBVOL_RDONLY : 0 ,
. fd = old_fd ,
} ;
int r ;
assert ( old_fd > = 0 ) ;
assert ( new_fd > = 0 ) ;
assert ( subvolume ) ;
strncpy ( vol_args . name , subvolume , sizeof ( vol_args . name ) - 1 ) ;
vol_args . fd = old_fd ;
if ( ioctl ( new_fd , BTRFS_IOC_SNAP_CREATE_V2 , & vol_args ) < 0 )
return - errno ;
if ( ! ( flags & BTRFS_SNAPSHOT_RECURSIVE ) )
return 0 ;
if ( subvol_id = = 0 ) {
r = btrfs_subvol_get_id_fd ( old_fd , & subvol_id ) ;
if ( r < 0 )
return r ;
}
args . key . min_offset = args . key . max_offset = subvol_id ;
while ( btrfs_ioctl_search_args_compare ( & args ) < = 0 ) {
const struct btrfs_ioctl_search_header * sh ;
unsigned i ;
args . key . nr_items = 256 ;
if ( ioctl ( old_fd , BTRFS_IOC_TREE_SEARCH , & args ) < 0 )
return - errno ;
if ( args . key . nr_items < = 0 )
break ;
FOREACH_BTRFS_IOCTL_SEARCH_HEADER ( i , sh , args ) {
_cleanup_free_ char * p = NULL , * c = NULL , * np = NULL ;
struct btrfs_ioctl_ino_lookup_args ino_args ;
const struct btrfs_root_ref * ref ;
_cleanup_close_ int old_child_fd = - 1 , new_child_fd = - 1 ;
btrfs_ioctl_search_args_set ( & args , sh ) ;
if ( sh - > type ! = BTRFS_ROOT_BACKREF_KEY )
continue ;
if ( sh - > offset ! = subvol_id )
continue ;
ref = BTRFS_IOCTL_SEARCH_HEADER_BODY ( sh ) ;
p = strndup ( ( char * ) ref + sizeof ( struct btrfs_root_ref ) , le64toh ( ref - > name_len ) ) ;
if ( ! p )
return - ENOMEM ;
zero ( ino_args ) ;
ino_args . treeid = subvol_id ;
ino_args . objectid = htole64 ( ref - > dirid ) ;
if ( ioctl ( old_fd , BTRFS_IOC_INO_LOOKUP , & ino_args ) < 0 )
return - errno ;
/* The kernel returns an empty name if the
* subvolume is in the top - level directory ,
* and otherwise appends a slash , so that we
* can just concatenate easily here , without
* adding a slash . */
c = strappend ( ino_args . name , p ) ;
if ( ! c )
return - ENOMEM ;
old_child_fd = openat ( old_fd , c , O_RDONLY | O_NOCTTY | O_CLOEXEC | O_DIRECTORY ) ;
if ( old_child_fd < 0 )
return - errno ;
np = strjoin ( subvolume , " / " , ino_args . name , NULL ) ;
if ( ! np )
return - ENOMEM ;
new_child_fd = openat ( new_fd , np , O_RDONLY | O_NOCTTY | O_CLOEXEC | O_DIRECTORY ) ;
if ( new_child_fd < 0 )
return - errno ;
/* When btrfs clones the subvolumes, child
* subvolumes appear as directories . Remove
* them , so that we can create a new snapshot
* in their place */
if ( unlinkat ( new_child_fd , p , AT_REMOVEDIR ) < 0 )
return - errno ;
r = subvol_snapshot_children ( old_child_fd , new_child_fd , p , sh - > objectid , flags & ~ BTRFS_SNAPSHOT_FALLBACK_COPY ) ;
if ( r < 0 )
return r ;
}
/* Increase search key by one, to read the next item, if we can. */
if ( ! btrfs_ioctl_search_args_inc ( & args ) )
break ;
}
return 0 ;
}
int btrfs_subvol_snapshot_fd ( int old_fd , const char * new_path , BtrfsSnapshotFlags flags ) {
_cleanup_close_ int new_fd = - 1 ;
const char * subvolume ;
int r ;
assert ( old_fd > = 0 ) ;
assert ( new_path ) ;
r = btrfs_is_snapshot ( old_fd ) ;
if ( r < 0 )
return r ;
if ( r = = 0 ) {
if ( ! ( flags & BTRFS_SNAPSHOT_FALLBACK_COPY ) )
return - EISDIR ;
r = btrfs_subvol_make ( new_path ) ;
if ( r < 0 )
return r ;
r = copy_directory_fd ( old_fd , new_path , true ) ;
if ( r < 0 ) {
btrfs_subvol_remove ( new_path , false ) ;
return r ;
}
if ( flags & BTRFS_SNAPSHOT_READ_ONLY ) {
r = btrfs_subvol_set_read_only ( new_path , true ) ;
if ( r < 0 ) {
btrfs_subvol_remove ( new_path , false ) ;
return r ;
}
}
return 0 ;
}
r = extract_subvolume_name ( new_path , & subvolume ) ;
if ( r < 0 )
return r ;
new_fd = open_parent ( new_path , O_RDONLY | O_NOCTTY | O_CLOEXEC | O_DIRECTORY ) ;
if ( new_fd < 0 )
return new_fd ;
return subvol_snapshot_children ( old_fd , new_fd , subvolume , 0 , flags ) ;
}
int btrfs_subvol_snapshot ( const char * old_path , const char * new_path , BtrfsSnapshotFlags flags ) {
_cleanup_close_ int old_fd = - 1 ;
assert ( old_path ) ;
assert ( new_path ) ;
old_fd = open ( old_path , O_RDONLY | O_NOCTTY | O_CLOEXEC | O_DIRECTORY ) ;
if ( old_fd < 0 )
return - errno ;
return btrfs_subvol_snapshot_fd ( old_fd , new_path , flags ) ;
}