xref: /reactos/drivers/filesystems/btrfs/send.c (revision 71fefa32)
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 inode;
23     BOOL dummy;
24     BTRFS_TIME atime;
25     BTRFS_TIME mtime;
26     BTRFS_TIME ctime;
27     struct send_dir* parent;
28     UINT16 namelen;
29     char* name;
30     LIST_ENTRY deleted_children;
31 } send_dir;
32 
33 typedef struct {
34     LIST_ENTRY list_entry;
35     UINT64 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 namelen;
51     char name[1];
52 } ref;
53 
54 typedef struct {
55     send_dir* sd;
56     UINT64 last_child_inode;
57     LIST_ENTRY list_entry;
58 } pending_rmdir;
59 
60 typedef struct {
61     UINT64 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* 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 inode;
84         BOOL deleting;
85         BOOL new;
86         UINT64 gen;
87         UINT64 uid;
88         UINT64 olduid;
89         UINT64 gid;
90         UINT64 oldgid;
91         UINT64 mode;
92         UINT64 oldmode;
93         UINT64 size;
94         UINT64 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 dir, UINT64 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 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*)bsc, context->datalen - pos);
129 }
130 
131 static void send_add_tlv(send_context* context, UINT16 type, void* data, UINT16 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 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 inode, UINT64 generation, char* name) {
164     char *ptr, *ptr2;
165     UINT64 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*)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 inode, char** link, UINT16* 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 (%llx,%x,%llx)\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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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)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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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 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)strlen(name));
435         send_add_tlv(context, BTRFS_SEND_TLV_INODE, &tp->item->key.obj_id, sizeof(UINT64));
436 
437         if (cmd == BTRFS_SEND_CMD_MKNOD || cmd == BTRFS_SEND_CMD_MKFIFO || cmd == BTRFS_SEND_CMD_MKSOCK) {
438             UINT64 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));
441             send_add_tlv(context, BTRFS_SEND_TLV_MODE, &mode, sizeof(UINT64));
442         } else if (cmd == BTRFS_SEND_CMD_SYMLINK && ii->st_size > 0) {
443             char* link;
444             UINT16 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 inode, send_dir* parent, char* name, UINT16 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 find_path_len(send_dir* parent, UINT16 namelen) {
560     UINT16 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 type, send_dir* parent, char* name, UINT16 namelen) {
586     UINT16 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 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)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)strlen(context->lastinode.path) : 0);
611 
612         send_command_finish(context, pos);
613     }
614 
615     if (context->lastinode.o) {
616         UINT16 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 dir, UINT64 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("(%llx,%x,%llx) 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)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 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 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("(%llx,%x,%llx) 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));
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("(%llx,%x,%llx) 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)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 len;
874 
875     if (tp->item->size < sizeof(INODE_EXTREF)) {
876         ERR("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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));
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)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));
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));
982     }
983 
984     send_command_finish(context, pos);
985 }
986 
987 static void send_chown_command(send_context* context, char* path, UINT64 uid, UINT64 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)strlen(path) : 0);
993     send_add_tlv(context, BTRFS_SEND_TLV_UID, &uid, sizeof(UINT64));
994     send_add_tlv(context, BTRFS_SEND_TLV_GID, &gid, sizeof(UINT64));
995 
996     send_command_finish(context, pos);
997 }
998 
999 static void send_chmod_command(send_context* context, char* path, UINT64 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)strlen(path) : 0);
1007     send_add_tlv(context, BTRFS_SEND_TLV_MODE, &mode, sizeof(UINT64));
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)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 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)strlen(path) : 0);
1031     send_add_tlv(context, BTRFS_SEND_TLV_SIZE, &size, sizeof(UINT64));
1032 
1033     send_command_finish(context, pos);
1034 }
1035 
1036 static NTSTATUS send_unlink_command(send_context* context, send_dir* parent, UINT16 namelen, char* name) {
1037     ULONG pos = context->datalen;
1038     UINT16 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 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* 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("(%llx,%x,%llx) 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 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* inode, BOOL* dir) {
1133     NTSTATUS Status;
1134     KEY searchkey;
1135     traverse_ptr tp;
1136     DIR_ITEM* di;
1137     UINT16 len;
1138 
1139     searchkey.obj_id = sd->inode;
1140     searchkey.obj_type = TYPE_DIR_ITEM;
1141     searchkey.offset = calc_crc32c(0xfffffffe, (UINT8*)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("(%llx,%x,%llx) 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)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 inode, BOOL dir, UINT64 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)strlen(name));
1233 
1234         send_command_finish(context, pos);
1235 
1236         if (sd->name)
1237             ExFreePool(sd->name);
1238 
1239         sd->namelen = (UINT16)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)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 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)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 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)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)strlen(context->lastinode.path));
1404                 send_add_tlv(context, BTRFS_SEND_TLV_PATH_TO, name, (UINT16)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)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 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 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 size) {
1652     UINT64 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 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 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 inode) {
1841     NTSTATUS Status;
1842     KEY searchkey;
1843     traverse_ptr tp;
1844     UINT16 len = 0;
1845     UINT64 num;
1846     UINT8* 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 %llx\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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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 %llx\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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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 clone_offset = tp.item->key.offset + ed2->offset - seed2->offset;
1988                             UINT64 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));
1996                                 send_add_tlv(context, BTRFS_SEND_TLV_CLONE_LENGTH, &clone_len, sizeof(UINT64));
1997                                 send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (UINT16)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));
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));
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 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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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 len = tp.item->size - sizeof(EXTENT_ITEM);
2059         UINT8* ptr = (UINT8*)&ei[1];
2060 
2061         while (len > 0) {
2062             UINT8 secttype = *ptr;
2063             ULONG sectlen = get_extent_data_len(secttype);
2064             UINT64 sectcount = get_extent_data_refcount(secttype, ptr + sizeof(UINT8));
2065 
2066             len--;
2067 
2068             if (sectlen > len) {
2069                 ERR("(%llx,%x,%llx): %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("(%llx,%x,%llx): 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));
2082 
2083                 if (try_clone_edr(context, se, sectedr))
2084                     return TRUE;
2085             }
2086 
2087             len -= sectlen;
2088             ptr += sizeof(UINT8) + 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("(%llx,%x,%llx) 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)strlen(context->lastinode.path) : 0);
2186             send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &se->offset, sizeof(UINT64));
2187 
2188             if (se->data.compression == BTRFS_COMPRESSION_NONE)
2189                 send_add_tlv(context, BTRFS_SEND_TLV_DATA, se->data.data, (UINT16)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)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)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)) {
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);
2212 
2213                     Status = lzo_decompress(se->data.data + sizeof(UINT32), inlen, &context->data[context->datalen - se->data.decoded_size], (UINT32)se->data.decoded_size, sizeof(UINT32));
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)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 off, offset;
2255 
2256             for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) {
2257                 UINT16 length = (UINT16)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)strlen(context->lastinode.path) : 0);
2280 
2281                 offset = se->offset + off;
2282                 send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(UINT64));
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 off, offset;
2291             UINT8* 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 length = (UINT16)min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE);
2303                 ULONG skip_start;
2304                 UINT64 addr = ed2->address + off;
2305                 UINT32* 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 len;
2332 
2333                     len = (UINT32)sector_align(length + skip_start, context->Vcb->superblock.sector_size) / context->Vcb->superblock.sector_size;
2334 
2335                     csum = ExAllocatePoolWithTag(PagedPool, len * sizeof(UINT32), 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)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)strlen(context->lastinode.path) : 0);
2374 
2375                 offset = se->offset + off;
2376                 send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(UINT64));
2377 
2378                 length = (UINT16)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 *buf, *compbuf;
2387             UINT64 off;
2388             UINT32* 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 len;
2411 
2412                 len = (UINT32)(ed2->size / context->Vcb->superblock.sector_size);
2413 
2414                 csum = ExAllocatePoolWithTag(PagedPool, len * sizeof(UINT32), 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)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)ed2->size, buf, (UINT32)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)], (UINT32)ed2->size, buf, (UINT32)se->data.decoded_size, sizeof(UINT32));
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)ed2->size, buf, (UINT32)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 length = (UINT16)min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE);
2486                 UINT64 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)strlen(context->lastinode.path) : 0);
2511 
2512                 offset = se->offset + off;
2513                 send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(UINT64));
2514 
2515                 length = (UINT16)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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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 namelen;
2761     char* name;
2762     UINT16 value1len;
2763     char* value1;
2764     UINT16 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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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)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("(%llx,%x,%llx) 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)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("(%llx,%x,%llx) 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("(%llx,%x,%llx) 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)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)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 #ifdef __REACTOS__
2971 static void NTAPI send_thread(void* ctx) {
2972 #else
2973 static void send_thread(void* ctx) {
2974 #endif
2975     send_context* context = (send_context*)ctx;
2976     NTSTATUS Status;
2977     KEY searchkey;
2978     traverse_ptr tp, tp2;
2979 
2980     InterlockedIncrement(&context->root->send_ops);
2981 
2982     if (context->parent)
2983         InterlockedIncrement(&context->parent->send_ops);
2984 
2985     if (context->clones) {
2986         ULONG i;
2987 
2988         for (i = 0; i < context->num_clones; i++) {
2989             InterlockedIncrement(&context->clones[i]->send_ops);
2990         }
2991     }
2992 
2993     ExAcquireResourceExclusiveLite(&context->Vcb->tree_lock, TRUE);
2994 
2995     flush_subvol_fcbs(context->root);
2996 
2997     if (context->parent)
2998         flush_subvol_fcbs(context->parent);
2999 
3000     if (context->Vcb->need_write)
3001         Status = do_write(context->Vcb, NULL);
3002     else
3003         Status = STATUS_SUCCESS;
3004 
3005     free_trees(context->Vcb);
3006 
3007     if (!NT_SUCCESS(Status)) {
3008         ERR("do_write returned %08x\n", Status);
3009         ExReleaseResourceLite(&context->Vcb->tree_lock);
3010         goto end;
3011     }
3012 
3013     ExConvertExclusiveToSharedLite(&context->Vcb->tree_lock);
3014 
3015     searchkey.obj_id = searchkey.offset = 0;
3016     searchkey.obj_type = 0;
3017 
3018     Status = find_item(context->Vcb, context->root, &tp, &searchkey, FALSE, NULL);
3019     if (!NT_SUCCESS(Status)) {
3020         ERR("find_item returned %08x\n", Status);
3021         ExReleaseResourceLite(&context->Vcb->tree_lock);
3022         goto end;
3023     }
3024 
3025     if (context->parent) {
3026         BOOL ended1 = FALSE, ended2 = FALSE;
3027         Status = find_item(context->Vcb, context->parent, &tp2, &searchkey, FALSE, NULL);
3028         if (!NT_SUCCESS(Status)) {
3029             ERR("find_item returned %08x\n", Status);
3030             ExReleaseResourceLite(&context->Vcb->tree_lock);
3031             goto end;
3032         }
3033 
3034         do {
3035             traverse_ptr next_tp;
3036 
3037             if (context->datalen > SEND_BUFFER_LENGTH) {
3038                 KEY key1 = tp.item->key, key2 = tp2.item->key;
3039 
3040                 ExReleaseResourceLite(&context->Vcb->tree_lock);
3041 
3042                 KeClearEvent(&context->send->cleared_event);
3043                 KeSetEvent(&context->buffer_event, 0, TRUE);
3044                 KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, FALSE, NULL);
3045 
3046                 if (context->send->cancelling)
3047                     goto end;
3048 
3049                 ExAcquireResourceSharedLite(&context->Vcb->tree_lock, TRUE);
3050 
3051                 if (!ended1) {
3052                     Status = find_item(context->Vcb, context->root, &tp, &key1, FALSE, NULL);
3053                     if (!NT_SUCCESS(Status)) {
3054                         ERR("find_item returned %08x\n", Status);
3055                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3056                         goto end;
3057                     }
3058 
3059                     if (keycmp(tp.item->key, key1)) {
3060                         ERR("readonly subvolume changed\n");
3061                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3062                         Status = STATUS_INTERNAL_ERROR;
3063                         goto end;
3064                     }
3065                 }
3066 
3067                 if (!ended2) {
3068                     Status = find_item(context->Vcb, context->parent, &tp2, &key2, FALSE, NULL);
3069                     if (!NT_SUCCESS(Status)) {
3070                         ERR("find_item returned %08x\n", Status);
3071                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3072                         goto end;
3073                     }
3074 
3075                     if (keycmp(tp2.item->key, key2)) {
3076                         ERR("readonly subvolume changed\n");
3077                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3078                         Status = STATUS_INTERNAL_ERROR;
3079                         goto end;
3080                     }
3081                 }
3082             }
3083 
3084             while (!ended1 && !ended2 && tp.tree->header.address == tp2.tree->header.address) {
3085                 Status = skip_to_difference(context->Vcb, &tp, &tp2, &ended1, &ended2);
3086                 if (!NT_SUCCESS(Status)) {
3087                     ERR("skip_to_difference returned %08x\n", Status);
3088                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3089                     goto end;
3090                 }
3091             }
3092 
3093             if (!ended1 && !ended2 && !keycmp(tp.item->key, tp2.item->key)) {
3094                 BOOL no_next = FALSE, no_next2 = FALSE;
3095 
3096                 TRACE("~ %llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
3097 
3098                 if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) {
3099                     Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2);
3100                     if (!NT_SUCCESS(Status)) {
3101                         ERR("finish_inode returned %08x\n", Status);
3102                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3103                         goto end;
3104                     }
3105 
3106                     if (context->send->cancelling) {
3107                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3108                         goto end;
3109                     }
3110                 }
3111 
3112                 if (tp.item->key.obj_type == TYPE_INODE_ITEM) {
3113                     if (tp.item->size == tp2.item->size && tp.item->size > 0 && RtlCompareMemory(tp.item->data, tp2.item->data, tp.item->size) == tp.item->size) {
3114                         UINT64 inode = tp.item->key.obj_id;
3115 
3116                         while (TRUE) {
3117                             if (!find_next_item(context->Vcb, &tp, &next_tp, FALSE, NULL)) {
3118                                 ended1 = TRUE;
3119                                 break;
3120                             }
3121 
3122                             tp = next_tp;
3123 
3124                             if (tp.item->key.obj_id != inode)
3125                                 break;
3126                         }
3127 
3128                         while (TRUE) {
3129                             if (!find_next_item(context->Vcb, &tp2, &next_tp, FALSE, NULL)) {
3130                                 ended2 = TRUE;
3131                                 break;
3132                             }
3133 
3134                             tp2 = next_tp;
3135 
3136                             if (tp2.item->key.obj_id != inode)
3137                                 break;
3138                         }
3139 
3140                         no_next = TRUE;
3141                     } else if (tp.item->size > sizeof(UINT64) && tp2.item->size > sizeof(UINT64) && *(UINT64*)tp.item->data != *(UINT64*)tp2.item->data) {
3142                         UINT64 inode = tp.item->key.obj_id;
3143 
3144                         Status = send_inode(context, NULL, &tp2);
3145                         if (!NT_SUCCESS(Status)) {
3146                             ERR("send_inode returned %08x\n", Status);
3147                             ExReleaseResourceLite(&context->Vcb->tree_lock);
3148                             goto end;
3149                         }
3150 
3151                         while (TRUE) {
3152                             if (!find_next_item(context->Vcb, &tp2, &next_tp, FALSE, NULL)) {
3153                                 ended2 = TRUE;
3154                                 break;
3155                             }
3156 
3157                             tp2 = next_tp;
3158 
3159                             if (tp2.item->key.obj_id != inode)
3160                                 break;
3161 
3162                             if (tp2.item->key.obj_type == TYPE_INODE_REF) {
3163                                 Status = send_inode_ref(context, &tp2, TRUE);
3164                                 if (!NT_SUCCESS(Status)) {
3165                                     ERR("send_inode_ref returned %08x\n", Status);
3166                                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3167                                     goto end;
3168                                 }
3169                             } else if (tp2.item->key.obj_type == TYPE_INODE_EXTREF) {
3170                                 Status = send_inode_extref(context, &tp2, TRUE);
3171                                 if (!NT_SUCCESS(Status)) {
3172                                     ERR("send_inode_extref returned %08x\n", Status);
3173                                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3174                                     goto end;
3175                                 }
3176                             }
3177                         }
3178 
3179                         Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2);
3180                         if (!NT_SUCCESS(Status)) {
3181                             ERR("finish_inode returned %08x\n", Status);
3182                             ExReleaseResourceLite(&context->Vcb->tree_lock);
3183                             goto end;
3184                         }
3185 
3186                         if (context->send->cancelling) {
3187                             ExReleaseResourceLite(&context->Vcb->tree_lock);
3188                             goto end;
3189                         }
3190 
3191                         no_next2 = TRUE;
3192 
3193                         Status = send_inode(context, &tp, NULL);
3194                         if (!NT_SUCCESS(Status)) {
3195                             ERR("send_inode returned %08x\n", Status);
3196                             ExReleaseResourceLite(&context->Vcb->tree_lock);
3197                             goto end;
3198                         }
3199                     } else {
3200                         Status = send_inode(context, &tp, &tp2);
3201                         if (!NT_SUCCESS(Status)) {
3202                             ERR("send_inode returned %08x\n", Status);
3203                             ExReleaseResourceLite(&context->Vcb->tree_lock);
3204                             goto end;
3205                         }
3206                     }
3207                 } else if (tp.item->key.obj_type == TYPE_INODE_REF) {
3208                     Status = send_inode_ref(context, &tp, FALSE);
3209                     if (!NT_SUCCESS(Status)) {
3210                         ERR("send_inode_ref returned %08x\n", Status);
3211                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3212                         goto end;
3213                     }
3214 
3215                     Status = send_inode_ref(context, &tp2, TRUE);
3216                     if (!NT_SUCCESS(Status)) {
3217                         ERR("send_inode_ref returned %08x\n", Status);
3218                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3219                         goto end;
3220                     }
3221                 } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) {
3222                     Status = send_inode_extref(context, &tp, FALSE);
3223                     if (!NT_SUCCESS(Status)) {
3224                         ERR("send_inode_extref returned %08x\n", Status);
3225                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3226                         goto end;
3227                     }
3228 
3229                     Status = send_inode_extref(context, &tp2, TRUE);
3230                     if (!NT_SUCCESS(Status)) {
3231                         ERR("send_inode_extref returned %08x\n", Status);
3232                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3233                         goto end;
3234                     }
3235                 } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) {
3236                     Status = send_extent_data(context, &tp, &tp2);
3237                     if (!NT_SUCCESS(Status)) {
3238                         ERR("send_extent_data returned %08x\n", Status);
3239                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3240                         goto end;
3241                     }
3242 
3243                     if (context->send->cancelling) {
3244                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3245                         goto end;
3246                     }
3247                 } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) {
3248                     Status = send_xattr(context, &tp, &tp2);
3249                     if (!NT_SUCCESS(Status)) {
3250                         ERR("send_xattr returned %08x\n", Status);
3251                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3252                         goto end;
3253                     }
3254 
3255                     if (context->send->cancelling) {
3256                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3257                         goto end;
3258                     }
3259                 }
3260 
3261                 if (!no_next) {
3262                     if (find_next_item(context->Vcb, &tp, &next_tp, FALSE, NULL))
3263                         tp = next_tp;
3264                     else
3265                         ended1 = TRUE;
3266 
3267                     if (!no_next2) {
3268                         if (find_next_item(context->Vcb, &tp2, &next_tp, FALSE, NULL))
3269                             tp2 = next_tp;
3270                         else
3271                             ended2 = TRUE;
3272                     }
3273                 }
3274             } else if (ended2 || (!ended1 && !ended2 && keycmp(tp.item->key, tp2.item->key) == -1)) {
3275                 TRACE("A %llx,%x,%llx\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
3276 
3277                 if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) {
3278                     Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2);
3279                     if (!NT_SUCCESS(Status)) {
3280                         ERR("finish_inode returned %08x\n", Status);
3281                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3282                         goto end;
3283                     }
3284 
3285                     if (context->send->cancelling) {
3286                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3287                         goto end;
3288                     }
3289                 }
3290 
3291                 if (tp.item->key.obj_type == TYPE_INODE_ITEM) {
3292                     Status = send_inode(context, &tp, NULL);
3293                     if (!NT_SUCCESS(Status)) {
3294                         ERR("send_inode returned %08x\n", Status);
3295                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3296                         goto end;
3297                     }
3298                 } else if (tp.item->key.obj_type == TYPE_INODE_REF) {
3299                     Status = send_inode_ref(context, &tp, FALSE);
3300                     if (!NT_SUCCESS(Status)) {
3301                         ERR("send_inode_ref returned %08x\n", Status);
3302                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3303                         goto end;
3304                     }
3305                 } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) {
3306                     Status = send_inode_extref(context, &tp, FALSE);
3307                     if (!NT_SUCCESS(Status)) {
3308                         ERR("send_inode_extref returned %08x\n", Status);
3309                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3310                         goto end;
3311                     }
3312                 } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) {
3313                     Status = send_extent_data(context, &tp, NULL);
3314                     if (!NT_SUCCESS(Status)) {
3315                         ERR("send_extent_data returned %08x\n", Status);
3316                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3317                         goto end;
3318                     }
3319 
3320                     if (context->send->cancelling) {
3321                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3322                         goto end;
3323                     }
3324                 } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) {
3325                     Status = send_xattr(context, &tp, NULL);
3326                     if (!NT_SUCCESS(Status)) {
3327                         ERR("send_xattr returned %08x\n", Status);
3328                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3329                         goto end;
3330                     }
3331 
3332                     if (context->send->cancelling) {
3333                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3334                         goto end;
3335                     }
3336                 }
3337 
3338                 if (find_next_item(context->Vcb, &tp, &next_tp, FALSE, NULL))
3339                     tp = next_tp;
3340                 else
3341                     ended1 = TRUE;
3342             } else if (ended1 || (!ended1 && !ended2 && keycmp(tp.item->key, tp2.item->key) == 1)) {
3343                 TRACE("B %llx,%x,%llx\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset);
3344 
3345                 if (context->lastinode.inode != 0 && tp2.item->key.obj_id > context->lastinode.inode) {
3346                     Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2);
3347                     if (!NT_SUCCESS(Status)) {
3348                         ERR("finish_inode returned %08x\n", Status);
3349                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3350                         goto end;
3351                     }
3352 
3353                     if (context->send->cancelling) {
3354                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3355                         goto end;
3356                     }
3357                 }
3358 
3359                 if (tp2.item->key.obj_type == TYPE_INODE_ITEM) {
3360                     Status = send_inode(context, NULL, &tp2);
3361                     if (!NT_SUCCESS(Status)) {
3362                         ERR("send_inode returned %08x\n", Status);
3363                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3364                         goto end;
3365                     }
3366                 } else if (tp2.item->key.obj_type == TYPE_INODE_REF) {
3367                     Status = send_inode_ref(context, &tp2, TRUE);
3368                     if (!NT_SUCCESS(Status)) {
3369                         ERR("send_inode_ref returned %08x\n", Status);
3370                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3371                         goto end;
3372                     }
3373                 } else if (tp2.item->key.obj_type == TYPE_INODE_EXTREF) {
3374                     Status = send_inode_extref(context, &tp2, TRUE);
3375                     if (!NT_SUCCESS(Status)) {
3376                         ERR("send_inode_extref returned %08x\n", Status);
3377                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3378                         goto end;
3379                     }
3380                 } else if (tp2.item->key.obj_type == TYPE_EXTENT_DATA && !context->lastinode.deleting) {
3381                     Status = send_extent_data(context, NULL, &tp2);
3382                     if (!NT_SUCCESS(Status)) {
3383                         ERR("send_extent_data returned %08x\n", Status);
3384                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3385                         goto end;
3386                     }
3387 
3388                     if (context->send->cancelling) {
3389                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3390                         goto end;
3391                     }
3392                 } else if (tp2.item->key.obj_type == TYPE_XATTR_ITEM && !context->lastinode.deleting) {
3393                     Status = send_xattr(context, NULL, &tp2);
3394                     if (!NT_SUCCESS(Status)) {
3395                         ERR("send_xattr returned %08x\n", Status);
3396                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3397                         goto end;
3398                     }
3399 
3400                     if (context->send->cancelling) {
3401                         ExReleaseResourceLite(&context->Vcb->tree_lock);
3402                         goto end;
3403                     }
3404                 }
3405 
3406                 if (find_next_item(context->Vcb, &tp2, &next_tp, FALSE, NULL))
3407                     tp2 = next_tp;
3408                 else
3409                     ended2 = TRUE;
3410             }
3411         } while (!ended1 || !ended2);
3412     } else {
3413         do {
3414             traverse_ptr next_tp;
3415 
3416             if (context->datalen > SEND_BUFFER_LENGTH) {
3417                 KEY key = tp.item->key;
3418 
3419                 ExReleaseResourceLite(&context->Vcb->tree_lock);
3420 
3421                 KeClearEvent(&context->send->cleared_event);
3422                 KeSetEvent(&context->buffer_event, 0, TRUE);
3423                 KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, FALSE, NULL);
3424 
3425                 if (context->send->cancelling)
3426                     goto end;
3427 
3428                 ExAcquireResourceSharedLite(&context->Vcb->tree_lock, TRUE);
3429 
3430                 Status = find_item(context->Vcb, context->root, &tp, &key, FALSE, NULL);
3431                 if (!NT_SUCCESS(Status)) {
3432                     ERR("find_item returned %08x\n", Status);
3433                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3434                     goto end;
3435                 }
3436 
3437                 if (keycmp(tp.item->key, key)) {
3438                     ERR("readonly subvolume changed\n");
3439                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3440                     Status = STATUS_INTERNAL_ERROR;
3441                     goto end;
3442                 }
3443             }
3444 
3445             if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) {
3446                 Status = finish_inode(context, &tp, NULL);
3447                 if (!NT_SUCCESS(Status)) {
3448                     ERR("finish_inode returned %08x\n", Status);
3449                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3450                     goto end;
3451                 }
3452 
3453                 if (context->send->cancelling) {
3454                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3455                     goto end;
3456                 }
3457             }
3458 
3459             if (tp.item->key.obj_type == TYPE_INODE_ITEM) {
3460                 Status = send_inode(context, &tp, NULL);
3461                 if (!NT_SUCCESS(Status)) {
3462                     ERR("send_inode returned %08x\n", Status);
3463                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3464                     goto end;
3465                 }
3466             } else if (tp.item->key.obj_type == TYPE_INODE_REF) {
3467                 Status = send_inode_ref(context, &tp, FALSE);
3468                 if (!NT_SUCCESS(Status)) {
3469                     ERR("send_inode_ref returned %08x\n", Status);
3470                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3471                     goto end;
3472                 }
3473             } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) {
3474                 Status = send_inode_extref(context, &tp, FALSE);
3475                 if (!NT_SUCCESS(Status)) {
3476                     ERR("send_inode_extref returned %08x\n", Status);
3477                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3478                     goto end;
3479                 }
3480             } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) {
3481                 Status = send_extent_data(context, &tp, NULL);
3482                 if (!NT_SUCCESS(Status)) {
3483                     ERR("send_extent_data returned %08x\n", Status);
3484                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3485                     goto end;
3486                 }
3487 
3488                 if (context->send->cancelling) {
3489                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3490                     goto end;
3491                 }
3492             } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) {
3493                 Status = send_xattr(context, &tp, NULL);
3494                 if (!NT_SUCCESS(Status)) {
3495                     ERR("send_xattr returned %08x\n", Status);
3496                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3497                     goto end;
3498                 }
3499 
3500                 if (context->send->cancelling) {
3501                     ExReleaseResourceLite(&context->Vcb->tree_lock);
3502                     goto end;
3503                 }
3504             }
3505 
3506             if (find_next_item(context->Vcb, &tp, &next_tp, FALSE, NULL))
3507                 tp = next_tp;
3508             else
3509                 break;
3510         } while (TRUE);
3511     }
3512 
3513     if (context->lastinode.inode != 0) {
3514         Status = finish_inode(context, NULL, NULL);
3515         if (!NT_SUCCESS(Status)) {
3516             ERR("finish_inode returned %08x\n", Status);
3517             ExReleaseResourceLite(&context->Vcb->tree_lock);
3518             goto end;
3519         }
3520 
3521         ExReleaseResourceLite(&context->Vcb->tree_lock);
3522 
3523         if (context->send->cancelling)
3524             goto end;
3525     } else
3526         ExReleaseResourceLite(&context->Vcb->tree_lock);
3527 
3528     KeClearEvent(&context->send->cleared_event);
3529     KeSetEvent(&context->buffer_event, 0, TRUE);
3530     KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, FALSE, NULL);
3531 
3532     Status = STATUS_SUCCESS;
3533 
3534 end:
3535     if (!NT_SUCCESS(Status)) {
3536         KeSetEvent(&context->buffer_event, 0, FALSE);
3537 
3538         if (context->send->ccb)
3539             context->send->ccb->send_status = Status;
3540     }
3541 
3542     ExAcquireResourceExclusiveLite(&context->Vcb->send_load_lock, TRUE);
3543 
3544     while (!IsListEmpty(&context->orphans)) {
3545         orphan* o = CONTAINING_RECORD(RemoveHeadList(&context->orphans), orphan, list_entry);
3546         ExFreePool(o);
3547     }
3548 
3549     while (!IsListEmpty(&context->dirs)) {
3550         send_dir* sd = CONTAINING_RECORD(RemoveHeadList(&context->dirs), send_dir, list_entry);
3551 
3552         if (sd->name)
3553             ExFreePool(sd->name);
3554 
3555         while (!IsListEmpty(&sd->deleted_children)) {
3556             deleted_child* dc = CONTAINING_RECORD(RemoveHeadList(&sd->deleted_children), deleted_child, list_entry);
3557             ExFreePool(dc);
3558         }
3559 
3560         ExFreePool(sd);
3561     }
3562 
3563     ZwClose(context->send->thread);
3564     context->send->thread = NULL;
3565 
3566     if (context->send->ccb)
3567         context->send->ccb->send = NULL;
3568 
3569     RemoveEntryList(&context->send->list_entry);
3570     ExFreePool(context->send);
3571     ExFreePool(context->data);
3572 
3573     InterlockedDecrement(&context->Vcb->running_sends);
3574     InterlockedDecrement(&context->root->send_ops);
3575 
3576     if (context->parent)
3577         InterlockedDecrement(&context->parent->send_ops);
3578 
3579     ExReleaseResourceLite(&context->Vcb->send_load_lock);
3580 
3581     if (context->clones) {
3582         ULONG i;
3583 
3584         for (i = 0; i < context->num_clones; i++) {
3585             InterlockedDecrement(&context->clones[i]->send_ops);
3586         }
3587 
3588         ExFreePool(context->clones);
3589     }
3590 
3591     ExFreePool(context);
3592 
3593     PsTerminateSystemThread(STATUS_SUCCESS);
3594 }
3595 
3596 NTSTATUS send_subvol(device_extension* Vcb, void* data, ULONG datalen, PFILE_OBJECT FileObject, PIRP Irp) {
3597     NTSTATUS Status;
3598     fcb* fcb;
3599     ccb* ccb;
3600     root* parsubvol = NULL;
3601     send_context* context;
3602     send_info* send;
3603     ULONG num_clones = 0;
3604     root** clones = NULL;
3605 
3606     if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb)
3607         return STATUS_INVALID_PARAMETER;
3608 
3609     if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode))
3610         return STATUS_PRIVILEGE_NOT_HELD;
3611 
3612     fcb = FileObject->FsContext;
3613     ccb = FileObject->FsContext2;
3614 
3615     if (fcb->inode != SUBVOL_ROOT_INODE || fcb == Vcb->root_fileref->fcb)
3616         return STATUS_INVALID_PARAMETER;
3617 
3618     if (!Vcb->readonly && !(fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY))
3619         return STATUS_INVALID_PARAMETER;
3620 
3621     if (data) {
3622         btrfs_send_subvol* bss = (btrfs_send_subvol*)data;
3623         HANDLE parent;
3624 
3625 #if defined(_WIN64)
3626         if (IoIs32bitProcess(Irp)) {
3627             btrfs_send_subvol32* bss32 = (btrfs_send_subvol32*)data;
3628 
3629             if (datalen < offsetof(btrfs_send_subvol32, num_clones))
3630                 return STATUS_INVALID_PARAMETER;
3631 
3632             parent = Handle32ToHandle(bss32->parent);
3633 
3634             if (datalen >= offsetof(btrfs_send_subvol32, clones[0]))
3635                 num_clones = bss32->num_clones;
3636 
3637             if (datalen < offsetof(btrfs_send_subvol32, clones[0]) + (num_clones * sizeof(UINT32)))
3638                 return STATUS_INVALID_PARAMETER;
3639         } else {
3640 #endif
3641             if (datalen < offsetof(btrfs_send_subvol, num_clones))
3642                 return STATUS_INVALID_PARAMETER;
3643 
3644             parent = bss->parent;
3645 
3646             if (datalen >= offsetof(btrfs_send_subvol, clones[0]))
3647                 num_clones = bss->num_clones;
3648 
3649             if (datalen < offsetof(btrfs_send_subvol, clones[0]) + (num_clones * sizeof(HANDLE)))
3650                 return STATUS_INVALID_PARAMETER;
3651 #if defined(_WIN64)
3652         }
3653 #endif
3654 
3655         if (parent) {
3656             PFILE_OBJECT fileobj;
3657             struct _fcb* parfcb;
3658 
3659             Status = ObReferenceObjectByHandle(parent, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL);
3660             if (!NT_SUCCESS(Status)) {
3661                 ERR("ObReferenceObjectByHandle returned %08x\n", Status);
3662                 return Status;
3663             }
3664 
3665             if (fileobj->DeviceObject != FileObject->DeviceObject) {
3666                 ObDereferenceObject(fileobj);
3667                 return STATUS_INVALID_PARAMETER;
3668             }
3669 
3670             parfcb = fileobj->FsContext;
3671 
3672             if (!parfcb || parfcb == Vcb->root_fileref->fcb || parfcb == Vcb->volume_fcb || parfcb->inode != SUBVOL_ROOT_INODE) {
3673                 ObDereferenceObject(fileobj);
3674                 return STATUS_INVALID_PARAMETER;
3675             }
3676 
3677             parsubvol = parfcb->subvol;
3678             ObDereferenceObject(fileobj);
3679 
3680             if (!Vcb->readonly && !(parsubvol->root_item.flags & BTRFS_SUBVOL_READONLY))
3681                 return STATUS_INVALID_PARAMETER;
3682 
3683             if (parsubvol == fcb->subvol)
3684                 return STATUS_INVALID_PARAMETER;
3685         }
3686 
3687         if (num_clones > 0) {
3688             ULONG i;
3689 
3690             clones = ExAllocatePoolWithTag(PagedPool, sizeof(root*) * num_clones, ALLOC_TAG);
3691             if (!clones) {
3692                 ERR("out of memory\n");
3693                 return STATUS_INSUFFICIENT_RESOURCES;
3694             }
3695 
3696             for (i = 0; i < num_clones; i++) {
3697                 HANDLE h;
3698                 PFILE_OBJECT fileobj;
3699                 struct _fcb* clonefcb;
3700 
3701 #if defined(_WIN64)
3702                 if (IoIs32bitProcess(Irp)) {
3703                     btrfs_send_subvol32* bss32 = (btrfs_send_subvol32*)data;
3704 
3705                     h = Handle32ToHandle(bss32->clones[i]);
3706                 } else
3707 #endif
3708                     h = bss->clones[i];
3709 
3710                 Status = ObReferenceObjectByHandle(h, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL);
3711                 if (!NT_SUCCESS(Status)) {
3712                     ERR("ObReferenceObjectByHandle returned %08x\n", Status);
3713                     ExFreePool(clones);
3714                     return Status;
3715                 }
3716 
3717                 if (fileobj->DeviceObject != FileObject->DeviceObject) {
3718                     ObDereferenceObject(fileobj);
3719                     ExFreePool(clones);
3720                     return STATUS_INVALID_PARAMETER;
3721                 }
3722 
3723                 clonefcb = fileobj->FsContext;
3724 
3725                 if (!clonefcb || clonefcb == Vcb->root_fileref->fcb || clonefcb == Vcb->volume_fcb || clonefcb->inode != SUBVOL_ROOT_INODE) {
3726                     ObDereferenceObject(fileobj);
3727                     ExFreePool(clones);
3728                     return STATUS_INVALID_PARAMETER;
3729                 }
3730 
3731                 clones[i] = clonefcb->subvol;
3732                 ObDereferenceObject(fileobj);
3733 
3734                 if (!Vcb->readonly && !(clones[i]->root_item.flags & BTRFS_SUBVOL_READONLY)) {
3735                     ExFreePool(clones);
3736                     return STATUS_INVALID_PARAMETER;
3737                 }
3738             }
3739         }
3740     }
3741 
3742     ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, TRUE);
3743 
3744     if (ccb->send) {
3745         WARN("send operation already running\n");
3746         ExReleaseResourceLite(&Vcb->send_load_lock);
3747         return STATUS_DEVICE_NOT_READY;
3748     }
3749 
3750     context = ExAllocatePoolWithTag(NonPagedPool, sizeof(send_context), ALLOC_TAG);
3751     if (!context) {
3752         ERR("out of memory\n");
3753 
3754         if (clones)
3755             ExFreePool(clones);
3756 
3757         ExReleaseResourceLite(&Vcb->send_load_lock);
3758         return STATUS_INSUFFICIENT_RESOURCES;
3759     }
3760 
3761     context->Vcb = Vcb;
3762     context->root = fcb->subvol;
3763     context->parent = parsubvol;
3764     InitializeListHead(&context->orphans);
3765     InitializeListHead(&context->dirs);
3766     InitializeListHead(&context->pending_rmdirs);
3767     context->lastinode.inode = 0;
3768     context->lastinode.path = NULL;
3769     context->lastinode.sd = NULL;
3770     context->root_dir = NULL;
3771     context->num_clones = num_clones;
3772     context->clones = clones;
3773     InitializeListHead(&context->lastinode.refs);
3774     InitializeListHead(&context->lastinode.oldrefs);
3775     InitializeListHead(&context->lastinode.exts);
3776     InitializeListHead(&context->lastinode.oldexts);
3777 
3778     context->data = ExAllocatePoolWithTag(PagedPool, SEND_BUFFER_LENGTH + (2 * MAX_SEND_WRITE), ALLOC_TAG); // give ourselves some wiggle room
3779     if (!context->data) {
3780         ExFreePool(context);
3781         ExReleaseResourceLite(&Vcb->send_load_lock);
3782         return STATUS_INSUFFICIENT_RESOURCES;
3783     }
3784 
3785     context->datalen = 0;
3786 
3787     send_subvol_header(context, fcb->subvol, ccb->fileref); // FIXME - fileref needs some sort of lock here
3788 
3789     KeInitializeEvent(&context->buffer_event, NotificationEvent, FALSE);
3790 
3791     send = ExAllocatePoolWithTag(NonPagedPool, sizeof(send_info), ALLOC_TAG);
3792     if (!send) {
3793         ERR("out of memory\n");
3794         ExFreePool(context->data);
3795         ExFreePool(context);
3796 
3797         if (clones)
3798             ExFreePool(clones);
3799 
3800         ExReleaseResourceLite(&Vcb->send_load_lock);
3801         return STATUS_INSUFFICIENT_RESOURCES;
3802     }
3803 
3804     KeInitializeEvent(&send->cleared_event, NotificationEvent, FALSE);
3805 
3806     send->context = context;
3807     context->send = send;
3808 
3809     ccb->send = send;
3810     send->ccb = ccb;
3811     ccb->send_status = STATUS_SUCCESS;
3812 
3813     send->cancelling = FALSE;
3814 
3815     InterlockedIncrement(&Vcb->running_sends);
3816 
3817     Status = PsCreateSystemThread(&send->thread, 0, NULL, NULL, NULL, send_thread, context);
3818     if (!NT_SUCCESS(Status)) {
3819         ERR("PsCreateSystemThread returned %08x\n", Status);
3820         ccb->send = NULL;
3821         InterlockedDecrement(&Vcb->running_sends);
3822         ExFreePool(send);
3823         ExFreePool(context->data);
3824         ExFreePool(context);
3825 
3826         if (clones)
3827             ExFreePool(clones);
3828 
3829         ExReleaseResourceLite(&Vcb->send_load_lock);
3830         return Status;
3831     }
3832 
3833     InsertTailList(&Vcb->send_ops, &send->list_entry);
3834     ExReleaseResourceLite(&Vcb->send_load_lock);
3835 
3836     return STATUS_SUCCESS;
3837 }
3838 
3839 NTSTATUS read_send_buffer(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, ULONG_PTR* retlen, KPROCESSOR_MODE processor_mode) {
3840     ccb* ccb;
3841     send_context* context;
3842 
3843     ccb = FileObject ? FileObject->FsContext2 : NULL;
3844     if (!ccb)
3845         return STATUS_INVALID_PARAMETER;
3846 
3847     if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))
3848         return STATUS_PRIVILEGE_NOT_HELD;
3849 
3850     ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, TRUE);
3851 
3852     if (!ccb->send) {
3853         ExReleaseResourceLite(&Vcb->send_load_lock);
3854         return !NT_SUCCESS(ccb->send_status) ? ccb->send_status : STATUS_END_OF_FILE;
3855     }
3856 
3857     context = (send_context*)ccb->send->context;
3858 
3859     KeWaitForSingleObject(&context->buffer_event, Executive, KernelMode, FALSE, NULL);
3860 
3861     if (datalen == 0) {
3862         ExReleaseResourceLite(&Vcb->send_load_lock);
3863         return STATUS_SUCCESS;
3864     }
3865 
3866     RtlCopyMemory(data, context->data, min(datalen, context->datalen));
3867 
3868     if (datalen < context->datalen) { // not empty yet
3869         *retlen = datalen;
3870         RtlMoveMemory(context->data, &context->data[datalen], context->datalen - datalen);
3871         context->datalen -= datalen;
3872         ExReleaseResourceLite(&Vcb->send_load_lock);
3873     } else {
3874         *retlen = context->datalen;
3875         context->datalen = 0;
3876         ExReleaseResourceLite(&Vcb->send_load_lock);
3877 
3878         KeClearEvent(&context->buffer_event);
3879         KeSetEvent(&ccb->send->cleared_event, 0, FALSE);
3880     }
3881 
3882     return STATUS_SUCCESS;
3883 }
3884