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