github.com/dennwc/btrfs@v0.0.0-20221026161108-3097362dc072/send.go (about)

     1  package btrfs
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"unsafe"
     9  )
    10  
    11  func Send(w io.Writer, parent string, subvols ...string) error {
    12  	if len(subvols) == 0 {
    13  		return nil
    14  	}
    15  	// use first send subvol to determine mount_root
    16  	subvol, err := filepath.Abs(subvols[0])
    17  	if err != nil {
    18  		return err
    19  	}
    20  	mountRoot, err := findMountRoot(subvol)
    21  	if err == os.ErrNotExist {
    22  		return fmt.Errorf("cannot find a mountpoint for %s", subvol)
    23  	} else if err != nil {
    24  		return err
    25  	}
    26  	var (
    27  		cloneSrc []objectID
    28  		parentID objectID
    29  	)
    30  	if parent != "" {
    31  		parent, err = filepath.Abs(parent)
    32  		if err != nil {
    33  			return err
    34  		}
    35  		id, err := getPathRootID(parent)
    36  		if err != nil {
    37  			return fmt.Errorf("cannot get parent root id: %v", err)
    38  		}
    39  		parentID = id
    40  		cloneSrc = append(cloneSrc, id)
    41  	}
    42  	// check all subvolumes
    43  	paths := make([]string, 0, len(subvols))
    44  	for _, sub := range subvols {
    45  		sub, err = filepath.Abs(sub)
    46  		if err != nil {
    47  			return err
    48  		}
    49  		paths = append(paths, sub)
    50  		mount, err := findMountRoot(sub)
    51  		if err != nil {
    52  			return fmt.Errorf("cannot find mount root for %v: %v", sub, err)
    53  		} else if mount != mountRoot {
    54  			return fmt.Errorf("all subvolumes must be from the same filesystem (%s is not)", sub)
    55  		}
    56  		ok, err := IsReadOnly(sub)
    57  		if err != nil {
    58  			return err
    59  		} else if !ok {
    60  			return fmt.Errorf("subvolume %s is not read-only", sub)
    61  		}
    62  	}
    63  	mfs, err := Open(mountRoot, true)
    64  	if err != nil {
    65  		return err
    66  	}
    67  	defer mfs.Close()
    68  	full := len(cloneSrc) == 0
    69  	for i, sub := range paths {
    70  		var rootID objectID
    71  		if !full && parent != "" {
    72  			rel, err := filepath.Rel(mountRoot, sub)
    73  			if err != nil {
    74  				return err
    75  			}
    76  			si, err := subvolSearchByPath(mfs.f, rel)
    77  			if err != nil {
    78  				return fmt.Errorf("cannot find subvolume %s: %v", rel, err)
    79  			}
    80  			rootID = objectID(si.RootID)
    81  			parentID, err = findGoodParent(mfs.f, rootID, cloneSrc)
    82  			if err != nil {
    83  				return fmt.Errorf("cannot find good parent for %v: %v", rel, err)
    84  			}
    85  		}
    86  		fs, err := Open(sub, true)
    87  		if err != nil {
    88  			return err
    89  		}
    90  		var flags uint64
    91  		if i != 0 { // not first
    92  			flags |= _BTRFS_SEND_FLAG_OMIT_STREAM_HEADER
    93  		}
    94  		if i < len(paths)-1 { // not last
    95  			flags |= _BTRFS_SEND_FLAG_OMIT_END_CMD
    96  		}
    97  		err = send(w, fs.f, parentID, cloneSrc, flags)
    98  		fs.Close()
    99  		if err != nil {
   100  			return fmt.Errorf("error sending %s: %v", sub, err)
   101  		}
   102  		if !full && parent != "" {
   103  			cloneSrc = append(cloneSrc, rootID)
   104  		}
   105  	}
   106  	return nil
   107  }
   108  
   109  func send(w io.Writer, subvol *os.File, parent objectID, sources []objectID, flags uint64) error {
   110  	pr, pw, err := os.Pipe()
   111  	if err != nil {
   112  		return err
   113  	}
   114  	errc := make(chan error, 1)
   115  	go func() {
   116  		defer pr.Close()
   117  		_, err := io.Copy(w, pr)
   118  		errc <- err
   119  	}()
   120  	fd := pw.Fd()
   121  	wait := func() error {
   122  		pw.Close()
   123  		return <-errc
   124  	}
   125  	args := &btrfs_ioctl_send_args{
   126  		send_fd:     int64(fd),
   127  		parent_root: parent,
   128  		flags:       flags,
   129  	}
   130  	if len(sources) != 0 {
   131  		args.clone_sources = &sources[0]
   132  		args.clone_sources_count = uint64(len(sources))
   133  	}
   134  	if err := iocSend(subvol, args); err != nil {
   135  		wait()
   136  		return err
   137  	}
   138  	return wait()
   139  }
   140  
   141  // readRootItem reads a root item from the tree.
   142  //
   143  // TODO(dennwc): support older kernels:
   144  // In case we detect a root item smaller then sizeof(root_item),
   145  // we know it's an old version of the root structure and initialize all new fields to zero.
   146  // The same happens if we detect mismatching generation numbers as then we know the root was
   147  // once mounted with an older kernel that was not aware of the root item structure change.
   148  func readRootItem(mnt *os.File, rootID objectID) (*rootItem, error) {
   149  	sk := btrfs_ioctl_search_key{
   150  		tree_id: rootTreeObjectid,
   151  		// There may be more than one ROOT_ITEM key if there are
   152  		// snapshots pending deletion, we have to loop through them.
   153  		min_objectid: rootID,
   154  		max_objectid: rootID,
   155  		min_type:     rootItemKey,
   156  		max_type:     rootItemKey,
   157  		max_offset:   maxUint64,
   158  		max_transid:  maxUint64,
   159  		nr_items:     4096,
   160  	}
   161  	for ; sk.min_offset < maxUint64; sk.min_offset++ {
   162  		results, err := treeSearchRaw(mnt, sk)
   163  		if err != nil {
   164  			return nil, err
   165  		} else if len(results) == 0 {
   166  			break
   167  		}
   168  		for _, r := range results {
   169  			sk.min_objectid = r.ObjectID
   170  			sk.min_type = r.Type
   171  			sk.min_offset = r.Offset
   172  			if r.ObjectID > rootID {
   173  				break
   174  			}
   175  			if r.ObjectID == rootID && r.Type == rootItemKey {
   176  				const sz = int(unsafe.Sizeof(btrfs_root_item_raw{}))
   177  				if len(r.Data) > sz {
   178  					return nil, fmt.Errorf("btrfs_root_item is larger than expected; kernel is newer than the library")
   179  				} else if len(r.Data) < sz { // TODO
   180  					return nil, fmt.Errorf("btrfs_root_item is smaller then expected; kernel version is too old")
   181  				}
   182  				p := asRootItem(r.Data).Decode()
   183  				return &p, nil
   184  			}
   185  		}
   186  		results = nil
   187  		if sk.min_type != rootItemKey || sk.min_objectid != rootID {
   188  			break
   189  		}
   190  	}
   191  	return nil, ErrNotFound
   192  }
   193  
   194  func getParent(mnt *os.File, rootID objectID) (*SubvolInfo, error) {
   195  	st, err := subvolSearchByRootID(mnt, rootID, "")
   196  	if err != nil {
   197  		return nil, fmt.Errorf("cannot find subvolume %d to determine parent: %v", rootID, err)
   198  	}
   199  	return subvolSearchByUUID(mnt, st.ParentUUID)
   200  }
   201  
   202  func findGoodParent(mnt *os.File, rootID objectID, cloneSrc []objectID) (objectID, error) {
   203  	parent, err := getParent(mnt, rootID)
   204  	if err != nil {
   205  		return 0, fmt.Errorf("get parent failed: %v", err)
   206  	}
   207  	for _, id := range cloneSrc {
   208  		if id == objectID(parent.RootID) {
   209  			return objectID(parent.RootID), nil
   210  		}
   211  	}
   212  	var (
   213  		bestParent *SubvolInfo
   214  		bestDiff   uint64 = maxUint64
   215  	)
   216  	for _, id := range cloneSrc {
   217  		parent2, err := getParent(mnt, id)
   218  		if err == ErrNotFound {
   219  			continue
   220  		} else if err != nil {
   221  			return 0, err
   222  		}
   223  		if parent2.RootID != parent.RootID {
   224  			continue
   225  		}
   226  		parent2, err = subvolSearchByRootID(mnt, id, "")
   227  		if err != nil {
   228  			return 0, err
   229  		}
   230  		diff := int64(parent2.CTransID - parent.CTransID)
   231  		if diff < 0 {
   232  			diff = -diff
   233  		}
   234  		if uint64(diff) < bestDiff {
   235  			bestParent, bestDiff = parent2, uint64(diff)
   236  		}
   237  	}
   238  	if bestParent != nil {
   239  		return objectID(bestParent.RootID), nil
   240  	}
   241  	if !parent.ParentUUID.IsZero() {
   242  		return findGoodParent(mnt, objectID(parent.RootID), cloneSrc)
   243  	}
   244  	return 0, ErrNotFound
   245  }