ÿØÿà JFIF    ÿÛ „ !.%+&8&+/1555$;@;4?.451 4,$,44444444444414444444444444444444444444444444444444ÿÀ  á á" ÿÄ     ÿÄ ?    !1AQaq"2‘¡±ÁðBRbrÑá#‚’¢²3S CñÿÄ   ÿÄ !    !1QAa‘2ÿÚ   ? 5˜Z¯V¦cø)›t/? z¨±>Õ5€¶‹Á¤·¼z¼Ü¬+ñ®v¤¨_ˆR­BFn©—˜ý®ç̝P8gýt·ÉSTŦˆìät?þé¼íìN/Þa)ì–í6ô… Ï¿øÃj´¿KÇü]ÿ ªô¹-eKànëÕHTx}ýSÜ›ÿ ”7Ø×&µ<¦  ¥ÑO¶[Ù¯ä¨ÞÃÿ PZ-¬;#õ|•oaÿ ©CìÞz3˜öː/¤­ñTûIØ}š^ mÓ%ªxˆ¥ÉŸu=Z+ISe¿45™¼u;ú&WØ÷€æßQ™®{|íx*TC“#ZŠìZ§²‹ 6pv…³¿¡äª*áZÐ%ÒOáˆo"x«OHk w±æ+¬V(kMúŸ5Vö«$ ÁrÏbàb57/luR ¸ÑÛj Òµì`Мq­û žICÀÊ•©4€Âcà¨Ï€O´<èÐ:›ù(Ë^L8þ‘ÍÌ#¸Ð_Ì©ÙK(Öz 4¬û+¸;ü’V’84‘¬ÃŽ:[â‡ÔÌáõp¢~§ªlæ£ö{®G>J¼"°‡7¯ÆÉèßû ‹É‹§ÁòÃýâßî ^ƾÙõ‹×óH#«LP½ïX=xÑÍ$|W?•~• îëÔ©ª‹ {ÝT…Kÿ ”hûâá)J*ö˜–ÔU;iÇ€/ ÆþjóZ\ýwØ=Ìm ºèËL9 ýèÆð/¨’¥öo=nË.%Îì ŽÕ¯È|{Oj²ƒE6e/ßdÄõ²Ìâ1O®ò×TsəԸhOMýíMˆ¿¼H˜l²,7Â¥#MF/Úf°Ö½± ¸–dr‹NýÊ íjqx{œÉ ä-È ¦ øÄër¨q°ð †nцýÑÄÆ’mä…n<0È™;ÁÝá¯ÁZƒ7FÀmì­ É&9ˆîéi¶ùN§Y• ÃZãAâ?•‡©‰ , ó¾IŸŠc1 4â&y­&pŠ­6;M À 0¹qç»p.á …ŸÅáK@%6·y6ƒ‰3?”úºŽ‰éX5ªPT §µ!=Mž«Ú½‹ÅgÂSâÉaþÓoö–¯ÁÔìR>5éÿ üs¶ÆUcÌ kÇR ]ÿ ù¬¼«VŽ;Â|‡~¢¦”ÏŰæ {L™Õ°Óv¹ò¸írޡעCÃ!íVÕ {¶»sŒNPg/ "uÕbkm²“$ďå¿é¹§°½æz¯6 †s¿!s–wÚÝ“™Œ °.ûj>·+™Òa…©Œ&rÝÎtÛë긪Ît’LAVp%c Úý[ÄzJ¾ÇàXXç@˜ó<êL]·T˜¾¥1Ó©V‡g´æ½¦Ý@¹óø!_@´ÞâSÁ —S3™•& ]@JHÚý©ZŽ €×æÔr»Áf!‡yÞ4Mv*èÓã_{‘åóUuљØ«Oïé*®EvÑ Œ÷‡U \"㪒ÍK+À 4“M¡ï:0¥5í!'<@î´”>Ç»&Z–ïCCV˜Ì5Šo&îhè.žû |ÓK©h$s6KìŒëã)¹hI¦GïOåóI;ììü#É$Š0…Ææ¥TØ.5­¾gn´ “ÂÖ\:hœ89G)J@„}œ:’Ò{/Š"¦_Æ×7Æ3VÇŠÊa]ÚŒÙ€Ä–=®uÁßâACZƒ§§£ Qnâ:«,×{tyø¬iÛcœÜÄ€H½ÄÍCk´÷šß .W'b¤Íåh]÷€=,Žv×cÚEÚHXJX¶îo¨FÒtèöŸ>ªª6[J®Fµ£sGÁeqõfe\íjÒÐïÄÐGˆe1Ø‹.Ø”‘Ëuø Y­ˆÜ ŽG|zùªüMpDnQWÄ”%JŠ™)â*p@Örš«ÕT2Ð%ˆG#ª„ ·¤!°ŸOTÂT¸aÚ%4&h™LµšØüÐ.F¿²ÐÞ_Ç‚¾ÅÃaÜ÷09Æ q€öy˜v‡85õN÷]¬äѼóS{°_MެúÔ#°Ç¸0åÞè2ëôPcvÆw9®ií1Ä8F™˜à‰´+‰Ik1òÝ7“Ñ×ÒsÝ\x‚h`ÞÑ`ó"|µEcý£n˜h`}GÞ !±ù²Ápü²ß6 0ïi󜵩SÈÇ7˜-ÕURO˜¦´f$ªž-Í6(œ}<„ éc øs]ŽŽ„*—¾ ìdŽ„)méª\¿êÎIg¾ØÞ~I#C/¼¼´EÁÈŽi8“©õådô·>euä ƒ'Ê×लR1ÉJE1ÐAát`t;ÇР%Ý<‡¥„ÍÆ`×Oyó)õiI€ñQaŸ4Ûù\áàaÃÔ¹HÃu¹*k€¦<„e S‡&õÏ B!ŽhüÞ`yj}mªf×\¿ Ç~æ­9‡û\՞Ǖg²1Žû5V7 !àöšm° c`ܬøÇìµÒ'P"?…´Ö,"§^•õލsÔ)6˜sæéÍR¼ ò|Sl”‹7 nPW Gòú÷½§O¯‡„l¡kSÞŒr½PÊ@æ¢pŽ-mÿ #Ÿ˜Àº¶Áä¦;ïÔæ$1££`“Õ>„—·ž)ßð³ñ#Ï Ô$¶œ‰ÊE‹À;÷º ¯«P:Ñ”8–IÊtpÞ3ª“>ê“þës4ò2OÏÕ­±zô†Õ§‰.÷ä¸;¿˜“'œ›žª}«Œ{ª±Ì 9ÔóÞÕ‡0 $íWV3Üì¬ —@kÝ4@¿r¼±½¬™›?øØæ´'Áé®CË3-g$˜ö‡×auÚi´Žp/êÛ æF›Ú2v‹ã¿¿,nB1̨ƃqÞa5͝@&Æû“él÷ \C²½UÍc ¯k×¢U ÖéQå™—-r wô ÞÏ<Ò=&=ÿ Ôê Òêˈt,i—;LîÜ á¸*ÚÃ1$êL•LÍ <É)ýÐà’ ;F™{ƒ™˜€&'}‚ãÄK`¡ÞT@I;®žZóè‚s’7®°›+§O­Åq©é»²9<Ô J ¼9O’HL»Ùïì¸rk¼Ž_ý‘TŸu[²ßÚŒ·ü÷B%¯E ŸÔX5êO´ Ç•€’I0 ÉJX` ñ¹õ%;µŸD‘«´€àwÒ™U ûئžÖö\×®×´8 ½‡ºÐÆÓ§?Àkmœ=;d5*@-ì0F Rªýš[Ü6âö̃ڸr*KA9· u*µæ£?U¸Âêí†8@¦X4 e-ò„0s{ HâUpU?¼mñRa°®a%Ð'tÉ×’\¾ÊÉ]t›h>·(Ë@R¼¡Ãt h}’O÷au<+nT…Ö…MӐ??Óe95 q>í/;&JSû °¯ÊéÞ øƒ*Ã2½Ài&:nôUl=¾¿5eˆ3”ñc|Ú2V”>„»&eE;«ÚäC p¢Û úy 9š[ŒÌx¼擼A&DåÒ¯ˆ¤ÀÌ;"˜ ÏQä¸åhÊ}Ûq«Û0WžÒ|»€ø®öCm5•\ÇÀ§Pe3£]0ÃàLDÉ‰1øªxjgwT‚÷¿LΨK‹›ùs—xˆÜ±µ kæ¸f‰‰ÜGk/LÛØ6d9ò¶ùA{ƒA3š/¬D¬khÓk‰`˜"㯒r¿±Óã jx‡°e}<Ñø\3y:'À•/h½Í€Ç4~g ?Û(¼]v‘ªlKÎâ~?O‚W%{Ì:“'©úNq¾›úo(X’¥¯ˆ nFê{Ç€ü?º'ë ø‹ì Þ09ŒÌç9Æ —ËC`j@ÓÄ(+a‹un¸#ÂꟋ{K`‘ÑÍÍ'à´»/Û,KW;Þ4²þð ï Nm|~fGÏ(…³Ã)«1ö­Õ ¥‡¨©ƒÃ™ü-s=à=U66Ï«Ýc蓦W¹íž®›nÔ%êÇìŒ<#Ü×84ån®Ð ÒåOC` ñânÑs‡¢ç 1õ%Îhì½Ã½® e:ݼUZo™`  ÅZŸŒÊ«ê1ÏÄo$q¹Þ€©ˆhÐÉä¯ñ[!…Ú˜àJ:x2$Íß&PåT£6ç— ‡Í*4Ýšçjÿ ‰É nófÐ ó(L5C•åÆ\rMÒ@ò }y-W}™üýVù—ú¢=Ù”c®‘< M ž ´Phr ¦©TD ‘ù.$´÷O‡‘V2Æò.=IUŒ=ž‡â¬i™aþÓåÙ?òUø'ØÖ•.~* šTŒ!•-×áºTâ®ä#õü'´ eýlYÅÓeÕKÂrT"CÚ@u!Óxƒ{š3€}1¿(r}%«nËamjÑ%ÑNEò v ˜à  σöK³,*º.àzù¨™Ó ÚçâU¦*¿ 9{%Ö¹ njûdaXöb) kÛÆ±ûÓ\°M7ˆÂ=û›ç¿Ã‚­V»Cg–8ÙêE- j)k$º`Ã-ùEýeBÆÇ]c¡°ñty&Òd0nõ'¡W+ƒ*|–øµFa\GQªEAÔp5\Ǽ·¼Ç8·õ -â§Ú[ ‡ uZeÖ 3}×d'+¹:ð+K†Û®s!Ï$úe€<Û”x)1»a­¡LC]¸µík…ÚàA»AYº{†ªS[¦5HÒ7ù --,ísòDØ€èk ÞÀîÜ ò@â( ËNˆë›4ô½•/¦o‡€Û7 ê•ÆêòðÜy'Án½µ á˜ݦ ndeo…[ì¶Ê,¥R³Ä=À±—–ß;£™´ñSâ*g§”ïaið‘Jå~™ÓÞ ß³Õ¢»8x埒²52>AÊb&-÷\7´éÄù€T˜,w;3{ï˜k…à¹ÄqÀ«œ{€\ ˆ¾[´¨јr &Úé„Ívˆ±8†¿]|¬ņ4I×pÞS1ÈÖz‰#Ìv‡G!YNògñ:màTz¢Ý1ô©^O=~ë|5Bã™ç•¼µõ•bÆ@úÕS¬ÈŒ#¬zünrŸ û” Z²•èðV"ÁHÚý©wÝ €7¼Ìu1hÑa3Éä û f$o¿É ™Ú›ÝçnpÒ3äÌ3†Í§,Äï]$‰/pê †«À¼¸e9­Æê_C]žƒ·ý·frÁN«, E=›Çq -‰öŒ:aÏ¿±í&£Í:-} 84‘ÿ eƒQÑeëSsuiA ³g㟥ú£?ÿ ʼn*”“÷aühe:ÊWa@ÒÞk±eØ] F Ô—r.åä˜ @ö¥ªZoÐýYL·¥S²G/‡ñ <~*ZÆ´è>JlòàÛÆ½ÿ 窘ìGN¢:I®KšJp/`íIÁÀõ#Ä-€ö­šµŒoF4|ÆQØÆ@Ì|£Ô…¢À{9˜è½Üó›€ôYÒÎYsið;ís¤€à²ˆ‚4qÉVŒI$ ‰"° æµ8cXGjœˏ¡Aâý•ËÜ¢ûï e·çLx']á"oÅÎê3¯Ç—¹”ó0nå‚âg{Œñ> S´˜îè°g238‚ãköÝfÚd´6Ò€;ò÷±¢™¼›º ¢Æ'¥Ðx'e¬ç ]bÈÆV¢ó‹kýBO ðÊâ$Ÿ!×T 3Mýמ žìٍàÌü‘8÷€àæØ8æ©6‰©L´«…oãpð„~Çk‰!ñ;‹”ÛžÍ àž±z Ÿôû øŸÝužÏ;ÿ #|u6™Þ¬ÚˆÐõA4¶â|ôl|Ê2ŽÇ¤ÝÅÇY.<#Aí.k§hóF‚”Y; M½Ö4hŸ4&›­¿tès´%FìL¥£Ãk‰ÇT¤haÁ¤ÚxfÉ`ÑìË›>i 3t‚:,–+^÷´–{Û–Nxi"x‘Ûg î¨>¥Õ܁ùZH,2Û“:8xÊ¢Çí9.É-Ìâã-=çjwµS˜dütžçwýGòú®®ûº_ˆýx$–¡ãøO EÚÛÏ÷R„×w+3£Á£öUMyR²¹âŒ°š›¸Ñãò9§Ó_Dl+Ùßc›úšGÅÌc†Ž!Ko=¶.‘Îÿ c²(2®V mª.ÿ ¹B›¹å ù„öŸSV>™ü¯$y:G¢Z×àøúdî¹û­·ýÇ´:•c LÍõi_‹ö+ÎæGÊè>OŠ•äž´§Þ{X}¨1ÚTc›»Qþ•êô°t¿OP?eæ~É{5]•ÙR£r5†nZ\ã@ &îJõ ¾àC°þV>fé¥/ü5ñÊIº_é5 ;e­h<@ Ä&æÃëE%;X,ÒãÆÞ`Oò¦kŸm#˜!ÀyÄ¢| óLšò¥Ä` ¶R=|ÈCâh5ò3DˆïF†ðÒ#ÅìÛœ?¸yhBãœí ZxßÎÄhºRK„`Þödvײ™ÀÈÑÒgŒuY w³%†ƒÓzõ ÖÏp‚dH®¦A´ù§»ÓÇMæ~)ˆð‡û:ù&Ä •vGD´À n ݇¼Ö8Fö óáà£~Ë¥x`oK|Ä?fxiØü%pìR>éò+Û±éÎ>núlFŤ'tq8LZÏvÃ?„¡ß±È⽆¯³íü@x|PöUäèØã¡ð‚ŒAìÏ"vÍwóŸÍ{ ý0.z È•Ö{,N¡£¡ŸKÕÙž>Ýœþ ÍÀ°<×EA!Å‚D™IúOÍ¡>ôG}Â` ÍßkÜL™Ž Þð™ {IøF²¹òQ3&!ÃÂÞz.d&Ï-sH¸,Ôõ˜ŽP€ 77ˆÝ¼ÊëÜw =cÕ Ú,ØÐ5ÎYÐ)ì´öœgŒ[¤ßv㙑8心>h]§µháYš£²ºÑ.{Ï7Sð•?´~×SÃKýJÛ˜ ™Íäiúu<µX¶1õ^kâçIÑ£sZ4h>j*ÔšD:4­¿_ ÷¸ Õxæÿ ¸?Mù _•­ÊÐ ä ÷ý ÑwL œ­ïnTkÛUÍN©ë:¦fV ¶ÜÔÜMªÅâA½–¿R×TXš-%iTÊT•‡Ù‚JôϐZxWÑè‰f‰òG º ×Õû2aZ7OU3[“×AT–ÞŒ…-‘¤”Ì ì&(ˆ¿­•ƒkï’:ðY¦W‘ Å)“†‘˜³Åtcø˜ñTÂwÚÇ4|üLÇªí–v- qˆèU qPE.†â‘˜µ Æ,ÐÅs]8¾„oúÑ i>ÜxxÈó)ƒ ´æÁâØ$À‰vžŸf$Ž |ãw;ÀÁIJ»b` {¦Ó¤Ú$©YÀ‘n@Óïž«9J¼êG m¤ ܯ¹ÌW4€ÐÒÅÛ‡#褕Ÿn-?í|с¥÷Ú¹¬'´ÞÜ9ÓK `hê£SÄSà?7—Wí_´…óB›»:=Ãïq`<8ñÓŒÑlú2d¬ê³£hÖ[l|$vÝro~'R®‰§°ñmY ͧäP |PUª¹·:3Œ[Û{Xÿ ºâ@‚W–Äé u‚ ¯´*=íή.pûÒdt @G‰¬ s¸ ëÉücr ÞæÑ¨Ê@>¤¢Ö±. Þ'¯°ÌME[YéïĵÂCå½ Ué©Áû'Ê9%eÔðNU”ë‘ÌsD3/®+UI˜9h.WC”빓$#:pz:YÓ ¿xž* ³$Í +$kñAŠ‹†¢ Uê>¸)_š¬÷©ßAÂÔb9ÇU ¯¾á•9¯ÏÏ÷O÷¼¼Fähal1‰3Ì[Ïr•´UCksNÐ] R‘¸¥H+§Šé†c©vÖÞ0iÓ76s†î!§=ß ¼~Ô'°Ãmäoäš³ªøi1úÉ)³yV8 CLÄØÁ‘WYïi€H6ÖÑiámø^ÈY´°Ñ7¥Û*—Ñ©L«Qƒï—Ùrÿ ›£Ð*š¸ˆL©ˆ$ˆ ÷¾D§9È®«qbqC)–ˆïv´çñsÑVT­Ø, <àïºÀO«Jý·õ àfPìð .wFšir´þ’2_Y *Æ€x\« ì€9š@ Ž|F⇥ˆkZ@hÖÄ0t¿-<“‹qµ¾*ZL¤Ú)&BJpÓF5=$„at*Zš$’ÑtdûÝRI1 2މ$€$I$#‰SÞ’Hë¬ï;Á$¡t$’`<(ñÇt)$‡Ð.Êf¢X’Kt=Éé$‚ˆªè¢oÝëòI%Rgcª÷ŠyI%¡‰ÿ !ñ)´õ $¤ Ô’IIGÿÙ#!/bin/bash VERBOSITY=0 TEMP_D="" UMOUNTS=( ) QEMU_DISCONNECT="" CR=$'\n' TAB=$'\t' error() { echo "$@" 1>&2; } Usage() { cat <&2; [ $# -eq 0 ] || error "$@"; exit 1; } has_cmd() { command -v "$1" >/dev/null 2>&1 } disconnect_qemu() { [ -n "$QEMU_DISCONNECT" ] || return 0 local out="" nbd="$QEMU_DISCONNECT" debug 1 "disconnecting $nbd" local pid="" pfile="/sys/block/${nbd#/dev/}/pid" { read pid < "$pfile" ; } >/dev/null 2>&1 [ -n "$pid" -a ! -d "/proc/$pid" ] && error "qemu-nbd process seems to have died. was '$pid'" out=$(qemu-nbd --disconnect "$nbd" 2>&1) && QEMU_DISCONNECT="" || { error "failed to disconnect $nbd"; error "$out" return 1; } } do_umounts() { local um="" fails=0 mydir="$PWD/" mounts="" i=0 mounts=( "$@" ) for((i=${#mounts[@]}-1;i>=0;i--)); do um=${mounts[$i]} um=$(readlink -f "$um") || { error "WARNING: failed to get full path to '$um'"; fails=$(($fails+1)) continue; } [ "${mydir#${um}/}" != "${mydir}" ] && { error "WARNING: leaving '$mydir' to unmount $um"; cd / } umount_r "$um" || { error "WARNING: unmounting filesystem at $um failed!" fails=$(($fails+1)) } done return $fails } cleanup() { if [ "${#UMOUNTS[@]}" -ne 0 ]; then debug 2 "umounts: ${UMOUNTS[*]}" do_umounts "${UMOUNTS[@]}" || { error "failed cleaning up mounts"; return 1; } fi disconnect_qemu rm -Rf "$TEMP_D" || error "removal of temp dir failed!" } debug() { local level="$1"; shift; [ "${level}" -gt "${VERBOSITY}" ] && return error "${@}" } get_image_format() { local img="$1" out="" out=$(qemu-img info "$img") && out=$(echo "$out" | awk '$0 ~ /^file format:/ { print $3 }') && _RET="$out" } get_partition() { # return in _RET the 'auto' partition for a image. # _RET=partition number for a partitioned image # _RET=0 for unpartitioned local img="$1" out=$(LANG=C sfdisk --list -uS "$img" 2>&1) || { error "failed determining if partitioned: $out"; return 1; } if echo "$out" | grep -q 'Device.*Start.*End'; then _RET=1 else _RET=0 fi } add_bin() { cat > "$1" || { error "failed to write to $1"; return 1; } chmod 755 "$1" || { error "failed to set perms on $1"; return 1; } } add_helpers() { local d="$1" local umap="$1" gmap="$2" [ -d "$1" ] || mkdir -p "$1" add_bin "$d/mchroot" <<"EOF" || return 1 #!/bin/sh exec chroot "$MOUNTPOINT" "$@" EOF return } mount_overlay() { local lower="$1" upper="$2" workdir="$3" local olayopts="lowerdir=$lower,upperdir=$upper" # 3.18+ require 'workdir=' option. case "$(uname -r)" in 2*|3.1[01234567]*|3.[0-9].*) :;; *) olayopts="${olayopts},workdir=$workdir" mkdir -p "$workdir" || { _ERR="Failed to create workdir '$workdir'"; return 1; } ;; esac local cmd="" fstype="" ret="" out="" fsfile="/proc/filesystems" _ERR="" for fstype in overlay overlayfs; do cmd=( mount -t "$fstype" -o "$olayopts" "$lower" "$upper" ) debug 2 "attempting '$fstype' mount with: ${cmd[*]}" out=$("${cmd[@]}" 2>&1) ret=$? if [ $ret -eq 0 ]; then debug 1 "mounted '$fstype' via $fstype: ${cmd[*]}" return 0 fi _ERR="${_ERR}Failed [$ret]: ${cmd[*]}:${CR}" _ERR="${_ERR}$out${CR}" if [ -r "$fsfile" ] && grep -q "${TAB}${fstype}$" "$fsfile"; then # this failed and we have support in kernel. do not try further. return $ret fi done return $ret } assert_nbd_support() { if [ ! -e /sys/block/nbd0 ] && ! grep -q nbd /proc/modules; then debug 1 "trying to load nbd module" modprobe nbd >/dev/null 2>&1 has_cmd udevadm && udevadm settle >/dev/null 2>&1 fi [ -e /sys/block/nbd0 ] || { error "Cannot use nbd: no nbd kernel support." return 1; } } find_unused_nbd() { # return a path to an unused nbd device (/dev/nbd?) local f roflag="" for f in /sys/block/nbd*; do [ -d "$f" -a ! -f "$f/pid" ] && { _RET="/dev/${f##*/}"; return 0; } done error "failed to find an nbd device" return 1; } connect_nbd() { local img="$1" fmt="$2" ptnum="${3:-auto}" rwmode="${3:-ro}" local nbd="" pidfile="" pid="" cmd="" ret="" roflag="" nptnum="" i="" if [ "$rwmode" = "ro" ]; then roflag="--read-only" fi # yes, there is a race condition here. find_unused_nbd || return nbd="$_RET" cmd=( qemu-nbd $roflag "--format=$fmt" --connect "$nbd" "$img" ) "${cmd[@]}" && QEMU_DISCONNECT="$nbd" ret=$? if [ $ret -ne 0 ]; then error "Failed [$ret]: $*" return $ret fi pidfile="/sys/block/${nbd#/dev/}/pid" if [ ! -f "$pidfile" ]; then debug 1 "waiting on pidfile for $nbd in $pidfile" i=0 while [ ! -f "$pidfile" ] && i=$(($i+1)); do if [ $i -eq 200 ]; then error "giving up on pidfile $pidfile for $nbd" disconnect_qemu return 1 fi sleep .1 debug 2 "." done fi read pid < "$pidfile" && debug 2 "pid for $nbd is $pid" || { error "reading pid from $pidfile for $nbd failed!"; disconnect_qemu return 1 } debug 1 "connected $img_in ($fmt) ${rwmode} to $nbd. waiting for device." local out="" # This can fail due to udev events, but we ignore that. We need to ensure # it happens for where it doesnt happen automatically (LP: #1741300) out=$(blockdev --rereadpt "$nbd" 2>&1) || debug 1 "blockdev rereadpt $nbd failed" has_cmd udevadm && udevadm settle i=0 while i=$(($i+1)); do get_partition "$nbd" && nptnum="$_RET" && break [ $i -eq 40 ] && { error "gave up on $nbd" disconnect_qemu return 1 } [ $(($i%10)) -eq 0 ] && debug 1 "waiting for $nbd to be ready." sleep .1 done if [ "${ptnum}" = "auto" ]; then if [ "$nptnum" = "0" ]; then debug 1 "unpartitioned disk." else debug 1 "partitioned disk." fi ptnum=$nptnum else if [ "$nptnum" = "0" -a "$ptnum" != "0" ]; then error "img $img does not appear partitioned but ptnum=$ptnum provided." return 1 fi fi if [ "$ptnum" -ne 0 ]; then mdev="${nbd}p${ptnum}" else mdev="${nbd}" fi i=0 while :; do [ -b "$mdev" ] && break i=$(($i+1)) [ $i -eq 100 ] && { error "gave up on waiting for $mdev" disconnect_qemu return 1 } [ $(($i%10)) -eq 0 ] && debug 1 "waiting for $mdev part=$ptnum to be ready." sleep .1 done _RET_NBD="$nbd" _RET_PT="$ptnum" _RET_DEV="$mdev" } mount_nbd() { local img="$1" mp="$2" fmt="$3" ptnum="$4" rwmode="${5:-rw}" opts="$6" if [ -z "$fmt" ]; then get_image_format "$img" && fmt="$_RET" || { error "failed to get image format for '$img' (try --format)" return 1 } fi assert_nbd_support || return connect_nbd "$img" "$fmt" "$ptnum" "$rwmode" || return local ptnum="$_RET_PT" mdev="$_RET_DEV" nbd="$_RET_NBD" if ( set -f; mount -o "$rwmode" $opts "$mdev" "$img_mp" ); then debug 1 "mounted $mdev via qemu-nbd $nbd at $img_mp" else error "failed to mount $mdev" return 1 fi } mount_callback_umount() { local img_in="$1" dev="" out="" mp="" ret="" img="" readonly=false local opts="" bmounts="" system_resolvconf=false ptnum=auto local cd_mountpoint=false fmt="" overlay=false rwmode="rw" local img_mp="" short_opts="Cdhm:P:psSv" long_opts="cd-mountpoint,dev,help,format:,mountpoint:,overlay,partition:,proc,read-only,sys,system-mounts,system-resolvconf,verbose" getopt_out=$(getopt -n "${0##*/}" \ -o "${short_opts}" -l "${long_opts}" -- "$@") && eval set -- "${getopt_out}" || { bad_Usage; return 1; } while [ $# -ne 0 ]; do cur=${1}; next=${2}; case "$cur" in -C|--cd-mountpoint) cd_mountpoint=true;; -d|--dev) bmounts="${bmounts:+${bmounts} }/dev";; --format) fmt=$next;; -h|--help) Usage ; exit 0;; -m|--mountpoint) mp=$next;; -P|--partition) ptnum=$next;; --overlay) overlay=true;; -p|--proc) bmounts="${bmounts:+${bmounts} }/proc";; -s|--sys) bmounts="${bmounts:+${bmounts} }/sys";; -S|--system-mounts) bmounts="/dev /proc /sys";; --system-resolvconf) system_resolvconf=true;; -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; --opts) opts="${opts} $next"; shift;; --read-only) readonly=true; rwmode="ro";; --) shift; break;; esac shift; done [ $# -ge 2 ] || { bad_Usage "must provide image and cmd"; return 1; } $readonly && { $system_resolvconf && ! $overlay; } && { error "--read-only is incompatible with system-resolvconf"; error "maybe try with --overlay" return 1; } img_in="$1" shift 1 if [ "${img_in#lxd:}" != "${img_in}" -a ! -f "${img_in}" ]; then error "${img_in}: lxd is no longer supported." return 1; fi img=$(readlink -f "$img_in") || { error "failed to get full path to $img_in"; return 1; } [ -f "$img" ] || { error "$img: not a file"; return 1; } [ "$(id -u)" = "0" ] || { error "sorry, must be root"; return 1; } trap cleanup EXIT TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || { error "failed to make tempdir"; return 1; } if [ -z "$mp" ]; then mp="${TEMP_D}/mp" mkdir "$mp" || return else [ -d "$mp" ] || { error "mountpoint '$mp': not a directory"; return 1; } mp=$(readlink -f "$mp") || { error "failed to get full path to provided mountpoint"; return 1; } fi if $overlay; then img_mp="${TEMP_D}/underlay" mkdir -p "$img_mp" || return else img_mp=$mp fi out="" if [ "$ptnum" = "auto" -o "$ptnum" = "0" ] && out=$(set -f; mount -o "loop,$rwmode" $opts "$img" "$img_mp" 2>&1); then debug 1 "mounted simple fs image $rwmode in '$img_in' at $img_mp" UMOUNTS[${#UMOUNTS[@]}]="$img_mp" else local hasqemu=false command -v "qemu-nbd" >/dev/null 2>&1 && hasqemu=true if ! $hasqemu; then error "simple mount of '$img_in' failed." error "if this is not a simple unpartitioned raw image, then" error "you must have qemu-nbd (apt-get install qemu-utils)" if [ -n "$out" ]; then error "mount failed with: $out" fi return 1 fi mount_nbd "$img" "$img_mp" "$fmt" "$ptnum" "$rwmode" "$opts" || return UMOUNTS[${#UMOUNTS[@]}]="$img_mp" fi if $overlay; then mount_overlay "$img_mp" "$mp" "${TEMP_D}/workdir" || { [ -n "${_ERR}" ] && error "${_ERR}" error "Unable to mount overlay filesystem. Maybe no kernel support?" return 1 } UMOUNTS[${#UMOUNTS[@]}]="$mp" fi local bindmp="" for bindmp in $bmounts; do [ -d "$mp${bindmp}" ] || mkdir "$mp${bindmp}" || { error "failed mkdir $bindmp in mount"; return 1; } mount --bind "$bindmp" "$mp${bindmp}" || { error "failed bind mount '$bindmp'"; return 1; } UMOUNTS[${#UMOUNTS[@]}]="$mp${bindmp}" debug 1 "mounted $bindmp to $mp${bindmp}" done if ${system_resolvconf}; then local rcf="$mp/etc/resolv.conf" debug 1 "replacing /etc/resolvconf" if [ -e "$rcf" -o -L "$rcf" ]; then local trcf="$rcf.${0##*/}.$$" rm -f "$trcf" && mv "$rcf" "$trcf" && ORIG_RESOLVCONF="$trcf" || { error "failed mv $rcf"; return 1; } fi cp "/etc/resolv.conf" "$rcf" || { error "failed copy /etc/resolv.conf"; return 1; } fi local cmd="" arg="" found=false cmd=( ) for arg in "$@"; do if [ "${arg}" = "_MOUNTPOINT_" ]; then debug 1 "replaced string _MOUNTPOINT_ in arguments arg ${#cmd[@]}" arg=$mp fi cmd[${#cmd[@]}]="$arg" done if [ "${cmd[0]##*/}" = "bash" -o "${cmd[0]##*/}" = "sh" ] && [ ${#cmd[@]} -eq 0 ]; then debug 1 "invoking shell ${cmd[0]}" error "MOUNTPOINT=$mp" fi add_helpers "$TEMP_D/bin" "$SUBUID" "$SUBGID" || { error "failed to add helpers to $TEMP_D"; return 1; } PATH="$TEMP_D/bin:$PATH" local startwd="$PWD" debug 1 "invoking: MOUNTPOINT=$mp" "${cmd[@]}" ${cd_mountpoint} && cd "$mp" MOUNTPOINT="$mp" "${cmd[@]}" ret=$? cd "$startwd" if ${system_resolvconf}; then local rcf="$mp/etc/resolv.conf" cmp -s "/etc/resolv.conf" "$rcf" >/dev/null || error "WARN: /etc/resolv.conf changed in image!" rm "$rcf" && { [ -z "$ORIG_RESOLVCONF" ] || mv "$ORIG_RESOLVCONF" "$rcf"; } || { error "failed to restore /etc/resolv.conf"; return 1; } fi debug 1 "cmd returned $ret. unmounting $mp" do_umounts "${UMOUNTS[@]}" && UMOUNTS=( ) || { error "failed umount $img"; return 1; } if [ -n "$QEMU_DISCONNECT" ]; then disconnect_qemu || return 1; fi return $ret } mount_callback_umount "$@" # vi: ts=4 noexpandtab