Something went wrong. Try again.
Reactos
Something went wrong. Try again.
142 kB · 3883 lines
C
at master
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884/* Copyright (c) Mark Harmstone 2017 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see <http://www.gnu.org/licenses/>. */
#include "btrfs_drv.h"#include "crc32c.h"
typedef struct send_dir { LIST_ENTRY list_entry; uint64_t inode; bool dummy; BTRFS_TIME atime; BTRFS_TIME mtime; BTRFS_TIME ctime; struct send_dir* parent; uint16_t namelen; char* name; LIST_ENTRY deleted_children;} send_dir;
typedef struct { LIST_ENTRY list_entry; uint64_t inode; bool dir; send_dir* sd; char tmpname[64];} orphan;
typedef struct { LIST_ENTRY list_entry; ULONG namelen; char name[1];} deleted_child;
typedef struct { LIST_ENTRY list_entry; send_dir* sd; uint16_t namelen; char name[1];} ref;
typedef struct { send_dir* sd; uint64_t last_child_inode; LIST_ENTRY list_entry;} pending_rmdir;
typedef struct { uint64_t offset; LIST_ENTRY list_entry; ULONG datalen; EXTENT_DATA data;} send_ext;
typedef struct { device_extension* Vcb; root* root; root* parent; uint8_t* data; ULONG datalen; ULONG num_clones; root** clones; LIST_ENTRY orphans; LIST_ENTRY dirs; LIST_ENTRY pending_rmdirs; KEVENT buffer_event; send_dir* root_dir; send_info* send;
struct { uint64_t inode; bool deleting; bool new; uint64_t gen; uint64_t uid; uint64_t olduid; uint64_t gid; uint64_t oldgid; uint64_t mode; uint64_t oldmode; uint64_t size; uint64_t flags; BTRFS_TIME atime; BTRFS_TIME mtime; BTRFS_TIME ctime; bool file; char* path; orphan* o; send_dir* sd; LIST_ENTRY refs; LIST_ENTRY oldrefs; LIST_ENTRY exts; LIST_ENTRY oldexts; } lastinode;} send_context;
#define MAX_SEND_WRITE 0xc000 // 48 KB#define SEND_BUFFER_LENGTH 0x100000 // 1 MB
static NTSTATUS find_send_dir(send_context* context, uint64_t dir, uint64_t generation, send_dir** psd, bool* added_dummy);static NTSTATUS wait_for_flush(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2);
static void send_command(send_context* context, uint16_t cmd) { btrfs_send_command* bsc = (btrfs_send_command*)&context->data[context->datalen];
bsc->cmd = cmd; bsc->csum = 0;
context->datalen += sizeof(btrfs_send_command);}
static void send_command_finish(send_context* context, ULONG pos) { btrfs_send_command* bsc = (btrfs_send_command*)&context->data[pos];
bsc->length = context->datalen - pos - sizeof(btrfs_send_command); bsc->csum = calc_crc32c(0, (uint8_t*)bsc, context->datalen - pos);}
static void send_add_tlv(send_context* context, uint16_t type, void* data, uint16_t length) { btrfs_send_tlv* tlv = (btrfs_send_tlv*)&context->data[context->datalen];
tlv->type = type; tlv->length = length;
if (length > 0 && data) RtlCopyMemory(&tlv[1], data, length);
context->datalen += sizeof(btrfs_send_tlv) + length;}
static char* uint64_to_char(uint64_t num, char* buf) { char *tmp, tmp2[20];
if (num == 0) { buf[0] = '0'; return buf + 1; }
tmp = &tmp2[20]; while (num > 0) { tmp--; *tmp = (num % 10) + '0'; num /= 10; }
RtlCopyMemory(buf, tmp, tmp2 + sizeof(tmp2) - tmp);
return &buf[tmp2 + sizeof(tmp2) - tmp];}
static NTSTATUS get_orphan_name(send_context* context, uint64_t inode, uint64_t generation, char* name) { char *ptr, *ptr2; uint64_t index = 0; KEY searchkey;
name[0] = 'o';
ptr = uint64_to_char(inode, &name[1]); *ptr = '-'; ptr++; ptr = uint64_to_char(generation, ptr); *ptr = '-'; ptr++; ptr2 = ptr;
searchkey.obj_id = SUBVOL_ROOT_INODE; searchkey.obj_type = TYPE_DIR_ITEM;
do { NTSTATUS Status; traverse_ptr tp;
ptr = uint64_to_char(index, ptr); *ptr = 0;
searchkey.offset = calc_crc32c(0xfffffffe, (uint8_t*)name, (ULONG)(ptr - name));
Status = find_item(context->Vcb, context->root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; }
if (!keycmp(searchkey, tp.item->key)) goto cont;
if (context->parent) { Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; }
if (!keycmp(searchkey, tp.item->key)) goto cont; }
return STATUS_SUCCESS;
cont: index++; ptr = ptr2; } while (true);}
static void add_orphan(send_context* context, orphan* o) { LIST_ENTRY* le;
le = context->orphans.Flink; while (le != &context->orphans) { orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry);
if (o2->inode > o->inode) { InsertHeadList(o2->list_entry.Blink, &o->list_entry); return; }
le = le->Flink; }
InsertTailList(&context->orphans, &o->list_entry);}
static NTSTATUS send_read_symlink(send_context* context, uint64_t inode, char** link, uint16_t* linklen) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; EXTENT_DATA* ed;
searchkey.obj_id = inode; searchkey.obj_type = TYPE_EXTENT_DATA; searchkey.offset = 0;
Status = find_item(context->Vcb, context->root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; }
if (keycmp(tp.item->key, searchkey)) { ERR("could not find (%I64x,%x,%I64x)\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return STATUS_INTERNAL_ERROR; }
if (tp.item->size < sizeof(EXTENT_DATA)) { 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_DATA)); return STATUS_INTERNAL_ERROR; }
ed = (EXTENT_DATA*)tp.item->data;
if (ed->type != EXTENT_TYPE_INLINE) { WARN("symlink data was not inline, returning blank string\n"); *link = NULL; *linklen = 0; return STATUS_SUCCESS; }
if (tp.item->size < offsetof(EXTENT_DATA, data[0]) + ed->decoded_size) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %I64u\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, offsetof(EXTENT_DATA, data[0]) + ed->decoded_size); return STATUS_INTERNAL_ERROR; }
*link = (char*)ed->data; *linklen = (uint16_t)ed->decoded_size;
return STATUS_SUCCESS;}
static NTSTATUS send_inode(send_context* context, traverse_ptr* tp, traverse_ptr* tp2) { NTSTATUS Status; INODE_ITEM* ii;
if (tp2 && !tp) { INODE_ITEM* ii2 = (INODE_ITEM*)tp2->item->data;
if (tp2->item->size < sizeof(INODE_ITEM)) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %Iu\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset, tp2->item->size, sizeof(INODE_ITEM)); return STATUS_INTERNAL_ERROR; }
context->lastinode.inode = tp2->item->key.obj_id; context->lastinode.deleting = true; context->lastinode.gen = ii2->generation; context->lastinode.mode = ii2->st_mode; context->lastinode.flags = ii2->flags; context->lastinode.o = NULL; context->lastinode.sd = NULL;
return STATUS_SUCCESS; }
ii = (INODE_ITEM*)tp->item->data;
if (tp->item->size < sizeof(INODE_ITEM)) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %Iu\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, tp->item->size, sizeof(INODE_ITEM)); return STATUS_INTERNAL_ERROR; }
context->lastinode.inode = tp->item->key.obj_id; context->lastinode.deleting = false; context->lastinode.gen = ii->generation; context->lastinode.uid = ii->st_uid; context->lastinode.gid = ii->st_gid; context->lastinode.mode = ii->st_mode; context->lastinode.size = ii->st_size; context->lastinode.atime = ii->st_atime; context->lastinode.mtime = ii->st_mtime; context->lastinode.ctime = ii->st_ctime; context->lastinode.flags = ii->flags; context->lastinode.file = false; context->lastinode.o = NULL; context->lastinode.sd = NULL;
if (context->lastinode.path) { ExFreePool(context->lastinode.path); context->lastinode.path = NULL; }
if (tp2) { INODE_ITEM* ii2 = (INODE_ITEM*)tp2->item->data; LIST_ENTRY* le;
if (tp2->item->size < sizeof(INODE_ITEM)) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %Iu\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset, tp2->item->size, sizeof(INODE_ITEM)); return STATUS_INTERNAL_ERROR; }
context->lastinode.oldmode = ii2->st_mode; context->lastinode.olduid = ii2->st_uid; context->lastinode.oldgid = ii2->st_gid;
if ((ii2->st_mode & __S_IFREG) == __S_IFREG && (ii2->st_mode & __S_IFLNK) != __S_IFLNK && (ii2->st_mode & __S_IFSOCK) != __S_IFSOCK) context->lastinode.file = true;
context->lastinode.new = false;
le = context->orphans.Flink; while (le != &context->orphans) { orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry);
if (o2->inode == tp->item->key.obj_id) { context->lastinode.o = o2; break; } else if (o2->inode > tp->item->key.obj_id) break;
le = le->Flink; } } else context->lastinode.new = true;
if (tp->item->key.obj_id == SUBVOL_ROOT_INODE) { send_dir* sd;
Status = find_send_dir(context, tp->item->key.obj_id, ii->generation, &sd, NULL); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; }
sd->atime = ii->st_atime; sd->mtime = ii->st_mtime; sd->ctime = ii->st_ctime; context->root_dir = sd; } else if (!tp2) { ULONG pos = context->datalen; uint16_t cmd; send_dir* sd;
char name[64]; orphan* o;
// skip creating orphan directory if we've already done so if (ii->st_mode & __S_IFDIR) { LIST_ENTRY* le;
le = context->orphans.Flink; while (le != &context->orphans) { orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry);
if (o2->inode == tp->item->key.obj_id) { context->lastinode.o = o2; o2->sd->atime = ii->st_atime; o2->sd->mtime = ii->st_mtime; o2->sd->ctime = ii->st_ctime; o2->sd->dummy = false; return STATUS_SUCCESS; } else if (o2->inode > tp->item->key.obj_id) break;
le = le->Flink; } }
if ((ii->st_mode & __S_IFSOCK) == __S_IFSOCK) cmd = BTRFS_SEND_CMD_MKSOCK; else if ((ii->st_mode & __S_IFLNK) == __S_IFLNK) cmd = BTRFS_SEND_CMD_SYMLINK; else if ((ii->st_mode & __S_IFCHR) == __S_IFCHR || (ii->st_mode & __S_IFBLK) == __S_IFBLK) cmd = BTRFS_SEND_CMD_MKNOD; else if ((ii->st_mode & __S_IFDIR) == __S_IFDIR) cmd = BTRFS_SEND_CMD_MKDIR; else if ((ii->st_mode & __S_IFIFO) == __S_IFIFO) cmd = BTRFS_SEND_CMD_MKFIFO; else { cmd = BTRFS_SEND_CMD_MKFILE; context->lastinode.file = true; }
send_command(context, cmd);
Status = get_orphan_name(context, tp->item->key.obj_id, ii->generation, name); if (!NT_SUCCESS(Status)) { ERR("get_orphan_name returned %08lx\n", Status); return Status; }
send_add_tlv(context, BTRFS_SEND_TLV_PATH, name, (uint16_t)strlen(name)); send_add_tlv(context, BTRFS_SEND_TLV_INODE, &tp->item->key.obj_id, sizeof(uint64_t));
if (cmd == BTRFS_SEND_CMD_MKNOD || cmd == BTRFS_SEND_CMD_MKFIFO || cmd == BTRFS_SEND_CMD_MKSOCK) { uint64_t rdev = makedev((ii->st_rdev & 0xFFFFFFFFFFF) >> 20, ii->st_rdev & 0xFFFFF), mode = ii->st_mode;
send_add_tlv(context, BTRFS_SEND_TLV_RDEV, &rdev, sizeof(uint64_t)); send_add_tlv(context, BTRFS_SEND_TLV_MODE, &mode, sizeof(uint64_t)); } else if (cmd == BTRFS_SEND_CMD_SYMLINK && ii->st_size > 0) { char* link; uint16_t linklen;
Status = send_read_symlink(context, tp->item->key.obj_id, &link, &linklen); if (!NT_SUCCESS(Status)) { ERR("send_read_symlink returned %08lx\n", Status); return Status; }
send_add_tlv(context, BTRFS_SEND_TLV_PATH_LINK, link, linklen); }
send_command_finish(context, pos);
if (ii->st_mode & __S_IFDIR) { Status = find_send_dir(context, tp->item->key.obj_id, ii->generation, &sd, NULL); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; }
sd->dummy = false; } else sd = NULL;
context->lastinode.sd = sd;
o = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG); if (!o) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
o->inode = tp->item->key.obj_id; o->dir = (ii->st_mode & __S_IFDIR && ii->st_size > 0) ? true : false; strcpy(o->tmpname, name); o->sd = sd; add_orphan(context, o);
context->lastinode.path = ExAllocatePoolWithTag(PagedPool, strlen(o->tmpname) + 1, ALLOC_TAG); if (!context->lastinode.path) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
strcpy(context->lastinode.path, o->tmpname);
context->lastinode.o = o; }
return STATUS_SUCCESS;}
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) { LIST_ENTRY* le; send_dir* sd = ExAllocatePoolWithTag(PagedPool, sizeof(send_dir), ALLOC_TAG);
if (!sd) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
sd->inode = inode; sd->dummy = dummy; sd->parent = parent;
if (!dummy) { sd->atime = context->lastinode.atime; sd->mtime = context->lastinode.mtime; sd->ctime = context->lastinode.ctime; }
if (namelen > 0) { sd->name = ExAllocatePoolWithTag(PagedPool, namelen, ALLOC_TAG); if (!sd->name) { ERR("out of memory\n"); ExFreePool(sd); return STATUS_INSUFFICIENT_RESOURCES; }
memcpy(sd->name, name, namelen); } else sd->name = NULL;
sd->namelen = namelen;
InitializeListHead(&sd->deleted_children);
if (lastentry) InsertHeadList(lastentry, &sd->list_entry); else { le = context->dirs.Flink; while (le != &context->dirs) { send_dir* sd2 = CONTAINING_RECORD(le, send_dir, list_entry);
if (sd2->inode > sd->inode) { InsertHeadList(sd2->list_entry.Blink, &sd->list_entry);
if (psd) *psd = sd;
return STATUS_SUCCESS; }
le = le->Flink; }
InsertTailList(&context->dirs, &sd->list_entry); }
if (psd) *psd = sd;
return STATUS_SUCCESS;}
static __inline uint16_t find_path_len(send_dir* parent, uint16_t namelen) { uint16_t len = namelen;
while (parent && parent->namelen > 0) { len += parent->namelen + 1; parent = parent->parent; }
return len;}
static void find_path(char* path, send_dir* parent, char* name, ULONG namelen) { ULONG len = namelen;
RtlCopyMemory(path, name, namelen);
while (parent && parent->namelen > 0) { RtlMoveMemory(path + parent->namelen + 1, path, len); RtlCopyMemory(path, parent->name, parent->namelen); path[parent->namelen] = '/'; len += parent->namelen + 1;
parent = parent->parent; }}
static void send_add_tlv_path(send_context* context, uint16_t type, send_dir* parent, char* name, uint16_t namelen) { uint16_t len = find_path_len(parent, namelen);
send_add_tlv(context, type, NULL, len);
if (len > 0) find_path((char*)&context->data[context->datalen - len], parent, name, namelen);}
static NTSTATUS found_path(send_context* context, send_dir* parent, char* name, uint16_t namelen) { ULONG pos = context->datalen;
if (context->lastinode.o) { send_command(context, BTRFS_SEND_CMD_RENAME);
send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, context->root_dir, context->lastinode.o->tmpname, (uint16_t)strlen(context->lastinode.o->tmpname));
send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, parent, name, namelen);
send_command_finish(context, pos); } else { send_command(context, BTRFS_SEND_CMD_LINK);
send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, parent, name, namelen);
send_add_tlv(context, BTRFS_SEND_TLV_PATH_LINK, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
send_command_finish(context, pos); }
if (context->lastinode.o) { uint16_t pathlen;
if (context->lastinode.o->sd) { if (context->lastinode.o->sd->name) ExFreePool(context->lastinode.o->sd->name);
context->lastinode.o->sd->name = ExAllocatePoolWithTag(PagedPool, namelen, ALLOC_TAG); if (!context->lastinode.o->sd->name) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
RtlCopyMemory(context->lastinode.o->sd->name, name, namelen); context->lastinode.o->sd->namelen = namelen; context->lastinode.o->sd->parent = parent; }
if (context->lastinode.path) ExFreePool(context->lastinode.path);
pathlen = find_path_len(parent, namelen); context->lastinode.path = ExAllocatePoolWithTag(PagedPool, pathlen + 1, ALLOC_TAG); if (!context->lastinode.path) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
find_path(context->lastinode.path, parent, name, namelen); context->lastinode.path[pathlen] = 0;
RemoveEntryList(&context->lastinode.o->list_entry); ExFreePool(context->lastinode.o);
context->lastinode.o = NULL; }
return STATUS_SUCCESS;}
static void send_utimes_command_dir(send_context* context, send_dir* sd, BTRFS_TIME* atime, BTRFS_TIME* mtime, BTRFS_TIME* ctime) { ULONG pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_UTIMES);
send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, sd->parent, sd->name, sd->namelen);
send_add_tlv(context, BTRFS_SEND_TLV_ATIME, atime, sizeof(BTRFS_TIME)); send_add_tlv(context, BTRFS_SEND_TLV_MTIME, mtime, sizeof(BTRFS_TIME)); send_add_tlv(context, BTRFS_SEND_TLV_CTIME, ctime, sizeof(BTRFS_TIME));
send_command_finish(context, pos);}
static NTSTATUS find_send_dir(send_context* context, uint64_t dir, uint64_t generation, send_dir** psd, bool* added_dummy) { NTSTATUS Status; LIST_ENTRY* le; char name[64];
le = context->dirs.Flink; while (le != &context->dirs) { send_dir* sd2 = CONTAINING_RECORD(le, send_dir, list_entry);
if (sd2->inode > dir) break; else if (sd2->inode == dir) { *psd = sd2;
if (added_dummy) *added_dummy = false;
return STATUS_SUCCESS; }
le = le->Flink; }
if (dir == SUBVOL_ROOT_INODE) { Status = send_add_dir(context, dir, NULL, NULL, 0, false, le, psd); if (!NT_SUCCESS(Status)) { ERR("send_add_dir returned %08lx\n", Status); return Status; }
if (added_dummy) *added_dummy = false;
return STATUS_SUCCESS; }
if (context->parent) { KEY searchkey; traverse_ptr tp;
searchkey.obj_id = dir; searchkey.obj_type = TYPE_INODE_REF; // directories should never have an extiref searchkey.offset = 0xffffffffffffffff;
Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; }
if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { INODE_REF* ir = (INODE_REF*)tp.item->data; send_dir* parent;
if (tp.item->size < sizeof(INODE_REF) || tp.item->size < offsetof(INODE_REF, name[0]) + ir->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return STATUS_INTERNAL_ERROR; }
if (tp.item->key.offset == SUBVOL_ROOT_INODE) parent = context->root_dir; else { Status = find_send_dir(context, tp.item->key.offset, generation, &parent, NULL); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; } }
Status = send_add_dir(context, dir, parent, ir->name, ir->n, true, NULL, psd); if (!NT_SUCCESS(Status)) { ERR("send_add_dir returned %08lx\n", Status); return Status; }
if (added_dummy) *added_dummy = false;
return STATUS_SUCCESS; } }
Status = get_orphan_name(context, dir, generation, name); if (!NT_SUCCESS(Status)) { ERR("get_orphan_name returned %08lx\n", Status); return Status; }
Status = send_add_dir(context, dir, NULL, name, (uint16_t)strlen(name), true, le, psd); if (!NT_SUCCESS(Status)) { ERR("send_add_dir returned %08lx\n", Status); return Status; }
if (added_dummy) *added_dummy = true;
return STATUS_SUCCESS;}
static NTSTATUS send_inode_ref(send_context* context, traverse_ptr* tp, bool tree2) { NTSTATUS Status; uint64_t inode = tp ? tp->item->key.obj_id : 0, dir = tp ? tp->item->key.offset : 0; LIST_ENTRY* le; INODE_REF* ir; uint16_t len; send_dir* sd = NULL; orphan* o2 = NULL;
if (inode == dir) // root return STATUS_SUCCESS;
if (tp->item->size < sizeof(INODE_REF)) { 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(INODE_REF)); return STATUS_INTERNAL_ERROR; }
if (dir != SUBVOL_ROOT_INODE) { bool added_dummy;
Status = find_send_dir(context, dir, context->root->root_item.ctransid, &sd, &added_dummy); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; }
// directory has higher inode number than file, so might need to be created if (added_dummy) { bool found = false;
le = context->orphans.Flink; while (le != &context->orphans) { o2 = CONTAINING_RECORD(le, orphan, list_entry);
if (o2->inode == dir) { found = true; break; } else if (o2->inode > dir) break;
le = le->Flink; }
if (!found) { ULONG pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_MKDIR);
send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, NULL, sd->name, sd->namelen);
send_add_tlv(context, BTRFS_SEND_TLV_INODE, &dir, sizeof(uint64_t));
send_command_finish(context, pos);
o2 = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG); if (!o2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
o2->inode = dir; o2->dir = true; memcpy(o2->tmpname, sd->name, sd->namelen); o2->tmpname[sd->namelen] = 0; o2->sd = sd; add_orphan(context, o2); } } } else sd = context->root_dir;
len = tp->item->size; ir = (INODE_REF*)tp->item->data;
while (len > 0) { ref* r;
if (len < sizeof(INODE_REF) || len < offsetof(INODE_REF, name[0]) + ir->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset); return STATUS_INTERNAL_ERROR; }
r = ExAllocatePoolWithTag(PagedPool, offsetof(ref, name[0]) + ir->n, ALLOC_TAG); if (!r) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
r->sd = sd; r->namelen = ir->n; RtlCopyMemory(r->name, ir->name, ir->n);
InsertTailList(tree2 ? &context->lastinode.oldrefs : &context->lastinode.refs, &r->list_entry);
len -= (uint16_t)offsetof(INODE_REF, name[0]) + ir->n; ir = (INODE_REF*)&ir->name[ir->n]; }
return STATUS_SUCCESS;}
static NTSTATUS send_inode_extref(send_context* context, traverse_ptr* tp, bool tree2) { INODE_EXTREF* ier; uint16_t len;
if (tp->item->size < sizeof(INODE_EXTREF)) { 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(INODE_EXTREF)); return STATUS_INTERNAL_ERROR; }
len = tp->item->size; ier = (INODE_EXTREF*)tp->item->data;
while (len > 0) { NTSTATUS Status; send_dir* sd = NULL; orphan* o2 = NULL; ref* r;
if (len < sizeof(INODE_EXTREF) || len < offsetof(INODE_EXTREF, name[0]) + ier->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset); return STATUS_INTERNAL_ERROR; }
if (ier->dir != SUBVOL_ROOT_INODE) { LIST_ENTRY* le; bool added_dummy;
Status = find_send_dir(context, ier->dir, context->root->root_item.ctransid, &sd, &added_dummy); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; }
// directory has higher inode number than file, so might need to be created if (added_dummy) { bool found = false;
le = context->orphans.Flink; while (le != &context->orphans) { o2 = CONTAINING_RECORD(le, orphan, list_entry);
if (o2->inode == ier->dir) { found = true; break; } else if (o2->inode > ier->dir) break;
le = le->Flink; }
if (!found) { ULONG pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_MKDIR);
send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, NULL, sd->name, sd->namelen); send_add_tlv(context, BTRFS_SEND_TLV_INODE, &ier->dir, sizeof(uint64_t));
send_command_finish(context, pos);
o2 = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG); if (!o2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
o2->inode = ier->dir; o2->dir = true; memcpy(o2->tmpname, sd->name, sd->namelen); o2->tmpname[sd->namelen] = 0; o2->sd = sd; add_orphan(context, o2); } } } else sd = context->root_dir;
r = ExAllocatePoolWithTag(PagedPool, offsetof(ref, name[0]) + ier->n, ALLOC_TAG); if (!r) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
r->sd = sd; r->namelen = ier->n; RtlCopyMemory(r->name, ier->name, ier->n);
InsertTailList(tree2 ? &context->lastinode.oldrefs : &context->lastinode.refs, &r->list_entry);
len -= (uint16_t)offsetof(INODE_EXTREF, name[0]) + ier->n; ier = (INODE_EXTREF*)&ier->name[ier->n]; }
return STATUS_SUCCESS;}
static void send_subvol_header(send_context* context, root* r, file_ref* fr) { ULONG pos = context->datalen;
send_command(context, context->parent ? BTRFS_SEND_CMD_SNAPSHOT : BTRFS_SEND_CMD_SUBVOL);
send_add_tlv(context, BTRFS_SEND_TLV_PATH, fr->dc->utf8.Buffer, fr->dc->utf8.Length);
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)); send_add_tlv(context, BTRFS_SEND_TLV_TRANSID, &r->root_item.ctransid, sizeof(uint64_t));
if (context->parent) { send_add_tlv(context, BTRFS_SEND_TLV_CLONE_UUID, context->parent->root_item.rtransid == 0 ? &context->parent->root_item.uuid : &context->parent->root_item.received_uuid, sizeof(BTRFS_UUID)); send_add_tlv(context, BTRFS_SEND_TLV_CLONE_CTRANSID, &context->parent->root_item.ctransid, sizeof(uint64_t)); }
send_command_finish(context, pos);}
static void send_chown_command(send_context* context, char* path, uint64_t uid, uint64_t gid) { ULONG pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_CHOWN);
send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_UID, &uid, sizeof(uint64_t)); send_add_tlv(context, BTRFS_SEND_TLV_GID, &gid, sizeof(uint64_t));
send_command_finish(context, pos);}
static void send_chmod_command(send_context* context, char* path, uint64_t mode) { ULONG pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_CHMOD);
mode &= 07777;
send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_MODE, &mode, sizeof(uint64_t));
send_command_finish(context, pos);}
static void send_utimes_command(send_context* context, char* path, BTRFS_TIME* atime, BTRFS_TIME* mtime, BTRFS_TIME* ctime) { ULONG pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_UTIMES);
send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_ATIME, atime, sizeof(BTRFS_TIME)); send_add_tlv(context, BTRFS_SEND_TLV_MTIME, mtime, sizeof(BTRFS_TIME)); send_add_tlv(context, BTRFS_SEND_TLV_CTIME, ctime, sizeof(BTRFS_TIME));
send_command_finish(context, pos);}
static void send_truncate_command(send_context* context, char* path, uint64_t size) { ULONG pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_TRUNCATE);
send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_SIZE, &size, sizeof(uint64_t));
send_command_finish(context, pos);}
static NTSTATUS send_unlink_command(send_context* context, send_dir* parent, uint16_t namelen, char* name) { ULONG pos = context->datalen; uint16_t pathlen;
send_command(context, BTRFS_SEND_CMD_UNLINK);
pathlen = find_path_len(parent, namelen); send_add_tlv(context, BTRFS_SEND_TLV_PATH, NULL, pathlen);
find_path((char*)&context->data[context->datalen - pathlen], parent, name, namelen);
send_command_finish(context, pos);
return STATUS_SUCCESS;}
static void send_rmdir_command(send_context* context, uint16_t pathlen, char* path) { ULONG pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_RMDIR); send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, pathlen); send_command_finish(context, pos);}
static NTSTATUS get_dir_last_child(send_context* context, uint64_t* last_inode) { NTSTATUS Status; KEY searchkey; traverse_ptr tp;
*last_inode = 0;
searchkey.obj_id = context->lastinode.inode; searchkey.obj_type = TYPE_DIR_INDEX; searchkey.offset = 2;
Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; }
do { traverse_ptr next_tp;
if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { DIR_ITEM* di = (DIR_ITEM*)tp.item->data;
if (tp.item->size < sizeof(DIR_ITEM) || tp.item->size < offsetof(DIR_ITEM, name[0]) + di->m + di->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return STATUS_INTERNAL_ERROR; }
if (di->key.obj_type == TYPE_INODE_ITEM) *last_inode = max(*last_inode, di->key.obj_id); } else break;
if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else break; } while (true);
return STATUS_SUCCESS;}
static NTSTATUS add_pending_rmdir(send_context* context, uint64_t last_inode) { pending_rmdir* pr; LIST_ENTRY* le;
pr = ExAllocatePoolWithTag(PagedPool, sizeof(pending_rmdir), ALLOC_TAG); if (!pr) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
pr->sd = context->lastinode.sd; pr->last_child_inode = last_inode;
le = context->pending_rmdirs.Flink; while (le != &context->pending_rmdirs) { pending_rmdir* pr2 = CONTAINING_RECORD(le, pending_rmdir, list_entry);
if (pr2->last_child_inode > pr->last_child_inode) { InsertHeadList(pr2->list_entry.Blink, &pr->list_entry); return STATUS_SUCCESS; }
le = le->Flink; }
InsertTailList(&context->pending_rmdirs, &pr->list_entry);
return STATUS_SUCCESS;}
static NTSTATUS look_for_collision(send_context* context, send_dir* sd, char* name, ULONG namelen, uint64_t* inode, bool* dir) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; DIR_ITEM* di; uint16_t len;
searchkey.obj_id = sd->inode; searchkey.obj_type = TYPE_DIR_ITEM; searchkey.offset = calc_crc32c(0xfffffffe, (uint8_t*)name, namelen);
Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; }
if (keycmp(tp.item->key, searchkey)) return STATUS_SUCCESS;
di = (DIR_ITEM*)tp.item->data; len = tp.item->size;
do { if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return STATUS_INTERNAL_ERROR; }
if (di->n == namelen && RtlCompareMemory(di->name, name, namelen) == namelen) { *inode = di->key.obj_type == TYPE_INODE_ITEM ? di->key.obj_id : 0; *dir = di->type == BTRFS_TYPE_DIRECTORY ? true: false; return STATUS_OBJECT_NAME_COLLISION; }
di = (DIR_ITEM*)&di->name[di->m + di->n]; len -= (uint16_t)offsetof(DIR_ITEM, name[0]) + di->m + di->n; } while (len > 0);
return STATUS_SUCCESS;}
static NTSTATUS make_file_orphan(send_context* context, uint64_t inode, bool dir, uint64_t generation, ref* r) { NTSTATUS Status; ULONG pos = context->datalen; send_dir* sd = NULL; orphan* o; LIST_ENTRY* le; char name[64];
if (!dir) { deleted_child* dc;
dc = ExAllocatePoolWithTag(PagedPool, offsetof(deleted_child, name[0]) + r->namelen, ALLOC_TAG); if (!dc) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
dc->namelen = r->namelen; RtlCopyMemory(dc->name, r->name, r->namelen); InsertTailList(&r->sd->deleted_children, &dc->list_entry); }
le = context->orphans.Flink; while (le != &context->orphans) { orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry);
if (o2->inode == inode) { send_command(context, BTRFS_SEND_CMD_UNLINK);
send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, r->sd, r->name, r->namelen);
send_command_finish(context, pos);
return STATUS_SUCCESS; } else if (o2->inode > inode) break;
le = le->Flink; }
Status = get_orphan_name(context, inode, generation, name); if (!NT_SUCCESS(Status)) { ERR("get_orphan_name returned %08lx\n", Status); return Status; }
if (dir) { Status = find_send_dir(context, inode, generation, &sd, NULL); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; }
sd->dummy = true;
send_command(context, BTRFS_SEND_CMD_RENAME);
send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, r->sd, r->name, r->namelen); send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, context->root_dir, name, (uint16_t)strlen(name));
send_command_finish(context, pos);
if (sd->name) ExFreePool(sd->name);
sd->namelen = (uint16_t)strlen(name); sd->name = ExAllocatePoolWithTag(PagedPool, sd->namelen, ALLOC_TAG); if (!sd->name) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
RtlCopyMemory(sd->name, name, sd->namelen); sd->parent = context->root_dir; } else { send_command(context, BTRFS_SEND_CMD_RENAME);
send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, r->sd, r->name, r->namelen);
send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, context->root_dir, name, (uint16_t)strlen(name));
send_command_finish(context, pos); }
o = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG); if (!o) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
o->inode = inode; o->dir = true; strcpy(o->tmpname, name); o->sd = sd; add_orphan(context, o);
return STATUS_SUCCESS;}
static NTSTATUS flush_refs(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) { NTSTATUS Status; LIST_ENTRY* le; ref *nameref = NULL, *nameref2 = NULL;
if (context->lastinode.mode & __S_IFDIR) { // directory ref* r = IsListEmpty(&context->lastinode.refs) ? NULL : CONTAINING_RECORD(context->lastinode.refs.Flink, ref, list_entry); ref* or = IsListEmpty(&context->lastinode.oldrefs) ? NULL : CONTAINING_RECORD(context->lastinode.oldrefs.Flink, ref, list_entry);
if (or && !context->lastinode.o) { ULONG len = find_path_len(or->sd, or->namelen);
context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG); if (!context->lastinode.path) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
find_path(context->lastinode.path, or->sd, or->name, or->namelen); context->lastinode.path[len] = 0;
if (!context->lastinode.sd) { Status = find_send_dir(context, context->lastinode.inode, context->lastinode.gen, &context->lastinode.sd, false); if (!NT_SUCCESS(Status)) { ERR("find_send_dir returned %08lx\n", Status); return Status; } } }
if (r && or) { uint64_t inode; bool dir;
Status = look_for_collision(context, r->sd, r->name, r->namelen, &inode, &dir); if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_COLLISION) { ERR("look_for_collision returned %08lx\n", Status); return Status; }
if (Status == STATUS_OBJECT_NAME_COLLISION && inode > context->lastinode.inode) { Status = make_file_orphan(context, inode, dir, context->parent->root_item.ctransid, r); if (!NT_SUCCESS(Status)) { ERR("make_file_orphan returned %08lx\n", Status); return Status; } }
if (context->lastinode.o) { Status = found_path(context, r->sd, r->name, r->namelen); if (!NT_SUCCESS(Status)) { ERR("found_path returned %08lx\n", Status); return Status; }
if (!r->sd->dummy) send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime); } else if (r->sd != or->sd || r->namelen != or->namelen || RtlCompareMemory(r->name, or->name, r->namelen) != r->namelen) { // moved or renamed ULONG pos = context->datalen, len;
send_command(context, BTRFS_SEND_CMD_RENAME);
send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, (uint16_t)strlen(context->lastinode.path));
send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, r->sd, r->name, r->namelen);
send_command_finish(context, pos);
if (!r->sd->dummy) send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime);
if (context->lastinode.sd->name) ExFreePool(context->lastinode.sd->name);
context->lastinode.sd->name = ExAllocatePoolWithTag(PagedPool, r->namelen, ALLOC_TAG); if (!context->lastinode.sd->name) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
RtlCopyMemory(context->lastinode.sd->name, r->name, r->namelen); context->lastinode.sd->parent = r->sd;
if (context->lastinode.path) ExFreePool(context->lastinode.path);
len = find_path_len(r->sd, r->namelen); context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG); if (!context->lastinode.path) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
find_path(context->lastinode.path, r->sd, r->name, r->namelen); context->lastinode.path[len] = 0; } } else if (r && !or) { // new Status = found_path(context, r->sd, r->name, r->namelen); if (!NT_SUCCESS(Status)) { ERR("found_path returned %08lx\n", Status); return Status; }
if (!r->sd->dummy) send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime); } else { // deleted uint64_t last_inode;
Status = get_dir_last_child(context, &last_inode); if (!NT_SUCCESS(Status)) { ERR("get_dir_last_child returned %08lx\n", Status); return Status; }
if (last_inode <= context->lastinode.inode) { send_rmdir_command(context, (uint16_t)strlen(context->lastinode.path), context->lastinode.path);
if (!or->sd->dummy) send_utimes_command_dir(context, or->sd, &or->sd->atime, &or->sd->mtime, &or->sd->ctime); } else { char name[64]; ULONG pos = context->datalen;
Status = get_orphan_name(context, context->lastinode.inode, context->lastinode.gen, name); if (!NT_SUCCESS(Status)) { ERR("get_orphan_name returned %08lx\n", Status); return Status; }
send_command(context, BTRFS_SEND_CMD_RENAME); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, (uint16_t)strlen(context->lastinode.path)); send_add_tlv(context, BTRFS_SEND_TLV_PATH_TO, name, (uint16_t)strlen(name)); send_command_finish(context, pos);
if (context->lastinode.sd->name) ExFreePool(context->lastinode.sd->name);
context->lastinode.sd->name = ExAllocatePoolWithTag(PagedPool, strlen(name), ALLOC_TAG); if (!context->lastinode.sd->name) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
RtlCopyMemory(context->lastinode.sd->name, name, strlen(name)); context->lastinode.sd->namelen = (uint16_t)strlen(name); context->lastinode.sd->dummy = true; context->lastinode.sd->parent = NULL;
send_utimes_command(context, NULL, &context->root_dir->atime, &context->root_dir->mtime, &context->root_dir->ctime);
Status = add_pending_rmdir(context, last_inode); if (!NT_SUCCESS(Status)) { ERR("add_pending_rmdir returned %08lx\n", Status); return Status; } } }
while (!IsListEmpty(&context->lastinode.refs)) { r = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.refs), ref, list_entry); ExFreePool(r); }
while (!IsListEmpty(&context->lastinode.oldrefs)) { or = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldrefs), ref, list_entry); ExFreePool(or); }
return STATUS_SUCCESS; } else { if (!IsListEmpty(&context->lastinode.oldrefs)) { ref* or = CONTAINING_RECORD(context->lastinode.oldrefs.Flink, ref, list_entry); ULONG len = find_path_len(or->sd, or->namelen);
context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG); if (!context->lastinode.path) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
find_path(context->lastinode.path, or->sd, or->name, or->namelen); context->lastinode.path[len] = 0; nameref = or; }
// remove unchanged refs le = context->lastinode.oldrefs.Flink; while (le != &context->lastinode.oldrefs) { ref* or = CONTAINING_RECORD(le, ref, list_entry); LIST_ENTRY* le2; bool matched = false;
le2 = context->lastinode.refs.Flink; while (le2 != &context->lastinode.refs) { ref* r = CONTAINING_RECORD(le2, ref, list_entry);
if (r->sd == or->sd && r->namelen == or->namelen && RtlCompareMemory(r->name, or->name, r->namelen) == r->namelen) { RemoveEntryList(&r->list_entry); ExFreePool(r); matched = true; break; }
le2 = le2->Flink; }
if (matched) { le = le->Flink; RemoveEntryList(&or->list_entry); ExFreePool(or); continue; }
le = le->Flink; }
while (!IsListEmpty(&context->lastinode.refs)) { ref* r = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.refs), ref, list_entry); uint64_t inode; bool dir;
if (context->parent) { Status = look_for_collision(context, r->sd, r->name, r->namelen, &inode, &dir); if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_COLLISION) { ERR("look_for_collision returned %08lx\n", Status); return Status; }
if (Status == STATUS_OBJECT_NAME_COLLISION && inode > context->lastinode.inode) { Status = make_file_orphan(context, inode, dir, context->lastinode.gen, r); if (!NT_SUCCESS(Status)) { ERR("make_file_orphan returned %08lx\n", Status); return Status; } } }
if (context->datalen > SEND_BUFFER_LENGTH) { Status = wait_for_flush(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("wait_for_flush returned %08lx\n", Status); return Status; }
if (context->send->cancelling) return STATUS_SUCCESS; }
Status = found_path(context, r->sd, r->name, r->namelen); if (!NT_SUCCESS(Status)) { ERR("found_path returned %08lx\n", Status); return Status; }
if (!r->sd->dummy) send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime);
if (nameref && !nameref2) nameref2 = r; else ExFreePool(r); }
while (!IsListEmpty(&context->lastinode.oldrefs)) { ref* or = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldrefs), ref, list_entry); bool deleted = false;
le = or->sd->deleted_children.Flink; while (le != &or->sd->deleted_children) { deleted_child* dc = CONTAINING_RECORD(le, deleted_child, list_entry);
if (dc->namelen == or->namelen && RtlCompareMemory(dc->name, or->name, or->namelen) == or->namelen) { RemoveEntryList(&dc->list_entry); ExFreePool(dc); deleted = true; break; }
le = le->Flink; }
if (!deleted) { if (context->datalen > SEND_BUFFER_LENGTH) { Status = wait_for_flush(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("wait_for_flush returned %08lx\n", Status); return Status; }
if (context->send->cancelling) return STATUS_SUCCESS; }
Status = send_unlink_command(context, or->sd, or->namelen, or->name); if (!NT_SUCCESS(Status)) { ERR("send_unlink_command returned %08lx\n", Status); return Status; }
if (!or->sd->dummy) send_utimes_command_dir(context, or->sd, &or->sd->atime, &or->sd->mtime, &or->sd->ctime); }
if (or == nameref && nameref2) { uint16_t len = find_path_len(nameref2->sd, nameref2->namelen);
if (context->lastinode.path) ExFreePool(context->lastinode.path);
context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG); if (!context->lastinode.path) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
find_path(context->lastinode.path, nameref2->sd, nameref2->name, nameref2->namelen); context->lastinode.path[len] = 0;
ExFreePool(nameref2); }
ExFreePool(or); } }
return STATUS_SUCCESS;}
static NTSTATUS wait_for_flush(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) { NTSTATUS Status; KEY key1, key2;
if (tp1) key1 = tp1->item->key;
if (tp2) key2 = tp2->item->key;
ExReleaseResourceLite(&context->Vcb->tree_lock);
KeClearEvent(&context->send->cleared_event); KeSetEvent(&context->buffer_event, 0, true); KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL);
ExAcquireResourceSharedLite(&context->Vcb->tree_lock, true);
if (context->send->cancelling) return STATUS_SUCCESS;
if (tp1) { Status = find_item(context->Vcb, context->root, tp1, &key1, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; }
if (keycmp(tp1->item->key, key1)) { ERR("readonly subvolume changed\n"); return STATUS_INTERNAL_ERROR; } }
if (tp2) { Status = find_item(context->Vcb, context->parent, tp2, &key2, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return Status; }
if (keycmp(tp2->item->key, key2)) { ERR("readonly subvolume changed\n"); return STATUS_INTERNAL_ERROR; } }
return STATUS_SUCCESS;}
static NTSTATUS add_ext_holes(device_extension* Vcb, LIST_ENTRY* exts, uint64_t size) { uint64_t lastoff = 0; LIST_ENTRY* le;
le = exts->Flink; while (le != exts) { send_ext* ext = CONTAINING_RECORD(le, send_ext, list_entry);
if (ext->offset > lastoff) { send_ext* ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data.data) + sizeof(EXTENT_DATA2), ALLOC_TAG); EXTENT_DATA2* ed2;
if (!ext2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
ed2 = (EXTENT_DATA2*)ext2->data.data;
ext2->offset = lastoff; ext2->datalen = offsetof(EXTENT_DATA, data) + sizeof(EXTENT_DATA2); ext2->data.decoded_size = ed2->num_bytes = ext->offset - lastoff; ext2->data.type = EXTENT_TYPE_REGULAR; ed2->address = ed2->size = ed2->offset = 0;
InsertHeadList(le->Blink, &ext2->list_entry); }
if (ext->data.type == EXTENT_TYPE_INLINE) lastoff = ext->offset + ext->data.decoded_size; else { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->data.data; lastoff = ext->offset + ed2->num_bytes; }
le = le->Flink; }
if (size > lastoff) { send_ext* ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data.data) + sizeof(EXTENT_DATA2), ALLOC_TAG); EXTENT_DATA2* ed2;
if (!ext2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
ed2 = (EXTENT_DATA2*)ext2->data.data;
ext2->offset = lastoff; ext2->datalen = offsetof(EXTENT_DATA, data) + sizeof(EXTENT_DATA2); ext2->data.decoded_size = ed2->num_bytes = sector_align(size - lastoff, Vcb->superblock.sector_size); ext2->data.type = EXTENT_TYPE_REGULAR; ed2->address = ed2->size = ed2->offset = 0;
InsertTailList(exts, &ext2->list_entry); }
return STATUS_SUCCESS;}
static NTSTATUS divide_ext(send_ext* ext, uint64_t len, bool trunc) { send_ext* ext2; EXTENT_DATA2 *ed2a, *ed2b;
if (ext->data.type == EXTENT_TYPE_INLINE) { if (!trunc) { ext2 = ExAllocatePoolWithTag(PagedPool, (ULONG)(offsetof(send_ext, data.data) + ext->data.decoded_size - len), ALLOC_TAG);
if (!ext2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
ext2->offset = ext->offset + len; ext2->datalen = (ULONG)(ext->data.decoded_size - len); ext2->data.decoded_size = ext->data.decoded_size - len; ext2->data.compression = ext->data.compression; ext2->data.encryption = ext->data.encryption; ext2->data.encoding = ext->data.encoding; ext2->data.type = ext->data.type; RtlCopyMemory(ext2->data.data, ext->data.data + len, (ULONG)(ext->data.decoded_size - len));
InsertHeadList(&ext->list_entry, &ext2->list_entry); }
ext->data.decoded_size = len;
return STATUS_SUCCESS; }
ed2a = (EXTENT_DATA2*)ext->data.data;
if (!trunc) { ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data.data) + sizeof(EXTENT_DATA2), ALLOC_TAG);
if (!ext2) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
ed2b = (EXTENT_DATA2*)ext2->data.data;
ext2->offset = ext->offset + len; ext2->datalen = offsetof(EXTENT_DATA, data) + sizeof(EXTENT_DATA2);
ext2->data.compression = ext->data.compression; ext2->data.encryption = ext->data.encryption; ext2->data.encoding = ext->data.encoding; ext2->data.type = ext->data.type; ed2b->num_bytes = ed2a->num_bytes - len;
if (ed2a->size == 0) { ext2->data.decoded_size = ed2b->num_bytes; ext->data.decoded_size = len;
ed2b->address = ed2b->size = ed2b->offset = 0; } else { ext2->data.decoded_size = ext->data.decoded_size;
ed2b->address = ed2a->address; ed2b->size = ed2a->size; ed2b->offset = ed2a->offset + len; }
InsertHeadList(&ext->list_entry, &ext2->list_entry); }
ed2a->num_bytes = len;
return STATUS_SUCCESS;}
static NTSTATUS sync_ext_cutoff_points(send_context* context) { NTSTATUS Status; send_ext *ext1, *ext2;
ext1 = CONTAINING_RECORD(context->lastinode.exts.Flink, send_ext, list_entry); ext2 = CONTAINING_RECORD(context->lastinode.oldexts.Flink, send_ext, list_entry);
do { uint64_t len1, len2; EXTENT_DATA2 *ed2a, *ed2b;
ed2a = ext1->data.type == EXTENT_TYPE_INLINE ? NULL : (EXTENT_DATA2*)ext1->data.data; ed2b = ext2->data.type == EXTENT_TYPE_INLINE ? NULL : (EXTENT_DATA2*)ext2->data.data;
len1 = ed2a ? ed2a->num_bytes : ext1->data.decoded_size; len2 = ed2b ? ed2b->num_bytes : ext2->data.decoded_size;
if (len1 < len2) { Status = divide_ext(ext2, len1, false); if (!NT_SUCCESS(Status)) { ERR("divide_ext returned %08lx\n", Status); return Status; } } else if (len2 < len1) { Status = divide_ext(ext1, len2, false); if (!NT_SUCCESS(Status)) { ERR("divide_ext returned %08lx\n", Status); return Status; } }
if (ext1->list_entry.Flink == &context->lastinode.exts || ext2->list_entry.Flink == &context->lastinode.oldexts) break;
ext1 = CONTAINING_RECORD(ext1->list_entry.Flink, send_ext, list_entry); ext2 = CONTAINING_RECORD(ext2->list_entry.Flink, send_ext, list_entry); } while (true);
ext1 = CONTAINING_RECORD(context->lastinode.exts.Blink, send_ext, list_entry); ext2 = CONTAINING_RECORD(context->lastinode.oldexts.Blink, send_ext, list_entry);
Status = divide_ext(ext1, context->lastinode.size - ext1->offset, true); if (!NT_SUCCESS(Status)) { ERR("divide_ext returned %08lx\n", Status); return Status; }
Status = divide_ext(ext2, context->lastinode.size - ext2->offset, true); if (!NT_SUCCESS(Status)) { ERR("divide_ext returned %08lx\n", Status); return Status; }
return STATUS_SUCCESS;}
static bool send_add_tlv_clone_path(send_context* context, root* r, uint64_t inode) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; uint16_t len = 0; uint64_t num; uint8_t* ptr;
num = inode;
while (num != SUBVOL_ROOT_INODE) { searchkey.obj_id = num; searchkey.obj_type = TYPE_INODE_EXTREF; searchkey.offset = 0xffffffffffffffff;
Status = find_item(context->Vcb, r, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return false; }
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)) { ERR("could not find INODE_REF for inode %I64x\n", searchkey.obj_id); return false; }
if (len > 0) len++;
if (tp.item->key.obj_type == TYPE_INODE_REF) { INODE_REF* ir = (INODE_REF*)tp.item->data;
if (tp.item->size < sizeof(INODE_REF) || tp.item->size < offsetof(INODE_REF, name[0]) + ir->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return false; }
len += ir->n; num = tp.item->key.offset; } else { INODE_EXTREF* ier = (INODE_EXTREF*)tp.item->data;
if (tp.item->size < sizeof(INODE_EXTREF) || tp.item->size < offsetof(INODE_EXTREF, name[0]) + ier->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset); return false; }
len += ier->n; num = ier->dir; } }
send_add_tlv(context, BTRFS_SEND_TLV_CLONE_PATH, NULL, len); ptr = &context->data[context->datalen];
num = inode;
while (num != SUBVOL_ROOT_INODE) { searchkey.obj_id = num; searchkey.obj_type = TYPE_INODE_EXTREF; searchkey.offset = 0xffffffffffffffff;
Status = find_item(context->Vcb, r, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return false; }
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)) { ERR("could not find INODE_REF for inode %I64x\n", searchkey.obj_id); return false; }
if (num != inode) { ptr--; *ptr = '/'; }
if (tp.item->key.obj_type == TYPE_INODE_REF) { INODE_REF* ir = (INODE_REF*)tp.item->data;
RtlCopyMemory(ptr - ir->n, ir->name, ir->n); ptr -= ir->n; num = tp.item->key.offset; } else { INODE_EXTREF* ier = (INODE_EXTREF*)tp.item->data;
RtlCopyMemory(ptr - ier->n, ier->name, ier->n); ptr -= ier->n; num = ier->dir; } }
return true;}
static bool try_clone_edr(send_context* context, send_ext* se, EXTENT_DATA_REF* edr) { NTSTATUS Status; root* r = NULL; KEY searchkey; traverse_ptr tp; EXTENT_DATA2* seed2 = (EXTENT_DATA2*)se->data.data;
if (context->parent && edr->root == context->parent->id) r = context->parent;
if (!r && context->num_clones > 0) { ULONG i;
for (i = 0; i < context->num_clones; i++) { if (context->clones[i]->id == edr->root && context->clones[i] != context->root) { r = context->clones[i]; break; } } }
if (!r) return false;
searchkey.obj_id = edr->objid; searchkey.obj_type = TYPE_EXTENT_DATA; searchkey.offset = 0;
Status = find_item(context->Vcb, r, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return false; }
while (true) { traverse_ptr next_tp;
if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { if (tp.item->size < sizeof(EXTENT_DATA)) 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)); else { EXTENT_DATA* ed = (EXTENT_DATA*)tp.item->data;
if (ed->type == EXTENT_TYPE_REGULAR) { if (tp.item->size < offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)) 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, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)); else { EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;
if (ed2->address == seed2->address && ed2->size == seed2->size && seed2->offset <= ed2->offset && seed2->offset + seed2->num_bytes >= ed2->offset + ed2->num_bytes) { uint64_t clone_offset = tp.item->key.offset + ed2->offset - seed2->offset; uint64_t clone_len = min(context->lastinode.size - se->offset, ed2->num_bytes);
if ((clone_offset & (context->Vcb->superblock.sector_size - 1)) == 0 && (clone_len & (context->Vcb->superblock.sector_size - 1)) == 0) { ULONG pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_CLONE);
send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &se->offset, sizeof(uint64_t)); send_add_tlv(context, BTRFS_SEND_TLV_CLONE_LENGTH, &clone_len, sizeof(uint64_t)); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); 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)); send_add_tlv(context, BTRFS_SEND_TLV_CLONE_CTRANSID, &r->root_item.ctransid, sizeof(uint64_t));
if (!send_add_tlv_clone_path(context, r, tp.item->key.obj_id)) context->datalen = pos; else { send_add_tlv(context, BTRFS_SEND_TLV_CLONE_OFFSET, &clone_offset, sizeof(uint64_t));
send_command_finish(context, pos);
return true; } } } } } } } 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)) break;
if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else break; }
return false;}
static bool try_clone(send_context* context, send_ext* se) { NTSTATUS Status; KEY searchkey; traverse_ptr tp; EXTENT_DATA2* ed2 = (EXTENT_DATA2*)se->data.data; EXTENT_ITEM* ei; uint64_t rc = 0;
searchkey.obj_id = ed2->address; searchkey.obj_type = TYPE_EXTENT_ITEM; searchkey.offset = ed2->size;
Status = find_item(context->Vcb, context->Vcb->extent_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return false; }
if (keycmp(tp.item->key, searchkey)) { ERR("(%I64x,%x,%I64x) not found\n", searchkey.obj_id, searchkey.obj_type, searchkey.offset); return false; }
if (tp.item->size < sizeof(EXTENT_ITEM)) { 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)); return false; }
ei = (EXTENT_ITEM*)tp.item->data;
if (tp.item->size > sizeof(EXTENT_ITEM)) { uint32_t len = tp.item->size - sizeof(EXTENT_ITEM); uint8_t* ptr = (uint8_t*)&ei[1];
while (len > 0) { uint8_t secttype = *ptr; ULONG sectlen = get_extent_data_len(secttype); uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));
len--;
if (sectlen > len) { 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); return false; }
if (sectlen == 0) { ERR("(%I64x,%x,%I64x): unrecognized extent type %x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype); return false; }
rc += sectcount;
if (secttype == TYPE_EXTENT_DATA_REF) { EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t));
if (try_clone_edr(context, se, sectedr)) return true; }
len -= sectlen; ptr += sizeof(uint8_t) + sectlen; } }
if (rc >= ei->refcount) return false;
searchkey.obj_type = TYPE_EXTENT_DATA_REF; searchkey.offset = 0;
Status = find_item(context->Vcb, context->Vcb->extent_root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); return false; }
while (true) { traverse_ptr next_tp;
if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) { if (tp.item->size < sizeof(EXTENT_DATA_REF)) 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)); else { if (try_clone_edr(context, se, (EXTENT_DATA_REF*)tp.item->data)) return true; } } 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)) break;
if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else break; }
return false;}
static NTSTATUS flush_extents(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) { NTSTATUS Status;
if ((IsListEmpty(&context->lastinode.exts) && IsListEmpty(&context->lastinode.oldexts)) || context->lastinode.size == 0) return STATUS_SUCCESS;
if (context->parent) { Status = add_ext_holes(context->Vcb, &context->lastinode.exts, context->lastinode.size); if (!NT_SUCCESS(Status)) { ERR("add_ext_holes returned %08lx\n", Status); return Status; }
Status = add_ext_holes(context->Vcb, &context->lastinode.oldexts, context->lastinode.size); if (!NT_SUCCESS(Status)) { ERR("add_ext_holes returned %08lx\n", Status); return Status; }
Status = sync_ext_cutoff_points(context); if (!NT_SUCCESS(Status)) { ERR("sync_ext_cutoff_points returned %08lx\n", Status); return Status; } }
while (!IsListEmpty(&context->lastinode.exts)) { send_ext* se = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.exts), send_ext, list_entry); send_ext* se2 = context->parent ? CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldexts), send_ext, list_entry) : NULL; ULONG pos; EXTENT_DATA2* ed2;
if (se2) { if (se->data.type == EXTENT_TYPE_INLINE && se2->data.type == EXTENT_TYPE_INLINE && RtlCompareMemory(se->data.data, se2->data.data, (ULONG)se->data.decoded_size) == (ULONG)se->data.decoded_size) { ExFreePool(se); ExFreePool(se2); continue; }
if (se->data.type == EXTENT_TYPE_REGULAR && se2->data.type == EXTENT_TYPE_REGULAR) { EXTENT_DATA2 *ed2a, *ed2b;
ed2a = (EXTENT_DATA2*)se->data.data; ed2b = (EXTENT_DATA2*)se2->data.data;
if (RtlCompareMemory(ed2a, ed2b, sizeof(EXTENT_DATA2)) == sizeof(EXTENT_DATA2)) { ExFreePool(se); ExFreePool(se2); continue; } } }
if (se->data.type == EXTENT_TYPE_INLINE) { pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_WRITE);
send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &se->offset, sizeof(uint64_t));
if (se->data.compression == BTRFS_COMPRESSION_NONE) send_add_tlv(context, BTRFS_SEND_TLV_DATA, se->data.data, (uint16_t)se->data.decoded_size); else if (se->data.compression == BTRFS_COMPRESSION_ZLIB || se->data.compression == BTRFS_COMPRESSION_LZO || se->data.compression == BTRFS_COMPRESSION_ZSTD) { ULONG inlen = se->datalen - (ULONG)offsetof(EXTENT_DATA, data[0]);
send_add_tlv(context, BTRFS_SEND_TLV_DATA, NULL, (uint16_t)se->data.decoded_size); RtlZeroMemory(&context->data[context->datalen - se->data.decoded_size], (ULONG)se->data.decoded_size);
if (se->data.compression == BTRFS_COMPRESSION_ZLIB) { Status = zlib_decompress(se->data.data, inlen, &context->data[context->datalen - se->data.decoded_size], (uint32_t)se->data.decoded_size); if (!NT_SUCCESS(Status)) { ERR("zlib_decompress returned %08lx\n", Status); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } } else if (se->data.compression == BTRFS_COMPRESSION_LZO) { if (inlen < sizeof(uint32_t)) { ERR("extent data was truncated\n"); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INTERNAL_ERROR; } else inlen -= sizeof(uint32_t);
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)); if (!NT_SUCCESS(Status)) { ERR("lzo_decompress returned %08lx\n", Status); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } } else if (se->data.compression == BTRFS_COMPRESSION_ZSTD) { Status = zstd_decompress(se->data.data, inlen, &context->data[context->datalen - se->data.decoded_size], (uint32_t)se->data.decoded_size); if (!NT_SUCCESS(Status)) { ERR("zlib_decompress returned %08lx\n", Status); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } } } else { ERR("unhandled compression type %x\n", se->data.compression); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_NOT_IMPLEMENTED; }
send_command_finish(context, pos);
ExFreePool(se); if (se2) ExFreePool(se2); continue; }
ed2 = (EXTENT_DATA2*)se->data.data;
if (ed2->size != 0 && (context->parent || context->num_clones > 0)) { if (try_clone(context, se)) { ExFreePool(se); if (se2) ExFreePool(se2); continue; } }
if (ed2->size == 0) { // write sparse uint64_t off, offset;
for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) { uint16_t length = (uint16_t)min(min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE), context->lastinode.size - se->offset - off);
if (context->datalen > SEND_BUFFER_LENGTH) { Status = wait_for_flush(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("wait_for_flush returned %08lx\n", Status); ExFreePool(se); if (se2) ExFreePool(se2); return Status; }
if (context->send->cancelling) { ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_SUCCESS; } }
pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_WRITE);
send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
offset = se->offset + off; send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(uint64_t));
send_add_tlv(context, BTRFS_SEND_TLV_DATA, NULL, length); RtlZeroMemory(&context->data[context->datalen - length], length);
send_command_finish(context, pos); } } else if (se->data.compression == BTRFS_COMPRESSION_NONE) { uint64_t off, offset; uint8_t* buf;
buf = ExAllocatePoolWithTag(NonPagedPool, MAX_SEND_WRITE + (2 * context->Vcb->superblock.sector_size), ALLOC_TAG); if (!buf) { ERR("out of memory\n"); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INSUFFICIENT_RESOURCES; }
for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) { uint16_t length = (uint16_t)min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE); ULONG skip_start; uint64_t addr = ed2->address + off; void* csum;
if (context->datalen > SEND_BUFFER_LENGTH) { Status = wait_for_flush(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("wait_for_flush returned %08lx\n", Status); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return Status; }
if (context->send->cancelling) { ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_SUCCESS; } }
skip_start = addr & (context->Vcb->superblock.sector_size - 1); addr -= skip_start;
if (context->lastinode.flags & BTRFS_INODE_NODATASUM) csum = NULL; else { uint32_t len;
len = (uint32_t)sector_align(length + skip_start, context->Vcb->superblock.sector_size) >> context->Vcb->sector_shift;
csum = ExAllocatePoolWithTag(PagedPool, len * context->Vcb->csum_size, ALLOC_TAG); if (!csum) { ERR("out of memory\n"); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INSUFFICIENT_RESOURCES; }
Status = load_csum(context->Vcb, csum, addr, len, NULL); if (!NT_SUCCESS(Status)) { ERR("load_csum returned %08lx\n", Status); ExFreePool(csum); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INSUFFICIENT_RESOURCES; } }
Status = read_data(context->Vcb, addr, (uint32_t)sector_align(length + skip_start, context->Vcb->superblock.sector_size), csum, false, buf, NULL, NULL, NULL, 0, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned %08lx\n", Status); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); if (csum) ExFreePool(csum); return Status; }
if (csum) ExFreePool(csum);
pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_WRITE);
send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
offset = se->offset + off; send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(uint64_t));
length = (uint16_t)min(context->lastinode.size - se->offset - off, length); send_add_tlv(context, BTRFS_SEND_TLV_DATA, buf + skip_start, length);
send_command_finish(context, pos); }
ExFreePool(buf); } else { uint8_t *buf, *compbuf; uint64_t off; void* csum;
buf = ExAllocatePoolWithTag(PagedPool, (ULONG)se->data.decoded_size, ALLOC_TAG); if (!buf) { ERR("out of memory\n"); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INSUFFICIENT_RESOURCES; }
compbuf = ExAllocatePoolWithTag(PagedPool, (ULONG)ed2->size, ALLOC_TAG); if (!compbuf) { ERR("out of memory\n"); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INSUFFICIENT_RESOURCES; }
if (context->lastinode.flags & BTRFS_INODE_NODATASUM) csum = NULL; else { uint32_t len;
len = (uint32_t)(ed2->size >> context->Vcb->sector_shift);
csum = ExAllocatePoolWithTag(PagedPool, len * context->Vcb->csum_size, ALLOC_TAG); if (!csum) { ERR("out of memory\n"); ExFreePool(compbuf); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_INSUFFICIENT_RESOURCES; }
Status = load_csum(context->Vcb, csum, ed2->address, len, NULL); if (!NT_SUCCESS(Status)) { ERR("load_csum returned %08lx\n", Status); ExFreePool(csum); ExFreePool(compbuf); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } }
Status = read_data(context->Vcb, ed2->address, (uint32_t)ed2->size, csum, false, compbuf, NULL, NULL, NULL, 0, false, NormalPagePriority); if (!NT_SUCCESS(Status)) { ERR("read_data returned %08lx\n", Status); ExFreePool(compbuf); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); if (csum) ExFreePool(csum); return Status; }
if (csum) ExFreePool(csum);
if (se->data.compression == BTRFS_COMPRESSION_ZLIB) { Status = zlib_decompress(compbuf, (uint32_t)ed2->size, buf, (uint32_t)se->data.decoded_size); if (!NT_SUCCESS(Status)) { ERR("zlib_decompress returned %08lx\n", Status); ExFreePool(compbuf); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } } else if (se->data.compression == BTRFS_COMPRESSION_LZO) { Status = lzo_decompress(&compbuf[sizeof(uint32_t)], (uint32_t)ed2->size, buf, (uint32_t)se->data.decoded_size, sizeof(uint32_t)); if (!NT_SUCCESS(Status)) { ERR("lzo_decompress returned %08lx\n", Status); ExFreePool(compbuf); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } } else if (se->data.compression == BTRFS_COMPRESSION_ZSTD) { Status = zstd_decompress(compbuf, (uint32_t)ed2->size, buf, (uint32_t)se->data.decoded_size); if (!NT_SUCCESS(Status)) { ERR("zstd_decompress returned %08lx\n", Status); ExFreePool(compbuf); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return Status; } }
ExFreePool(compbuf);
for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) { uint16_t length = (uint16_t)min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE); uint64_t offset;
if (context->datalen > SEND_BUFFER_LENGTH) { Status = wait_for_flush(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("wait_for_flush returned %08lx\n", Status); ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return Status; }
if (context->send->cancelling) { ExFreePool(buf); ExFreePool(se); if (se2) ExFreePool(se2); return STATUS_SUCCESS; } }
pos = context->datalen;
send_command(context, BTRFS_SEND_CMD_WRITE);
send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);
offset = se->offset + off; send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(uint64_t));
length = (uint16_t)min(context->lastinode.size - se->offset - off, length); send_add_tlv(context, BTRFS_SEND_TLV_DATA, &buf[off], length);
send_command_finish(context, pos); }
ExFreePool(buf); }
ExFreePool(se); if (se2) ExFreePool(se2); }
return STATUS_SUCCESS;}
static NTSTATUS finish_inode(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) { LIST_ENTRY* le;
if (!IsListEmpty(&context->lastinode.refs) || !IsListEmpty(&context->lastinode.oldrefs)) { NTSTATUS Status = flush_refs(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("flush_refs returned %08lx\n", Status); return Status; }
if (context->send->cancelling) return STATUS_SUCCESS; }
if (!context->lastinode.deleting) { if (context->lastinode.file) { NTSTATUS Status = flush_extents(context, tp1, tp2); if (!NT_SUCCESS(Status)) { ERR("flush_extents returned %08lx\n", Status); return Status; }
if (context->send->cancelling) return STATUS_SUCCESS;
send_truncate_command(context, context->lastinode.path, context->lastinode.size); }
if (context->lastinode.new || context->lastinode.uid != context->lastinode.olduid || context->lastinode.gid != context->lastinode.oldgid) send_chown_command(context, context->lastinode.path, context->lastinode.uid, context->lastinode.gid);
if (((context->lastinode.mode & __S_IFLNK) != __S_IFLNK || ((context->lastinode.mode & 07777) != 0777)) && (context->lastinode.new || context->lastinode.mode != context->lastinode.oldmode)) send_chmod_command(context, context->lastinode.path, context->lastinode.mode);
send_utimes_command(context, context->lastinode.path, &context->lastinode.atime, &context->lastinode.mtime, &context->lastinode.ctime); }
while (!IsListEmpty(&context->lastinode.exts)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&context->lastinode.exts), send_ext, list_entry)); }
while (!IsListEmpty(&context->lastinode.oldexts)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldexts), send_ext, list_entry)); }
if (context->parent) { le = context->pending_rmdirs.Flink;
while (le != &context->pending_rmdirs) { pending_rmdir* pr = CONTAINING_RECORD(le, pending_rmdir, list_entry);
if (pr->last_child_inode <= context->lastinode.inode) { le = le->Flink;
send_rmdir_command(context, pr->sd->namelen, pr->sd->name);
RemoveEntryList(&pr->sd->list_entry);
if (pr->sd->name) ExFreePool(pr->sd->name);
while (!IsListEmpty(&pr->sd->deleted_children)) { deleted_child* dc = CONTAINING_RECORD(RemoveHeadList(&pr->sd->deleted_children), deleted_child, list_entry); ExFreePool(dc); }
ExFreePool(pr->sd);
RemoveEntryList(&pr->list_entry); ExFreePool(pr); } else break; } }
context->lastinode.inode = 0; context->lastinode.o = NULL;
if (context->lastinode.path) { ExFreePool(context->lastinode.path); context->lastinode.path = NULL; }
return STATUS_SUCCESS;}
static NTSTATUS send_extent_data(send_context* context, traverse_ptr* tp, traverse_ptr* tp2) { NTSTATUS Status;
if (tp && tp2 && tp->item->size == tp2->item->size && RtlCompareMemory(tp->item->data, tp2->item->data, tp->item->size) == tp->item->size) return STATUS_SUCCESS;
if (!IsListEmpty(&context->lastinode.refs) || !IsListEmpty(&context->lastinode.oldrefs)) { Status = flush_refs(context, tp, tp2); if (!NT_SUCCESS(Status)) { ERR("flush_refs returned %08lx\n", Status); return Status; }
if (context->send->cancelling) return STATUS_SUCCESS; }
if ((context->lastinode.mode & __S_IFLNK) == __S_IFLNK) return STATUS_SUCCESS;
if (tp) { EXTENT_DATA* ed; EXTENT_DATA2* ed2 = NULL;
if (tp->item->size < sizeof(EXTENT_DATA)) { 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_DATA)); return STATUS_INTERNAL_ERROR; }
ed = (EXTENT_DATA*)tp->item->data;
if (ed->encryption != BTRFS_ENCRYPTION_NONE) { ERR("unknown encryption type %u\n", ed->encryption); return STATUS_INTERNAL_ERROR; }
if (ed->encoding != BTRFS_ENCODING_NONE) { ERR("unknown encoding type %u\n", ed->encoding); return STATUS_INTERNAL_ERROR; }
if (ed->compression != BTRFS_COMPRESSION_NONE && ed->compression != BTRFS_COMPRESSION_ZLIB && ed->compression != BTRFS_COMPRESSION_LZO && ed->compression != BTRFS_COMPRESSION_ZSTD) { ERR("unknown compression type %u\n", ed->compression); return STATUS_INTERNAL_ERROR; }
if (ed->type == EXTENT_TYPE_REGULAR) { if (tp->item->size < offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %Iu\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, tp->item->size, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)); return STATUS_INTERNAL_ERROR; }
ed2 = (EXTENT_DATA2*)ed->data; } else if (ed->type == EXTENT_TYPE_INLINE) { if (tp->item->size < offsetof(EXTENT_DATA, data[0]) + ed->decoded_size && ed->compression == BTRFS_COMPRESSION_NONE) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %I64u\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, tp->item->size, offsetof(EXTENT_DATA, data[0]) + ed->decoded_size); return STATUS_INTERNAL_ERROR; } }
if ((ed->type == EXTENT_TYPE_INLINE || (ed->type == EXTENT_TYPE_REGULAR && ed2->size != 0)) && ed->decoded_size != 0) { send_ext* se = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data) + tp->item->size, ALLOC_TAG);
if (!se) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
se->offset = tp->item->key.offset; se->datalen = tp->item->size; RtlCopyMemory(&se->data, tp->item->data, tp->item->size); InsertTailList(&context->lastinode.exts, &se->list_entry); } }
if (tp2) { EXTENT_DATA* ed; EXTENT_DATA2* ed2 = NULL;
if (tp2->item->size < sizeof(EXTENT_DATA)) { 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, tp2->item->size, sizeof(EXTENT_DATA)); return STATUS_INTERNAL_ERROR; }
ed = (EXTENT_DATA*)tp2->item->data;
if (ed->encryption != BTRFS_ENCRYPTION_NONE) { ERR("unknown encryption type %u\n", ed->encryption); return STATUS_INTERNAL_ERROR; }
if (ed->encoding != BTRFS_ENCODING_NONE) { ERR("unknown encoding type %u\n", ed->encoding); return STATUS_INTERNAL_ERROR; }
if (ed->compression != BTRFS_COMPRESSION_NONE && ed->compression != BTRFS_COMPRESSION_ZLIB && ed->compression != BTRFS_COMPRESSION_LZO && ed->compression != BTRFS_COMPRESSION_ZSTD) { ERR("unknown compression type %u\n", ed->compression); return STATUS_INTERNAL_ERROR; }
if (ed->type == EXTENT_TYPE_REGULAR) { if (tp2->item->size < offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %Iu\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset, tp2->item->size, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)); return STATUS_INTERNAL_ERROR; }
ed2 = (EXTENT_DATA2*)ed->data; } else if (ed->type == EXTENT_TYPE_INLINE) { if (tp2->item->size < offsetof(EXTENT_DATA, data[0]) + ed->decoded_size) { ERR("(%I64x,%x,%I64x) was %u bytes, expected %I64u\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset, tp2->item->size, offsetof(EXTENT_DATA, data[0]) + ed->decoded_size); return STATUS_INTERNAL_ERROR; } }
if ((ed->type == EXTENT_TYPE_INLINE || (ed->type == EXTENT_TYPE_REGULAR && ed2->size != 0)) && ed->decoded_size != 0) { send_ext* se = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data) + tp2->item->size, ALLOC_TAG);
if (!se) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
se->offset = tp2->item->key.offset; se->datalen = tp2->item->size; RtlCopyMemory(&se->data, tp2->item->data, tp2->item->size); InsertTailList(&context->lastinode.oldexts, &se->list_entry); } }
return STATUS_SUCCESS;}
typedef struct { uint16_t namelen; char* name; uint16_t value1len; char* value1; uint16_t value2len; char* value2; LIST_ENTRY list_entry;} xattr_cmp;
static NTSTATUS send_xattr(send_context* context, traverse_ptr* tp, traverse_ptr* tp2) { if (tp && tp2 && tp->item->size == tp2->item->size && RtlCompareMemory(tp->item->data, tp2->item->data, tp->item->size) == tp->item->size) return STATUS_SUCCESS;
if (!IsListEmpty(&context->lastinode.refs) || !IsListEmpty(&context->lastinode.oldrefs)) { NTSTATUS Status = flush_refs(context, tp, tp2); if (!NT_SUCCESS(Status)) { ERR("flush_refs returned %08lx\n", Status); return Status; }
if (context->send->cancelling) return STATUS_SUCCESS; }
if (tp && tp->item->size < sizeof(DIR_ITEM)) { 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(DIR_ITEM)); return STATUS_INTERNAL_ERROR; }
if (tp2 && tp2->item->size < sizeof(DIR_ITEM)) { 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, tp2->item->size, sizeof(DIR_ITEM)); return STATUS_INTERNAL_ERROR; }
if (tp && !tp2) { ULONG len; DIR_ITEM* di;
len = tp->item->size; di = (DIR_ITEM*)tp->item->data;
do { ULONG pos;
if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset); return STATUS_INTERNAL_ERROR; }
pos = context->datalen; send_command(context, BTRFS_SEND_CMD_SET_XATTR); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, di->name, di->n); send_add_tlv(context, BTRFS_SEND_TLV_XATTR_DATA, &di->name[di->n], di->m); send_command_finish(context, pos);
len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n; di = (DIR_ITEM*)&di->name[di->m + di->n]; } while (len > 0); } else if (!tp && tp2) { ULONG len; DIR_ITEM* di;
len = tp2->item->size; di = (DIR_ITEM*)tp2->item->data;
do { ULONG pos;
if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset); return STATUS_INTERNAL_ERROR; }
pos = context->datalen; send_command(context, BTRFS_SEND_CMD_REMOVE_XATTR); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, di->name, di->n); send_command_finish(context, pos);
len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n; di = (DIR_ITEM*)&di->name[di->m + di->n]; } while (len > 0); } else { ULONG len; DIR_ITEM* di; LIST_ENTRY xattrs;
InitializeListHead(&xattrs);
len = tp->item->size; di = (DIR_ITEM*)tp->item->data;
do { xattr_cmp* xa;
if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset); return STATUS_INTERNAL_ERROR; }
xa = ExAllocatePoolWithTag(PagedPool, sizeof(xattr_cmp), ALLOC_TAG); if (!xa) { ERR("out of memory\n");
while (!IsListEmpty(&xattrs)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&xattrs), xattr_cmp, list_entry)); }
return STATUS_INSUFFICIENT_RESOURCES; }
xa->namelen = di->n; xa->name = di->name; xa->value1len = di->m; xa->value1 = di->name + di->n; xa->value2len = 0; xa->value2 = NULL;
InsertTailList(&xattrs, &xa->list_entry);
len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n; di = (DIR_ITEM*)&di->name[di->m + di->n]; } while (len > 0);
len = tp2->item->size; di = (DIR_ITEM*)tp2->item->data;
do { xattr_cmp* xa; LIST_ENTRY* le; bool found = false;
if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) { ERR("(%I64x,%x,%I64x) was truncated\n", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset); return STATUS_INTERNAL_ERROR; }
le = xattrs.Flink; while (le != &xattrs) { xa = CONTAINING_RECORD(le, xattr_cmp, list_entry);
if (xa->namelen == di->n && RtlCompareMemory(xa->name, di->name, di->n) == di->n) { xa->value2len = di->m; xa->value2 = di->name + di->n; found = true; break; }
le = le->Flink; }
if (!found) { xa = ExAllocatePoolWithTag(PagedPool, sizeof(xattr_cmp), ALLOC_TAG); if (!xa) { ERR("out of memory\n");
while (!IsListEmpty(&xattrs)) { ExFreePool(CONTAINING_RECORD(RemoveHeadList(&xattrs), xattr_cmp, list_entry)); }
return STATUS_INSUFFICIENT_RESOURCES; }
xa->namelen = di->n; xa->name = di->name; xa->value1len = 0; xa->value1 = NULL; xa->value2len = di->m; xa->value2 = di->name + di->n;
InsertTailList(&xattrs, &xa->list_entry); }
len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n; di = (DIR_ITEM*)&di->name[di->m + di->n]; } while (len > 0);
while (!IsListEmpty(&xattrs)) { xattr_cmp* xa = CONTAINING_RECORD(RemoveHeadList(&xattrs), xattr_cmp, list_entry);
if (xa->value1len != xa->value2len || !xa->value1 || !xa->value2 || RtlCompareMemory(xa->value1, xa->value2, xa->value1len) != xa->value1len) { ULONG pos;
if (!xa->value1) { pos = context->datalen; send_command(context, BTRFS_SEND_CMD_REMOVE_XATTR); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, xa->name, xa->namelen); send_command_finish(context, pos); } else { pos = context->datalen; send_command(context, BTRFS_SEND_CMD_SET_XATTR); send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0); send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, xa->name, xa->namelen); send_add_tlv(context, BTRFS_SEND_TLV_XATTR_DATA, xa->value1, xa->value1len); send_command_finish(context, pos); } }
ExFreePool(xa); } }
return STATUS_SUCCESS;}
_Function_class_(KSTART_ROUTINE)static void __stdcall send_thread(void* ctx) { send_context* context = (send_context*)ctx; NTSTATUS Status; KEY searchkey; traverse_ptr tp, tp2;
InterlockedIncrement(&context->root->send_ops);
if (context->parent) InterlockedIncrement(&context->parent->send_ops);
if (context->clones) { ULONG i;
for (i = 0; i < context->num_clones; i++) { InterlockedIncrement(&context->clones[i]->send_ops); } }
ExAcquireResourceExclusiveLite(&context->Vcb->tree_lock, true);
flush_subvol_fcbs(context->root);
if (context->parent) flush_subvol_fcbs(context->parent);
if (context->Vcb->need_write) Status = do_write(context->Vcb, NULL); else Status = STATUS_SUCCESS;
free_trees(context->Vcb);
if (!NT_SUCCESS(Status)) { ERR("do_write returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
ExConvertExclusiveToSharedLite(&context->Vcb->tree_lock);
searchkey.obj_id = searchkey.offset = 0; searchkey.obj_type = 0;
Status = find_item(context->Vcb, context->root, &tp, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->parent) { bool ended1 = false, ended2 = false; Status = find_item(context->Vcb, context->parent, &tp2, &searchkey, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
do { traverse_ptr next_tp;
if (context->datalen > SEND_BUFFER_LENGTH) { KEY key1 = tp.item->key, key2 = tp2.item->key;
ExReleaseResourceLite(&context->Vcb->tree_lock);
KeClearEvent(&context->send->cleared_event); KeSetEvent(&context->buffer_event, 0, true); KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL);
if (context->send->cancelling) goto end;
ExAcquireResourceSharedLite(&context->Vcb->tree_lock, true);
if (!ended1) { Status = find_item(context->Vcb, context->root, &tp, &key1, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (keycmp(tp.item->key, key1)) { ERR("readonly subvolume changed\n"); ExReleaseResourceLite(&context->Vcb->tree_lock); Status = STATUS_INTERNAL_ERROR; goto end; } }
if (!ended2) { Status = find_item(context->Vcb, context->parent, &tp2, &key2, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (keycmp(tp2.item->key, key2)) { ERR("readonly subvolume changed\n"); ExReleaseResourceLite(&context->Vcb->tree_lock); Status = STATUS_INTERNAL_ERROR; goto end; } } }
while (!ended1 && !ended2 && tp.tree->header.address == tp2.tree->header.address) { Status = skip_to_difference(context->Vcb, &tp, &tp2, &ended1, &ended2); if (!NT_SUCCESS(Status)) { ERR("skip_to_difference returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } }
if (!ended1 && !ended2 && !keycmp(tp.item->key, tp2.item->key)) { bool no_next = false, no_next2 = false;
TRACE("~ %I64x,%x,%I64x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) { Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2); if (!NT_SUCCESS(Status)) { ERR("finish_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } }
if (tp.item->key.obj_type == TYPE_INODE_ITEM) { if (tp.item->size == tp2.item->size && tp.item->size > 0 && RtlCompareMemory(tp.item->data, tp2.item->data, tp.item->size) == tp.item->size) { uint64_t inode = tp.item->key.obj_id;
while (true) { if (!find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) { ended1 = true; break; }
tp = next_tp;
if (tp.item->key.obj_id != inode) break; }
while (true) { if (!find_next_item(context->Vcb, &tp2, &next_tp, false, NULL)) { ended2 = true; break; }
tp2 = next_tp;
if (tp2.item->key.obj_id != inode) break; }
no_next = true; } else if (tp.item->size > sizeof(uint64_t) && tp2.item->size > sizeof(uint64_t) && *(uint64_t*)tp.item->data != *(uint64_t*)tp2.item->data) { uint64_t inode = tp.item->key.obj_id;
Status = send_inode(context, NULL, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
while (true) { if (!find_next_item(context->Vcb, &tp2, &next_tp, false, NULL)) { ended2 = true; break; }
tp2 = next_tp;
if (tp2.item->key.obj_id != inode) break;
if (tp2.item->key.obj_type == TYPE_INODE_REF) { Status = send_inode_ref(context, &tp2, true); if (!NT_SUCCESS(Status)) { ERR("send_inode_ref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp2.item->key.obj_type == TYPE_INODE_EXTREF) { Status = send_inode_extref(context, &tp2, true); if (!NT_SUCCESS(Status)) { ERR("send_inode_extref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } }
Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2); if (!NT_SUCCESS(Status)) { ERR("finish_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
no_next2 = true;
Status = send_inode(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else { Status = send_inode(context, &tp, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } } else if (tp.item->key.obj_type == TYPE_INODE_REF) { Status = send_inode_ref(context, &tp, false); if (!NT_SUCCESS(Status)) { ERR("send_inode_ref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
Status = send_inode_ref(context, &tp2, true); if (!NT_SUCCESS(Status)) { ERR("send_inode_ref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) { Status = send_inode_extref(context, &tp, false); if (!NT_SUCCESS(Status)) { ERR("send_inode_extref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
Status = send_inode_extref(context, &tp2, true); if (!NT_SUCCESS(Status)) { ERR("send_inode_extref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) { Status = send_extent_data(context, &tp, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_extent_data returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) { Status = send_xattr(context, &tp, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_xattr returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } }
if (!no_next) { if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else ended1 = true;
if (!no_next2) { if (find_next_item(context->Vcb, &tp2, &next_tp, false, NULL)) tp2 = next_tp; else ended2 = true; } } } else if (ended2 || (!ended1 && !ended2 && keycmp(tp.item->key, tp2.item->key) == -1)) { TRACE("A %I64x,%x,%I64x\n", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);
if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) { Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2); if (!NT_SUCCESS(Status)) { ERR("finish_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } }
if (tp.item->key.obj_type == TYPE_INODE_ITEM) { Status = send_inode(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_INODE_REF) { Status = send_inode_ref(context, &tp, false); if (!NT_SUCCESS(Status)) { ERR("send_inode_ref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) { Status = send_inode_extref(context, &tp, false); if (!NT_SUCCESS(Status)) { ERR("send_inode_extref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) { Status = send_extent_data(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_extent_data returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) { Status = send_xattr(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_xattr returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } }
if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else ended1 = true; } else if (ended1 || (!ended1 && !ended2 && keycmp(tp.item->key, tp2.item->key) == 1)) { TRACE("B %I64x,%x,%I64x\n", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset);
if (context->lastinode.inode != 0 && tp2.item->key.obj_id > context->lastinode.inode) { Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2); if (!NT_SUCCESS(Status)) { ERR("finish_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } }
if (tp2.item->key.obj_type == TYPE_INODE_ITEM) { Status = send_inode(context, NULL, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp2.item->key.obj_type == TYPE_INODE_REF) { Status = send_inode_ref(context, &tp2, true); if (!NT_SUCCESS(Status)) { ERR("send_inode_ref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp2.item->key.obj_type == TYPE_INODE_EXTREF) { Status = send_inode_extref(context, &tp2, true); if (!NT_SUCCESS(Status)) { ERR("send_inode_extref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp2.item->key.obj_type == TYPE_EXTENT_DATA && !context->lastinode.deleting) { Status = send_extent_data(context, NULL, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_extent_data returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp2.item->key.obj_type == TYPE_XATTR_ITEM && !context->lastinode.deleting) { Status = send_xattr(context, NULL, &tp2); if (!NT_SUCCESS(Status)) { ERR("send_xattr returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } }
if (find_next_item(context->Vcb, &tp2, &next_tp, false, NULL)) tp2 = next_tp; else ended2 = true; } } while (!ended1 || !ended2); } else { do { traverse_ptr next_tp;
if (context->datalen > SEND_BUFFER_LENGTH) { KEY key = tp.item->key;
ExReleaseResourceLite(&context->Vcb->tree_lock);
KeClearEvent(&context->send->cleared_event); KeSetEvent(&context->buffer_event, 0, true); KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL);
if (context->send->cancelling) goto end;
ExAcquireResourceSharedLite(&context->Vcb->tree_lock, true);
Status = find_item(context->Vcb, context->root, &tp, &key, false, NULL); if (!NT_SUCCESS(Status)) { ERR("find_item returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (keycmp(tp.item->key, key)) { ERR("readonly subvolume changed\n"); ExReleaseResourceLite(&context->Vcb->tree_lock); Status = STATUS_INTERNAL_ERROR; goto end; } }
if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) { Status = finish_inode(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("finish_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } }
if (tp.item->key.obj_type == TYPE_INODE_ITEM) { Status = send_inode(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_INODE_REF) { Status = send_inode_ref(context, &tp, false); if (!NT_SUCCESS(Status)) { ERR("send_inode_ref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) { Status = send_inode_extref(context, &tp, false); if (!NT_SUCCESS(Status)) { ERR("send_inode_extref returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) { Status = send_extent_data(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_extent_data returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) { Status = send_xattr(context, &tp, NULL); if (!NT_SUCCESS(Status)) { ERR("send_xattr returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
if (context->send->cancelling) { ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; } }
if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) tp = next_tp; else break; } while (true); }
if (context->lastinode.inode != 0) { Status = finish_inode(context, NULL, NULL); if (!NT_SUCCESS(Status)) { ERR("finish_inode returned %08lx\n", Status); ExReleaseResourceLite(&context->Vcb->tree_lock); goto end; }
ExReleaseResourceLite(&context->Vcb->tree_lock);
if (context->send->cancelling) goto end; } else ExReleaseResourceLite(&context->Vcb->tree_lock);
KeClearEvent(&context->send->cleared_event); KeSetEvent(&context->buffer_event, 0, true); KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL);
Status = STATUS_SUCCESS;
end: if (!NT_SUCCESS(Status)) { KeSetEvent(&context->buffer_event, 0, false);
if (context->send->ccb) context->send->ccb->send_status = Status; }
ExAcquireResourceExclusiveLite(&context->Vcb->send_load_lock, true);
while (!IsListEmpty(&context->orphans)) { orphan* o = CONTAINING_RECORD(RemoveHeadList(&context->orphans), orphan, list_entry); ExFreePool(o); }
while (!IsListEmpty(&context->dirs)) { send_dir* sd = CONTAINING_RECORD(RemoveHeadList(&context->dirs), send_dir, list_entry);
if (sd->name) ExFreePool(sd->name);
while (!IsListEmpty(&sd->deleted_children)) { deleted_child* dc = CONTAINING_RECORD(RemoveHeadList(&sd->deleted_children), deleted_child, list_entry); ExFreePool(dc); }
ExFreePool(sd); }
ZwClose(context->send->thread); context->send->thread = NULL;
if (context->send->ccb) context->send->ccb->send = NULL;
RemoveEntryList(&context->send->list_entry); ExFreePool(context->send); ExFreePool(context->data);
InterlockedDecrement(&context->Vcb->running_sends); InterlockedDecrement(&context->root->send_ops);
if (context->parent) InterlockedDecrement(&context->parent->send_ops);
ExReleaseResourceLite(&context->Vcb->send_load_lock);
if (context->clones) { ULONG i;
for (i = 0; i < context->num_clones; i++) { InterlockedDecrement(&context->clones[i]->send_ops); }
ExFreePool(context->clones); }
ExFreePool(context);
PsTerminateSystemThread(STATUS_SUCCESS);}
NTSTATUS send_subvol(device_extension* Vcb, void* data, ULONG datalen, PFILE_OBJECT FileObject, PIRP Irp) { NTSTATUS Status; fcb* fcb; ccb* ccb; root* parsubvol = NULL; send_context* context; send_info* send; ULONG num_clones = 0; root** clones = NULL; OBJECT_ATTRIBUTES oa;
if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb) return STATUS_INVALID_PARAMETER;
if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode)) return STATUS_PRIVILEGE_NOT_HELD;
fcb = FileObject->FsContext; ccb = FileObject->FsContext2;
if (fcb->inode != SUBVOL_ROOT_INODE || fcb == Vcb->root_fileref->fcb) return STATUS_INVALID_PARAMETER;
if (!Vcb->readonly && !(fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY)) return STATUS_INVALID_PARAMETER;
if (data) { btrfs_send_subvol* bss = (btrfs_send_subvol*)data; HANDLE parent;
#if defined(_WIN64) if (IoIs32bitProcess(Irp)) { btrfs_send_subvol32* bss32 = (btrfs_send_subvol32*)data;
if (datalen < offsetof(btrfs_send_subvol32, num_clones)) return STATUS_INVALID_PARAMETER;
parent = Handle32ToHandle(bss32->parent);
if (datalen >= offsetof(btrfs_send_subvol32, clones[0])) num_clones = bss32->num_clones;
if (datalen < offsetof(btrfs_send_subvol32, clones[0]) + (num_clones * sizeof(uint32_t))) return STATUS_INVALID_PARAMETER; } else {#endif if (datalen < offsetof(btrfs_send_subvol, num_clones)) return STATUS_INVALID_PARAMETER;
parent = bss->parent;
if (datalen >= offsetof(btrfs_send_subvol, clones[0])) num_clones = bss->num_clones;
if (datalen < offsetof(btrfs_send_subvol, clones[0]) + (num_clones * sizeof(HANDLE))) return STATUS_INVALID_PARAMETER;#if defined(_WIN64) }#endif
if (parent) { PFILE_OBJECT fileobj; struct _fcb* parfcb;
Status = ObReferenceObjectByHandle(parent, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL); if (!NT_SUCCESS(Status)) { ERR("ObReferenceObjectByHandle returned %08lx\n", Status); return Status; }
if (fileobj->DeviceObject != FileObject->DeviceObject) { ObDereferenceObject(fileobj); return STATUS_INVALID_PARAMETER; }
parfcb = fileobj->FsContext;
if (!parfcb || parfcb == Vcb->root_fileref->fcb || parfcb == Vcb->volume_fcb || parfcb->inode != SUBVOL_ROOT_INODE) { ObDereferenceObject(fileobj); return STATUS_INVALID_PARAMETER; }
parsubvol = parfcb->subvol; ObDereferenceObject(fileobj);
if (!Vcb->readonly && !(parsubvol->root_item.flags & BTRFS_SUBVOL_READONLY)) return STATUS_INVALID_PARAMETER;
if (parsubvol == fcb->subvol) return STATUS_INVALID_PARAMETER; }
if (num_clones > 0) { ULONG i;
clones = ExAllocatePoolWithTag(PagedPool, sizeof(root*) * num_clones, ALLOC_TAG); if (!clones) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; }
for (i = 0; i < num_clones; i++) { HANDLE h; PFILE_OBJECT fileobj; struct _fcb* clonefcb;
#if defined(_WIN64) if (IoIs32bitProcess(Irp)) { btrfs_send_subvol32* bss32 = (btrfs_send_subvol32*)data;
h = Handle32ToHandle(bss32->clones[i]); } else#endif h = bss->clones[i];
Status = ObReferenceObjectByHandle(h, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL); if (!NT_SUCCESS(Status)) { ERR("ObReferenceObjectByHandle returned %08lx\n", Status); ExFreePool(clones); return Status; }
if (fileobj->DeviceObject != FileObject->DeviceObject) { ObDereferenceObject(fileobj); ExFreePool(clones); return STATUS_INVALID_PARAMETER; }
clonefcb = fileobj->FsContext;
if (!clonefcb || clonefcb == Vcb->root_fileref->fcb || clonefcb == Vcb->volume_fcb || clonefcb->inode != SUBVOL_ROOT_INODE) { ObDereferenceObject(fileobj); ExFreePool(clones); return STATUS_INVALID_PARAMETER; }
clones[i] = clonefcb->subvol; ObDereferenceObject(fileobj);
if (!Vcb->readonly && !(clones[i]->root_item.flags & BTRFS_SUBVOL_READONLY)) { ExFreePool(clones); return STATUS_INVALID_PARAMETER; } } } }
ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, true);
if (ccb->send) { WARN("send operation already running\n"); ExReleaseResourceLite(&Vcb->send_load_lock); return STATUS_DEVICE_NOT_READY; }
context = ExAllocatePoolWithTag(NonPagedPool, sizeof(send_context), ALLOC_TAG); if (!context) { ERR("out of memory\n");
if (clones) ExFreePool(clones);
ExReleaseResourceLite(&Vcb->send_load_lock); return STATUS_INSUFFICIENT_RESOURCES; }
context->Vcb = Vcb; context->root = fcb->subvol; context->parent = parsubvol; InitializeListHead(&context->orphans); InitializeListHead(&context->dirs); InitializeListHead(&context->pending_rmdirs); context->lastinode.inode = 0; context->lastinode.path = NULL; context->lastinode.sd = NULL; context->root_dir = NULL; context->num_clones = num_clones; context->clones = clones; InitializeListHead(&context->lastinode.refs); InitializeListHead(&context->lastinode.oldrefs); InitializeListHead(&context->lastinode.exts); InitializeListHead(&context->lastinode.oldexts);
context->data = ExAllocatePoolWithTag(PagedPool, SEND_BUFFER_LENGTH + (2 * MAX_SEND_WRITE), ALLOC_TAG); // give ourselves some wiggle room if (!context->data) { ExFreePool(context); ExReleaseResourceLite(&Vcb->send_load_lock); return STATUS_INSUFFICIENT_RESOURCES; }
context->datalen = 0;
send_subvol_header(context, fcb->subvol, ccb->fileref); // FIXME - fileref needs some sort of lock here
KeInitializeEvent(&context->buffer_event, NotificationEvent, false);
send = ExAllocatePoolWithTag(NonPagedPool, sizeof(send_info), ALLOC_TAG); if (!send) { ERR("out of memory\n"); ExFreePool(context->data); ExFreePool(context);
if (clones) ExFreePool(clones);
ExReleaseResourceLite(&Vcb->send_load_lock); return STATUS_INSUFFICIENT_RESOURCES; }
KeInitializeEvent(&send->cleared_event, NotificationEvent, false);
send->context = context; context->send = send;
ccb->send = send; send->ccb = ccb; ccb->send_status = STATUS_SUCCESS;
send->cancelling = false;
InterlockedIncrement(&Vcb->running_sends);
InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
Status = PsCreateSystemThread(&send->thread, 0, &oa, NULL, NULL, send_thread, context); if (!NT_SUCCESS(Status)) { ERR("PsCreateSystemThread returned %08lx\n", Status); ccb->send = NULL; InterlockedDecrement(&Vcb->running_sends); ExFreePool(send); ExFreePool(context->data); ExFreePool(context);
if (clones) ExFreePool(clones);
ExReleaseResourceLite(&Vcb->send_load_lock); return Status; }
InsertTailList(&Vcb->send_ops, &send->list_entry); ExReleaseResourceLite(&Vcb->send_load_lock);
return STATUS_SUCCESS;}
NTSTATUS read_send_buffer(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, ULONG_PTR* retlen, KPROCESSOR_MODE processor_mode) { ccb* ccb; send_context* context;
ccb = FileObject ? FileObject->FsContext2 : NULL; if (!ccb) return STATUS_INVALID_PARAMETER;
if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode)) return STATUS_PRIVILEGE_NOT_HELD;
ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, true);
if (!ccb->send) { ExReleaseResourceLite(&Vcb->send_load_lock); return !NT_SUCCESS(ccb->send_status) ? ccb->send_status : STATUS_END_OF_FILE; }
context = (send_context*)ccb->send->context;
KeWaitForSingleObject(&context->buffer_event, Executive, KernelMode, false, NULL);
if (datalen == 0) { ExReleaseResourceLite(&Vcb->send_load_lock); return STATUS_SUCCESS; }
RtlCopyMemory(data, context->data, min(datalen, context->datalen));
if (datalen < context->datalen) { // not empty yet *retlen = datalen; RtlMoveMemory(context->data, &context->data[datalen], context->datalen - datalen); context->datalen -= datalen; ExReleaseResourceLite(&Vcb->send_load_lock); } else { *retlen = context->datalen; context->datalen = 0; ExReleaseResourceLite(&Vcb->send_load_lock);
KeClearEvent(&context->buffer_event); KeSetEvent(&ccb->send->cleared_event, 0, false); }
return STATUS_SUCCESS;}