Skip to content
Browse files

Implement setting received uuid ioctl

When using send/receive, the received subvolume gets some attributes set
right before it's made read only: received_uuid, rtime, rtransid, stime
and stransid. Because receive is user space code, it uses the ioctl to
do so.

* The received_uuid is set to the uuid of the subvolume that was sent.
* rtime and rtransid is set by the kernel code to current time and
  generation of the fs.
* stime and stransid are provided by us. The receive code puts the
  ctransid value of the sent subvolume into rtransid, and as it seems
  never sets the value of stime, so it's always zero.

Having this ioctl available also means we can abuse it ourselves to play
around with it. :]

Here's the struct from the kernel source:

struct btrfs_ioctl_received_subvol_args {
        char    uuid[BTRFS_UUID_SIZE];  /* in */
        __u64   stransid;               /* in */
        __u64   rtransid;               /* out */
        struct btrfs_ioctl_timespec stime; /* in */
        struct btrfs_ioctl_timespec rtime; /* out */
        __u64   flags;                  /* in */
        __u64   reserved[16];           /* in */

The flags are not used at all. The ioctl code also does not do strict
checks to make sure that unused fields in the input are all zeroed out.
This means that the flags and reserved space can't be used for anything
in the future without creating an IOC_SET_RECEIVED_SUBVOL_V2... D:

The input to set_received_subvol is:
* an open file descriptor to the subvolume root directory (inode 256)
* the uuid (a python uuid object) we want to have set as received_uuid
* stransid as a number
* stime we want to have set, as a btrfs.ctree.TimeSpec object

Since all objects in the btrfs.ctree module are created from search
ioctl results, there is no nice way to just create new ones from
scratch. For TimeSpec, I started adding this possibility by adding a
factory method called from_values, which initialized a new object based
on the values we want to have for all attributes.

I yolo'ed around a bit with the args structs to keep the amount of lines
of code down.

Also, an example script is added, which shows how to call this new
  • Loading branch information
knorrie committed Nov 22, 2017
1 parent 16e44b8 commit 1ace623f95300ecf581b1182780fd6432a46b24d
Showing with 76 additions and 0 deletions.
  1. +7 −0 btrfs/
  2. +17 −0 btrfs/
  3. +52 −0 examples/
@@ -899,6 +899,13 @@ def __str__(self):
class TimeSpec(object):
timespec = struct.Struct('<QL')

def from_values(sec, nsec):
t = TimeSpec.__new__(TimeSpec)
t.sec = sec
t.nsec = nsec
return t

def __init__(self, data):
self.sec, self.nsec = TimeSpec.timespec.unpack_from(data)

@@ -654,3 +654,20 @@ def balance_progress(fd):
state, = _ioctl_balance_args[1].unpack_from(args, pos)
pos = sum(x.size for x in _ioctl_balance_args[:5])
return BalanceProgress(state, *_balance_progress.unpack_from(args, pos))

ioctl_received_subvol_args = struct.Struct('=16sQQQLQLQ128x')
_ioctl_received_subvol_args_in = struct.Struct('=16sQ8xQL148x')
_ioctl_received_subvol_args_out_up_to_rtime = struct.Struct('=24xQ12x')
IOC_SET_RECEIVED_SUBVOL = _IOWR(BTRFS_IOCTL_MAGIC, 37, ioctl_received_subvol_args)

def set_received_subvol(fd, received_uuid, stransid, stime):
args = bytearray(_ioctl_received_subvol_args_in.size)
_ioctl_received_subvol_args_in.pack_into(args, 0, received_uuid.bytes, stransid,
stime.sec, stime.nsec)
fcntl.ioctl(fd, IOC_SET_RECEIVED_SUBVOL, args)
rtransid, = _ioctl_received_subvol_args_out_up_to_rtime.unpack_from(args, 0)
pos = _ioctl_received_subvol_args_out_up_to_rtime.size
rtime = btrfs.ctree.TimeSpec(args[pos:pos+btrfs.ctree.TimeSpec.timespec.size])
return rtransid, rtime
@@ -0,0 +1,52 @@

import btrfs
import os
import sys
import uuid

if len(sys.argv) < 5:
print("Usage: {} <received-uuid> <stransid> <stime> <snapshot>".format(sys.argv[0]))
print("Example: {} 00000000-1234-5678-90ab-cdef00000000 "
"31337 1234.5678 /path/to/snapshot".format(sys.argv[0]))

received_uuid = uuid.UUID(sys.argv[1])
stransid = int(sys.argv[2])
stime_sec, stime_nsec = [int(x) for x in sys.argv[3].split('.')]
stime = btrfs.ctree.TimeSpec.from_values(stime_sec, stime_nsec)
subvol_path = sys.argv[4]

inum = os.stat(subvol_path).st_ino
if inum != 256:
print("{} is not the start of a subvolume (inum {} != 256)".format(subvol_path, inum))

subvol_fd =, os.O_RDONLY)
tree, _ = btrfs.ioctl.ino_lookup(subvol_fd)

def print_subvol_info(root):
print(" subvol_id: {}".format(root.key.objectid))
print(" received_uuid: {}".format(root.received_uuid))
print(" stime: {}".format(root.stime))
print(" stransid: {}".format(root.stransid))
print(" rtime: {}".format(root.rtime))
print(" rtransid: {}".format(root.rtransid))

fs = btrfs.FileSystem(subvol_path)

print("Current subvolume information:")
root = list(fs.subvolumes(min_id=tree, max_id=tree))[0]

print("Setting received subvolume...")
rtransid, rtime = btrfs.ioctl.set_received_subvol(subvol_fd, received_uuid, stransid, stime)

print("Resulting subvolume information:")
root = list(fs.subvolumes(min_id=tree, max_id=tree))[0]

0 comments on commit 1ace623

Please sign in to comment.
You can’t perform that action at this time.