xref: /reactos/drivers/filesystems/btrfs/send.c (revision 37b2c145)
1 /* Copyright (c) Mark Harmstone 2017
2  *
3  * This file is part of WinBtrfs.
4  *
5  * WinBtrfs is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public Licence as published by
7  * the Free Software Foundation, either version 3 of the Licence, or
8  * (at your option) any later version.
9  *
10  * WinBtrfs is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public Licence for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public Licence
16  * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */
17 
18 #include "btrfs_drv.h"
19 
20 typedef struct send_dir {
21     LIST_ENTRY list_entry;
22     uint64_t inode;
23     bool dummy;
24     BTRFS_TIME atime;
25     BTRFS_TIME mtime;
26     BTRFS_TIME ctime;
27     struct send_dir* parent;
28     uint16_t namelen;
29     char* name;
30     LIST_ENTRY deleted_children;
31 } send_dir;
32 
33 typedef struct {
34     LIST_ENTRY list_entry;
35     uint64_t inode;
36     bool dir;
37     send_dir* sd;
38     char tmpname[64];
39 } orphan;
40 
41 typedef struct {
42     LIST_ENTRY list_entry;
43     ULONG namelen;
44     char name[1];
45 } deleted_child;
46 
47 typedef struct {
48     LIST_ENTRY list_entry;
49     send_dir* sd;
50     uint16_t namelen;
51     char name[1];
52 } ref;
53 
54 typedef struct {
55     send_dir* sd;
56     uint64_t last_child_inode;
57     LIST_ENTRY list_entry;
58 } pending_rmdir;
59 
60 typedef struct {
61     uint64_t offset;
62     LIST_ENTRY list_entry;
63     ULONG datalen;
64     EXTENT_DATA data;
65 } send_ext;
66 
67 typedef struct {
68     device_extension* Vcb;
69     root* root;
70     root* parent;
71     uint8_t* data;
72     ULONG datalen;
73     ULONG num_clones;
74     root** clones;
75     LIST_ENTRY orphans;
76     LIST_ENTRY dirs;
77     LIST_ENTRY pending_rmdirs;
78     KEVENT buffer_event;
79     send_dir* root_dir;
80     send_info* send;
81 
82     struct {
83         uint64_t inode;
84         bool deleting;
85         bool new;
86         uint64_t gen;
87         uint64_t uid;
88         uint64_t olduid;
89         uint64_t gid;
90         uint64_t oldgid;
91         uint64_t mode;
92         uint64_t oldmode;
93         uint64_t size;
94         uint64_t flags;
95         BTRFS_TIME atime;
96         BTRFS_TIME mtime;
97         BTRFS_TIME ctime;
98         bool file;
99         char* path;
100         orphan* o;
101         send_dir* sd;
102         LIST_ENTRY refs;
103         LIST_ENTRY oldrefs;
104         LIST_ENTRY exts;
105         LIST_ENTRY oldexts;
106     } lastinode;
107 } send_context;
108 
109 #define MAX_SEND_WRITE 0xc000 // 48 KB
110 #define SEND_BUFFER_LENGTH 0x100000 // 1 MB
111 
112 static NTSTATUS find_send_dir(send_context* context, uint64_t dir, uint64_t generation, send_dir** psd, bool* added_dummy);
113 static NTSTATUS wait_for_flush(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2);
114 
115 static void send_command(send_context* context, uint16_t cmd) {
116     btrfs_send_command* bsc = (btrfs_send_command*)&context->data[context->datalen];
117 
118     bsc->cmd = cmd;
119     bsc->csum = 0;
120 
121     context->datalen += sizeof(btrfs_send_command);
122 }
123 
124 static void send_command_finish(send_context* context, ULONG pos) {
125     btrfs_send_command* bsc = (btrfs_send_command*)&context->data[pos];
126 
127     bsc->length = context->datalen - pos - sizeof(btrfs_send_command);
128     bsc->csum = calc_crc32c(0, (uint8_t*)bsc, context->datalen - pos);
129 }
130 
131 static void send_add_tlv(send_context* context, uint16_t type, void* data, uint16_t length) {
132     btrfs_send_tlv* tlv = (btrfs_send_tlv*)&context->data[context->datalen];
133 
134     tlv->type = type;
135     tlv->length = length;
136 
137     if (length > 0 && data)
138         RtlCopyMemory(&tlv[1], data, length);
139 
140     context->datalen += sizeof(btrfs_send_tlv) + length;
141 }
142 
143 static char* uint64_to_char(uint64_t num, char* buf) {
144     char *tmp, tmp2[20];
145 
146     if (num == 0) {
147         buf[0] = '0';
148         return buf + 1;
149     }
150 
151     tmp = &tmp2[20];
152     while (num > 0) {
153         tmp--;
154         *tmp = (num % 10) + '0';
155         num /= 10;
156     }
157 
158     RtlCopyMemory(buf, tmp, tmp2 + sizeof(tmp2) - tmp);
159 
160     return &buf[tmp2 + sizeof(tmp2) - tmp];
161 }
162 
163 static NTSTATUS get_orphan_name(send_context* context, uint64_t inode, uint64_t generation, char* name) {
164     char *ptr, *ptr2;
165     uint64_t index = 0;
166     KEY searchkey;
167 
168     name[0] = 'o';
169 
170     ptr = uint64_to_char(inode, &name[1]);
171     *ptr = '-'; ptr++;
172     ptr = uint64_to_char(generation, ptr);
173     *ptr = '-'; ptr++;
174     ptr2 = ptr;
175 
176     searchkey.obj_id = SUBVOL_ROOT_INODE;
177     searchkey.obj_type = TYPE_DIR_ITEM;
178 
179     do {
180         NTSTATUS Status;
181         traverse_ptr tp;
182 
183         ptr = uint64_to_char(index, ptr);
184         *ptr = 0;
185 
186         searchkey.offset = calc_crc32c(0xfffffffe, (uint8_t*)name, (ULONG)(ptr - name));
187 
188         Status = find_item(context->Vcb, context->root, &tp, &searchkey, false, NULL);
189         if (!NT_SUCCESS(Status)) {
190             ERR("find_item returned %08x\n", Status);
191             return Status;
192         }
193 
194         if (!keycmp(searchkey, tp.item->key))
195             goto cont;
196 
197         if (context->parent) {
198             Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL);
199             if (!NT_SUCCESS(Status)) {
200                 ERR("find_item returned %08x\n", Status);
201                 return Status;
202             }
203 
204             if (!keycmp(searchkey, tp.item->key))
205                 goto cont;
206         }
207 
208         return STATUS_SUCCESS;
209 
210 cont:
211         index++;
212         ptr = ptr2;
213     } while (true);
214 }
215 
216 static void add_orphan(send_context* context, orphan* o) {
217     LIST_ENTRY* le;
218 
219     le = context->orphans.Flink;
220     while (le != &context->orphans) {
221         orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry);
222 
223         if (o2->inode > o->inode) {
224             InsertHeadList(o2->list_entry.Blink, &o->list_entry);
225             return;
226         }
227 
228         le = le->Flink;
229     }
230 
231     InsertTailList(&context->orphans, &o->list_entry);
232 }
233 
234 static NTSTATUS send_read_symlink(send_context* context, uint64_t inode, char** link, uint16_t* linklen) {
235     NTSTATUS Status;
236     KEY searchkey;
237     traverse_ptr tp;
238     EXTENT_DATA* ed;
239 
240     searchkey.obj_id = inode;
241     searchkey.obj_type = TYPE_EXTENT_DATA;
242     searchkey.offset = 0;
243 
244     Status = find_item(context->Vcb, context->root, &tp, &searchkey, false, NULL);
245     if (!NT_SUCCESS(Status)) {
246         ERR("find_item returned %08x\n", Status);
247         return Status;
248     }
249 
250     if (keycmp(tp.item->key, searchkey)) {
251         ERR("could not find (%I64x,%x,%I64x)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
252         return STATUS_INTERNAL_ERROR;
253     }
254 
255     if (tp.item->size < sizeof(EXTENT_DATA)) {
256         ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,
257             tp.item->size, sizeof(EXTENT_DATA));
258         return STATUS_INTERNAL_ERROR;
259     }
260 
261     ed = (EXTENT_DATA*)tp.item->data;
262 
263     if (ed->type != EXTENT_TYPE_INLINE) {
264         WARN("symlink data was not inline, returning blank string\n");
265         *link = NULL;
266         *linklen = 0;
267         return STATUS_SUCCESS;
268     }
269 
270     if (tp.item->size < offsetof(EXTENT_DATA, data[0]) + ed->decoded_size) {
271         ERR("(%I64x,%x,%I64x) was %u bytes, expected %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,
272             tp.item->size, offsetof(EXTENT_DATA, data[0]) + ed->decoded_size);
273         return STATUS_INTERNAL_ERROR;
274     }
275 
276     *link = (char*)ed->data;
277     *linklen = (uint16_t)ed->decoded_size;
278 
279     return STATUS_SUCCESS;
280 }
281 
282 static NTSTATUS send_inode(send_context* context, traverse_ptr* tp, traverse_ptr* tp2) {
283     NTSTATUS Status;
284     INODE_ITEM* ii;
285 
286     if (tp2 && !tp) {
287         INODE_ITEM* ii2 = (INODE_ITEM*)tp2->item->data;
288 
289         if (tp2->item->size < sizeof(INODE_ITEM)) {
290             ERR("(%I64x,%x,%I64x) was %u bytes, expected %u\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset,
291                 tp2->item->size, sizeof(INODE_ITEM));
292             return STATUS_INTERNAL_ERROR;
293         }
294 
295         context->lastinode.inode = tp2->item->key.obj_id;
296         context->lastinode.deleting = true;
297         context->lastinode.gen = ii2->generation;
298         context->lastinode.mode = ii2->st_mode;
299         context->lastinode.flags = ii2->flags;
300         context->lastinode.o = NULL;
301         context->lastinode.sd = NULL;
302 
303         return STATUS_SUCCESS;
304     }
305 
306     ii = (INODE_ITEM*)tp->item->data;
307 
308     if (tp->item->size < sizeof(INODE_ITEM)) {
309         ERR("(%I64x,%x,%I64x) was %u bytes, expected %u\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,
310             tp->item->size, sizeof(INODE_ITEM));
311         return STATUS_INTERNAL_ERROR;
312     }
313 
314     context->lastinode.inode = tp->item->key.obj_id;
315     context->lastinode.deleting = false;
316     context->lastinode.gen = ii->generation;
317     context->lastinode.uid = ii->st_uid;
318     context->lastinode.gid = ii->st_gid;
319     context->lastinode.mode = ii->st_mode;
320     context->lastinode.size = ii->st_size;
321     context->lastinode.atime = ii->st_atime;
322     context->lastinode.mtime = ii->st_mtime;
323     context->lastinode.ctime = ii->st_ctime;
324     context->lastinode.flags = ii->flags;
325     context->lastinode.file = false;
326     context->lastinode.o = NULL;
327     context->lastinode.sd = NULL;
328 
329     if (context->lastinode.path) {
330         ExFreePool(context->lastinode.path);
331         context->lastinode.path = NULL;
332     }
333 
334     if (tp2) {
335         INODE_ITEM* ii2 = (INODE_ITEM*)tp2->item->data;
336         LIST_ENTRY* le;
337 
338         if (tp2->item->size < sizeof(INODE_ITEM)) {
339             ERR("(%I64x,%x,%I64x) was %u bytes, expected %u\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset,
340                 tp2->item->size, sizeof(INODE_ITEM));
341             return STATUS_INTERNAL_ERROR;
342         }
343 
344         context->lastinode.oldmode = ii2->st_mode;
345         context->lastinode.olduid = ii2->st_uid;
346         context->lastinode.oldgid = ii2->st_gid;
347 
348         if ((ii2->st_mode & __S_IFREG) == __S_IFREG && (ii2->st_mode & __S_IFLNK) != __S_IFLNK && (ii2->st_mode & __S_IFSOCK) != __S_IFSOCK)
349             context->lastinode.file = true;
350 
351         context->lastinode.new = false;
352 
353         le = context->orphans.Flink;
354         while (le != &context->orphans) {
355             orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry);
356 
357             if (o2->inode == tp->item->key.obj_id) {
358                 context->lastinode.o = o2;
359                 break;
360             } else if (o2->inode > tp->item->key.obj_id)
361                 break;
362 
363             le = le->Flink;
364         }
365     } else
366         context->lastinode.new = true;
367 
368     if (tp->item->key.obj_id == SUBVOL_ROOT_INODE) {
369         send_dir* sd;
370 
371         Status = find_send_dir(context, tp->item->key.obj_id, ii->generation, &sd, NULL);
372         if (!NT_SUCCESS(Status)) {
373             ERR("find_send_dir returned %08x\n", Status);
374             return Status;
375         }
376 
377         sd->atime = ii->st_atime;
378         sd->mtime = ii->st_mtime;
379         sd->ctime = ii->st_ctime;
380         context->root_dir = sd;
381     } else if (!tp2) {
382         ULONG pos = context->datalen;
383         uint16_t cmd;
384         send_dir* sd;
385 
386         char name[64];
387         orphan* o;
388 
389         // skip creating orphan directory if we've already done so
390         if (ii->st_mode & __S_IFDIR) {
391             LIST_ENTRY* le;
392 
393             le = context->orphans.Flink;
394             while (le != &context->orphans) {
395                 orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry);
396 
397                 if (o2->inode == tp->item->key.obj_id) {
398                     context->lastinode.o = o2;
399                     o2->sd->atime = ii->st_atime;
400                     o2->sd->mtime = ii->st_mtime;
401                     o2->sd->ctime = ii->st_ctime;
402                     o2->sd->dummy = false;
403                     return STATUS_SUCCESS;
404                 } else if (o2->inode > tp->item->key.obj_id)
405                     break;
406 
407                 le = le->Flink;
408             }
409         }
410 
411         if ((ii->st_mode & __S_IFSOCK) == __S_IFSOCK)
412             cmd = BTRFS_SEND_CMD_MKSOCK;
413         else if ((ii->st_mode & __S_IFLNK) == __S_IFLNK)
414             cmd = BTRFS_SEND_CMD_SYMLINK;
415         else if ((ii->st_mode & __S_IFCHR) == __S_IFCHR || (ii->st_mode & __S_IFBLK) == __S_IFBLK)
416             cmd = BTRFS_SEND_CMD_MKNOD;
417         else if ((ii->st_mode & __S_IFDIR) == __S_IFDIR)
418             cmd = BTRFS_SEND_CMD_MKDIR;
419         else if ((ii->st_mode & __S_IFIFO) == __S_IFIFO)
420             cmd = BTRFS_SEND_CMD_MKFIFO;
421         else {
422             cmd = BTRFS_SEND_CMD_MKFILE;
423             context->lastinode.file = true;
424         }
425 
426         send_command(context, cmd);
427 
428         Status = get_orphan_name(context, tp->item->key.obj_id, ii->generation, name);
429         if (!NT_SUCCESS(Status)) {
430             ERR("get_orphan_name returned %08x\n", Status);
431             return Status;
432         }
433 
434         send_add_tlv(context, BTRFS_SEND_TLV_PATH, name, (uint16_t)strlen(name));
435         send_add_tlv(context, BTRFS_SEND_TLV_INODE, &tp->item->key.obj_id, sizeof(uint64_t));
436 
437         if (cmd == BTRFS_SEND_CMD_MKNOD || cmd == BTRFS_SEND_CMD_MKFIFO || cmd == BTRFS_SEND_CMD_MKSOCK) {
438             uint64_t rdev = makedev((ii->st_rdev & 0xFFFFFFFFFFF) >> 20, ii->st_rdev & 0xFFFFF), mode = ii->st_mode;
439 
440             send_add_tlv(context, BTRFS_SEND_TLV_RDEV, &rdev, sizeof(uint64_t));
441             send_add_tlv(context, BTRFS_SEND_TLV_MODE, &mode, sizeof(uint64_t));
442         } else if (cmd == BTRFS_SEND_CMD_SYMLINK && ii->st_size > 0) {
443             char* link;
444             uint16_t linklen;
445 
446             Status = send_read_symlink(context, tp->item->key.obj_id, &link, &linklen);
447             if (!NT_SUCCESS(Status)) {
448                 ERR("send_read_symlink returned %08x\n", Status);
449                 return Status;
450             }
451 
452             send_add_tlv(context, BTRFS_SEND_TLV_PATH_LINK, link, linklen);
453         }
454 
455         send_command_finish(context, pos);
456 
457         if (ii->st_mode & __S_IFDIR) {
458             Status = find_send_dir(context, tp->item->key.obj_id, ii->generation, &sd, NULL);
459             if (!NT_SUCCESS(Status)) {
460                 ERR("find_send_dir returned %08x\n", Status);
461                 return Status;
462             }
463 
464             sd->dummy = false;
465         } else
466             sd = NULL;
467 
468         context->lastinode.sd = sd;
469 
470         o = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG);
471         if (!o) {
472             ERR("out of memory\n");
473             return STATUS_INSUFFICIENT_RESOURCES;
474         }
475 
476         o->inode = tp->item->key.obj_id;
477         o->dir = (ii->st_mode & __S_IFDIR && ii->st_size > 0) ? true : false;
478         strcpy(o->tmpname, name);
479         o->sd = sd;
480         add_orphan(context, o);
481 
482         context->lastinode.path = ExAllocatePoolWithTag(PagedPool, strlen(o->tmpname) + 1, ALLOC_TAG);
483         if (!context->lastinode.path) {
484             ERR("out of memory\n");
485             return STATUS_INSUFFICIENT_RESOURCES;
486         }
487 
488         strcpy(context->lastinode.path, o->tmpname);
489 
490         context->lastinode.o = o;
491     }
492 
493     return STATUS_SUCCESS;
494 }
495 
496 static NTSTATUS send_add_dir(send_context* context, uint64_t inode, send_dir* parent, char* name, uint16_t namelen, bool dummy, LIST_ENTRY* lastentry, send_dir** psd) {
497     LIST_ENTRY* le;
498     send_dir* sd = ExAllocatePoolWithTag(PagedPool, sizeof(send_dir), ALLOC_TAG);
499 
500     if (!sd) {
501         ERR("out of memory\n");
502         return STATUS_INSUFFICIENT_RESOURCES;
503     }
504 
505     sd->inode = inode;
506     sd->dummy = dummy;
507     sd->parent = parent;
508 
509     if (!dummy) {
510         sd->atime = context->lastinode.atime;
511         sd->mtime = context->lastinode.mtime;
512         sd->ctime = context->lastinode.ctime;
513     }
514 
515     if (namelen > 0) {
516         sd->name = ExAllocatePoolWithTag(PagedPool, namelen, ALLOC_TAG);
517         if (!sd->name) {
518             ERR("out of memory\n");
519             ExFreePool(sd);
520             return STATUS_INSUFFICIENT_RESOURCES;
521         }
522 
523         memcpy(sd->name, name, namelen);
524     } else
525         sd->name = NULL;
526 
527     sd->namelen = namelen;
528 
529     InitializeListHead(&sd->deleted_children);
530 
531     if (lastentry)
532         InsertHeadList(lastentry, &sd->list_entry);
533     else {
534         le = context->dirs.Flink;
535         while (le != &context->dirs) {
536             send_dir* sd2 = CONTAINING_RECORD(le, send_dir, list_entry);
537 
538             if (sd2->inode > sd->inode) {
539                 InsertHeadList(sd2->list_entry.Blink, &sd->list_entry);
540 
541                 if (psd)
542                     *psd = sd;
543 
544                 return STATUS_SUCCESS;
545             }
546 
547             le = le->Flink;
548         }
549 
550         InsertTailList(&context->dirs, &sd->list_entry);
551     }
552 
553     if (psd)
554         *psd = sd;
555 
556     return STATUS_SUCCESS;
557 }
558 
559 static __inline uint16_t find_path_len(send_dir* parent, uint16_t namelen) {
560     uint16_t len = namelen;
561 
562     while (parent && parent->namelen > 0) {
563         len += parent->namelen + 1;
564         parent = parent->parent;
565     }
566 
567     return len;
568 }
569 
570 static void find_path(char* path, send_dir* parent, char* name, ULONG namelen) {
571     ULONG len = namelen;
572 
573     RtlCopyMemory(path, name, namelen);
574 
575     while (parent && parent->namelen > 0) {
576         RtlMoveMemory(path + parent->namelen + 1, path, len);
577         RtlCopyMemory(path, parent->name, parent->namelen);
578         path[parent->namelen] = '/';
579         len += parent->namelen + 1;
580 
581         parent = parent->parent;
582     }
583 }
584 
585 static void send_add_tlv_path(send_context* context, uint16_t type, send_dir* parent, char* name, uint16_t namelen) {
586     uint16_t len = find_path_len(parent, namelen);
587 
588     send_add_tlv(context, type, NULL, len);
589 
590     if (len > 0)
591         find_path((char*)&context->data[context->datalen - len], parent, name, namelen);
592 }
593 
594 static NTSTATUS found_path(send_context* context, send_dir* parent, char* name, uint16_t namelen) {
595     ULONG pos = context->datalen;
596 
597     if (context->lastinode.o) {
598         send_command(context, BTRFS_SEND_CMD_RENAME);
599 
600         send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, context->root_dir, context->lastinode.o->tmpname, (uint16_t)strlen(context->lastinode.o->tmpname));
601 
602         send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, parent, name, namelen);
603 
604         send_command_finish(context, pos);
605     } else {
606         send_command(context, BTRFS_SEND_CMD_LINK);
607 
608         send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, parent, name, namelen);
609 
610         send_add_tlv(context, BTRFS_SEND_TLV_PATH_LINK, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
611 
612         send_command_finish(context, pos);
613     }
614 
615     if (context->lastinode.o) {
616         uint16_t pathlen;
617 
618         if (context->lastinode.o->sd) {
619             if (context->lastinode.o->sd->name)
620                 ExFreePool(context->lastinode.o->sd->name);
621 
622             context->lastinode.o->sd->name = ExAllocatePoolWithTag(PagedPool, namelen, ALLOC_TAG);
623             if (!context->lastinode.o->sd->name) {
624                 ERR("out of memory\n");
625                 return STATUS_INSUFFICIENT_RESOURCES;
626             }
627 
628             RtlCopyMemory(context->lastinode.o->sd->name, name, namelen);
629             context->lastinode.o->sd->namelen = namelen;
630             context->lastinode.o->sd->parent = parent;
631         }
632 
633         if (context->lastinode.path)
634             ExFreePool(context->lastinode.path);
635 
636         pathlen = find_path_len(parent, namelen);
637         context->lastinode.path = ExAllocatePoolWithTag(PagedPool, pathlen + 1, ALLOC_TAG);
638         if (!context->lastinode.path) {
639             ERR("out of memory\n");
640             return STATUS_INSUFFICIENT_RESOURCES;
641         }
642 
643         find_path(context->lastinode.path, parent, name, namelen);
644         context->lastinode.path[pathlen] = 0;
645 
646         RemoveEntryList(&context->lastinode.o->list_entry);
647         ExFreePool(context->lastinode.o);
648 
649         context->lastinode.o = NULL;
650     }
651 
652     return STATUS_SUCCESS;
653 }
654 
655 static void send_utimes_command_dir(send_context* context, send_dir* sd, BTRFS_TIME* atime, BTRFS_TIME* mtime, BTRFS_TIME* ctime) {
656     ULONG pos = context->datalen;
657 
658     send_command(context, BTRFS_SEND_CMD_UTIMES);
659 
660     send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, sd->parent, sd->name, sd->namelen);
661 
662     send_add_tlv(context, BTRFS_SEND_TLV_ATIME, atime, sizeof(BTRFS_TIME));
663     send_add_tlv(context, BTRFS_SEND_TLV_MTIME, mtime, sizeof(BTRFS_TIME));
664     send_add_tlv(context, BTRFS_SEND_TLV_CTIME, ctime, sizeof(BTRFS_TIME));
665 
666     send_command_finish(context, pos);
667 }
668 
669 static NTSTATUS find_send_dir(send_context* context, uint64_t dir, uint64_t generation, send_dir** psd, bool* added_dummy) {
670     NTSTATUS Status;
671     LIST_ENTRY* le;
672     char name[64];
673 
674     le = context->dirs.Flink;
675     while (le != &context->dirs) {
676         send_dir* sd2 = CONTAINING_RECORD(le, send_dir, list_entry);
677 
678         if (sd2->inode > dir)
679             break;
680         else if (sd2->inode == dir) {
681             *psd = sd2;
682 
683             if (added_dummy)
684                 *added_dummy = false;
685 
686             return STATUS_SUCCESS;
687         }
688 
689         le = le->Flink;
690     }
691 
692     if (dir == SUBVOL_ROOT_INODE) {
693         Status = send_add_dir(context, dir, NULL, NULL, 0, false, le, psd);
694         if (!NT_SUCCESS(Status)) {
695             ERR("send_add_dir returned %08x\n", Status);
696             return Status;
697         }
698 
699         if (added_dummy)
700             *added_dummy = false;
701 
702         return STATUS_SUCCESS;
703     }
704 
705     if (context->parent) {
706         KEY searchkey;
707         traverse_ptr tp;
708 
709         searchkey.obj_id = dir;
710         searchkey.obj_type = TYPE_INODE_REF; // directories should never have an extiref
711         searchkey.offset = 0xffffffffffffffff;
712 
713         Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL);
714         if (!NT_SUCCESS(Status)) {
715             ERR("find_item returned %08x\n", Status);
716             return Status;
717         }
718 
719         if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
720             INODE_REF* ir = (INODE_REF*)tp.item->data;
721             send_dir* parent;
722 
723             if (tp.item->size < sizeof(INODE_REF) || tp.item->size < offsetof(INODE_REF, name[0]) + ir->n) {
724                 ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
725                 return STATUS_INTERNAL_ERROR;
726             }
727 
728             if (tp.item->key.offset == SUBVOL_ROOT_INODE)
729                 parent = context->root_dir;
730             else {
731                 Status = find_send_dir(context, tp.item->key.offset, generation, &parent, NULL);
732                 if (!NT_SUCCESS(Status)) {
733                     ERR("find_send_dir returned %08x\n", Status);
734                     return Status;
735                 }
736             }
737 
738             Status = send_add_dir(context, dir, parent, ir->name, ir->n, true, NULL, psd);
739             if (!NT_SUCCESS(Status)) {
740                 ERR("send_add_dir returned %08x\n", Status);
741                 return Status;
742             }
743 
744             if (added_dummy)
745                 *added_dummy = false;
746 
747             return STATUS_SUCCESS;
748         }
749     }
750 
751     Status = get_orphan_name(context, dir, generation, name);
752     if (!NT_SUCCESS(Status)) {
753         ERR("get_orphan_name returned %08x\n", Status);
754         return Status;
755     }
756 
757     Status = send_add_dir(context, dir, NULL, name, (uint16_t)strlen(name), true, le, psd);
758     if (!NT_SUCCESS(Status)) {
759         ERR("send_add_dir returned %08x\n", Status);
760         return Status;
761     }
762 
763     if (added_dummy)
764         *added_dummy = true;
765 
766     return STATUS_SUCCESS;
767 }
768 
769 static NTSTATUS send_inode_ref(send_context* context, traverse_ptr* tp, bool tree2) {
770     NTSTATUS Status;
771     uint64_t inode = tp ? tp->item->key.obj_id : 0, dir = tp ? tp->item->key.offset : 0;
772     LIST_ENTRY* le;
773     INODE_REF* ir;
774     uint16_t len;
775     send_dir* sd = NULL;
776     orphan* o2 = NULL;
777 
778     if (inode == dir) // root
779         return STATUS_SUCCESS;
780 
781     if (tp->item->size < sizeof(INODE_REF)) {
782         ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %u\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,
783             tp->item->size, sizeof(INODE_REF));
784         return STATUS_INTERNAL_ERROR;
785     }
786 
787     if (dir != SUBVOL_ROOT_INODE) {
788         bool added_dummy;
789 
790         Status = find_send_dir(context, dir, context->root->root_item.ctransid, &sd, &added_dummy);
791         if (!NT_SUCCESS(Status)) {
792             ERR("find_send_dir returned %08x\n", Status);
793             return Status;
794         }
795 
796         // directory has higher inode number than file, so might need to be created
797         if (added_dummy) {
798             bool found = false;
799 
800             le = context->orphans.Flink;
801             while (le != &context->orphans) {
802                 o2 = CONTAINING_RECORD(le, orphan, list_entry);
803 
804                 if (o2->inode == dir) {
805                     found = true;
806                     break;
807                 } else if (o2->inode > dir)
808                     break;
809 
810                 le = le->Flink;
811             }
812 
813             if (!found) {
814                 ULONG pos = context->datalen;
815 
816                 send_command(context, BTRFS_SEND_CMD_MKDIR);
817 
818                 send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, NULL, sd->name, sd->namelen);
819 
820                 send_add_tlv(context, BTRFS_SEND_TLV_INODE, &dir, sizeof(uint64_t));
821 
822                 send_command_finish(context, pos);
823 
824                 o2 = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG);
825                 if (!o2) {
826                     ERR("out of memory\n");
827                     return STATUS_INSUFFICIENT_RESOURCES;
828                 }
829 
830                 o2->inode = dir;
831                 o2->dir = true;
832                 memcpy(o2->tmpname, sd->name, sd->namelen);
833                 o2->tmpname[sd->namelen] = 0;
834                 o2->sd = sd;
835                 add_orphan(context, o2);
836             }
837         }
838     } else
839         sd = context->root_dir;
840 
841     len = tp->item->size;
842     ir = (INODE_REF*)tp->item->data;
843 
844     while (len > 0) {
845         ref* r;
846 
847         if (len < sizeof(INODE_REF) || len < offsetof(INODE_REF, name[0]) + ir->n) {
848             ERR("(%I64x,%x,%I64x) was truncated\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset);
849             return STATUS_INTERNAL_ERROR;
850         }
851 
852         r = ExAllocatePoolWithTag(PagedPool, offsetof(ref, name[0]) + ir->n, ALLOC_TAG);
853         if (!r) {
854             ERR("out of memory\n");
855             return STATUS_INSUFFICIENT_RESOURCES;
856         }
857 
858         r->sd = sd;
859         r->namelen = ir->n;
860         RtlCopyMemory(r->name, ir->name, ir->n);
861 
862         InsertTailList(tree2 ? &context->lastinode.oldrefs : &context->lastinode.refs, &r->list_entry);
863 
864         len -= (uint16_t)offsetof(INODE_REF, name[0]) + ir->n;
865         ir = (INODE_REF*)&ir->name[ir->n];
866     }
867 
868     return STATUS_SUCCESS;
869 }
870 
871 static NTSTATUS send_inode_extref(send_context* context, traverse_ptr* tp, bool tree2) {
872     INODE_EXTREF* ier;
873     uint16_t len;
874 
875     if (tp->item->size < sizeof(INODE_EXTREF)) {
876         ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %u\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,
877             tp->item->size, sizeof(INODE_EXTREF));
878         return STATUS_INTERNAL_ERROR;
879     }
880 
881     len = tp->item->size;
882     ier = (INODE_EXTREF*)tp->item->data;
883 
884     while (len > 0) {
885         NTSTATUS Status;
886         send_dir* sd = NULL;
887         orphan* o2 = NULL;
888         ref* r;
889 
890         if (len < sizeof(INODE_EXTREF) || len < offsetof(INODE_EXTREF, name[0]) + ier->n) {
891             ERR("(%I64x,%x,%I64x) was truncated\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset);
892             return STATUS_INTERNAL_ERROR;
893         }
894 
895         if (ier->dir != SUBVOL_ROOT_INODE) {
896             LIST_ENTRY* le;
897             bool added_dummy;
898 
899             Status = find_send_dir(context, ier->dir, context->root->root_item.ctransid, &sd, &added_dummy);
900             if (!NT_SUCCESS(Status)) {
901                 ERR("find_send_dir returned %08x\n", Status);
902                 return Status;
903             }
904 
905             // directory has higher inode number than file, so might need to be created
906             if (added_dummy) {
907                 bool found = false;
908 
909                 le = context->orphans.Flink;
910                 while (le != &context->orphans) {
911                     o2 = CONTAINING_RECORD(le, orphan, list_entry);
912 
913                     if (o2->inode == ier->dir) {
914                         found = true;
915                         break;
916                     } else if (o2->inode > ier->dir)
917                         break;
918 
919                     le = le->Flink;
920                 }
921 
922                 if (!found) {
923                     ULONG pos = context->datalen;
924 
925                     send_command(context, BTRFS_SEND_CMD_MKDIR);
926 
927                     send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, NULL, sd->name, sd->namelen);
928                     send_add_tlv(context, BTRFS_SEND_TLV_INODE, &ier->dir, sizeof(uint64_t));
929 
930                     send_command_finish(context, pos);
931 
932                     o2 = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG);
933                     if (!o2) {
934                         ERR("out of memory\n");
935                         return STATUS_INSUFFICIENT_RESOURCES;
936                     }
937 
938                     o2->inode = ier->dir;
939                     o2->dir = true;
940                     memcpy(o2->tmpname, sd->name, sd->namelen);
941                     o2->tmpname[sd->namelen] = 0;
942                     o2->sd = sd;
943                     add_orphan(context, o2);
944                 }
945             }
946         } else
947             sd = context->root_dir;
948 
949         r = ExAllocatePoolWithTag(PagedPool, offsetof(ref, name[0]) + ier->n, ALLOC_TAG);
950         if (!r) {
951             ERR("out of memory\n");
952             return STATUS_INSUFFICIENT_RESOURCES;
953         }
954 
955         r->sd = sd;
956         r->namelen = ier->n;
957         RtlCopyMemory(r->name, ier->name, ier->n);
958 
959         InsertTailList(tree2 ? &context->lastinode.oldrefs : &context->lastinode.refs, &r->list_entry);
960 
961         len -= (uint16_t)offsetof(INODE_EXTREF, name[0]) + ier->n;
962         ier = (INODE_EXTREF*)&ier->name[ier->n];
963     }
964 
965     return STATUS_SUCCESS;
966 }
967 
968 static void send_subvol_header(send_context* context, root* r, file_ref* fr) {
969     ULONG pos = context->datalen;
970 
971     send_command(context, context->parent ? BTRFS_SEND_CMD_SNAPSHOT : BTRFS_SEND_CMD_SUBVOL);
972 
973     send_add_tlv(context, BTRFS_SEND_TLV_PATH, fr->dc->utf8.Buffer, fr->dc->utf8.Length);
974 
975     send_add_tlv(context, BTRFS_SEND_TLV_UUID, r->root_item.rtransid == 0 ? &r->root_item.uuid : &r->root_item.received_uuid, sizeof(BTRFS_UUID));
976     send_add_tlv(context, BTRFS_SEND_TLV_TRANSID, &r->root_item.ctransid, sizeof(uint64_t));
977 
978     if (context->parent) {
979         send_add_tlv(context, BTRFS_SEND_TLV_CLONE_UUID,
980                      context->parent->root_item.rtransid == 0 ? &context->parent->root_item.uuid : &context->parent->root_item.received_uuid, sizeof(BTRFS_UUID));
981         send_add_tlv(context, BTRFS_SEND_TLV_CLONE_CTRANSID, &context->parent->root_item.ctransid, sizeof(uint64_t));
982     }
983 
984     send_command_finish(context, pos);
985 }
986 
987 static void send_chown_command(send_context* context, char* path, uint64_t uid, uint64_t gid) {
988     ULONG pos = context->datalen;
989 
990     send_command(context, BTRFS_SEND_CMD_CHOWN);
991 
992     send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0);
993     send_add_tlv(context, BTRFS_SEND_TLV_UID, &uid, sizeof(uint64_t));
994     send_add_tlv(context, BTRFS_SEND_TLV_GID, &gid, sizeof(uint64_t));
995 
996     send_command_finish(context, pos);
997 }
998 
999 static void send_chmod_command(send_context* context, char* path, uint64_t mode) {
1000     ULONG pos = context->datalen;
1001 
1002     send_command(context, BTRFS_SEND_CMD_CHMOD);
1003 
1004     mode &= 07777;
1005 
1006     send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0);
1007     send_add_tlv(context, BTRFS_SEND_TLV_MODE, &mode, sizeof(uint64_t));
1008 
1009     send_command_finish(context, pos);
1010 }
1011 
1012 static void send_utimes_command(send_context* context, char* path, BTRFS_TIME* atime, BTRFS_TIME* mtime, BTRFS_TIME* ctime) {
1013     ULONG pos = context->datalen;
1014 
1015     send_command(context, BTRFS_SEND_CMD_UTIMES);
1016 
1017     send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0);
1018     send_add_tlv(context, BTRFS_SEND_TLV_ATIME, atime, sizeof(BTRFS_TIME));
1019     send_add_tlv(context, BTRFS_SEND_TLV_MTIME, mtime, sizeof(BTRFS_TIME));
1020     send_add_tlv(context, BTRFS_SEND_TLV_CTIME, ctime, sizeof(BTRFS_TIME));
1021 
1022     send_command_finish(context, pos);
1023 }
1024 
1025 static void send_truncate_command(send_context* context, char* path, uint64_t size) {
1026     ULONG pos = context->datalen;
1027 
1028     send_command(context, BTRFS_SEND_CMD_TRUNCATE);
1029 
1030     send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0);
1031     send_add_tlv(context, BTRFS_SEND_TLV_SIZE, &size, sizeof(uint64_t));
1032 
1033     send_command_finish(context, pos);
1034 }
1035 
1036 static NTSTATUS send_unlink_command(send_context* context, send_dir* parent, uint16_t namelen, char* name) {
1037     ULONG pos = context->datalen;
1038     uint16_t pathlen;
1039 
1040     send_command(context, BTRFS_SEND_CMD_UNLINK);
1041 
1042     pathlen = find_path_len(parent, namelen);
1043     send_add_tlv(context, BTRFS_SEND_TLV_PATH, NULL, pathlen);
1044 
1045     find_path((char*)&context->data[context->datalen - pathlen], parent, name, namelen);
1046 
1047     send_command_finish(context, pos);
1048 
1049     return STATUS_SUCCESS;
1050 }
1051 
1052 static void send_rmdir_command(send_context* context, uint16_t pathlen, char* path) {
1053     ULONG pos = context->datalen;
1054 
1055     send_command(context, BTRFS_SEND_CMD_RMDIR);
1056     send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, pathlen);
1057     send_command_finish(context, pos);
1058 }
1059 
1060 static NTSTATUS get_dir_last_child(send_context* context, uint64_t* last_inode) {
1061     NTSTATUS Status;
1062     KEY searchkey;
1063     traverse_ptr tp;
1064 
1065     *last_inode = 0;
1066 
1067     searchkey.obj_id = context->lastinode.inode;
1068     searchkey.obj_type = TYPE_DIR_INDEX;
1069     searchkey.offset = 2;
1070 
1071     Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL);
1072     if (!NT_SUCCESS(Status)) {
1073         ERR("find_item returned %08x\n", Status);
1074         return Status;
1075     }
1076 
1077     do {
1078         traverse_ptr next_tp;
1079 
1080         if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
1081             DIR_ITEM* di = (DIR_ITEM*)tp.item->data;
1082 
1083             if (tp.item->size < sizeof(DIR_ITEM) || tp.item->size < offsetof(DIR_ITEM, name[0]) + di->m + di->n) {
1084                 ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
1085                 return STATUS_INTERNAL_ERROR;
1086             }
1087 
1088             if (di->key.obj_type == TYPE_INODE_ITEM)
1089                 *last_inode = max(*last_inode, di->key.obj_id);
1090         } else
1091             break;
1092 
1093         if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL))
1094             tp = next_tp;
1095         else
1096             break;
1097     } while (true);
1098 
1099     return STATUS_SUCCESS;
1100 }
1101 
1102 static NTSTATUS add_pending_rmdir(send_context* context, uint64_t last_inode) {
1103     pending_rmdir* pr;
1104     LIST_ENTRY* le;
1105 
1106     pr = ExAllocatePoolWithTag(PagedPool, sizeof(pending_rmdir), ALLOC_TAG);
1107     if (!pr) {
1108         ERR("out of memory\n");
1109         return STATUS_INSUFFICIENT_RESOURCES;
1110     }
1111 
1112     pr->sd = context->lastinode.sd;
1113     pr->last_child_inode = last_inode;
1114 
1115     le = context->pending_rmdirs.Flink;
1116     while (le != &context->pending_rmdirs) {
1117         pending_rmdir* pr2 = CONTAINING_RECORD(le, pending_rmdir, list_entry);
1118 
1119         if (pr2->last_child_inode > pr->last_child_inode) {
1120             InsertHeadList(pr2->list_entry.Blink, &pr->list_entry);
1121             return STATUS_SUCCESS;
1122         }
1123 
1124         le = le->Flink;
1125     }
1126 
1127     InsertTailList(&context->pending_rmdirs, &pr->list_entry);
1128 
1129     return STATUS_SUCCESS;
1130 }
1131 
1132 static NTSTATUS look_for_collision(send_context* context, send_dir* sd, char* name, ULONG namelen, uint64_t* inode, bool* dir) {
1133     NTSTATUS Status;
1134     KEY searchkey;
1135     traverse_ptr tp;
1136     DIR_ITEM* di;
1137     uint16_t len;
1138 
1139     searchkey.obj_id = sd->inode;
1140     searchkey.obj_type = TYPE_DIR_ITEM;
1141     searchkey.offset = calc_crc32c(0xfffffffe, (uint8_t*)name, namelen);
1142 
1143     Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL);
1144     if (!NT_SUCCESS(Status)) {
1145         ERR("find_item returned %08x\n", Status);
1146         return Status;
1147     }
1148 
1149     if (keycmp(tp.item->key, searchkey))
1150         return STATUS_SUCCESS;
1151 
1152     di = (DIR_ITEM*)tp.item->data;
1153     len = tp.item->size;
1154 
1155     do {
1156         if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) {
1157             ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
1158             return STATUS_INTERNAL_ERROR;
1159         }
1160 
1161         if (di->n == namelen && RtlCompareMemory(di->name, name, namelen) == namelen) {
1162             *inode = di->key.obj_type == TYPE_INODE_ITEM ? di->key.obj_id : 0;
1163             *dir = di->type == BTRFS_TYPE_DIRECTORY ? true: false;
1164             return STATUS_OBJECT_NAME_COLLISION;
1165         }
1166 
1167         di = (DIR_ITEM*)&di->name[di->m + di->n];
1168         len -= (uint16_t)offsetof(DIR_ITEM, name[0]) + di->m + di->n;
1169     } while (len > 0);
1170 
1171     return STATUS_SUCCESS;
1172 }
1173 
1174 static NTSTATUS make_file_orphan(send_context* context, uint64_t inode, bool dir, uint64_t generation, ref* r) {
1175     NTSTATUS Status;
1176     ULONG pos = context->datalen;
1177     send_dir* sd = NULL;
1178     orphan* o;
1179     LIST_ENTRY* le;
1180     char name[64];
1181 
1182     if (!dir) {
1183         deleted_child* dc;
1184 
1185         dc = ExAllocatePoolWithTag(PagedPool, offsetof(deleted_child, name[0]) + r->namelen, ALLOC_TAG);
1186         if (!dc) {
1187             ERR("out of memory\n");
1188             return STATUS_INSUFFICIENT_RESOURCES;
1189         }
1190 
1191         dc->namelen = r->namelen;
1192         RtlCopyMemory(dc->name, r->name, r->namelen);
1193         InsertTailList(&r->sd->deleted_children, &dc->list_entry);
1194     }
1195 
1196     le = context->orphans.Flink;
1197     while (le != &context->orphans) {
1198         orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry);
1199 
1200         if (o2->inode == inode) {
1201             send_command(context, BTRFS_SEND_CMD_UNLINK);
1202 
1203             send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, r->sd, r->name, r->namelen);
1204 
1205             send_command_finish(context, pos);
1206 
1207             return STATUS_SUCCESS;
1208         } else if (o2->inode > inode)
1209             break;
1210 
1211         le = le->Flink;
1212     }
1213 
1214     Status = get_orphan_name(context, inode, generation, name);
1215     if (!NT_SUCCESS(Status)) {
1216         ERR("get_orphan_name returned %08x\n", Status);
1217         return Status;
1218     }
1219 
1220     if (dir) {
1221         Status = find_send_dir(context, inode, generation, &sd, NULL);
1222         if (!NT_SUCCESS(Status)) {
1223             ERR("find_send_dir returned %08x\n", Status);
1224             return Status;
1225         }
1226 
1227         sd->dummy = true;
1228 
1229         send_command(context, BTRFS_SEND_CMD_RENAME);
1230 
1231         send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, r->sd, r->name, r->namelen);
1232         send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, context->root_dir, name, (uint16_t)strlen(name));
1233 
1234         send_command_finish(context, pos);
1235 
1236         if (sd->name)
1237             ExFreePool(sd->name);
1238 
1239         sd->namelen = (uint16_t)strlen(name);
1240         sd->name = ExAllocatePoolWithTag(PagedPool, sd->namelen, ALLOC_TAG);
1241         if (!sd->name) {
1242             ERR("out of memory\n");
1243             return STATUS_INSUFFICIENT_RESOURCES;
1244         }
1245 
1246         RtlCopyMemory(sd->name, name, sd->namelen);
1247         sd->parent = context->root_dir;
1248     } else {
1249         send_command(context, BTRFS_SEND_CMD_RENAME);
1250 
1251         send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, r->sd, r->name, r->namelen);
1252 
1253         send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, context->root_dir, name, (uint16_t)strlen(name));
1254 
1255         send_command_finish(context, pos);
1256     }
1257 
1258     o = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG);
1259     if (!o) {
1260         ERR("out of memory\n");
1261         return STATUS_INSUFFICIENT_RESOURCES;
1262     }
1263 
1264     o->inode = inode;
1265     o->dir = true;
1266     strcpy(o->tmpname, name);
1267     o->sd = sd;
1268     add_orphan(context, o);
1269 
1270     return STATUS_SUCCESS;
1271 }
1272 
1273 static NTSTATUS flush_refs(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) {
1274     NTSTATUS Status;
1275     LIST_ENTRY* le;
1276     ref *nameref = NULL, *nameref2 = NULL;
1277 
1278     if (context->lastinode.mode & __S_IFDIR) { // directory
1279         ref* r = IsListEmpty(&context->lastinode.refs) ? NULL : CONTAINING_RECORD(context->lastinode.refs.Flink, ref, list_entry);
1280         ref* or = IsListEmpty(&context->lastinode.oldrefs) ? NULL : CONTAINING_RECORD(context->lastinode.oldrefs.Flink, ref, list_entry);
1281 
1282         if (or && !context->lastinode.o) {
1283             ULONG len = find_path_len(or->sd, or->namelen);
1284 
1285             context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG);
1286             if (!context->lastinode.path) {
1287                 ERR("out of memory\n");
1288                 return STATUS_INSUFFICIENT_RESOURCES;
1289             }
1290 
1291             find_path(context->lastinode.path, or->sd, or->name, or->namelen);
1292             context->lastinode.path[len] = 0;
1293 
1294             if (!context->lastinode.sd) {
1295                 Status = find_send_dir(context, context->lastinode.inode, context->lastinode.gen, &context->lastinode.sd, false);
1296                 if (!NT_SUCCESS(Status)) {
1297                     ERR("find_send_dir returned %08x\n", Status);
1298                     return Status;
1299                 }
1300             }
1301         }
1302 
1303         if (r && or) {
1304             uint64_t inode;
1305             bool dir;
1306 
1307             Status = look_for_collision(context, r->sd, r->name, r->namelen, &inode, &dir);
1308             if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_COLLISION) {
1309                 ERR("look_for_collision returned %08x\n", Status);
1310                 return Status;
1311             }
1312 
1313             if (Status == STATUS_OBJECT_NAME_COLLISION && inode > context->lastinode.inode) {
1314                 Status = make_file_orphan(context, inode, dir, context->parent->root_item.ctransid, r);
1315                 if (!NT_SUCCESS(Status)) {
1316                     ERR("make_file_orphan returned %08x\n", Status);
1317                     return Status;
1318                 }
1319             }
1320 
1321             if (context->lastinode.o) {
1322                 Status = found_path(context, r->sd, r->name, r->namelen);
1323                 if (!NT_SUCCESS(Status)) {
1324                     ERR("found_path returned %08x\n", Status);
1325                     return Status;
1326                 }
1327 
1328                 if (!r->sd->dummy)
1329                     send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime);
1330             } else if (r->sd != or->sd || r->namelen != or->namelen || RtlCompareMemory(r->name, or->name, r->namelen) != r->namelen) { // moved or renamed
1331                 ULONG pos = context->datalen, len;
1332 
1333                 send_command(context, BTRFS_SEND_CMD_RENAME);
1334 
1335                 send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, (uint16_t)strlen(context->lastinode.path));
1336 
1337                 send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, r->sd, r->name, r->namelen);
1338 
1339                 send_command_finish(context, pos);
1340 
1341                 if (!r->sd->dummy)
1342                     send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime);
1343 
1344                 if (context->lastinode.sd->name)
1345                     ExFreePool(context->lastinode.sd->name);
1346 
1347                 context->lastinode.sd->name = ExAllocatePoolWithTag(PagedPool, r->namelen, ALLOC_TAG);
1348                 if (!context->lastinode.sd->name) {
1349                     ERR("out of memory\n");
1350                     return STATUS_INSUFFICIENT_RESOURCES;
1351                 }
1352 
1353                 RtlCopyMemory(context->lastinode.sd->name, r->name, r->namelen);
1354                 context->lastinode.sd->parent = r->sd;
1355 
1356                 if (context->lastinode.path)
1357                     ExFreePool(context->lastinode.path);
1358 
1359                 len = find_path_len(r->sd, r->namelen);
1360                 context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG);
1361                 if (!context->lastinode.path) {
1362                     ERR("out of memory\n");
1363                     return STATUS_INSUFFICIENT_RESOURCES;
1364                 }
1365 
1366                 find_path(context->lastinode.path, r->sd, r->name, r->namelen);
1367                 context->lastinode.path[len] = 0;
1368             }
1369         } else if (r && !or) { // new
1370             Status = found_path(context, r->sd, r->name, r->namelen);
1371             if (!NT_SUCCESS(Status)) {
1372                 ERR("found_path returned %08x\n", Status);
1373                 return Status;
1374             }
1375 
1376             if (!r->sd->dummy)
1377                 send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime);
1378         } else { // deleted
1379             uint64_t last_inode;
1380 
1381             Status = get_dir_last_child(context, &last_inode);
1382             if (!NT_SUCCESS(Status)) {
1383                 ERR("get_dir_last_child returned %08x\n", Status);
1384                 return Status;
1385             }
1386 
1387             if (last_inode <= context->lastinode.inode) {
1388                 send_rmdir_command(context, (uint16_t)strlen(context->lastinode.path), context->lastinode.path);
1389 
1390                 if (!or->sd->dummy)
1391                     send_utimes_command_dir(context, or->sd, &or->sd->atime, &or->sd->mtime, &or->sd->ctime);
1392             } else {
1393                 char name[64];
1394                 ULONG pos = context->datalen;
1395 
1396                 Status = get_orphan_name(context, context->lastinode.inode, context->lastinode.gen, name);
1397                 if (!NT_SUCCESS(Status)) {
1398                     ERR("get_orphan_name returned %08x\n", Status);
1399                     return Status;
1400                 }
1401 
1402                 send_command(context, BTRFS_SEND_CMD_RENAME);
1403                 send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, (uint16_t)strlen(context->lastinode.path));
1404                 send_add_tlv(context, BTRFS_SEND_TLV_PATH_TO, name, (uint16_t)strlen(name));
1405                 send_command_finish(context, pos);
1406 
1407                 if (context->lastinode.sd->name)
1408                     ExFreePool(context->lastinode.sd->name);
1409 
1410                 context->lastinode.sd->name = ExAllocatePoolWithTag(PagedPool, strlen(name), ALLOC_TAG);
1411                 if (!context->lastinode.sd->name) {
1412                     ERR("out of memory\n");
1413                     return STATUS_INSUFFICIENT_RESOURCES;
1414                 }
1415 
1416                 RtlCopyMemory(context->lastinode.sd->name, name, strlen(name));
1417                 context->lastinode.sd->namelen = (uint16_t)strlen(name);
1418                 context->lastinode.sd->dummy = true;
1419                 context->lastinode.sd->parent = NULL;
1420 
1421                 send_utimes_command(context, NULL, &context->root_dir->atime, &context->root_dir->mtime, &context->root_dir->ctime);
1422 
1423                 Status = add_pending_rmdir(context, last_inode);
1424                 if (!NT_SUCCESS(Status)) {
1425                     ERR("add_pending_rmdir returned %08x\n", Status);
1426                     return Status;
1427                 }
1428             }
1429         }
1430 
1431         while (!IsListEmpty(&context->lastinode.refs)) {
1432             r = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.refs), ref, list_entry);
1433             ExFreePool(r);
1434         }
1435 
1436         while (!IsListEmpty(&context->lastinode.oldrefs)) {
1437             or = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldrefs), ref, list_entry);
1438             ExFreePool(or);
1439         }
1440 
1441         return STATUS_SUCCESS;
1442     } else {
1443         if (!IsListEmpty(&context->lastinode.oldrefs)) {
1444             ref* or = CONTAINING_RECORD(context->lastinode.oldrefs.Flink, ref, list_entry);
1445             ULONG len = find_path_len(or->sd, or->namelen);
1446 
1447             context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG);
1448             if (!context->lastinode.path) {
1449                 ERR("out of memory\n");
1450                 return STATUS_INSUFFICIENT_RESOURCES;
1451             }
1452 
1453             find_path(context->lastinode.path, or->sd, or->name, or->namelen);
1454             context->lastinode.path[len] = 0;
1455             nameref = or;
1456         }
1457 
1458         // remove unchanged refs
1459         le = context->lastinode.oldrefs.Flink;
1460         while (le != &context->lastinode.oldrefs) {
1461             ref* or = CONTAINING_RECORD(le, ref, list_entry);
1462             LIST_ENTRY* le2;
1463             bool matched = false;
1464 
1465             le2 = context->lastinode.refs.Flink;
1466             while (le2 != &context->lastinode.refs) {
1467                 ref* r = CONTAINING_RECORD(le2, ref, list_entry);
1468 
1469                 if (r->sd == or->sd && r->namelen == or->namelen && RtlCompareMemory(r->name, or->name, r->namelen) == r->namelen) {
1470                     RemoveEntryList(&r->list_entry);
1471                     ExFreePool(r);
1472                     matched = true;
1473                     break;
1474                 }
1475 
1476                 le2 = le2->Flink;
1477             }
1478 
1479             if (matched) {
1480                 le = le->Flink;
1481                 RemoveEntryList(&or->list_entry);
1482                 ExFreePool(or);
1483                 continue;
1484             }
1485 
1486             le = le->Flink;
1487         }
1488 
1489         while (!IsListEmpty(&context->lastinode.refs)) {
1490             ref* r = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.refs), ref, list_entry);
1491             uint64_t inode;
1492             bool dir;
1493 
1494             if (context->parent) {
1495                 Status = look_for_collision(context, r->sd, r->name, r->namelen, &inode, &dir);
1496                 if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_COLLISION) {
1497                     ERR("look_for_collision returned %08x\n", Status);
1498                     return Status;
1499                 }
1500 
1501                 if (Status == STATUS_OBJECT_NAME_COLLISION && inode > context->lastinode.inode) {
1502                     Status = make_file_orphan(context, inode, dir, context->lastinode.gen, r);
1503                     if (!NT_SUCCESS(Status)) {
1504                         ERR("make_file_orphan returned %08x\n", Status);
1505                         return Status;
1506                     }
1507                 }
1508             }
1509 
1510             if (context->datalen > SEND_BUFFER_LENGTH) {
1511                 Status = wait_for_flush(context, tp1, tp2);
1512                 if (!NT_SUCCESS(Status)) {
1513                     ERR("wait_for_flush returned %08x\n", Status);
1514                     return Status;
1515                 }
1516 
1517                 if (context->send->cancelling)
1518                     return STATUS_SUCCESS;
1519             }
1520 
1521             Status = found_path(context, r->sd, r->name, r->namelen);
1522             if (!NT_SUCCESS(Status)) {
1523                 ERR("found_path returned %08x\n", Status);
1524                 return Status;
1525             }
1526 
1527             if (!r->sd->dummy)
1528                 send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime);
1529 
1530             if (nameref && !nameref2)
1531                 nameref2 = r;
1532             else
1533                 ExFreePool(r);
1534         }
1535 
1536         while (!IsListEmpty(&context->lastinode.oldrefs)) {
1537             ref* or = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldrefs), ref, list_entry);
1538             bool deleted = false;
1539 
1540             le = or->sd->deleted_children.Flink;
1541             while (le != &or->sd->deleted_children) {
1542                 deleted_child* dc = CONTAINING_RECORD(le, deleted_child, list_entry);
1543 
1544                 if (dc->namelen == or->namelen && RtlCompareMemory(dc->name, or->name, or->namelen) == or->namelen) {
1545                     RemoveEntryList(&dc->list_entry);
1546                     ExFreePool(dc);
1547                     deleted = true;
1548                     break;
1549                 }
1550 
1551                 le = le->Flink;
1552             }
1553 
1554             if (!deleted) {
1555                 if (context->datalen > SEND_BUFFER_LENGTH) {
1556                     Status = wait_for_flush(context, tp1, tp2);
1557                     if (!NT_SUCCESS(Status)) {
1558                         ERR("wait_for_flush returned %08x\n", Status);
1559                         return Status;
1560                     }
1561 
1562                     if (context->send->cancelling)
1563                         return STATUS_SUCCESS;
1564                 }
1565 
1566                 Status = send_unlink_command(context, or->sd, or->namelen, or->name);
1567                 if (!NT_SUCCESS(Status)) {
1568                     ERR("send_unlink_command returned %08x\n", Status);
1569                     return Status;
1570                 }
1571 
1572                 if (!or->sd->dummy)
1573                     send_utimes_command_dir(context, or->sd, &or->sd->atime, &or->sd->mtime, &or->sd->ctime);
1574             }
1575 
1576             if (or == nameref && nameref2) {
1577                 uint16_t len = find_path_len(nameref2->sd, nameref2->namelen);
1578 
1579                 if (context->lastinode.path)
1580                     ExFreePool(context->lastinode.path);
1581 
1582                 context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG);
1583                 if (!context->lastinode.path) {
1584                     ERR("out of memory\n");
1585                     return STATUS_INSUFFICIENT_RESOURCES;
1586                 }
1587 
1588                 find_path(context->lastinode.path, nameref2->sd, nameref2->name, nameref2->namelen);
1589                 context->lastinode.path[len] = 0;
1590 
1591                 ExFreePool(nameref2);
1592             }
1593 
1594             ExFreePool(or);
1595         }
1596     }
1597 
1598     return STATUS_SUCCESS;
1599 }
1600 
1601 static NTSTATUS wait_for_flush(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) {
1602     NTSTATUS Status;
1603     KEY key1, key2;
1604 
1605     if (tp1)
1606         key1 = tp1->item->key;
1607 
1608     if (tp2)
1609         key2 = tp2->item->key;
1610 
1611     ExReleaseResourceLite(&context->Vcb->tree_lock);
1612 
1613     KeClearEvent(&context->send->cleared_event);
1614     KeSetEvent(&context->buffer_event, 0, true);
1615     KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL);
1616 
1617     ExAcquireResourceSharedLite(&context->Vcb->tree_lock, true);
1618 
1619     if (context->send->cancelling)
1620         return STATUS_SUCCESS;
1621 
1622     if (tp1) {
1623         Status = find_item(context->Vcb, context->root, tp1, &key1, false, NULL);
1624         if (!NT_SUCCESS(Status)) {
1625             ERR("find_item returned %08x\n", Status);
1626             return Status;
1627         }
1628 
1629         if (keycmp(tp1->item->key, key1)) {
1630             ERR("readonly subvolume changed\n");
1631             return STATUS_INTERNAL_ERROR;
1632         }
1633     }
1634 
1635     if (tp2) {
1636         Status = find_item(context->Vcb, context->parent, tp2, &key2, false, NULL);
1637         if (!NT_SUCCESS(Status)) {
1638             ERR("find_item returned %08x\n", Status);
1639             return Status;
1640         }
1641 
1642         if (keycmp(tp2->item->key, key2)) {
1643             ERR("readonly subvolume changed\n");
1644             return STATUS_INTERNAL_ERROR;
1645         }
1646     }
1647 
1648     return STATUS_SUCCESS;
1649 }
1650 
1651 static NTSTATUS add_ext_holes(device_extension* Vcb, LIST_ENTRY* exts, uint64_t size) {
1652     uint64_t lastoff = 0;
1653     LIST_ENTRY* le;
1654 
1655     le = exts->Flink;
1656     while (le != exts) {
1657         send_ext* ext = CONTAINING_RECORD(le, send_ext, list_entry);
1658 
1659         if (ext->offset > lastoff) {
1660             send_ext* ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data.data) + sizeof(EXTENT_DATA2), ALLOC_TAG);
1661             EXTENT_DATA2* ed2;
1662 
1663             if (!ext2) {
1664                 ERR("out of memory\n");
1665                 return STATUS_INSUFFICIENT_RESOURCES;
1666             }
1667 
1668             ed2 = (EXTENT_DATA2*)ext2->data.data;
1669 
1670             ext2->offset = lastoff;
1671             ext2->datalen = offsetof(EXTENT_DATA, data) + sizeof(EXTENT_DATA2);
1672             ext2->data.decoded_size = ed2->num_bytes = ext->offset - lastoff;
1673             ext2->data.type = EXTENT_TYPE_REGULAR;
1674             ed2->address = ed2->size = ed2->offset = 0;
1675 
1676             InsertHeadList(le->Blink, &ext2->list_entry);
1677         }
1678 
1679         if (ext->data.type == EXTENT_TYPE_INLINE)
1680             lastoff = ext->offset + ext->data.decoded_size;
1681         else {
1682             EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->data.data;
1683             lastoff = ext->offset + ed2->num_bytes;
1684         }
1685 
1686         le = le->Flink;
1687     }
1688 
1689     if (size > lastoff) {
1690         send_ext* ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data.data) + sizeof(EXTENT_DATA2), ALLOC_TAG);
1691         EXTENT_DATA2* ed2;
1692 
1693         if (!ext2) {
1694             ERR("out of memory\n");
1695             return STATUS_INSUFFICIENT_RESOURCES;
1696         }
1697 
1698         ed2 = (EXTENT_DATA2*)ext2->data.data;
1699 
1700         ext2->offset = lastoff;
1701         ext2->datalen = offsetof(EXTENT_DATA, data) + sizeof(EXTENT_DATA2);
1702         ext2->data.decoded_size = ed2->num_bytes = sector_align(size - lastoff, Vcb->superblock.sector_size);
1703         ext2->data.type = EXTENT_TYPE_REGULAR;
1704         ed2->address = ed2->size = ed2->offset = 0;
1705 
1706         InsertTailList(exts, &ext2->list_entry);
1707     }
1708 
1709     return STATUS_SUCCESS;
1710 }
1711 
1712 static NTSTATUS divide_ext(send_ext* ext, uint64_t len, bool trunc) {
1713     send_ext* ext2;
1714     EXTENT_DATA2 *ed2a, *ed2b;
1715 
1716     if (ext->data.type == EXTENT_TYPE_INLINE) {
1717         if (!trunc) {
1718             ext2 = ExAllocatePoolWithTag(PagedPool, (ULONG)(offsetof(send_ext, data.data) + ext->data.decoded_size - len), ALLOC_TAG);
1719 
1720             if (!ext2) {
1721                 ERR("out of memory\n");
1722                 return STATUS_INSUFFICIENT_RESOURCES;
1723             }
1724 
1725             ext2->offset = ext->offset + len;
1726             ext2->datalen = (ULONG)(ext->data.decoded_size - len);
1727             ext2->data.decoded_size = ext->data.decoded_size - len;
1728             ext2->data.compression = ext->data.compression;
1729             ext2->data.encryption = ext->data.encryption;
1730             ext2->data.encoding = ext->data.encoding;
1731             ext2->data.type = ext->data.type;
1732             RtlCopyMemory(ext2->data.data, ext->data.data + len, (ULONG)(ext->data.decoded_size - len));
1733 
1734             InsertHeadList(&ext->list_entry, &ext2->list_entry);
1735         }
1736 
1737         ext->data.decoded_size = len;
1738 
1739         return STATUS_SUCCESS;
1740     }
1741 
1742     ed2a = (EXTENT_DATA2*)ext->data.data;
1743 
1744     if (!trunc) {
1745         ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data.data) + sizeof(EXTENT_DATA2), ALLOC_TAG);
1746 
1747         if (!ext2) {
1748             ERR("out of memory\n");
1749             return STATUS_INSUFFICIENT_RESOURCES;
1750         }
1751 
1752         ed2b = (EXTENT_DATA2*)ext2->data.data;
1753 
1754         ext2->offset = ext->offset + len;
1755         ext2->datalen = offsetof(EXTENT_DATA, data) + sizeof(EXTENT_DATA2);
1756 
1757         ext2->data.compression = ext->data.compression;
1758         ext2->data.encryption = ext->data.encryption;
1759         ext2->data.encoding = ext->data.encoding;
1760         ext2->data.type = ext->data.type;
1761         ed2b->num_bytes = ed2a->num_bytes - len;
1762 
1763         if (ed2a->size == 0) {
1764             ext2->data.decoded_size = ed2b->num_bytes;
1765             ext->data.decoded_size = len;
1766 
1767             ed2b->address = ed2b->size = ed2b->offset = 0;
1768         } else {
1769             ext2->data.decoded_size = ext->data.decoded_size;
1770 
1771             ed2b->address = ed2a->address;
1772             ed2b->size = ed2a->size;
1773             ed2b->offset = ed2a->offset + len;
1774         }
1775 
1776         InsertHeadList(&ext->list_entry, &ext2->list_entry);
1777     }
1778 
1779     ed2a->num_bytes = len;
1780 
1781     return STATUS_SUCCESS;
1782 }
1783 
1784 static NTSTATUS sync_ext_cutoff_points(send_context* context) {
1785     NTSTATUS Status;
1786     send_ext *ext1, *ext2;
1787 
1788     ext1 = CONTAINING_RECORD(context->lastinode.exts.Flink, send_ext, list_entry);
1789     ext2 = CONTAINING_RECORD(context->lastinode.oldexts.Flink, send_ext, list_entry);
1790 
1791     do {
1792         uint64_t len1, len2;
1793         EXTENT_DATA2 *ed2a, *ed2b;
1794 
1795         ed2a = ext1->data.type == EXTENT_TYPE_INLINE ? NULL : (EXTENT_DATA2*)ext1->data.data;
1796         ed2b = ext2->data.type == EXTENT_TYPE_INLINE ? NULL : (EXTENT_DATA2*)ext2->data.data;
1797 
1798         len1 = ed2a ? ed2a->num_bytes : ext1->data.decoded_size;
1799         len2 = ed2b ? ed2b->num_bytes : ext2->data.decoded_size;
1800 
1801         if (len1 < len2) {
1802             Status = divide_ext(ext2, len1, false);
1803             if (!NT_SUCCESS(Status)) {
1804                 ERR("divide_ext returned %08x\n", Status);
1805                 return Status;
1806             }
1807         } else if (len2 < len1) {
1808             Status = divide_ext(ext1, len2, false);
1809             if (!NT_SUCCESS(Status)) {
1810                 ERR("divide_ext returned %08x\n", Status);
1811                 return Status;
1812             }
1813         }
1814 
1815         if (ext1->list_entry.Flink == &context->lastinode.exts || ext2->list_entry.Flink == &context->lastinode.oldexts)
1816             break;
1817 
1818         ext1 = CONTAINING_RECORD(ext1->list_entry.Flink, send_ext, list_entry);
1819         ext2 = CONTAINING_RECORD(ext2->list_entry.Flink, send_ext, list_entry);
1820     } while (true);
1821 
1822     ext1 = CONTAINING_RECORD(context->lastinode.exts.Blink, send_ext, list_entry);
1823     ext2 = CONTAINING_RECORD(context->lastinode.oldexts.Blink, send_ext, list_entry);
1824 
1825     Status = divide_ext(ext1, context->lastinode.size - ext1->offset, true);
1826     if (!NT_SUCCESS(Status)) {
1827         ERR("divide_ext returned %08x\n", Status);
1828         return Status;
1829     }
1830 
1831     Status = divide_ext(ext2, context->lastinode.size - ext2->offset, true);
1832     if (!NT_SUCCESS(Status)) {
1833         ERR("divide_ext returned %08x\n", Status);
1834         return Status;
1835     }
1836 
1837     return STATUS_SUCCESS;
1838 }
1839 
1840 static bool send_add_tlv_clone_path(send_context* context, root* r, uint64_t inode) {
1841     NTSTATUS Status;
1842     KEY searchkey;
1843     traverse_ptr tp;
1844     uint16_t len = 0;
1845     uint64_t num;
1846     uint8_t* ptr;
1847 
1848     num = inode;
1849 
1850     while (num != SUBVOL_ROOT_INODE) {
1851         searchkey.obj_id = num;
1852         searchkey.obj_type = TYPE_INODE_EXTREF;
1853         searchkey.offset = 0xffffffffffffffff;
1854 
1855         Status = find_item(context->Vcb, r, &tp, &searchkey, false, NULL);
1856         if (!NT_SUCCESS(Status)) {
1857             ERR("find_item returned %08x\n", Status);
1858             return false;
1859         }
1860 
1861         if (tp.item->key.obj_id != searchkey.obj_id || (tp.item->key.obj_type != TYPE_INODE_REF && tp.item->key.obj_type != TYPE_INODE_EXTREF)) {
1862             ERR("could not find INODE_REF for inode %I64x\n", searchkey.obj_id);
1863             return false;
1864         }
1865 
1866         if (len > 0)
1867             len++;
1868 
1869         if (tp.item->key.obj_type == TYPE_INODE_REF) {
1870             INODE_REF* ir = (INODE_REF*)tp.item->data;
1871 
1872             if (tp.item->size < sizeof(INODE_REF) || tp.item->size < offsetof(INODE_REF, name[0]) + ir->n) {
1873                 ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
1874                 return false;
1875             }
1876 
1877             len += ir->n;
1878             num = tp.item->key.offset;
1879         } else {
1880             INODE_EXTREF* ier = (INODE_EXTREF*)tp.item->data;
1881 
1882             if (tp.item->size < sizeof(INODE_EXTREF) || tp.item->size < offsetof(INODE_EXTREF, name[0]) + ier->n) {
1883                 ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
1884                 return false;
1885             }
1886 
1887             len += ier->n;
1888             num = ier->dir;
1889         }
1890     }
1891 
1892     send_add_tlv(context, BTRFS_SEND_TLV_CLONE_PATH, NULL, len);
1893     ptr = &context->data[context->datalen];
1894 
1895     num = inode;
1896 
1897     while (num != SUBVOL_ROOT_INODE) {
1898         searchkey.obj_id = num;
1899         searchkey.obj_type = TYPE_INODE_EXTREF;
1900         searchkey.offset = 0xffffffffffffffff;
1901 
1902         Status = find_item(context->Vcb, r, &tp, &searchkey, false, NULL);
1903         if (!NT_SUCCESS(Status)) {
1904             ERR("find_item returned %08x\n", Status);
1905             return false;
1906         }
1907 
1908         if (tp.item->key.obj_id != searchkey.obj_id || (tp.item->key.obj_type != TYPE_INODE_REF && tp.item->key.obj_type != TYPE_INODE_EXTREF)) {
1909             ERR("could not find INODE_REF for inode %I64x\n", searchkey.obj_id);
1910             return false;
1911         }
1912 
1913         if (num != inode) {
1914             ptr--;
1915             *ptr = '/';
1916         }
1917 
1918         if (tp.item->key.obj_type == TYPE_INODE_REF) {
1919             INODE_REF* ir = (INODE_REF*)tp.item->data;
1920 
1921             RtlCopyMemory(ptr - ir->n, ir->name, ir->n);
1922             ptr -= ir->n;
1923             num = tp.item->key.offset;
1924         } else {
1925             INODE_EXTREF* ier = (INODE_EXTREF*)tp.item->data;
1926 
1927             RtlCopyMemory(ptr - ier->n, ier->name, ier->n);
1928             ptr -= ier->n;
1929             num = ier->dir;
1930         }
1931     }
1932 
1933     return true;
1934 }
1935 
1936 static bool try_clone_edr(send_context* context, send_ext* se, EXTENT_DATA_REF* edr) {
1937     NTSTATUS Status;
1938     root* r = NULL;
1939     KEY searchkey;
1940     traverse_ptr tp;
1941     EXTENT_DATA2* seed2 = (EXTENT_DATA2*)se->data.data;
1942 
1943     if (context->parent && edr->root == context->parent->id)
1944         r = context->parent;
1945 
1946     if (!r && context->num_clones > 0) {
1947         ULONG i;
1948 
1949         for (i = 0; i < context->num_clones; i++) {
1950             if (context->clones[i]->id == edr->root && context->clones[i] != context->root) {
1951                 r = context->clones[i];
1952                 break;
1953             }
1954         }
1955     }
1956 
1957     if (!r)
1958         return false;
1959 
1960     searchkey.obj_id = edr->objid;
1961     searchkey.obj_type = TYPE_EXTENT_DATA;
1962     searchkey.offset = 0;
1963 
1964     Status = find_item(context->Vcb, r, &tp, &searchkey, false, NULL);
1965     if (!NT_SUCCESS(Status)) {
1966         ERR("find_item returned %08x\n", Status);
1967         return false;
1968     }
1969 
1970     while (true) {
1971         traverse_ptr next_tp;
1972 
1973         if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
1974             if (tp.item->size < sizeof(EXTENT_DATA))
1975                 ERR("(%I64x,%x,%I64x) has size %u, not at least %u as expected\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));
1976             else {
1977                 EXTENT_DATA* ed = (EXTENT_DATA*)tp.item->data;
1978 
1979                 if (ed->type == EXTENT_TYPE_REGULAR) {
1980                     if (tp.item->size < offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2))
1981                         ERR("(%I64x,%x,%I64x) has size %u, not %u as expected\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,
1982                             tp.item->size, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2));
1983                     else {
1984                         EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
1985 
1986                         if (ed2->address == seed2->address && ed2->size == seed2->size && seed2->offset <= ed2->offset && seed2->offset + seed2->num_bytes >= ed2->offset + ed2->num_bytes) {
1987                             uint64_t clone_offset = tp.item->key.offset + ed2->offset - seed2->offset;
1988                             uint64_t clone_len = min(context->lastinode.size - se->offset, ed2->num_bytes);
1989 
1990                             if (clone_offset % context->Vcb->superblock.sector_size == 0 && clone_len % context->Vcb->superblock.sector_size == 0) {
1991                                 ULONG pos = context->datalen;
1992 
1993                                 send_command(context, BTRFS_SEND_CMD_CLONE);
1994 
1995                                 send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &se->offset, sizeof(uint64_t));
1996                                 send_add_tlv(context, BTRFS_SEND_TLV_CLONE_LENGTH, &clone_len, sizeof(uint64_t));
1997                                 send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
1998                                 send_add_tlv(context, BTRFS_SEND_TLV_CLONE_UUID, r->root_item.rtransid == 0 ? &r->root_item.uuid : &r->root_item.received_uuid, sizeof(BTRFS_UUID));
1999                                 send_add_tlv(context, BTRFS_SEND_TLV_CLONE_CTRANSID, &r->root_item.ctransid, sizeof(uint64_t));
2000 
2001                                 if (!send_add_tlv_clone_path(context, r, tp.item->key.obj_id))
2002                                     context->datalen = pos;
2003                                 else {
2004                                     send_add_tlv(context, BTRFS_SEND_TLV_CLONE_OFFSET, &clone_offset, sizeof(uint64_t));
2005 
2006                                     send_command_finish(context, pos);
2007 
2008                                     return true;
2009                                 }
2010                             }
2011                         }
2012                     }
2013                 }
2014             }
2015         } else if (tp.item->key.obj_id > searchkey.obj_id || (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type > searchkey.obj_type))
2016             break;
2017 
2018         if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL))
2019             tp = next_tp;
2020         else
2021             break;
2022     }
2023 
2024     return false;
2025 }
2026 
2027 static bool try_clone(send_context* context, send_ext* se) {
2028     NTSTATUS Status;
2029     KEY searchkey;
2030     traverse_ptr tp;
2031     EXTENT_DATA2* ed2 = (EXTENT_DATA2*)se->data.data;
2032     EXTENT_ITEM* ei;
2033     uint64_t rc = 0;
2034 
2035     searchkey.obj_id = ed2->address;
2036     searchkey.obj_type = TYPE_EXTENT_ITEM;
2037     searchkey.offset = ed2->size;
2038 
2039     Status = find_item(context->Vcb, context->Vcb->extent_root, &tp, &searchkey, false, NULL);
2040     if (!NT_SUCCESS(Status)) {
2041         ERR("find_item returned %08x\n", Status);
2042         return false;
2043     }
2044 
2045     if (keycmp(tp.item->key, searchkey)) {
2046         ERR("(%I64x,%x,%I64x) not found\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset);
2047         return false;
2048     }
2049 
2050     if (tp.item->size < sizeof(EXTENT_ITEM)) {
2051         ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));
2052         return false;
2053     }
2054 
2055     ei = (EXTENT_ITEM*)tp.item->data;
2056 
2057     if (tp.item->size > sizeof(EXTENT_ITEM)) {
2058         uint32_t len = tp.item->size - sizeof(EXTENT_ITEM);
2059         uint8_t* ptr = (uint8_t*)&ei[1];
2060 
2061         while (len > 0) {
2062             uint8_t secttype = *ptr;
2063             ULONG sectlen = get_extent_data_len(secttype);
2064             uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));
2065 
2066             len--;
2067 
2068             if (sectlen > len) {
2069                 ERR("(%I64x,%x,%I64x): %x bytes left, expecting at least %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);
2070                 return false;
2071             }
2072 
2073             if (sectlen == 0) {
2074                 ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);
2075                 return false;
2076             }
2077 
2078             rc += sectcount;
2079 
2080             if (secttype == TYPE_EXTENT_DATA_REF) {
2081                 EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t));
2082 
2083                 if (try_clone_edr(context, se, sectedr))
2084                     return true;
2085             }
2086 
2087             len -= sectlen;
2088             ptr += sizeof(uint8_t) + sectlen;
2089         }
2090     }
2091 
2092     if (rc >= ei->refcount)
2093         return false;
2094 
2095     searchkey.obj_type = TYPE_EXTENT_DATA_REF;
2096     searchkey.offset = 0;
2097 
2098     Status = find_item(context->Vcb, context->Vcb->extent_root, &tp, &searchkey, false, NULL);
2099     if (!NT_SUCCESS(Status)) {
2100         ERR("find_item returned %08x\n", Status);
2101         return false;
2102     }
2103 
2104     while (true) {
2105         traverse_ptr next_tp;
2106 
2107         if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {
2108             if (tp.item->size < sizeof(EXTENT_DATA_REF))
2109                 ERR("(%I64x,%x,%I64x) has size %u, not %u as expected\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA_REF));
2110             else {
2111                 if (try_clone_edr(context, se, (EXTENT_DATA_REF*)tp.item->data))
2112                     return true;
2113             }
2114         } else if (tp.item->key.obj_id > searchkey.obj_id || (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type > searchkey.obj_type))
2115             break;
2116 
2117         if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL))
2118             tp = next_tp;
2119         else
2120             break;
2121     }
2122 
2123     return false;
2124 }
2125 
2126 static NTSTATUS flush_extents(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) {
2127     NTSTATUS Status;
2128 
2129     if ((IsListEmpty(&context->lastinode.exts) && IsListEmpty(&context->lastinode.oldexts)) || context->lastinode.size == 0)
2130         return STATUS_SUCCESS;
2131 
2132     if (context->parent) {
2133         Status = add_ext_holes(context->Vcb, &context->lastinode.exts, context->lastinode.size);
2134         if (!NT_SUCCESS(Status)) {
2135             ERR("add_ext_holes returned %08x\n", Status);
2136             return Status;
2137         }
2138 
2139         Status = add_ext_holes(context->Vcb, &context->lastinode.oldexts, context->lastinode.size);
2140         if (!NT_SUCCESS(Status)) {
2141             ERR("add_ext_holes returned %08x\n", Status);
2142             return Status;
2143         }
2144 
2145         Status = sync_ext_cutoff_points(context);
2146         if (!NT_SUCCESS(Status)) {
2147             ERR("sync_ext_cutoff_points returned %08x\n", Status);
2148             return Status;
2149         }
2150     }
2151 
2152     while (!IsListEmpty(&context->lastinode.exts)) {
2153         send_ext* se = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.exts), send_ext, list_entry);
2154         send_ext* se2 = context->parent ? CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldexts), send_ext, list_entry) : NULL;
2155         ULONG pos;
2156         EXTENT_DATA2* ed2;
2157 
2158         if (se2) {
2159             if (se->data.type == EXTENT_TYPE_INLINE && se2->data.type == EXTENT_TYPE_INLINE &&
2160                 RtlCompareMemory(se->data.data, se2->data.data, (ULONG)se->data.decoded_size) == (ULONG)se->data.decoded_size) {
2161                 ExFreePool(se);
2162                 ExFreePool(se2);
2163                 continue;
2164             }
2165 
2166             if (se->data.type == EXTENT_TYPE_REGULAR && se2->data.type == EXTENT_TYPE_REGULAR) {
2167                 EXTENT_DATA2 *ed2a, *ed2b;
2168 
2169                 ed2a = (EXTENT_DATA2*)se->data.data;
2170                 ed2b = (EXTENT_DATA2*)se2->data.data;
2171 
2172                 if (RtlCompareMemory(ed2a, ed2b, sizeof(EXTENT_DATA2)) == sizeof(EXTENT_DATA2)) {
2173                     ExFreePool(se);
2174                     ExFreePool(se2);
2175                     continue;
2176                 }
2177             }
2178         }
2179 
2180         if (se->data.type == EXTENT_TYPE_INLINE) {
2181             pos = context->datalen;
2182 
2183             send_command(context, BTRFS_SEND_CMD_WRITE);
2184 
2185             send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
2186             send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &se->offset, sizeof(uint64_t));
2187 
2188             if (se->data.compression == BTRFS_COMPRESSION_NONE)
2189                 send_add_tlv(context, BTRFS_SEND_TLV_DATA, se->data.data, (uint16_t)se->data.decoded_size);
2190             else if (se->data.compression == BTRFS_COMPRESSION_ZLIB || se->data.compression == BTRFS_COMPRESSION_LZO || se->data.compression == BTRFS_COMPRESSION_ZSTD) {
2191                 ULONG inlen = se->datalen - (ULONG)offsetof(EXTENT_DATA, data[0]);
2192 
2193                 send_add_tlv(context, BTRFS_SEND_TLV_DATA, NULL, (uint16_t)se->data.decoded_size);
2194                 RtlZeroMemory(&context->data[context->datalen - se->data.decoded_size], (ULONG)se->data.decoded_size);
2195 
2196                 if (se->data.compression == BTRFS_COMPRESSION_ZLIB) {
2197                     Status = zlib_decompress(se->data.data, inlen, &context->data[context->datalen - se->data.decoded_size], (uint32_t)se->data.decoded_size);
2198                     if (!NT_SUCCESS(Status)) {
2199                         ERR("zlib_decompress returned %08x\n", Status);
2200                         ExFreePool(se);
2201                         if (se2) ExFreePool(se2);
2202                         return Status;
2203                     }
2204                 } else if (se->data.compression == BTRFS_COMPRESSION_LZO) {
2205                     if (inlen < sizeof(uint32_t)) {
2206                         ERR("extent data was truncated\n");
2207                         ExFreePool(se);
2208                         if (se2) ExFreePool(se2);
2209                         return STATUS_INTERNAL_ERROR;
2210                     } else
2211                         inlen -= sizeof(uint32_t);
2212 
2213                     Status = lzo_decompress(se->data.data + sizeof(uint32_t), inlen, &context->data[context->datalen - se->data.decoded_size], (uint32_t)se->data.decoded_size, sizeof(uint32_t));
2214                     if (!NT_SUCCESS(Status)) {
2215                         ERR("lzo_decompress returned %08x\n", Status);
2216                         ExFreePool(se);
2217                         if (se2) ExFreePool(se2);
2218                         return Status;
2219                     }
2220                 } else if (se->data.compression == BTRFS_COMPRESSION_ZSTD) {
2221                     Status = zstd_decompress(se->data.data, inlen, &context->data[context->datalen - se->data.decoded_size], (uint32_t)se->data.decoded_size);
2222                     if (!NT_SUCCESS(Status)) {
2223                         ERR("zlib_decompress returned %08x\n", Status);
2224                         ExFreePool(se);
2225                         if (se2) ExFreePool(se2);
2226                         return Status;
2227                     }
2228                 }
2229             } else {
2230                 ERR("unhandled compression type %x\n", se->data.compression);
2231                 ExFreePool(se);
2232                 if (se2) ExFreePool(se2);
2233                 return STATUS_NOT_IMPLEMENTED;
2234             }
2235 
2236             send_command_finish(context, pos);
2237 
2238             ExFreePool(se);
2239             if (se2) ExFreePool(se2);
2240             continue;
2241         }
2242 
2243         ed2 = (EXTENT_DATA2*)se->data.data;
2244 
2245         if (ed2->size != 0 && (context->parent || context->num_clones > 0)) {
2246             if (try_clone(context, se)) {
2247                 ExFreePool(se);
2248                 if (se2) ExFreePool(se2);
2249                 continue;
2250             }
2251         }
2252 
2253         if (ed2->size == 0) { // write sparse
2254             uint64_t off, offset;
2255 
2256             for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) {
2257                 uint16_t length = (uint16_t)min(min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE), context->lastinode.size - se->offset - off);
2258 
2259                 if (context->datalen > SEND_BUFFER_LENGTH) {
2260                     Status = wait_for_flush(context, tp1, tp2);
2261                     if (!NT_SUCCESS(Status)) {
2262                         ERR("wait_for_flush returned %08x\n", Status);
2263                         ExFreePool(se);
2264                         if (se2) ExFreePool(se2);
2265                         return Status;
2266                     }
2267 
2268                     if (context->send->cancelling) {
2269                         ExFreePool(se);
2270                         if (se2) ExFreePool(se2);
2271                         return STATUS_SUCCESS;
2272                     }
2273                 }
2274 
2275                 pos = context->datalen;
2276 
2277                 send_command(context, BTRFS_SEND_CMD_WRITE);
2278 
2279                 send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
2280 
2281                 offset = se->offset + off;
2282                 send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(uint64_t));
2283 
2284                 send_add_tlv(context, BTRFS_SEND_TLV_DATA, NULL, length);
2285                 RtlZeroMemory(&context->data[context->datalen - length], length);
2286 
2287                 send_command_finish(context, pos);
2288             }
2289         } else if (se->data.compression == BTRFS_COMPRESSION_NONE) {
2290             uint64_t off, offset;
2291             uint8_t* buf;
2292 
2293             buf = ExAllocatePoolWithTag(NonPagedPool, MAX_SEND_WRITE + (2 * context->Vcb->superblock.sector_size), ALLOC_TAG);
2294             if (!buf) {
2295                 ERR("out of memory\n");
2296                 ExFreePool(se);
2297                 if (se2) ExFreePool(se2);
2298                 return STATUS_INSUFFICIENT_RESOURCES;
2299             }
2300 
2301             for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) {
2302                 uint16_t length = (uint16_t)min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE);
2303                 ULONG skip_start;
2304                 uint64_t addr = ed2->address + off;
2305                 uint32_t* csum;
2306 
2307                 if (context->datalen > SEND_BUFFER_LENGTH) {
2308                     Status = wait_for_flush(context, tp1, tp2);
2309                     if (!NT_SUCCESS(Status)) {
2310                         ERR("wait_for_flush returned %08x\n", Status);
2311                         ExFreePool(buf);
2312                         ExFreePool(se);
2313                         if (se2) ExFreePool(se2);
2314                         return Status;
2315                     }
2316 
2317                     if (context->send->cancelling) {
2318                         ExFreePool(buf);
2319                         ExFreePool(se);
2320                         if (se2) ExFreePool(se2);
2321                         return STATUS_SUCCESS;
2322                     }
2323                 }
2324 
2325                 skip_start = addr % context->Vcb->superblock.sector_size;
2326                 addr -= skip_start;
2327 
2328                 if (context->lastinode.flags & BTRFS_INODE_NODATASUM)
2329                     csum = NULL;
2330                 else {
2331                     uint32_t len;
2332 
2333                     len = (uint32_t)sector_align(length + skip_start, context->Vcb->superblock.sector_size) / context->Vcb->superblock.sector_size;
2334 
2335                     csum = ExAllocatePoolWithTag(PagedPool, len * sizeof(uint32_t), ALLOC_TAG);
2336                     if (!csum) {
2337                         ERR("out of memory\n");
2338                         ExFreePool(buf);
2339                         ExFreePool(se);
2340                         if (se2) ExFreePool(se2);
2341                         return STATUS_INSUFFICIENT_RESOURCES;
2342                     }
2343 
2344                     Status = load_csum(context->Vcb, csum, addr, len, NULL);
2345                     if (!NT_SUCCESS(Status)) {
2346                         ERR("load_csum returned %08x\n", Status);
2347                         ExFreePool(csum);
2348                         ExFreePool(buf);
2349                         ExFreePool(se);
2350                         if (se2) ExFreePool(se2);
2351                         return STATUS_INSUFFICIENT_RESOURCES;
2352                     }
2353                 }
2354 
2355                 Status = read_data(context->Vcb, addr, (uint32_t)sector_align(length + skip_start, context->Vcb->superblock.sector_size),
2356                                    csum, false, buf, NULL, NULL, NULL, 0, false, NormalPagePriority);
2357                 if (!NT_SUCCESS(Status)) {
2358                     ERR("read_data returned %08x\n", Status);
2359                     ExFreePool(buf);
2360                     ExFreePool(se);
2361                     if (se2) ExFreePool(se2);
2362                     if (csum) ExFreePool(csum);
2363                     return Status;
2364                 }
2365 
2366                 if (csum)
2367                     ExFreePool(csum);
2368 
2369                 pos = context->datalen;
2370 
2371                 send_command(context, BTRFS_SEND_CMD_WRITE);
2372 
2373                 send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
2374 
2375                 offset = se->offset + off;
2376                 send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(uint64_t));
2377 
2378                 length = (uint16_t)min(context->lastinode.size - se->offset - off, length);
2379                 send_add_tlv(context, BTRFS_SEND_TLV_DATA, buf + skip_start, length);
2380 
2381                 send_command_finish(context, pos);
2382             }
2383 
2384             ExFreePool(buf);
2385         } else {
2386             uint8_t *buf, *compbuf;
2387             uint64_t off;
2388             uint32_t* csum;
2389 
2390             buf = ExAllocatePoolWithTag(PagedPool, (ULONG)se->data.decoded_size, ALLOC_TAG);
2391             if (!buf) {
2392                 ERR("out of memory\n");
2393                 ExFreePool(se);
2394                 if (se2) ExFreePool(se2);
2395                 return STATUS_INSUFFICIENT_RESOURCES;
2396             }
2397 
2398             compbuf = ExAllocatePoolWithTag(PagedPool, (ULONG)ed2->size, ALLOC_TAG);
2399             if (!compbuf) {
2400                 ERR("out of memory\n");
2401                 ExFreePool(buf);
2402                 ExFreePool(se);
2403                 if (se2) ExFreePool(se2);
2404                 return STATUS_INSUFFICIENT_RESOURCES;
2405             }
2406 
2407             if (context->lastinode.flags & BTRFS_INODE_NODATASUM)
2408                 csum = NULL;
2409             else {
2410                 uint32_t len;
2411 
2412                 len = (uint32_t)(ed2->size / context->Vcb->superblock.sector_size);
2413 
2414                 csum = ExAllocatePoolWithTag(PagedPool, len * sizeof(uint32_t), ALLOC_TAG);
2415                 if (!csum) {
2416                     ERR("out of memory\n");
2417                     ExFreePool(compbuf);
2418                     ExFreePool(buf);
2419                     ExFreePool(se);
2420                     if (se2) ExFreePool(se2);
2421                     return STATUS_INSUFFICIENT_RESOURCES;
2422                 }
2423 
2424                 Status = load_csum(context->Vcb, csum, ed2->address, len, NULL);
2425                 if (!NT_SUCCESS(Status)) {
2426                     ERR("load_csum returned %08x\n", Status);
2427                     ExFreePool(csum);
2428                     ExFreePool(compbuf);
2429                     ExFreePool(buf);
2430                     ExFreePool(se);
2431                     if (se2) ExFreePool(se2);
2432                     return Status;
2433                 }
2434             }
2435 
2436             Status = read_data(context->Vcb, ed2->address, (uint32_t)ed2->size, csum, false, compbuf, NULL, NULL, NULL, 0, false, NormalPagePriority);
2437             if (!NT_SUCCESS(Status)) {
2438                 ERR("read_data returned %08x\n", Status);
2439                 ExFreePool(compbuf);
2440                 ExFreePool(buf);
2441                 ExFreePool(se);
2442                 if (se2) ExFreePool(se2);
2443                 if (csum) ExFreePool(csum);
2444                 return Status;
2445             }
2446 
2447             if (csum)
2448                 ExFreePool(csum);
2449 
2450             if (se->data.compression == BTRFS_COMPRESSION_ZLIB) {
2451                 Status = zlib_decompress(compbuf, (uint32_t)ed2->size, buf, (uint32_t)se->data.decoded_size);
2452                 if (!NT_SUCCESS(Status)) {
2453                     ERR("zlib_decompress returned %08x\n", Status);
2454                     ExFreePool(compbuf);
2455                     ExFreePool(buf);
2456                     ExFreePool(se);
2457                     if (se2) ExFreePool(se2);
2458                     return Status;
2459                 }
2460             } else if (se->data.compression == BTRFS_COMPRESSION_LZO) {
2461                 Status = lzo_decompress(&compbuf[sizeof(uint32_t)], (uint32_t)ed2->size, buf, (uint32_t)se->data.decoded_size, sizeof(uint32_t));
2462                 if (!NT_SUCCESS(Status)) {
2463                     ERR("lzo_decompress returned %08x\n", Status);
2464                     ExFreePool(compbuf);
2465                     ExFreePool(buf);
2466                     ExFreePool(se);
2467                     if (se2) ExFreePool(se2);
2468                     return Status;
2469                 }
2470             } else if (se->data.compression == BTRFS_COMPRESSION_ZSTD) {
2471                 Status = zstd_decompress(compbuf, (uint32_t)ed2->size, buf, (uint32_t)se->data.decoded_size);
2472                 if (!NT_SUCCESS(Status)) {
2473                     ERR("zstd_decompress returned %08x\n", Status);
2474                     ExFreePool(compbuf);
2475                     ExFreePool(buf);
2476                     ExFreePool(se);
2477                     if (se2) ExFreePool(se2);
2478                     return Status;
2479                 }
2480             }
2481 
2482             ExFreePool(compbuf);
2483 
2484             for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) {
2485                 uint16_t length = (uint16_t)min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE);
2486                 uint64_t offset;
2487 
2488                 if (context->datalen > SEND_BUFFER_LENGTH) {
2489                     Status = wait_for_flush(context, tp1, tp2);
2490                     if (!NT_SUCCESS(Status)) {
2491                         ERR("wait_for_flush returned %08x\n", Status);
2492                         ExFreePool(buf);
2493                         ExFreePool(se);
2494                         if (se2) ExFreePool(se2);
2495                         return Status;
2496                     }
2497 
2498                     if (context->send->cancelling) {
2499                         ExFreePool(buf);
2500                         ExFreePool(se);
2501                         if (se2) ExFreePool(se2);
2502                         return STATUS_SUCCESS;
2503                     }
2504                 }
2505 
2506                 pos = context->datalen;
2507 
2508                 send_command(context, BTRFS_SEND_CMD_WRITE);
2509 
2510                 send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
2511 
2512                 offset = se->offset + off;
2513                 send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(uint64_t));
2514 
2515                 length = (uint16_t)min(context->lastinode.size - se->offset - off, length);
2516                 send_add_tlv(context, BTRFS_SEND_TLV_DATA, &buf[off], length);
2517 
2518                 send_command_finish(context, pos);
2519             }
2520 
2521             ExFreePool(buf);
2522         }
2523 
2524         ExFreePool(se);
2525         if (se2) ExFreePool(se2);
2526     }
2527 
2528     return STATUS_SUCCESS;
2529 }
2530 
2531 static NTSTATUS finish_inode(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) {
2532     LIST_ENTRY* le;
2533 
2534     if (!IsListEmpty(&context->lastinode.refs) || !IsListEmpty(&context->lastinode.oldrefs)) {
2535         NTSTATUS Status = flush_refs(context, tp1, tp2);
2536         if (!NT_SUCCESS(Status)) {
2537             ERR("flush_refs returned %08x\n", Status);
2538             return Status;
2539         }
2540 
2541         if (context->send->cancelling)
2542             return STATUS_SUCCESS;
2543     }
2544 
2545     if (!context->lastinode.deleting) {
2546         if (context->lastinode.file) {
2547             NTSTATUS Status = flush_extents(context, tp1, tp2);
2548             if (!NT_SUCCESS(Status)) {
2549                 ERR("flush_extents returned %08x\n", Status);
2550                 return Status;
2551             }
2552 
2553             if (context->send->cancelling)
2554                 return STATUS_SUCCESS;
2555 
2556             send_truncate_command(context, context->lastinode.path, context->lastinode.size);
2557         }
2558 
2559         if (context->lastinode.new || context->lastinode.uid != context->lastinode.olduid || context->lastinode.gid != context->lastinode.oldgid)
2560             send_chown_command(context, context->lastinode.path, context->lastinode.uid, context->lastinode.gid);
2561 
2562         if (((context->lastinode.mode & __S_IFLNK) != __S_IFLNK || ((context->lastinode.mode & 07777) != 0777)) &&
2563             (context->lastinode.new || context->lastinode.mode != context->lastinode.oldmode))
2564             send_chmod_command(context, context->lastinode.path, context->lastinode.mode);
2565 
2566         send_utimes_command(context, context->lastinode.path, &context->lastinode.atime, &context->lastinode.mtime, &context->lastinode.ctime);
2567     }
2568 
2569     while (!IsListEmpty(&context->lastinode.exts)) {
2570         ExFreePool(CONTAINING_RECORD(RemoveHeadList(&context->lastinode.exts), send_ext, list_entry));
2571     }
2572 
2573     while (!IsListEmpty(&context->lastinode.oldexts)) {
2574         ExFreePool(CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldexts), send_ext, list_entry));
2575     }
2576 
2577     if (context->parent) {
2578         le = context->pending_rmdirs.Flink;
2579 
2580         while (le != &context->pending_rmdirs) {
2581             pending_rmdir* pr = CONTAINING_RECORD(le, pending_rmdir, list_entry);
2582 
2583             if (pr->last_child_inode <= context->lastinode.inode) {
2584                 le = le->Flink;
2585 
2586                 send_rmdir_command(context, pr->sd->namelen, pr->sd->name);
2587 
2588                 RemoveEntryList(&pr->sd->list_entry);
2589 
2590                 if (pr->sd->name)
2591                     ExFreePool(pr->sd->name);
2592 
2593                 while (!IsListEmpty(&pr->sd->deleted_children)) {
2594                     deleted_child* dc = CONTAINING_RECORD(RemoveHeadList(&pr->sd->deleted_children), deleted_child, list_entry);
2595                     ExFreePool(dc);
2596                 }
2597 
2598                 ExFreePool(pr->sd);
2599 
2600                 RemoveEntryList(&pr->list_entry);
2601                 ExFreePool(pr);
2602             } else
2603                 break;
2604         }
2605     }
2606 
2607     context->lastinode.inode = 0;
2608     context->lastinode.o = NULL;
2609 
2610     if (context->lastinode.path) {
2611         ExFreePool(context->lastinode.path);
2612         context->lastinode.path = NULL;
2613     }
2614 
2615     return STATUS_SUCCESS;
2616 }
2617 
2618 static NTSTATUS send_extent_data(send_context* context, traverse_ptr* tp, traverse_ptr* tp2) {
2619     NTSTATUS Status;
2620 
2621     if (tp && tp2 && tp->item->size == tp2->item->size && RtlCompareMemory(tp->item->data, tp2->item->data, tp->item->size) == tp->item->size)
2622         return STATUS_SUCCESS;
2623 
2624     if (!IsListEmpty(&context->lastinode.refs) || !IsListEmpty(&context->lastinode.oldrefs)) {
2625         Status = flush_refs(context, tp, tp2);
2626         if (!NT_SUCCESS(Status)) {
2627             ERR("flush_refs returned %08x\n", Status);
2628             return Status;
2629         }
2630 
2631         if (context->send->cancelling)
2632             return STATUS_SUCCESS;
2633     }
2634 
2635     if ((context->lastinode.mode & __S_IFLNK) == __S_IFLNK)
2636         return STATUS_SUCCESS;
2637 
2638     if (tp) {
2639         EXTENT_DATA* ed;
2640         EXTENT_DATA2* ed2 = NULL;
2641 
2642         if (tp->item->size < sizeof(EXTENT_DATA)) {
2643             ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %u\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,
2644                 tp->item->size, sizeof(EXTENT_DATA));
2645             return STATUS_INTERNAL_ERROR;
2646         }
2647 
2648         ed = (EXTENT_DATA*)tp->item->data;
2649 
2650         if (ed->encryption != BTRFS_ENCRYPTION_NONE) {
2651             ERR("unknown encryption type %u\n", ed->encryption);
2652             return STATUS_INTERNAL_ERROR;
2653         }
2654 
2655         if (ed->encoding != BTRFS_ENCODING_NONE) {
2656             ERR("unknown encoding type %u\n", ed->encoding);
2657             return STATUS_INTERNAL_ERROR;
2658         }
2659 
2660         if (ed->compression != BTRFS_COMPRESSION_NONE && ed->compression != BTRFS_COMPRESSION_ZLIB &&
2661             ed->compression != BTRFS_COMPRESSION_LZO && ed->compression != BTRFS_COMPRESSION_ZSTD) {
2662             ERR("unknown compression type %u\n", ed->compression);
2663             return STATUS_INTERNAL_ERROR;
2664         }
2665 
2666         if (ed->type == EXTENT_TYPE_REGULAR) {
2667             if (tp->item->size < offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)) {
2668                 ERR("(%I64x,%x,%I64x) was %u bytes, expected %u\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,
2669                     tp->item->size, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2));
2670                 return STATUS_INTERNAL_ERROR;
2671             }
2672 
2673             ed2 = (EXTENT_DATA2*)ed->data;
2674         } else if (ed->type == EXTENT_TYPE_INLINE) {
2675             if (tp->item->size < offsetof(EXTENT_DATA, data[0]) + ed->decoded_size && ed->compression == BTRFS_COMPRESSION_NONE) {
2676                 ERR("(%I64x,%x,%I64x) was %u bytes, expected %u\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,
2677                     tp->item->size, offsetof(EXTENT_DATA, data[0]) + ed->decoded_size);
2678                 return STATUS_INTERNAL_ERROR;
2679             }
2680         }
2681 
2682         if ((ed->type == EXTENT_TYPE_INLINE || (ed->type == EXTENT_TYPE_REGULAR && ed2->size != 0)) && ed->decoded_size != 0) {
2683             send_ext* se = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data) + tp->item->size, ALLOC_TAG);
2684 
2685             if (!se) {
2686                 ERR("out of memory\n");
2687                 return STATUS_INSUFFICIENT_RESOURCES;
2688             }
2689 
2690             se->offset = tp->item->key.offset;
2691             se->datalen = tp->item->size;
2692             RtlCopyMemory(&se->data, tp->item->data, tp->item->size);
2693             InsertTailList(&context->lastinode.exts, &se->list_entry);
2694         }
2695     }
2696 
2697     if (tp2) {
2698         EXTENT_DATA* ed;
2699         EXTENT_DATA2* ed2 = NULL;
2700 
2701         if (tp2->item->size < sizeof(EXTENT_DATA)) {
2702             ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %u\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset,
2703                 tp2->item->size, sizeof(EXTENT_DATA));
2704             return STATUS_INTERNAL_ERROR;
2705         }
2706 
2707         ed = (EXTENT_DATA*)tp2->item->data;
2708 
2709         if (ed->encryption != BTRFS_ENCRYPTION_NONE) {
2710             ERR("unknown encryption type %u\n", ed->encryption);
2711             return STATUS_INTERNAL_ERROR;
2712         }
2713 
2714         if (ed->encoding != BTRFS_ENCODING_NONE) {
2715             ERR("unknown encoding type %u\n", ed->encoding);
2716             return STATUS_INTERNAL_ERROR;
2717         }
2718 
2719         if (ed->compression != BTRFS_COMPRESSION_NONE && ed->compression != BTRFS_COMPRESSION_ZLIB &&
2720             ed->compression != BTRFS_COMPRESSION_LZO && ed->compression != BTRFS_COMPRESSION_ZSTD) {
2721             ERR("unknown compression type %u\n", ed->compression);
2722             return STATUS_INTERNAL_ERROR;
2723         }
2724 
2725         if (ed->type == EXTENT_TYPE_REGULAR) {
2726             if (tp2->item->size < offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)) {
2727                 ERR("(%I64x,%x,%I64x) was %u bytes, expected %u\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset,
2728                     tp2->item->size, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2));
2729                 return STATUS_INTERNAL_ERROR;
2730             }
2731 
2732             ed2 = (EXTENT_DATA2*)ed->data;
2733         } else if (ed->type == EXTENT_TYPE_INLINE) {
2734             if (tp2->item->size < offsetof(EXTENT_DATA, data[0]) + ed->decoded_size) {
2735                 ERR("(%I64x,%x,%I64x) was %u bytes, expected %u\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset,
2736                     tp2->item->size, offsetof(EXTENT_DATA, data[0]) + ed->decoded_size);
2737                 return STATUS_INTERNAL_ERROR;
2738             }
2739         }
2740 
2741         if ((ed->type == EXTENT_TYPE_INLINE || (ed->type == EXTENT_TYPE_REGULAR && ed2->size != 0)) && ed->decoded_size != 0) {
2742             send_ext* se = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data) + tp2->item->size, ALLOC_TAG);
2743 
2744             if (!se) {
2745                 ERR("out of memory\n");
2746                 return STATUS_INSUFFICIENT_RESOURCES;
2747             }
2748 
2749             se->offset = tp2->item->key.offset;
2750             se->datalen = tp2->item->size;
2751             RtlCopyMemory(&se->data, tp2->item->data, tp2->item->size);
2752             InsertTailList(&context->lastinode.oldexts, &se->list_entry);
2753         }
2754     }
2755 
2756     return STATUS_SUCCESS;
2757 }
2758 
2759 typedef struct {
2760     uint16_t namelen;
2761     char* name;
2762     uint16_t value1len;
2763     char* value1;
2764     uint16_t value2len;
2765     char* value2;
2766     LIST_ENTRY list_entry;
2767 } xattr_cmp;
2768 
2769 static NTSTATUS send_xattr(send_context* context, traverse_ptr* tp, traverse_ptr* tp2) {
2770     if (tp && tp2 && tp->item->size == tp2->item->size && RtlCompareMemory(tp->item->data, tp2->item->data, tp->item->size) == tp->item->size)
2771         return STATUS_SUCCESS;
2772 
2773     if (!IsListEmpty(&context->lastinode.refs) || !IsListEmpty(&context->lastinode.oldrefs)) {
2774         NTSTATUS Status = flush_refs(context, tp, tp2);
2775         if (!NT_SUCCESS(Status)) {
2776             ERR("flush_refs returned %08x\n", Status);
2777             return Status;
2778         }
2779 
2780         if (context->send->cancelling)
2781             return STATUS_SUCCESS;
2782     }
2783 
2784     if (tp && tp->item->size < sizeof(DIR_ITEM)) {
2785         ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %u\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,
2786             tp->item->size, sizeof(DIR_ITEM));
2787         return STATUS_INTERNAL_ERROR;
2788     }
2789 
2790     if (tp2 && tp2->item->size < sizeof(DIR_ITEM)) {
2791         ERR("(%I64x,%x,%I64x) was %u bytes, expected at least %u\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset,
2792             tp2->item->size, sizeof(DIR_ITEM));
2793         return STATUS_INTERNAL_ERROR;
2794     }
2795 
2796     if (tp && !tp2) {
2797         ULONG len;
2798         DIR_ITEM* di;
2799 
2800         len = tp->item->size;
2801         di = (DIR_ITEM*)tp->item->data;
2802 
2803         do {
2804             ULONG pos;
2805 
2806             if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) {
2807                 ERR("(%I64x,%x,%I64x) was truncated\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset);
2808                 return STATUS_INTERNAL_ERROR;
2809             }
2810 
2811             pos = context->datalen;
2812             send_command(context, BTRFS_SEND_CMD_SET_XATTR);
2813             send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
2814             send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, di->name, di->n);
2815             send_add_tlv(context, BTRFS_SEND_TLV_XATTR_DATA, &di->name[di->n], di->m);
2816             send_command_finish(context, pos);
2817 
2818             len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n;
2819             di = (DIR_ITEM*)&di->name[di->m + di->n];
2820         } while (len > 0);
2821     } else if (!tp && tp2) {
2822         ULONG len;
2823         DIR_ITEM* di;
2824 
2825         len = tp2->item->size;
2826         di = (DIR_ITEM*)tp2->item->data;
2827 
2828         do {
2829             ULONG pos;
2830 
2831             if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) {
2832                 ERR("(%I64x,%x,%I64x) was truncated\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset);
2833                 return STATUS_INTERNAL_ERROR;
2834             }
2835 
2836             pos = context->datalen;
2837             send_command(context, BTRFS_SEND_CMD_REMOVE_XATTR);
2838             send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
2839             send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, di->name, di->n);
2840             send_command_finish(context, pos);
2841 
2842             len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n;
2843             di = (DIR_ITEM*)&di->name[di->m + di->n];
2844         } while (len > 0);
2845     } else {
2846         ULONG len;
2847         DIR_ITEM* di;
2848         LIST_ENTRY xattrs;
2849 
2850         InitializeListHead(&xattrs);
2851 
2852         len = tp->item->size;
2853         di = (DIR_ITEM*)tp->item->data;
2854 
2855         do {
2856             xattr_cmp* xa;
2857 
2858             if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) {
2859                 ERR("(%I64x,%x,%I64x) was truncated\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset);
2860                 return STATUS_INTERNAL_ERROR;
2861             }
2862 
2863             xa = ExAllocatePoolWithTag(PagedPool, sizeof(xattr_cmp), ALLOC_TAG);
2864             if (!xa) {
2865                 ERR("out of memory\n");
2866 
2867                 while (!IsListEmpty(&xattrs)) {
2868                     ExFreePool(CONTAINING_RECORD(RemoveHeadList(&xattrs), xattr_cmp, list_entry));
2869                 }
2870 
2871                 return STATUS_INSUFFICIENT_RESOURCES;
2872             }
2873 
2874             xa->namelen = di->n;
2875             xa->name = di->name;
2876             xa->value1len = di->m;
2877             xa->value1 = di->name + di->n;
2878             xa->value2len = 0;
2879             xa->value2 = NULL;
2880 
2881             InsertTailList(&xattrs, &xa->list_entry);
2882 
2883             len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n;
2884             di = (DIR_ITEM*)&di->name[di->m + di->n];
2885         } while (len > 0);
2886 
2887         len = tp2->item->size;
2888         di = (DIR_ITEM*)tp2->item->data;
2889 
2890         do {
2891             xattr_cmp* xa;
2892             LIST_ENTRY* le;
2893             bool found = false;
2894 
2895             if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) {
2896                 ERR("(%I64x,%x,%I64x) was truncated\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset);
2897                 return STATUS_INTERNAL_ERROR;
2898             }
2899 
2900             le = xattrs.Flink;
2901             while (le != &xattrs) {
2902                 xa = CONTAINING_RECORD(le, xattr_cmp, list_entry);
2903 
2904                 if (xa->namelen == di->n && RtlCompareMemory(xa->name, di->name, di->n) == di->n) {
2905                     xa->value2len = di->m;
2906                     xa->value2 = di->name + di->n;
2907                     found = true;
2908                     break;
2909                 }
2910 
2911                 le = le->Flink;
2912             }
2913 
2914             if (!found) {
2915                 xa = ExAllocatePoolWithTag(PagedPool, sizeof(xattr_cmp), ALLOC_TAG);
2916                 if (!xa) {
2917                     ERR("out of memory\n");
2918 
2919                     while (!IsListEmpty(&xattrs)) {
2920                         ExFreePool(CONTAINING_RECORD(RemoveHeadList(&xattrs), xattr_cmp, list_entry));
2921                     }
2922 
2923                     return STATUS_INSUFFICIENT_RESOURCES;
2924                 }
2925 
2926                 xa->namelen = di->n;
2927                 xa->name = di->name;
2928                 xa->value1len = 0;
2929                 xa->value1 = NULL;
2930                 xa->value2len = di->m;
2931                 xa->value2 = di->name + di->n;
2932 
2933                 InsertTailList(&xattrs, &xa->list_entry);
2934             }
2935 
2936             len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n;
2937             di = (DIR_ITEM*)&di->name[di->m + di->n];
2938         } while (len > 0);
2939 
2940         while (!IsListEmpty(&xattrs)) {
2941             xattr_cmp* xa = CONTAINING_RECORD(RemoveHeadList(&xattrs), xattr_cmp, list_entry);
2942 
2943             if (xa->value1len != xa->value2len || !xa->value1 || !xa->value2 || RtlCompareMemory(xa->value1, xa->value2, xa->value1len) != xa->value1len) {
2944                 ULONG pos;
2945 
2946                 if (!xa->value1) {
2947                     pos = context->datalen;
2948                     send_command(context, BTRFS_SEND_CMD_REMOVE_XATTR);
2949                     send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
2950                     send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, xa->name, xa->namelen);
2951                     send_command_finish(context, pos);
2952                 } else {
2953                     pos = context->datalen;
2954                     send_command(context, BTRFS_SEND_CMD_SET_XATTR);
2955                     send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
2956                     send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, xa->name, xa->namelen);
2957                     send_add_tlv(context, BTRFS_SEND_TLV_XATTR_DATA, xa->value1, xa->value1len);
2958                     send_command_finish(context, pos);
2959                 }
2960             }
2961 
2962             ExFreePool(xa);
2963         }
2964     }
2965 
2966     return STATUS_SUCCESS;
2967 }
2968 
2969 _Function_class_(KSTART_ROUTINE)
2970 static void __stdcall send_thread(void* ctx) {
2971     send_context* context = (send_context*)ctx;
2972     NTSTATUS Status;
2973     KEY searchkey;
2974     traverse_ptr tp, tp2;
2975 
2976     InterlockedIncrement(&context->root->send_ops);
2977 
2978     if (context->parent)
2979         InterlockedIncrement(&context->parent->send_ops);
2980 
2981     if (context->clones) {
2982         ULONG i;
2983 
2984         for (i = 0; i < context->num_clones; i++) {
2985             InterlockedIncrement(&context->clones[i]->send_ops);
2986         }
2987     }
2988 
2989     ExAcquireResourceExclusiveLite(&context->Vcb->tree_lock, true);
2990 
2991     flush_subvol_fcbs(context->root);
2992 
2993     if (context->parent)
2994         flush_subvol_fcbs(context->parent);
2995 
2996     if (context->Vcb->need_write)
2997         Status = do_write(context->Vcb, NULL);
2998     else
2999         Status = STATUS_SUCCESS;
3000 
3001     free_trees(context->Vcb);
3002 
3003     if (!NT_SUCCESS(Status)) {
3004         ERR("do_write returned %08x\n", Status);
3005         ExReleaseResourceLite(&context->Vcb->tree_lock);
3006         goto end;
3007     }
3008 
3009     ExConvertExclusiveToSharedLite(&context->Vcb->tree_lock);
3010 
3011     searchkey.obj_id = searchkey.offset = 0;
3012     searchkey.obj_type = 0;
3013 
3014     Status = find_item(context->Vcb, context->root, &tp, &searchkey, false, NULL);
3015     if (!NT_SUCCESS(Status)) {
3016         ERR("find_item returned %08x\n", Status);
3017         ExReleaseResourceLite(&context->Vcb->tree_lock);
3018         goto end;
3019     }
3020 
3021     if (context->parent) {
3022         bool ended1 = false, ended2 = false;
3023         Status = find_item(context->Vcb, context->parent, &tp2, &searchkey, false, NULL);
3024         if (!NT_SUCCESS(Status)) {
3025             ERR("find_item returned %08x\n", Status);
3026             ExReleaseResourceLite(&context->Vcb->tree_lock);
3027             goto end;
3028         }
3029 
3030         do {
3031             traverse_ptr next_tp;
3032 
3033             if (context->datalen > SEND_BUFFER_LENGTH) {
3034                 KEY key1 = tp.item->key, key2 = tp2.item->key;
3035 
3036                 ExReleaseResourceLite(&context->Vcb->tree_lock);
3037 
3038                 KeClearEvent(&context->send->cleared_event);
3039                 KeSetEvent(&context->buffer_event, 0, true);
3040                 KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL);
3041 
3042                 if (context->send->cancelling)
3043                     goto end;
3044 
3045                 ExAcquireResourceSharedLite(&context->Vcb->tree_lock, true);
3046 
3047                 if (!ended1) {
3048                     Status = find_item(context->Vcb, context->root, &tp, &key1, false, NULL);
3049                     if (!NT_SUCCESS(Status)) {
3050                         ERR("find_item returned %08x\n", Status);
3051                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3052                         goto end;
3053                     }
3054 
3055                     if (keycmp(tp.item->key, key1)) {
3056                         ERR("readonly subvolume changed\n");
3057                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3058                         Status = STATUS_INTERNAL_ERROR;
3059                         goto end;
3060                     }
3061                 }
3062 
3063                 if (!ended2) {
3064                     Status = find_item(context->Vcb, context->parent, &tp2, &key2, false, NULL);
3065                     if (!NT_SUCCESS(Status)) {
3066                         ERR("find_item returned %08x\n", Status);
3067                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3068                         goto end;
3069                     }
3070 
3071                     if (keycmp(tp2.item->key, key2)) {
3072                         ERR("readonly subvolume changed\n");
3073                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3074                         Status = STATUS_INTERNAL_ERROR;
3075                         goto end;
3076                     }
3077                 }
3078             }
3079 
3080             while (!ended1 && !ended2 && tp.tree->header.address == tp2.tree->header.address) {
3081                 Status = skip_to_difference(context->Vcb, &tp, &tp2, &ended1, &ended2);
3082                 if (!NT_SUCCESS(Status)) {
3083                     ERR("skip_to_difference returned %08x\n", Status);
3084                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3085                     goto end;
3086                 }
3087             }
3088 
3089             if (!ended1 && !ended2 && !keycmp(tp.item->key, tp2.item->key)) {
3090                 bool no_next = false, no_next2 = false;
3091 
3092                 TRACE("~ %I64x,%x,%I64x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
3093 
3094                 if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) {
3095                     Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2);
3096                     if (!NT_SUCCESS(Status)) {
3097                         ERR("finish_inode returned %08x\n", Status);
3098                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3099                         goto end;
3100                     }
3101 
3102                     if (context->send->cancelling) {
3103                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3104                         goto end;
3105                     }
3106                 }
3107 
3108                 if (tp.item->key.obj_type == TYPE_INODE_ITEM) {
3109                     if (tp.item->size == tp2.item->size && tp.item->size > 0 && RtlCompareMemory(tp.item->data, tp2.item->data, tp.item->size) == tp.item->size) {
3110                         uint64_t inode = tp.item->key.obj_id;
3111 
3112                         while (true) {
3113                             if (!find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) {
3114                                 ended1 = true;
3115                                 break;
3116                             }
3117 
3118                             tp = next_tp;
3119 
3120                             if (tp.item->key.obj_id != inode)
3121                                 break;
3122                         }
3123 
3124                         while (true) {
3125                             if (!find_next_item(context->Vcb, &tp2, &next_tp, false, NULL)) {
3126                                 ended2 = true;
3127                                 break;
3128                             }
3129 
3130                             tp2 = next_tp;
3131 
3132                             if (tp2.item->key.obj_id != inode)
3133                                 break;
3134                         }
3135 
3136                         no_next = true;
3137                     } else if (tp.item->size > sizeof(uint64_t) && tp2.item->size > sizeof(uint64_t) && *(uint64_t*)tp.item->data != *(uint64_t*)tp2.item->data) {
3138                         uint64_t inode = tp.item->key.obj_id;
3139 
3140                         Status = send_inode(context, NULL, &tp2);
3141                         if (!NT_SUCCESS(Status)) {
3142                             ERR("send_inode returned %08x\n", Status);
3143                             ExReleaseResourceLite(&context->Vcb->tree_lock);
3144                             goto end;
3145                         }
3146 
3147                         while (true) {
3148                             if (!find_next_item(context->Vcb, &tp2, &next_tp, false, NULL)) {
3149                                 ended2 = true;
3150                                 break;
3151                             }
3152 
3153                             tp2 = next_tp;
3154 
3155                             if (tp2.item->key.obj_id != inode)
3156                                 break;
3157 
3158                             if (tp2.item->key.obj_type == TYPE_INODE_REF) {
3159                                 Status = send_inode_ref(context, &tp2, true);
3160                                 if (!NT_SUCCESS(Status)) {
3161                                     ERR("send_inode_ref returned %08x\n", Status);
3162                                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3163                                     goto end;
3164                                 }
3165                             } else if (tp2.item->key.obj_type == TYPE_INODE_EXTREF) {
3166                                 Status = send_inode_extref(context, &tp2, true);
3167                                 if (!NT_SUCCESS(Status)) {
3168                                     ERR("send_inode_extref returned %08x\n", Status);
3169                                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3170                                     goto end;
3171                                 }
3172                             }
3173                         }
3174 
3175                         Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2);
3176                         if (!NT_SUCCESS(Status)) {
3177                             ERR("finish_inode returned %08x\n", Status);
3178                             ExReleaseResourceLite(&context->Vcb->tree_lock);
3179                             goto end;
3180                         }
3181 
3182                         if (context->send->cancelling) {
3183                             ExReleaseResourceLite(&context->Vcb->tree_lock);
3184                             goto end;
3185                         }
3186 
3187                         no_next2 = true;
3188 
3189                         Status = send_inode(context, &tp, NULL);
3190                         if (!NT_SUCCESS(Status)) {
3191                             ERR("send_inode returned %08x\n", Status);
3192                             ExReleaseResourceLite(&context->Vcb->tree_lock);
3193                             goto end;
3194                         }
3195                     } else {
3196                         Status = send_inode(context, &tp, &tp2);
3197                         if (!NT_SUCCESS(Status)) {
3198                             ERR("send_inode returned %08x\n", Status);
3199                             ExReleaseResourceLite(&context->Vcb->tree_lock);
3200                             goto end;
3201                         }
3202                     }
3203                 } else if (tp.item->key.obj_type == TYPE_INODE_REF) {
3204                     Status = send_inode_ref(context, &tp, false);
3205                     if (!NT_SUCCESS(Status)) {
3206                         ERR("send_inode_ref returned %08x\n", Status);
3207                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3208                         goto end;
3209                     }
3210 
3211                     Status = send_inode_ref(context, &tp2, true);
3212                     if (!NT_SUCCESS(Status)) {
3213                         ERR("send_inode_ref returned %08x\n", Status);
3214                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3215                         goto end;
3216                     }
3217                 } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) {
3218                     Status = send_inode_extref(context, &tp, false);
3219                     if (!NT_SUCCESS(Status)) {
3220                         ERR("send_inode_extref returned %08x\n", Status);
3221                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3222                         goto end;
3223                     }
3224 
3225                     Status = send_inode_extref(context, &tp2, true);
3226                     if (!NT_SUCCESS(Status)) {
3227                         ERR("send_inode_extref returned %08x\n", Status);
3228                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3229                         goto end;
3230                     }
3231                 } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) {
3232                     Status = send_extent_data(context, &tp, &tp2);
3233                     if (!NT_SUCCESS(Status)) {
3234                         ERR("send_extent_data returned %08x\n", Status);
3235                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3236                         goto end;
3237                     }
3238 
3239                     if (context->send->cancelling) {
3240                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3241                         goto end;
3242                     }
3243                 } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) {
3244                     Status = send_xattr(context, &tp, &tp2);
3245                     if (!NT_SUCCESS(Status)) {
3246                         ERR("send_xattr returned %08x\n", Status);
3247                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3248                         goto end;
3249                     }
3250 
3251                     if (context->send->cancelling) {
3252                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3253                         goto end;
3254                     }
3255                 }
3256 
3257                 if (!no_next) {
3258                     if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL))
3259                         tp = next_tp;
3260                     else
3261                         ended1 = true;
3262 
3263                     if (!no_next2) {
3264                         if (find_next_item(context->Vcb, &tp2, &next_tp, false, NULL))
3265                             tp2 = next_tp;
3266                         else
3267                             ended2 = true;
3268                     }
3269                 }
3270             } else if (ended2 || (!ended1 && !ended2 && keycmp(tp.item->key, tp2.item->key) == -1)) {
3271                 TRACE("A %I64x,%x,%I64x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
3272 
3273                 if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) {
3274                     Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2);
3275                     if (!NT_SUCCESS(Status)) {
3276                         ERR("finish_inode returned %08x\n", Status);
3277                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3278                         goto end;
3279                     }
3280 
3281                     if (context->send->cancelling) {
3282                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3283                         goto end;
3284                     }
3285                 }
3286 
3287                 if (tp.item->key.obj_type == TYPE_INODE_ITEM) {
3288                     Status = send_inode(context, &tp, NULL);
3289                     if (!NT_SUCCESS(Status)) {
3290                         ERR("send_inode returned %08x\n", Status);
3291                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3292                         goto end;
3293                     }
3294                 } else if (tp.item->key.obj_type == TYPE_INODE_REF) {
3295                     Status = send_inode_ref(context, &tp, false);
3296                     if (!NT_SUCCESS(Status)) {
3297                         ERR("send_inode_ref returned %08x\n", Status);
3298                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3299                         goto end;
3300                     }
3301                 } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) {
3302                     Status = send_inode_extref(context, &tp, false);
3303                     if (!NT_SUCCESS(Status)) {
3304                         ERR("send_inode_extref returned %08x\n", Status);
3305                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3306                         goto end;
3307                     }
3308                 } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) {
3309                     Status = send_extent_data(context, &tp, NULL);
3310                     if (!NT_SUCCESS(Status)) {
3311                         ERR("send_extent_data returned %08x\n", Status);
3312                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3313                         goto end;
3314                     }
3315 
3316                     if (context->send->cancelling) {
3317                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3318                         goto end;
3319                     }
3320                 } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) {
3321                     Status = send_xattr(context, &tp, NULL);
3322                     if (!NT_SUCCESS(Status)) {
3323                         ERR("send_xattr returned %08x\n", Status);
3324                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3325                         goto end;
3326                     }
3327 
3328                     if (context->send->cancelling) {
3329                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3330                         goto end;
3331                     }
3332                 }
3333 
3334                 if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL))
3335                     tp = next_tp;
3336                 else
3337                     ended1 = true;
3338             } else if (ended1 || (!ended1 && !ended2 && keycmp(tp.item->key, tp2.item->key) == 1)) {
3339                 TRACE("B %I64x,%x,%I64x\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset);
3340 
3341                 if (context->lastinode.inode != 0 && tp2.item->key.obj_id > context->lastinode.inode) {
3342                     Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2);
3343                     if (!NT_SUCCESS(Status)) {
3344                         ERR("finish_inode returned %08x\n", Status);
3345                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3346                         goto end;
3347                     }
3348 
3349                     if (context->send->cancelling) {
3350                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3351                         goto end;
3352                     }
3353                 }
3354 
3355                 if (tp2.item->key.obj_type == TYPE_INODE_ITEM) {
3356                     Status = send_inode(context, NULL, &tp2);
3357                     if (!NT_SUCCESS(Status)) {
3358                         ERR("send_inode returned %08x\n", Status);
3359                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3360                         goto end;
3361                     }
3362                 } else if (tp2.item->key.obj_type == TYPE_INODE_REF) {
3363                     Status = send_inode_ref(context, &tp2, true);
3364                     if (!NT_SUCCESS(Status)) {
3365                         ERR("send_inode_ref returned %08x\n", Status);
3366                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3367                         goto end;
3368                     }
3369                 } else if (tp2.item->key.obj_type == TYPE_INODE_EXTREF) {
3370                     Status = send_inode_extref(context, &tp2, true);
3371                     if (!NT_SUCCESS(Status)) {
3372                         ERR("send_inode_extref returned %08x\n", Status);
3373                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3374                         goto end;
3375                     }
3376                 } else if (tp2.item->key.obj_type == TYPE_EXTENT_DATA && !context->lastinode.deleting) {
3377                     Status = send_extent_data(context, NULL, &tp2);
3378                     if (!NT_SUCCESS(Status)) {
3379                         ERR("send_extent_data returned %08x\n", Status);
3380                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3381                         goto end;
3382                     }
3383 
3384                     if (context->send->cancelling) {
3385                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3386                         goto end;
3387                     }
3388                 } else if (tp2.item->key.obj_type == TYPE_XATTR_ITEM && !context->lastinode.deleting) {
3389                     Status = send_xattr(context, NULL, &tp2);
3390                     if (!NT_SUCCESS(Status)) {
3391                         ERR("send_xattr returned %08x\n", Status);
3392                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3393                         goto end;
3394                     }
3395 
3396                     if (context->send->cancelling) {
3397                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3398                         goto end;
3399                     }
3400                 }
3401 
3402                 if (find_next_item(context->Vcb, &tp2, &next_tp, false, NULL))
3403                     tp2 = next_tp;
3404                 else
3405                     ended2 = true;
3406             }
3407         } while (!ended1 || !ended2);
3408     } else {
3409         do {
3410             traverse_ptr next_tp;
3411 
3412             if (context->datalen > SEND_BUFFER_LENGTH) {
3413                 KEY key = tp.item->key;
3414 
3415                 ExReleaseResourceLite(&context->Vcb->tree_lock);
3416 
3417                 KeClearEvent(&context->send->cleared_event);
3418                 KeSetEvent(&context->buffer_event, 0, true);
3419                 KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL);
3420 
3421                 if (context->send->cancelling)
3422                     goto end;
3423 
3424                 ExAcquireResourceSharedLite(&context->Vcb->tree_lock, true);
3425 
3426                 Status = find_item(context->Vcb, context->root, &tp, &key, false, NULL);
3427                 if (!NT_SUCCESS(Status)) {
3428                     ERR("find_item returned %08x\n", Status);
3429                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3430                     goto end;
3431                 }
3432 
3433                 if (keycmp(tp.item->key, key)) {
3434                     ERR("readonly subvolume changed\n");
3435                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3436                     Status = STATUS_INTERNAL_ERROR;
3437                     goto end;
3438                 }
3439             }
3440 
3441             if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) {
3442                 Status = finish_inode(context, &tp, NULL);
3443                 if (!NT_SUCCESS(Status)) {
3444                     ERR("finish_inode returned %08x\n", Status);
3445                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3446                     goto end;
3447                 }
3448 
3449                 if (context->send->cancelling) {
3450                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3451                     goto end;
3452                 }
3453             }
3454 
3455             if (tp.item->key.obj_type == TYPE_INODE_ITEM) {
3456                 Status = send_inode(context, &tp, NULL);
3457                 if (!NT_SUCCESS(Status)) {
3458                     ERR("send_inode returned %08x\n", Status);
3459                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3460                     goto end;
3461                 }
3462             } else if (tp.item->key.obj_type == TYPE_INODE_REF) {
3463                 Status = send_inode_ref(context, &tp, false);
3464                 if (!NT_SUCCESS(Status)) {
3465                     ERR("send_inode_ref returned %08x\n", Status);
3466                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3467                     goto end;
3468                 }
3469             } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) {
3470                 Status = send_inode_extref(context, &tp, false);
3471                 if (!NT_SUCCESS(Status)) {
3472                     ERR("send_inode_extref returned %08x\n", Status);
3473                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3474                     goto end;
3475                 }
3476             } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) {
3477                 Status = send_extent_data(context, &tp, NULL);
3478                 if (!NT_SUCCESS(Status)) {
3479                     ERR("send_extent_data returned %08x\n", Status);
3480                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3481                     goto end;
3482                 }
3483 
3484                 if (context->send->cancelling) {
3485                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3486                     goto end;
3487                 }
3488             } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) {
3489                 Status = send_xattr(context, &tp, NULL);
3490                 if (!NT_SUCCESS(Status)) {
3491                     ERR("send_xattr returned %08x\n", Status);
3492                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3493                     goto end;
3494                 }
3495 
3496                 if (context->send->cancelling) {
3497                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3498                     goto end;
3499                 }
3500             }
3501 
3502             if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL))
3503                 tp = next_tp;
3504             else
3505                 break;
3506         } while (true);
3507     }
3508 
3509     if (context->lastinode.inode != 0) {
3510         Status = finish_inode(context, NULL, NULL);
3511         if (!NT_SUCCESS(Status)) {
3512             ERR("finish_inode returned %08x\n", Status);
3513             ExReleaseResourceLite(&context->Vcb->tree_lock);
3514             goto end;
3515         }
3516 
3517         ExReleaseResourceLite(&context->Vcb->tree_lock);
3518 
3519         if (context->send->cancelling)
3520             goto end;
3521     } else
3522         ExReleaseResourceLite(&context->Vcb->tree_lock);
3523 
3524     KeClearEvent(&context->send->cleared_event);
3525     KeSetEvent(&context->buffer_event, 0, true);
3526     KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL);
3527 
3528     Status = STATUS_SUCCESS;
3529 
3530 end:
3531     if (!NT_SUCCESS(Status)) {
3532         KeSetEvent(&context->buffer_event, 0, false);
3533 
3534         if (context->send->ccb)
3535             context->send->ccb->send_status = Status;
3536     }
3537 
3538     ExAcquireResourceExclusiveLite(&context->Vcb->send_load_lock, true);
3539 
3540     while (!IsListEmpty(&context->orphans)) {
3541         orphan* o = CONTAINING_RECORD(RemoveHeadList(&context->orphans), orphan, list_entry);
3542         ExFreePool(o);
3543     }
3544 
3545     while (!IsListEmpty(&context->dirs)) {
3546         send_dir* sd = CONTAINING_RECORD(RemoveHeadList(&context->dirs), send_dir, list_entry);
3547 
3548         if (sd->name)
3549             ExFreePool(sd->name);
3550 
3551         while (!IsListEmpty(&sd->deleted_children)) {
3552             deleted_child* dc = CONTAINING_RECORD(RemoveHeadList(&sd->deleted_children), deleted_child, list_entry);
3553             ExFreePool(dc);
3554         }
3555 
3556         ExFreePool(sd);
3557     }
3558 
3559     ZwClose(context->send->thread);
3560     context->send->thread = NULL;
3561 
3562     if (context->send->ccb)
3563         context->send->ccb->send = NULL;
3564 
3565     RemoveEntryList(&context->send->list_entry);
3566     ExFreePool(context->send);
3567     ExFreePool(context->data);
3568 
3569     InterlockedDecrement(&context->Vcb->running_sends);
3570     InterlockedDecrement(&context->root->send_ops);
3571 
3572     if (context->parent)
3573         InterlockedDecrement(&context->parent->send_ops);
3574 
3575     ExReleaseResourceLite(&context->Vcb->send_load_lock);
3576 
3577     if (context->clones) {
3578         ULONG i;
3579 
3580         for (i = 0; i < context->num_clones; i++) {
3581             InterlockedDecrement(&context->clones[i]->send_ops);
3582         }
3583 
3584         ExFreePool(context->clones);
3585     }
3586 
3587     ExFreePool(context);
3588 
3589     PsTerminateSystemThread(STATUS_SUCCESS);
3590 }
3591 
3592 NTSTATUS send_subvol(device_extension* Vcb, void* data, ULONG datalen, PFILE_OBJECT FileObject, PIRP Irp) {
3593     NTSTATUS Status;
3594     fcb* fcb;
3595     ccb* ccb;
3596     root* parsubvol = NULL;
3597     send_context* context;
3598     send_info* send;
3599     ULONG num_clones = 0;
3600     root** clones = NULL;
3601     OBJECT_ATTRIBUTES oa;
3602 
3603     if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb)
3604         return STATUS_INVALID_PARAMETER;
3605 
3606     if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode))
3607         return STATUS_PRIVILEGE_NOT_HELD;
3608 
3609     fcb = FileObject->FsContext;
3610     ccb = FileObject->FsContext2;
3611 
3612     if (fcb->inode != SUBVOL_ROOT_INODE || fcb == Vcb->root_fileref->fcb)
3613         return STATUS_INVALID_PARAMETER;
3614 
3615     if (!Vcb->readonly && !(fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY))
3616         return STATUS_INVALID_PARAMETER;
3617 
3618     if (data) {
3619         btrfs_send_subvol* bss = (btrfs_send_subvol*)data;
3620         HANDLE parent;
3621 
3622 #if defined(_WIN64)
3623         if (IoIs32bitProcess(Irp)) {
3624             btrfs_send_subvol32* bss32 = (btrfs_send_subvol32*)data;
3625 
3626             if (datalen < offsetof(btrfs_send_subvol32, num_clones))
3627                 return STATUS_INVALID_PARAMETER;
3628 
3629             parent = Handle32ToHandle(bss32->parent);
3630 
3631             if (datalen >= offsetof(btrfs_send_subvol32, clones[0]))
3632                 num_clones = bss32->num_clones;
3633 
3634             if (datalen < offsetof(btrfs_send_subvol32, clones[0]) + (num_clones * sizeof(uint32_t)))
3635                 return STATUS_INVALID_PARAMETER;
3636         } else {
3637 #endif
3638             if (datalen < offsetof(btrfs_send_subvol, num_clones))
3639                 return STATUS_INVALID_PARAMETER;
3640 
3641             parent = bss->parent;
3642 
3643             if (datalen >= offsetof(btrfs_send_subvol, clones[0]))
3644                 num_clones = bss->num_clones;
3645 
3646             if (datalen < offsetof(btrfs_send_subvol, clones[0]) + (num_clones * sizeof(HANDLE)))
3647                 return STATUS_INVALID_PARAMETER;
3648 #if defined(_WIN64)
3649         }
3650 #endif
3651 
3652         if (parent) {
3653             PFILE_OBJECT fileobj;
3654             struct _fcb* parfcb;
3655 
3656             Status = ObReferenceObjectByHandle(parent, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL);
3657             if (!NT_SUCCESS(Status)) {
3658                 ERR("ObReferenceObjectByHandle returned %08x\n", Status);
3659                 return Status;
3660             }
3661 
3662             if (fileobj->DeviceObject != FileObject->DeviceObject) {
3663                 ObDereferenceObject(fileobj);
3664                 return STATUS_INVALID_PARAMETER;
3665             }
3666 
3667             parfcb = fileobj->FsContext;
3668 
3669             if (!parfcb || parfcb == Vcb->root_fileref->fcb || parfcb == Vcb->volume_fcb || parfcb->inode != SUBVOL_ROOT_INODE) {
3670                 ObDereferenceObject(fileobj);
3671                 return STATUS_INVALID_PARAMETER;
3672             }
3673 
3674             parsubvol = parfcb->subvol;
3675             ObDereferenceObject(fileobj);
3676 
3677             if (!Vcb->readonly && !(parsubvol->root_item.flags & BTRFS_SUBVOL_READONLY))
3678                 return STATUS_INVALID_PARAMETER;
3679 
3680             if (parsubvol == fcb->subvol)
3681                 return STATUS_INVALID_PARAMETER;
3682         }
3683 
3684         if (num_clones > 0) {
3685             ULONG i;
3686 
3687             clones = ExAllocatePoolWithTag(PagedPool, sizeof(root*) * num_clones, ALLOC_TAG);
3688             if (!clones) {
3689                 ERR("out of memory\n");
3690                 return STATUS_INSUFFICIENT_RESOURCES;
3691             }
3692 
3693             for (i = 0; i < num_clones; i++) {
3694                 HANDLE h;
3695                 PFILE_OBJECT fileobj;
3696                 struct _fcb* clonefcb;
3697 
3698 #if defined(_WIN64)
3699                 if (IoIs32bitProcess(Irp)) {
3700                     btrfs_send_subvol32* bss32 = (btrfs_send_subvol32*)data;
3701 
3702                     h = Handle32ToHandle(bss32->clones[i]);
3703                 } else
3704 #endif
3705                     h = bss->clones[i];
3706 
3707                 Status = ObReferenceObjectByHandle(h, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL);
3708                 if (!NT_SUCCESS(Status)) {
3709                     ERR("ObReferenceObjectByHandle returned %08x\n", Status);
3710                     ExFreePool(clones);
3711                     return Status;
3712                 }
3713 
3714                 if (fileobj->DeviceObject != FileObject->DeviceObject) {
3715                     ObDereferenceObject(fileobj);
3716                     ExFreePool(clones);
3717                     return STATUS_INVALID_PARAMETER;
3718                 }
3719 
3720                 clonefcb = fileobj->FsContext;
3721 
3722                 if (!clonefcb || clonefcb == Vcb->root_fileref->fcb || clonefcb == Vcb->volume_fcb || clonefcb->inode != SUBVOL_ROOT_INODE) {
3723                     ObDereferenceObject(fileobj);
3724                     ExFreePool(clones);
3725                     return STATUS_INVALID_PARAMETER;
3726                 }
3727 
3728                 clones[i] = clonefcb->subvol;
3729                 ObDereferenceObject(fileobj);
3730 
3731                 if (!Vcb->readonly && !(clones[i]->root_item.flags & BTRFS_SUBVOL_READONLY)) {
3732                     ExFreePool(clones);
3733                     return STATUS_INVALID_PARAMETER;
3734                 }
3735             }
3736         }
3737     }
3738 
3739     ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, true);
3740 
3741     if (ccb->send) {
3742         WARN("send operation already running\n");
3743         ExReleaseResourceLite(&Vcb->send_load_lock);
3744         return STATUS_DEVICE_NOT_READY;
3745     }
3746 
3747     context = ExAllocatePoolWithTag(NonPagedPool, sizeof(send_context), ALLOC_TAG);
3748     if (!context) {
3749         ERR("out of memory\n");
3750 
3751         if (clones)
3752             ExFreePool(clones);
3753 
3754         ExReleaseResourceLite(&Vcb->send_load_lock);
3755         return STATUS_INSUFFICIENT_RESOURCES;
3756     }
3757 
3758     context->Vcb = Vcb;
3759     context->root = fcb->subvol;
3760     context->parent = parsubvol;
3761     InitializeListHead(&context->orphans);
3762     InitializeListHead(&context->dirs);
3763     InitializeListHead(&context->pending_rmdirs);
3764     context->lastinode.inode = 0;
3765     context->lastinode.path = NULL;
3766     context->lastinode.sd = NULL;
3767     context->root_dir = NULL;
3768     context->num_clones = num_clones;
3769     context->clones = clones;
3770     InitializeListHead(&context->lastinode.refs);
3771     InitializeListHead(&context->lastinode.oldrefs);
3772     InitializeListHead(&context->lastinode.exts);
3773     InitializeListHead(&context->lastinode.oldexts);
3774 
3775     context->data = ExAllocatePoolWithTag(PagedPool, SEND_BUFFER_LENGTH + (2 * MAX_SEND_WRITE), ALLOC_TAG); // give ourselves some wiggle room
3776     if (!context->data) {
3777         ExFreePool(context);
3778         ExReleaseResourceLite(&Vcb->send_load_lock);
3779         return STATUS_INSUFFICIENT_RESOURCES;
3780     }
3781 
3782     context->datalen = 0;
3783 
3784     send_subvol_header(context, fcb->subvol, ccb->fileref); // FIXME - fileref needs some sort of lock here
3785 
3786     KeInitializeEvent(&context->buffer_event, NotificationEvent, false);
3787 
3788     send = ExAllocatePoolWithTag(NonPagedPool, sizeof(send_info), ALLOC_TAG);
3789     if (!send) {
3790         ERR("out of memory\n");
3791         ExFreePool(context->data);
3792         ExFreePool(context);
3793 
3794         if (clones)
3795             ExFreePool(clones);
3796 
3797         ExReleaseResourceLite(&Vcb->send_load_lock);
3798         return STATUS_INSUFFICIENT_RESOURCES;
3799     }
3800 
3801     KeInitializeEvent(&send->cleared_event, NotificationEvent, false);
3802 
3803     send->context = context;
3804     context->send = send;
3805 
3806     ccb->send = send;
3807     send->ccb = ccb;
3808     ccb->send_status = STATUS_SUCCESS;
3809 
3810     send->cancelling = false;
3811 
3812     InterlockedIncrement(&Vcb->running_sends);
3813 
3814     InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
3815 
3816     Status = PsCreateSystemThread(&send->thread, 0, &oa, NULL, NULL, send_thread, context);
3817     if (!NT_SUCCESS(Status)) {
3818         ERR("PsCreateSystemThread returned %08x\n", Status);
3819         ccb->send = NULL;
3820         InterlockedDecrement(&Vcb->running_sends);
3821         ExFreePool(send);
3822         ExFreePool(context->data);
3823         ExFreePool(context);
3824 
3825         if (clones)
3826             ExFreePool(clones);
3827 
3828         ExReleaseResourceLite(&Vcb->send_load_lock);
3829         return Status;
3830     }
3831 
3832     InsertTailList(&Vcb->send_ops, &send->list_entry);
3833     ExReleaseResourceLite(&Vcb->send_load_lock);
3834 
3835     return STATUS_SUCCESS;
3836 }
3837 
3838 NTSTATUS read_send_buffer(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, ULONG_PTR* retlen, KPROCESSOR_MODE processor_mode) {
3839     ccb* ccb;
3840     send_context* context;
3841 
3842     ccb = FileObject ? FileObject->FsContext2 : NULL;
3843     if (!ccb)
3844         return STATUS_INVALID_PARAMETER;
3845 
3846     if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))
3847         return STATUS_PRIVILEGE_NOT_HELD;
3848 
3849     ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, true);
3850 
3851     if (!ccb->send) {
3852         ExReleaseResourceLite(&Vcb->send_load_lock);
3853         return !NT_SUCCESS(ccb->send_status) ? ccb->send_status : STATUS_END_OF_FILE;
3854     }
3855 
3856     context = (send_context*)ccb->send->context;
3857 
3858     KeWaitForSingleObject(&context->buffer_event, Executive, KernelMode, false, NULL);
3859 
3860     if (datalen == 0) {
3861         ExReleaseResourceLite(&Vcb->send_load_lock);
3862         return STATUS_SUCCESS;
3863     }
3864 
3865     RtlCopyMemory(data, context->data, min(datalen, context->datalen));
3866 
3867     if (datalen < context->datalen) { // not empty yet
3868         *retlen = datalen;
3869         RtlMoveMemory(context->data, &context->data[datalen], context->datalen - datalen);
3870         context->datalen -= datalen;
3871         ExReleaseResourceLite(&Vcb->send_load_lock);
3872     } else {
3873         *retlen = context->datalen;
3874         context->datalen = 0;
3875         ExReleaseResourceLite(&Vcb->send_load_lock);
3876 
3877         KeClearEvent(&context->buffer_event);
3878         KeSetEvent(&ccb->send->cleared_event, 0, false);
3879     }
3880 
3881     return STATUS_SUCCESS;
3882 }
3883