From 07dd1fb4e273313f4474c0944f30d824c2514790 Mon Sep 17 00:00:00 2001 From: Dominik Roth Date: Tue, 9 Jun 2020 18:09:13 +0200 Subject: [PATCH] started fuse implementation --- __pycache__/fuse.cpython-38.pyc | Bin 0 -> 30211 bytes fuse.py | 1257 +++++++++++++++++++++++++++++++ main.py | 480 +++++++++--- 3 files changed, 1630 insertions(+), 107 deletions(-) create mode 100644 __pycache__/fuse.cpython-38.pyc create mode 100644 fuse.py diff --git a/__pycache__/fuse.cpython-38.pyc b/__pycache__/fuse.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac2ee67197040f322994c8f5ca54758cb476f029 GIT binary patch literal 30211 zcmchA4Uinib>7bG@9q8Ia5(%BB*DcmaU^i~B}nm400A5b3Os-SNRTU1i@ojH!`|)f z9(v~Rv)-d5f)Z)RqG`ug5<9W)6uZi_%Zg>Ytdh%i%865PR3(=!rxMphu`5owT&yUL z|E7W3?CBA<{rnNLcb%BLhw=hG5r@)^YOYTsNo zpOtqK)&9AG{J`8`eo*d{)uFke{LtK*{F=Gp{IKMus%z&)@*|Q?SJ%yr=0~kqV;D7M zs_W-AhqR|B*5yO!Q2@IR1$z5wdQ5!(KQ|@-f< zY8^_g3ranNoKdwNIqQR*hn@7yo~6BN1K=HZ#^1Hw3ADITApLD8v9!<0s!g{nl-|E= z=MUiSo?C!m{;NoB4paTN@OJG+0IRK^ZVX5-59SX6pIZz*-?gej)y9*p535|wR$Dj39*v>B3}BF!1@4!eN7c4l)*DtW zD{zmaN5Gl%#nuhyBhF)L`z;$#G}I1^%Kd8RZ0g;({u{L`|2X=$Gyep_C-YB9_%y;@ z`Hvzznm>kcYyKI8JM+gS{{&h%iPQwHQ|4NF7V+t2V(K|ue6njfo&VT}mfW?R%AX0~ zjO)*<2c0L%w%Y9+LpX+7jv{^t@pi-yJ0}qCaXyN0uap~i#*p48;eH7ZNO;hB2Kk5Z z-d4ni5${C&NDvooEc)s>(00Udyg||9kxf>W(Y<+r=wZEIJ+34*@ zKw)Y9P4&3oeo}BhDY&1^6ZdBY_h$w7=LGi?g8Q@iyTCm#`XP8iS_7~Gp)61OiPkGN*sGa}PN((laGZ=uDf&|*pt2Q9LvY>9Nn z_-GIl^KA%P@I)ao5Rfuij2qq*^{Rv_dN7U3VWy)66GLGd>-} zq-@>;yAq!X;^%{SGKkLx@wp&=A&Ach@r5A1XyWM|5e&U37+8n@?6vz!nj{7ouTF<+WOlb&V=ctkt3hoa*6MeOVc}UAx#?jUTMJUnNWhGX`5#EK zjQ(aT7jxn@@b*s#KR641umjv+$Nw*05E+3^Y9oevsm)-%hpJ>AwZ?#SC%o9qfAs%? zDmeFVh*3tN*OxK&eR>Ai-!6P_JNx=!QswSA8>D}Eq7i&)jcd`$O}(Qu7Msg<8q0 z)NB5l;*?vjE_hC%GB;n>o}W>bYZVudCdVv)pj@e`LbWobi~8o6?WgC8r7M-1cZ($rzbD^X~cyS6KC*t`as} zz~$x;ylpSVmSc-{(_XTg*0N>A-nLLlY-6lxzhhm9y=P6LV~J9I{-&Q=m{:H?wW z<)WT;^L^!6=jQdgR&Kt(=3H;xychQqeC{WRM?Vwh$3ED*?@E2n*@vO5-`F?l-0(`( z`hwbbPSOMUl z6da4|btt7r7>ptqOLS%EjeNA8!CD3z7z`rVj?0zhiZz@}S^SfH_QG}i^d*dS{kmHv z`fh*n_9x@9eCqfsr!JlD#4w%;hL0k4??d3k@|KF_ZDl!eumPLlw2dVVhUdM6pDh)< z%ADiQJ0(BmT`RbV(}-(GOpYb}OrcOK&N+pGpDh&T>T03N^gy9-b)i^oJt-BgS89c7 zy*7>V<%&~PZo%|XIx2y5^j-k;EFrTIWGv1560VLPdw3L=5J(jKnS|*8V{{?OF6hlD zIhN8}5NVQ?&LQwqCGY0E<02NY^fo?e1Jeo7$M7(KXmwyd43}dGzpqqS05Yc*%6_Vd z2|R~k1Im~HD!V|GzON1!m!^|#xHRv3fa453%0dGO+L*$;v)OakD|(Y4$xhs?h=$#$ z;04wc3=Hq{YbRd3aI$db^fTv=oqwfp?%2g={nUw~##rZ*l~TQSEuSK79=?%J*WDXT zmN<5;UlNHvT*q;_RRqd<)n1G*CK}H*_It4ozEU`U^4N*|8m2Cty>#So;m|>({kZFCf8d1$ zN8fy@SY2>V-tY&4NqAE0x*kjB)4E>wz+Uoc1e^@{^ffap{5W#_IPN0o<`nP;&pXqH z4xVy6skBJkkXw+mB&<#N8?v%?#u~8n0pvE;TsS*%@zSyLCtpR|6YhLId%A{ktyWJ| zbYtyQrS>X@qwLP_-RX|2YBe9fapW+9$Br~MyjUwvRh^ty&rwd~ASXCPFhLYcOcbs; znv&#$Av30XpD&gskPm_xL(BRg0zW;W9p{+~C*I%7yOiq{lyl8bBL-nReg=tJwNjgv zN0nM#;)RNmcp5Q_YP^#lK%&@UHF7uCaVPVIY$!jBl2aA!U1>d+O594r@%#7)Ja^Xh zvyz;ZY=Tj)79kP{;j~lBvtZQ)n|365Rg!-4Or^GPh=GD9jht{(NGt zGVkWW?r?XYa3D+_3{!`K)WPuS!NS4d{X;?As_&8D{z&l7qv1P`;vLh(euDF;ZYBxqFg(Q%RPgj z&8XTENyR0daLD455~d_fJIR?8@-ji(XX5ET7~is(=KbWlWOUccuAeCvri#~!m8zdc zstnSVEJHAOSPmbSbvaXo9!-g*u$+3Rf0N zRch1k-_HVe`HG+A)GXAm*BsqD(adB@me8_Cb!LHa#>7VsuTDHKq4pncPcE5I(Av$P zOfHt_L2Wp>JUm^IdPq0mQ$i6$Z7M{qNfEUsMbw%UDaoV+$*Ldd9zb3ibq$&r(=Vfj zfbKP#fayyNUS{wLgFJ(eGay;(*AQU2Q-^}?%u>MT2g`y*?Nptj>*!CQ!249J^<@S{ z1e0TZT{F_q>J$qI!4ex0!O5$q@WFHH)X4h7ww7(Y3*zj`0g<+I z2$Q@Cf?XjO#JfT+_(ddlK;8#&NAiZ8H3B0@J(4>tC5M7ut?771ysQoIGTied#EZjc zrX-1H?Om4~=_xr9w6?D2NehnARiCWyd@{qaFi>&aHaP1LZgfTwZt5(FQej^NL-%w( z4@P5iXKn;H_jW#+iH?QvaXc3abB33bUD&#>^W8|T_jf+&>d%(oDeLbSs(PR&C)Ztr zjP=%zT+&DzOWQi0wa1kh+}`o5)uZU!J9>w^*B)%j$GRL*h1 z4y269gGg^wyUTId-CL=L!qmf(+JmQiyXx79Cme6-BTMmq8SHKP;ru;n9Bgf$zed1hXQEY_SE?GzOkRrwUMAXfe4+-$9`81XK(V3u4-J;&R$dvne#!L-9Q zsGpF#Wa$c)Xg(r{S3l!v*s;JP{Ul8kD1?@4eu7Q;Ns;1QvK76uD-y;}2jT>>+r3FE zn(KHs40r&_r248AH%RttXRdzDG5KlMEugV_CbB_oOlPD*rV^D}1#DP3SZUW`-z%0% zj!T;jUIV0kPYEI zQ5PUq&YgvU_2hX?(+Sm;shj8no^ure*)kgx=epU*9IT3wUq!y2<3q@!dbx}&(E_Lf zW04!+8gFDXNLEa$lh&dmRBa>qIOO%iK=HO)6_8 zzLQ9_^6i+7C!^MgH4>Cege5yuqrr3YhN+A1Qd0J`5VK<$v=X-1ao>hzrBuQ)Z>ECw z47{eMHblXjR>J(Dhj=e$f5(p7R_0rYY&T4%)|GmR@*$+rG<2OXABLM4vDRB_7P*0b z)WV-YBIQ4$ITjNo8)56mbR5@lTrLTzY3b2sYzC&YQCP)itc|e?j3uq?dtoQP04EhN zl=$TPv9JE<6MuqtV$RpTc5KYnv{-7Y+OP)N9_AJGsG1vDaBaXtf0+CLlo#58p8ynr zwL4)A+xn+aszj(-Kk9TzTmt3yAy^ueo{rmZJE+lx07w=5OEWxr}SlZ6DXD83vIl#fnqa-#9=RDJ7p%0 zD}f6uK%mBIJaFFe7Ie+c6?40xLhs2z|J{>=?mL$A7UruCdXeFN5=E0gECLElr=@#? zy-*3Sd9I#DYx)X<3W70gCzQM!^L`v*S0D8+qQDCwMF~TYypFDb7h#eH6qQ4QUGeOt z_%aN`Gs$IZF@`&gI}N-F`$A)OaSL9`=J30C1y-o*m1;HTURm(4Nm9#sSDYM;*SWd+ zLe0w+Vg4)?(J6U=9h)kxqAaj`e0&_8uS-Q*u(K!UR^}?zqGlPFf~+>3Guvm(Vb$}x zPDwSMmDeU{d6fJbi0)d@vWo(c>$xHxIeM;wXO0p`1XKe~v34^D!CYe#1V`#~t^von zYP~*>SL-?FM#amaA~syZ5-3*}TiXI;=gsllZY%?H9I5frcC1XavsjR?WS8Ieg6@3@R)+)oQC!F>d`jo|=t5Lg=)r$Nm@ZSH$B{JaEgP z@b;294@Qb@yO&i`rLYg3bW$p`V8lK{bRpYrOOF{qV>e z!To?*r$!MEs`Y9E;vuzBZ9=?8-J>=m9u9oq*3$b8_ao{7l|#HvZB^S4kE-oz2jcZ= zr`m;hgL+WyM!ZptsfQ46QV**=i0@H*)i~nKYM(?Eft72+aa}WiNE*DFV%x3pWeW40pKv==292ml<ZX~*hNB#F-39E)=8l~^oeOqpatFtc z897PiOf5{~M&YIpa9yDm?jpmT_yF2}dGBK- zO7GTh7>Nw7eq8QvgXEf+Zw1QL7`E~uObWJQPTa%XQ!qx{;*%tVOwxVKOD&~U!pq2v zOio*isb*@a?=1_H9_0CE2BoqmK%!=6f2m((AdUuR23Z~@hZM{lK`Ohn2Bn9~@Bny+ zb3Dk$Mj%qcMo8G1W{UMKro6Q?Bi_1Z>J~Ml)H|`ov^RPywwUooXVya#(Tjb}J}{oa z&tMY~T1j@XznNVeXbz|$XeNJ#c#X37Y;l17!o39zWw1F2%_Rf3*38x4^fom6)iB&N zE&Wil-`fc2Hlb{+nZ0G>&srSx?rCP4+2)|=AHUfc@-}<-diUM37l*KAxfxrQ_s?eW z_GPna>D{+DWHv6nElUp=*qdo@?NY7@LlEMvjFq*VTwG)7Y7X53o>13@u&$quZh$sp zmnrPyXyaEIzhO1kApKQuTXPLvdqh&MZG2tG1(Gs%!SOuG8eP1OoexTvi+aH^!qZ43 zk($V#2WJ$@7oiXufWw;1?0|6N1gYn+pBk)V_h5d&`J||F^>S`z!SzfnSFi~Qzkonw z5*C?c(~y^L?q+=|jy2Kv%0(!zl(DdZLinO< z^L5Nm$ZhBx+#gbW-mgt20Nrd9kpT&`@`l%Xl4R9L_NCZ-OO#n z^o91kEks(SMz}S^ajWY&gA_E$x8)(RHxC)_TwOq~=BPZef+r7;ivaHMv7yN0#N-Z| zyA44z_S$BUuC-{t9b4jFd2AVe%T4dpKm9`#LzJ|MSjrl>-4OjF6mf0Omhc5^_Pew%fRWTc zu=aF-PH9Qd*WnT!cACHsQ3+jwQ_!;YgZ5PjegTAVIkgg&!C0gGN4%ha3juO7l8Z5n zz?*%NHilAtZvN>XCBW1`QdloGwvtd+0yixGK7m3LKfos5Wk4f(J_C%1Z^+noKieKV zKV{~G{%sWU?u)R=i9y4GOuxD`?HXn7 z^L!y*b!xJHG%Nj|V@-p6kSSc(tW4`KGLMX1(+NX!=g7|*PX;PPq7UZH{?1$x?mLhY z*f*8wl&E3-tE}sH5#+O?TT6NUb>l2B+Y zh9$lH*+VoqSa!nxyF@x=Z;lUR>9h$R6=rdt#d;Rz4Qs$2K)#(Eur}C3whf;Pl(Q2S zNsIeM_O}yBlbb@WG50qHJ4miWZR6A>Y0}8!K8>J>2`ek&{bm9~hqXOO4M-A9T&#f~ zYg#gmGYWcb69)PXvEbgU<}WsqJKd*ty70#M8Pw-z;q^zyk6H<~yQzA;N;kM!ncZAy z`boH&z*VKanJP&71|Izq4G%j#3cAEh%u8ZD z5BD8BNvP!KlF$f{?#E2O6niE1h5f7trBezkK-fN7)o z_oo&EwcPL{3yF16XBagg{|fJ zfWQ#!4V1{MT0a8HOIm#v3AT!Lj0HDAn8%i3Ls%m3L25DKC6Ba#M_}0^}x< zn`*)?2`gg~D;V$!I-HIrCiPX+XSU?Q5a|d)fvEqQfe68`F|{3mKWg?eVB~{i_jCbW zDnqQL=bSlim+1eFGH_lEJFWjOUrM8xcq74U5h9bj+TDmC1Q{ZN8`3dr)PnIlxdDdy zfMU4h<2FzSPKcinC$#@`;{?5pFVjF{Pil?l@5Q@%_Fke5tDg`Tz&N%sx}oE{Bavas z$}q_$Ru5@}08wD|0z?uaI4+**+XkjKx-8(oyK$CWze3|!R`krDuyOifbvM3D*L*Zh ze-LR}{|!@JB9>1#MVjlEzM3t%e^`49cWjSO?~Sx)a4TDZJ-AJy>~UN!*=5i@I@$}_ zN2Y?{_=ArcTBUV3*iy`02IXOP<@P?EbFtPbIhS+0o$=}MT(y?l>jrr_Q)oFyACgU3E0 zLtz1U68@MDiT=(r?k0*FvIpnuX_HUK*(Rg{4W)(*WzTtao^t|4J)G=N*vGnM&A^%j zEpmg3FG4rwo>3%s4|2hum*NW68CZQ`ZCZxR2>4TSvdwPY1vY} z1NVw-VRTJs)^vb9GdlAKWF0O$>>>6SdZ^(pQ2HJ?$@dU2GO-Rpm%>_Zxgnh|o8df+^d^2NY{p)DY=;Px!(}6=TxuV%rqKM7y zRJmy+V3UPHU#vlk#cm=udUh)K+zGa1x8*Wx5xjkiDaw6+2xeZGmS8QA#kE=8{~ey- z%@B`9BS?rkkb-*vEK3?65D1+6zRh~zs^Rf@H>L^sDPo%3pO}W@8Zd3P=&>~pH#Tfe zW0QW!3NX(ubzq+6gAntN<3^5Ia25)_XKcM0*pDx!WfZWU1GZyJNg0I{cWRm`j6!-j zX(Z_oji1u-uP!KA6ek z^wKen*wb((PvE9Cjh0|Z#RS5El^Gl{LEjV2#8N`m_gk>OPr@3F6)dzwTW`beG*Ylj zgNCkdljD8yN&P9*)-jQ~=FrdZxma<&h!k+t+3`FMqg>NJ&qCq0a(ACUg{KbtY*fM& zltQlehwMO*8SkEs4tMa4hwy$dv20ANIEEB4CYE8tk%`sP$zH=7??no(Q|eruOX?;`n;UOeSp; z!>4e$`wFoNJ6KUt6TXG_0BRTQZbz=h=Sn)s_K%q> zl9-d$hfkQextXJpT{OUY-(q)+Od#22>8@kj-wD<;V0+?Q*0FO%xj_6&pQeyly-#J# zN%jLXHO@UF@Ts_axCzS)_2(;q>3vZ$& zu;?xmmGyxzJ4j&Jp5LHAuqqB+0h)rSU0fh1!QQY0 zRz%bUoOrkmFN*e~vDPz)U)z1D372gkFOG6$FGbTF5Sm%)BSQp2(@1AU#KPB@+kQYO zRJqRp7C-iZ6>!M@Zq)uaJlC?O4w0E#BVMxpXy|5Sqy+eUo9r!(V@Fu+67M7H`-T3WYpnMjkqAMm%%ZC=~=$axd z`HKXPBWT9IF)YADJTH5|35WYWywnX2=C?f)FCMfKpGOA~ahdK-_0wJR^?5PV* zR`O-SDeN3_H+wr14_0+l?6?Y-E}$HLRI)=TKZ~0HN*?E={*asjjLUKC=w89xg+}L5 z%0AYVPcX${u3tLqf}gebL?wn__@;>U2apsk8NCIz&3@y$k8KLAqgg-;3uE<-+c|y8ZwsVHb2P z{&hkpqb$%h)&ztq2UG|Q6_3`B=@1xpsT&yD`#}SNUF!Q#LhnGs(ba~&hmeR)*aZ%Y z|5t)z=y3?@Z|ZiyC0mZjJ!Tq$bR3tcQU)A_^T@ylR5=>xKd|)pVAf{F?z zttd|Id#qOzg(RnIYrXmo>M<-VW!9^I%?wUHqooQFU&d`z;nHG76yjM+9C!7Ib^bAX z)Y7ZDni$4HD$uKqX=nAmeYc9C9wHD_QagZL!`$}DuM5Z@0FeKOKn@|%(xOT1_U%As zh0gRHkfu?Z*Ku4fRVcH-Qx;;l=VGx#Ic;NMmw>{wqX{S8+6IhEQXj;4C2&VIH!+if zb8if;Kv*I{WfI1{90$Yh<071l)-Yi4SpaKAUt(q_J;#3bizLsdU>4{4_J8o{ZU(|- zWfgsyw{0HuCEordf}W+4!I3aed1oaK`YvAl7O^#o1gz&6P;ySQNaV7q#UNTc3nbRo zk3<`yY#cY}9<32vG!Y$U(L{7atr1)wCR*#@AH-W`grl~94lZ11=aiP`mszN5nE4Ie`sl*%{ox@%kW5a9o;jektp7Sm6aZ)apzQM!SjgH|P*W zCOd<9hPlbHOj9h&8W7x&t z;n~-9T*glq3aVa$AMEe5;jbX@@d*eFbjj>`>u)gs4;YA6G0)Uh2AYBF_jpV#FyK~! zrjcIXU_k4$n0$>%b&eCFkSeB)$tlVU^p+C#Kw-~(5fJ%!N%|f={ zDMhtNJu?4@2a!xnQo9yvA+91*R3&N$r@bQOhVczp^1(k7+dTpF846T zz0-eK#o_Uqpz8x-_z~mWzxWX2SiAYMXhuCN{=?Q?_z#~J4`K@*#LuZs>K^zcuTh)T zy{LIu-KXwHyjE>d4IdwyEt%uU9+NPQ)A3F7+T{oU>D7h&QQ+)WeAH zQG3*0#GBQ)+K2dFwO<`Te4jd~4k5l@9afJZ-lC4EM-e}u9#bDdoP+=G__yH39nz!BEn zs#?|eXsVs0&fy)Zc$IZi;l8z?kyJF(BoSQ?%Jo#^&GCLGn zQXsEWq{3|oAYZWMf6I19Jsj(pfwK?H?T|Lig98z)1c58Rv4MUWuZS&0|0Ppo4Z)5H zociZiG9l6JpRUw}!htq7h84+btZR=X+yzaU4iQN}F~Wu~Q({m_;cg8x&FtQdWMrPp z6eoK)!nQEYQ9)yEq#dK~PWCjzqQ`NGmd1k(GjJn@n{KQBSY3x*&nHiwKRa>WZd4pslA@ZHt>>#98ad^lEL1ziQ(Tg}QS13AqNI z$nedbz=zbF-0r|R5;TTS5$ypz(r&Nt+T8rpz#wc(ZFLZbD4dh*1tw}MRe-}+^dPR1(KwaWeq=hIK3e67WJgpzoY*g68 zHYDK9Wx*yj(q5v}Jhll%AMTQ+;g*5Jc_!b!)^%W$M z%ne8*5=WF;-kr8Z!h~00!d2%bU({HvI4wvki=jq0p2p1FsUUX^4G_fF)mll}zqzz>|6qvSb zCsACy_)3TIV%1ez*O+k8aV>{Nsp}m`;d6MbeWaar#iM=S{hBSmK4?%(SIT= zzpI5%Tas@I2NHv8iyHVh__^ag9#+VS$|pFUTrJ~>In)p8a*=6F?TP#~(sT>ZF4Zf- zbBPhbD`r!BAL>#o*PvX~bD9LcH^g>-2dNUUj`a%3b|qxz+9Gvj%`&n^Sc#lzVEB9p zM5ii6ARt&eHnfu{zZxGg+Vj3PL}s@MqORuQ_zqWRBGuqKAji$H8->p?^7F4D+wtQk zPoM2DD!2V+9Q7s_VOIY$!;O7F7 z*(M*~8gJO+K5ew2J+B}$>;>!-t(J6El2 zc|}Lg9+GWq-7D%CpGG4;8)+n}U$p#Nx`*_;ZG)DyFGku}e>OCvjJH1m)WhmREUu`* z=#LO@{NO`3iPDburATudI+_bVNwj)Z0f43xJ8e54h z7SPz2BaKDnlX3YLbrGfbWBM)-*2AfjuT&@ZRE�#@ zyDE^x`25Jf3)w)Mi{1TF1s{05(`p5ecOQx_SkBhk>w)0&uClUdec3fP$HU2xHAS2m z=IMU0po1ZVyYa=2%G|07?o>i@3+GCI%P}CeIAd)AJZXm+==QtmIt!ycp0yDPdvIZe=TgjlVOEE%H2e9 zSM9IJAGs6oJCWw1vo1Jm@u8dxmXgOWa%;&*irup5v?nKP+28{>_&!6~7@KfzJ6BqO z?UUA9yfoex^&Ps$!z&C8Hwb<&1h#W%TH90<^{wBX#D0ek+abGU2WOPcUq(gUzIX;ziRLxs)k$jC;HY|Bt8nmruZpdqt2n?U4?W=rQ&OmQxl zogmVQY!#ZFp!U*9QXBYmKlk z-GqNAgEtVyvq`D9gep6JB+8?>#0R^z7sIaw;xiBUhKs`IG_dQCQgm)f`U7X`)6>x8 zp0C_EU2DkM>~WlG+&fpf0gFkLz{%<SK|Ml zQC{Gx345Ysgqq{2S=RV5{u)!q*v(&I>OUjEC;sG91>@ivRZTVlQw_cVgY(04nk1lU zdFVdmpP;xAOOxN{X?&+EX=TO@;~a56QsXC^}<>BL$aN9Qa;HAz{!MI9Fz8z{{YnL$)A@?sb!vh#$ y-bXMgxQyHb&!hdIo}rO72nR-nMlwV3kx$0r*;IBIUK0bu$>C4N_;(cXk^cv#7-gma literal 0 HcmV?d00001 diff --git a/fuse.py b/fuse.py new file mode 100644 index 0000000..7c2ddc4 --- /dev/null +++ b/fuse.py @@ -0,0 +1,1257 @@ +# Copyright (c) 2012 Terence Honles (maintainer) +# Copyright (c) 2008 Giorgos Verigakis (author) +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from __future__ import print_function, absolute_import, division + +import ctypes +import errno +import logging +import os +import warnings + +from ctypes.util import find_library +from platform import machine, system +from signal import signal, SIGINT, SIG_DFL +from stat import S_IFDIR +from traceback import print_exc + + +try: + from functools import partial +except ImportError: + # http://docs.python.org/library/functools.html#functools.partial + def partial(func, *args, **keywords): + def newfunc(*fargs, **fkeywords): + newkeywords = keywords.copy() + newkeywords.update(fkeywords) + return func(*(args + fargs), **newkeywords) + + newfunc.func = func + newfunc.args = args + newfunc.keywords = keywords + return newfunc + +try: + basestring +except NameError: + basestring = str + +log = logging.getLogger("fuse") +_system = system() +_machine = machine() + +if _system == 'Windows': + # NOTE: + # + # sizeof(long)==4 on Windows 32-bit and 64-bit + # sizeof(long)==4 on Cygwin 32-bit and ==8 on Cygwin 64-bit + # + # We have to fix up c_long and c_ulong so that it matches the + # Cygwin (and UNIX) sizes when run on Windows. + import sys + if sys.maxsize > 0xffffffff: + c_win_long = ctypes.c_int64 + c_win_ulong = ctypes.c_uint64 + else: + c_win_long = ctypes.c_int32 + c_win_ulong = ctypes.c_uint32 + +if _system == 'Windows' or _system.startswith('CYGWIN'): + class c_timespec(ctypes.Structure): + _fields_ = [('tv_sec', c_win_long), ('tv_nsec', c_win_long)] +else: + class c_timespec(ctypes.Structure): + _fields_ = [('tv_sec', ctypes.c_long), ('tv_nsec', ctypes.c_long)] + +class c_utimbuf(ctypes.Structure): + _fields_ = [('actime', c_timespec), ('modtime', c_timespec)] + +class c_stat(ctypes.Structure): + pass # Platform dependent + +_libfuse_path = os.environ.get('FUSE_LIBRARY_PATH') +if not _libfuse_path: + if _system == 'Darwin': + # libfuse dependency + _libiconv = ctypes.CDLL(find_library('iconv'), ctypes.RTLD_GLOBAL) + + _libfuse_path = (find_library('fuse4x') or find_library('osxfuse') or + find_library('fuse')) + elif _system == 'Windows': + try: + import _winreg as reg + except ImportError: + import winreg as reg + def Reg32GetValue(rootkey, keyname, valname): + key, val = None, None + try: + key = reg.OpenKey(rootkey, keyname, 0, reg.KEY_READ | reg.KEY_WOW64_32KEY) + val = str(reg.QueryValueEx(key, valname)[0]) + except WindowsError: + pass + finally: + if key is not None: + reg.CloseKey(key) + return val + _libfuse_path = Reg32GetValue(reg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WinFsp", r"InstallDir") + if _libfuse_path: + _libfuse_path += r"bin\winfsp-%s.dll" % ("x64" if sys.maxsize > 0xffffffff else "x86") + else: + _libfuse_path = find_library('fuse') + +if not _libfuse_path: + raise EnvironmentError('Unable to find libfuse') +else: + _libfuse = ctypes.CDLL(_libfuse_path) + +if _system == 'Darwin' and hasattr(_libfuse, 'macfuse_version'): + _system = 'Darwin-MacFuse' + + +if _system in ('Darwin', 'Darwin-MacFuse', 'FreeBSD'): + ENOTSUP = 45 + + c_dev_t = ctypes.c_int32 + c_fsblkcnt_t = ctypes.c_ulong + c_fsfilcnt_t = ctypes.c_ulong + c_gid_t = ctypes.c_uint32 + c_mode_t = ctypes.c_uint16 + c_off_t = ctypes.c_int64 + c_pid_t = ctypes.c_int32 + c_uid_t = ctypes.c_uint32 + setxattr_t = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p, + ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int, + ctypes.c_uint32) + getxattr_t = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p, + ctypes.POINTER(ctypes.c_byte), + ctypes.c_size_t, ctypes.c_uint32) + if _system == 'Darwin': + c_stat._fields_ = [ + ('st_dev', c_dev_t), + ('st_mode', c_mode_t), + ('st_nlink', ctypes.c_uint16), + ('st_ino', ctypes.c_uint64), + ('st_uid', c_uid_t), + ('st_gid', c_gid_t), + ('st_rdev', c_dev_t), + ('st_atimespec', c_timespec), + ('st_mtimespec', c_timespec), + ('st_ctimespec', c_timespec), + ('st_birthtimespec', c_timespec), + ('st_size', c_off_t), + ('st_blocks', ctypes.c_int64), + ('st_blksize', ctypes.c_int32), + ('st_flags', ctypes.c_int32), + ('st_gen', ctypes.c_int32), + ('st_lspare', ctypes.c_int32), + ('st_qspare', ctypes.c_int64)] + else: + c_stat._fields_ = [ + ('st_dev', c_dev_t), + ('st_ino', ctypes.c_uint32), + ('st_mode', c_mode_t), + ('st_nlink', ctypes.c_uint16), + ('st_uid', c_uid_t), + ('st_gid', c_gid_t), + ('st_rdev', c_dev_t), + ('st_atimespec', c_timespec), + ('st_mtimespec', c_timespec), + ('st_ctimespec', c_timespec), + ('st_size', c_off_t), + ('st_blocks', ctypes.c_int64), + ('st_blksize', ctypes.c_int32)] +elif _system == 'Linux': + ENOTSUP = 95 + + c_dev_t = ctypes.c_ulonglong + c_fsblkcnt_t = ctypes.c_ulonglong + c_fsfilcnt_t = ctypes.c_ulonglong + c_gid_t = ctypes.c_uint + c_mode_t = ctypes.c_uint + c_off_t = ctypes.c_longlong + c_pid_t = ctypes.c_int + c_uid_t = ctypes.c_uint + setxattr_t = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p, + ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int) + + getxattr_t = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p, + ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t) + + if _machine == 'x86_64': + c_stat._fields_ = [ + ('st_dev', c_dev_t), + ('st_ino', ctypes.c_ulong), + ('st_nlink', ctypes.c_ulong), + ('st_mode', c_mode_t), + ('st_uid', c_uid_t), + ('st_gid', c_gid_t), + ('__pad0', ctypes.c_int), + ('st_rdev', c_dev_t), + ('st_size', c_off_t), + ('st_blksize', ctypes.c_long), + ('st_blocks', ctypes.c_long), + ('st_atimespec', c_timespec), + ('st_mtimespec', c_timespec), + ('st_ctimespec', c_timespec)] + elif _machine == 'mips': + c_stat._fields_ = [ + ('st_dev', c_dev_t), + ('__pad1_1', ctypes.c_ulong), + ('__pad1_2', ctypes.c_ulong), + ('__pad1_3', ctypes.c_ulong), + ('st_ino', ctypes.c_ulong), + ('st_mode', c_mode_t), + ('st_nlink', ctypes.c_ulong), + ('st_uid', c_uid_t), + ('st_gid', c_gid_t), + ('st_rdev', c_dev_t), + ('__pad2_1', ctypes.c_ulong), + ('__pad2_2', ctypes.c_ulong), + ('st_size', c_off_t), + ('__pad3', ctypes.c_ulong), + ('st_atimespec', c_timespec), + ('__pad4', ctypes.c_ulong), + ('st_mtimespec', c_timespec), + ('__pad5', ctypes.c_ulong), + ('st_ctimespec', c_timespec), + ('__pad6', ctypes.c_ulong), + ('st_blksize', ctypes.c_long), + ('st_blocks', ctypes.c_long), + ('__pad7_1', ctypes.c_ulong), + ('__pad7_2', ctypes.c_ulong), + ('__pad7_3', ctypes.c_ulong), + ('__pad7_4', ctypes.c_ulong), + ('__pad7_5', ctypes.c_ulong), + ('__pad7_6', ctypes.c_ulong), + ('__pad7_7', ctypes.c_ulong), + ('__pad7_8', ctypes.c_ulong), + ('__pad7_9', ctypes.c_ulong), + ('__pad7_10', ctypes.c_ulong), + ('__pad7_11', ctypes.c_ulong), + ('__pad7_12', ctypes.c_ulong), + ('__pad7_13', ctypes.c_ulong), + ('__pad7_14', ctypes.c_ulong)] + elif _machine == 'ppc': + c_stat._fields_ = [ + ('st_dev', c_dev_t), + ('st_ino', ctypes.c_ulonglong), + ('st_mode', c_mode_t), + ('st_nlink', ctypes.c_uint), + ('st_uid', c_uid_t), + ('st_gid', c_gid_t), + ('st_rdev', c_dev_t), + ('__pad2', ctypes.c_ushort), + ('st_size', c_off_t), + ('st_blksize', ctypes.c_long), + ('st_blocks', ctypes.c_longlong), + ('st_atimespec', c_timespec), + ('st_mtimespec', c_timespec), + ('st_ctimespec', c_timespec)] + elif _machine == 'ppc64' or _machine == 'ppc64le': + c_stat._fields_ = [ + ('st_dev', c_dev_t), + ('st_ino', ctypes.c_ulong), + ('st_nlink', ctypes.c_ulong), + ('st_mode', c_mode_t), + ('st_uid', c_uid_t), + ('st_gid', c_gid_t), + ('__pad', ctypes.c_uint), + ('st_rdev', c_dev_t), + ('st_size', c_off_t), + ('st_blksize', ctypes.c_long), + ('st_blocks', ctypes.c_long), + ('st_atimespec', c_timespec), + ('st_mtimespec', c_timespec), + ('st_ctimespec', c_timespec)] + elif _machine == 'aarch64': + c_stat._fields_ = [ + ('st_dev', c_dev_t), + ('st_ino', ctypes.c_ulong), + ('st_mode', c_mode_t), + ('st_nlink', ctypes.c_uint), + ('st_uid', c_uid_t), + ('st_gid', c_gid_t), + ('st_rdev', c_dev_t), + ('__pad1', ctypes.c_ulong), + ('st_size', c_off_t), + ('st_blksize', ctypes.c_int), + ('__pad2', ctypes.c_int), + ('st_blocks', ctypes.c_long), + ('st_atimespec', c_timespec), + ('st_mtimespec', c_timespec), + ('st_ctimespec', c_timespec)] + else: + # i686, use as fallback for everything else + c_stat._fields_ = [ + ('st_dev', c_dev_t), + ('__pad1', ctypes.c_ushort), + ('__st_ino', ctypes.c_ulong), + ('st_mode', c_mode_t), + ('st_nlink', ctypes.c_uint), + ('st_uid', c_uid_t), + ('st_gid', c_gid_t), + ('st_rdev', c_dev_t), + ('__pad2', ctypes.c_ushort), + ('st_size', c_off_t), + ('st_blksize', ctypes.c_long), + ('st_blocks', ctypes.c_longlong), + ('st_atimespec', c_timespec), + ('st_mtimespec', c_timespec), + ('st_ctimespec', c_timespec), + ('st_ino', ctypes.c_ulonglong)] +elif _system == 'Windows' or _system.startswith('CYGWIN'): + ENOTSUP = 129 if _system == 'Windows' else 134 + c_dev_t = ctypes.c_uint + c_fsblkcnt_t = c_win_ulong + c_fsfilcnt_t = c_win_ulong + c_gid_t = ctypes.c_uint + c_mode_t = ctypes.c_uint + c_off_t = ctypes.c_longlong + c_pid_t = ctypes.c_int + c_uid_t = ctypes.c_uint + setxattr_t = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p, + ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int) + getxattr_t = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p, + ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t) + c_stat._fields_ = [ + ('st_dev', c_dev_t), + ('st_ino', ctypes.c_ulonglong), + ('st_mode', c_mode_t), + ('st_nlink', ctypes.c_ushort), + ('st_uid', c_uid_t), + ('st_gid', c_gid_t), + ('st_rdev', c_dev_t), + ('st_size', c_off_t), + ('st_atimespec', c_timespec), + ('st_mtimespec', c_timespec), + ('st_ctimespec', c_timespec), + ('st_blksize', ctypes.c_int), + ('st_blocks', ctypes.c_longlong), + ('st_birthtimespec', c_timespec)] +else: + raise NotImplementedError('%s is not supported.' % _system) + + +if _system == 'FreeBSD': + c_fsblkcnt_t = ctypes.c_uint64 + c_fsfilcnt_t = ctypes.c_uint64 + setxattr_t = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p, + ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int) + + getxattr_t = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p, + ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t) + + class c_statvfs(ctypes.Structure): + _fields_ = [ + ('f_bavail', c_fsblkcnt_t), + ('f_bfree', c_fsblkcnt_t), + ('f_blocks', c_fsblkcnt_t), + ('f_favail', c_fsfilcnt_t), + ('f_ffree', c_fsfilcnt_t), + ('f_files', c_fsfilcnt_t), + ('f_bsize', ctypes.c_ulong), + ('f_flag', ctypes.c_ulong), + ('f_frsize', ctypes.c_ulong)] +elif _system == 'Windows' or _system.startswith('CYGWIN'): + class c_statvfs(ctypes.Structure): + _fields_ = [ + ('f_bsize', c_win_ulong), + ('f_frsize', c_win_ulong), + ('f_blocks', c_fsblkcnt_t), + ('f_bfree', c_fsblkcnt_t), + ('f_bavail', c_fsblkcnt_t), + ('f_files', c_fsfilcnt_t), + ('f_ffree', c_fsfilcnt_t), + ('f_favail', c_fsfilcnt_t), + ('f_fsid', c_win_ulong), + ('f_flag', c_win_ulong), + ('f_namemax', c_win_ulong)] +else: + class c_statvfs(ctypes.Structure): + _fields_ = [ + ('f_bsize', ctypes.c_ulong), + ('f_frsize', ctypes.c_ulong), + ('f_blocks', c_fsblkcnt_t), + ('f_bfree', c_fsblkcnt_t), + ('f_bavail', c_fsblkcnt_t), + ('f_files', c_fsfilcnt_t), + ('f_ffree', c_fsfilcnt_t), + ('f_favail', c_fsfilcnt_t), + ('f_fsid', ctypes.c_ulong), + # ('unused', ctypes.c_int), + ('f_flag', ctypes.c_ulong), + ('f_namemax', ctypes.c_ulong)] + +if _system == 'Windows' or _system.startswith('CYGWIN'): + class fuse_file_info(ctypes.Structure): + _fields_ = [ + ('flags', ctypes.c_int), + ('fh_old', ctypes.c_int), + ('writepage', ctypes.c_int), + ('direct_io', ctypes.c_uint, 1), + ('keep_cache', ctypes.c_uint, 1), + ('flush', ctypes.c_uint, 1), + ('padding', ctypes.c_uint, 29), + ('fh', ctypes.c_uint64), + ('lock_owner', ctypes.c_uint64)] +else: + class fuse_file_info(ctypes.Structure): + _fields_ = [ + ('flags', ctypes.c_int), + ('fh_old', ctypes.c_ulong), + ('writepage', ctypes.c_int), + ('direct_io', ctypes.c_uint, 1), + ('keep_cache', ctypes.c_uint, 1), + ('flush', ctypes.c_uint, 1), + ('nonseekable', ctypes.c_uint, 1), + ('flock_release', ctypes.c_uint, 1), + ('padding', ctypes.c_uint, 27), + ('fh', ctypes.c_uint64), + ('lock_owner', ctypes.c_uint64)] + +class fuse_context(ctypes.Structure): + _fields_ = [ + ('fuse', ctypes.c_voidp), + ('uid', c_uid_t), + ('gid', c_gid_t), + ('pid', c_pid_t), + ('private_data', ctypes.c_voidp)] + +_libfuse.fuse_get_context.restype = ctypes.POINTER(fuse_context) + + +class fuse_operations(ctypes.Structure): + _fields_ = [ + ('getattr', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(c_stat))), + + ('readlink', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(ctypes.c_byte), + ctypes.c_size_t)), + + ('getdir', ctypes.c_voidp), # Deprecated, use readdir + + ('mknod', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, c_mode_t, c_dev_t)), + + ('mkdir', ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p, c_mode_t)), + ('unlink', ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p)), + ('rmdir', ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p)), + + ('symlink', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p)), + + ('rename', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p)), + + ('link', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p)), + + ('chmod', ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p, c_mode_t)), + + ('chown', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, c_uid_t, c_gid_t)), + + ('truncate', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, c_off_t)), + + ('utime', ctypes.c_voidp), # Deprecated, use utimens + ('open', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(fuse_file_info))), + + ('read', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(ctypes.c_byte), + ctypes.c_size_t, c_off_t, ctypes.POINTER(fuse_file_info))), + + ('write', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(ctypes.c_byte), + ctypes.c_size_t, c_off_t, ctypes.POINTER(fuse_file_info))), + + ('statfs', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(c_statvfs))), + + ('flush', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(fuse_file_info))), + + ('release', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(fuse_file_info))), + + ('fsync', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_int, + ctypes.POINTER(fuse_file_info))), + + ('setxattr', setxattr_t), + ('getxattr', getxattr_t), + + ('listxattr', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(ctypes.c_byte), + ctypes.c_size_t)), + + ('removexattr', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p)), + + ('opendir', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(fuse_file_info))), + + ('readdir', ctypes.CFUNCTYPE( + ctypes.c_int, + ctypes.c_char_p, + ctypes.c_voidp, + ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_voidp, ctypes.c_char_p, + ctypes.POINTER(c_stat), c_off_t), + c_off_t, + ctypes.POINTER(fuse_file_info))), + + ('releasedir', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(fuse_file_info))), + + ('fsyncdir', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_int, + ctypes.POINTER(fuse_file_info))), + + ('init', ctypes.CFUNCTYPE(ctypes.c_voidp, ctypes.c_voidp)), + ('destroy', ctypes.CFUNCTYPE(ctypes.c_voidp, ctypes.c_voidp)), + + ('access', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_int)), + + ('create', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, c_mode_t, + ctypes.POINTER(fuse_file_info))), + + ('ftruncate', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, c_off_t, + ctypes.POINTER(fuse_file_info))), + + ('fgetattr', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(c_stat), + ctypes.POINTER(fuse_file_info))), + + ('lock', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(fuse_file_info), + ctypes.c_int, ctypes.c_voidp)), + + ('utimens', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(c_utimbuf))), + + ('bmap', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_size_t, + ctypes.POINTER(ctypes.c_ulonglong))), + + ('flag_nullpath_ok', ctypes.c_uint, 1), + ('flag_nopath', ctypes.c_uint, 1), + ('flag_utime_omit_ok', ctypes.c_uint, 1), + ('flag_reserved', ctypes.c_uint, 29), + + ('ioctl', ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_char_p, ctypes.c_uint, ctypes.c_void_p, + ctypes.POINTER(fuse_file_info), ctypes.c_uint, ctypes.c_void_p)), + ] + + +def time_of_timespec(ts, use_ns=False): + if use_ns: + return ts.tv_sec * 10 ** 9 + ts.tv_nsec + else: + return ts.tv_sec + ts.tv_nsec / 1E9 + +def set_st_attrs(st, attrs, use_ns=False): + for key, val in attrs.items(): + if key in ('st_atime', 'st_mtime', 'st_ctime', 'st_birthtime'): + timespec = getattr(st, key + 'spec', None) + if timespec is None: + continue + + if use_ns: + timespec.tv_sec, timespec.tv_nsec = divmod(int(val), 10 ** 9) + else: + timespec.tv_sec = int(val) + timespec.tv_nsec = int((val - timespec.tv_sec) * 1E9) + elif hasattr(st, key): + setattr(st, key, val) + + +def fuse_get_context(): + 'Returns a (uid, gid, pid) tuple' + + ctxp = _libfuse.fuse_get_context() + ctx = ctxp.contents + return ctx.uid, ctx.gid, ctx.pid + + +def fuse_exit(): + ''' + This will shutdown the FUSE mount and cause the call to FUSE(...) to + return, similar to sending SIGINT to the process. + + Flags the native FUSE session as terminated and will cause any running FUSE + event loops to exit on the next opportunity. (see fuse.c::fuse_exit) + ''' + fuse_ptr = ctypes.c_void_p(_libfuse.fuse_get_context().contents.fuse) + _libfuse.fuse_exit(fuse_ptr) + + +class FuseOSError(OSError): + def __init__(self, errno): + super(FuseOSError, self).__init__(errno, os.strerror(errno)) + + +class FUSE(object): + ''' + This class is the lower level interface and should not be subclassed under + normal use. Its methods are called by fuse. + + Assumes API version 2.6 or later. + ''' + + OPTIONS = ( + ('foreground', '-f'), + ('debug', '-d'), + ('nothreads', '-s'), + ) + + def __init__(self, operations, mountpoint, raw_fi=False, encoding='utf-8', + **kwargs): + + ''' + Setting raw_fi to True will cause FUSE to pass the fuse_file_info + class as is to Operations, instead of just the fh field. + + This gives you access to direct_io, keep_cache, etc. + ''' + + self.operations = operations + self.raw_fi = raw_fi + self.encoding = encoding + self.__critical_exception = None + + self.use_ns = getattr(operations, 'use_ns', False) + if not self.use_ns: + warnings.warn( + 'Time as floating point seconds for utimens is deprecated!\n' + 'To enable time as nanoseconds set the property "use_ns" to ' + 'True in your operations class or set your fusepy ' + 'requirements to <4.', + DeprecationWarning) + + args = ['fuse'] + + args.extend(flag for arg, flag in self.OPTIONS + if kwargs.pop(arg, False)) + + kwargs.setdefault('fsname', operations.__class__.__name__) + args.append('-o') + args.append(','.join(self._normalize_fuse_options(**kwargs))) + args.append(mountpoint) + + args = [arg.encode(encoding) for arg in args] + argv = (ctypes.c_char_p * len(args))(*args) + + fuse_ops = fuse_operations() + for ent in fuse_operations._fields_: + name, prototype = ent[:2] + + check_name = name + + # ftruncate()/fgetattr() are implemented in terms of their + # non-f-prefixed versions in the operations object + if check_name in ["ftruncate", "fgetattr"]: + check_name = check_name[1:] + + val = getattr(operations, check_name, None) + if val is None: + continue + + # Function pointer members are tested for using the + # getattr(operations, name) above but are dynamically + # invoked using self.operations(name) + if hasattr(prototype, 'argtypes'): + val = prototype(partial(self._wrapper, getattr(self, name))) + + setattr(fuse_ops, name, val) + + try: + old_handler = signal(SIGINT, SIG_DFL) + except ValueError: + old_handler = SIG_DFL + + err = _libfuse.fuse_main_real( + len(args), argv, ctypes.pointer(fuse_ops), + ctypes.sizeof(fuse_ops), + None) + + try: + signal(SIGINT, old_handler) + except ValueError: + pass + + del self.operations # Invoke the destructor + if self.__critical_exception: + raise self.__critical_exception + if err: + raise RuntimeError(err) + + @staticmethod + def _normalize_fuse_options(**kargs): + for key, value in kargs.items(): + if isinstance(value, bool): + if value is True: + yield key + else: + yield '%s=%s' % (key, value) + + @staticmethod + def _wrapper(func, *args, **kwargs): + 'Decorator for the methods that follow' + + try: + if func.__name__ == "init": + # init may not fail, as its return code is just stored as + # private_data field of struct fuse_context + return func(*args, **kwargs) or 0 + + else: + try: + return func(*args, **kwargs) or 0 + + except OSError as e: + if e.errno > 0: + log.debug( + "FUSE operation %s raised a %s, returning errno %s.", + func.__name__, type(e), e.errno, exc_info=True) + return -e.errno + else: + log.error( + "FUSE operation %s raised an OSError with negative " + "errno %s, returning errno.EINVAL.", + func.__name__, e.errno, exc_info=True) + return -errno.EINVAL + + except Exception: + log.error("Uncaught exception from FUSE operation %s, " + "returning errno.EINVAL.", + func.__name__, exc_info=True) + return -errno.EINVAL + + except BaseException as e: + self.__critical_exception = e + log.critical( + "Uncaught critical exception from FUSE operation %s, aborting.", + func.__name__, exc_info=True) + # the raised exception (even SystemExit) will be caught by FUSE + # potentially causing SIGSEGV, so tell system to stop/interrupt FUSE + fuse_exit() + return -errno.EFAULT + + def _decode_optional_path(self, path): + # NB: this method is intended for fuse operations that + # allow the path argument to be NULL, + # *not* as a generic path decoding method + if path is None: + return None + return path.decode(self.encoding) + + def getattr(self, path, buf): + return self.fgetattr(path, buf, None) + + def readlink(self, path, buf, bufsize): + ret = self.operations('readlink', path.decode(self.encoding)) \ + .encode(self.encoding) + + # copies a string into the given buffer + # (null terminated and truncated if necessary) + data = ctypes.create_string_buffer(ret[:bufsize - 1]) + ctypes.memmove(buf, data, len(data)) + return 0 + + def mknod(self, path, mode, dev): + return self.operations('mknod', path.decode(self.encoding), mode, dev) + + def mkdir(self, path, mode): + return self.operations('mkdir', path.decode(self.encoding), mode) + + def unlink(self, path): + return self.operations('unlink', path.decode(self.encoding)) + + def rmdir(self, path): + return self.operations('rmdir', path.decode(self.encoding)) + + def symlink(self, source, target): + 'creates a symlink `target -> source` (e.g. ln -s source target)' + + return self.operations('symlink', target.decode(self.encoding), + source.decode(self.encoding)) + + def rename(self, old, new): + return self.operations('rename', old.decode(self.encoding), + new.decode(self.encoding)) + + def link(self, source, target): + 'creates a hard link `target -> source` (e.g. ln source target)' + + return self.operations('link', target.decode(self.encoding), + source.decode(self.encoding)) + + def chmod(self, path, mode): + return self.operations('chmod', path.decode(self.encoding), mode) + + def chown(self, path, uid, gid): + # Check if any of the arguments is a -1 that has overflowed + if c_uid_t(uid + 1).value == 0: + uid = -1 + if c_gid_t(gid + 1).value == 0: + gid = -1 + + return self.operations('chown', path.decode(self.encoding), uid, gid) + + def truncate(self, path, length): + return self.operations('truncate', path.decode(self.encoding), length) + + def open(self, path, fip): + fi = fip.contents + if self.raw_fi: + return self.operations('open', path.decode(self.encoding), fi) + else: + fi.fh = self.operations('open', path.decode(self.encoding), + fi.flags) + + return 0 + + def read(self, path, buf, size, offset, fip): + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + ret = self.operations('read', self._decode_optional_path(path), size, + offset, fh) + + if not ret: + return 0 + + retsize = len(ret) + assert retsize <= size, \ + 'actual amount read %d greater than expected %d' % (retsize, size) + + ctypes.memmove(buf, ret, retsize) + return retsize + + def write(self, path, buf, size, offset, fip): + data = ctypes.string_at(buf, size) + + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('write', self._decode_optional_path(path), data, + offset, fh) + + def statfs(self, path, buf): + stv = buf.contents + attrs = self.operations('statfs', path.decode(self.encoding)) + for key, val in attrs.items(): + if hasattr(stv, key): + setattr(stv, key, val) + + return 0 + + def flush(self, path, fip): + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('flush', self._decode_optional_path(path), fh) + + def release(self, path, fip): + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('release', self._decode_optional_path(path), fh) + + def fsync(self, path, datasync, fip): + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('fsync', self._decode_optional_path(path), datasync, + fh) + + def setxattr(self, path, name, value, size, options, *args): + return self.operations('setxattr', path.decode(self.encoding), + name.decode(self.encoding), + ctypes.string_at(value, size), options, *args) + + def getxattr(self, path, name, value, size, *args): + ret = self.operations('getxattr', path.decode(self.encoding), + name.decode(self.encoding), *args) + + retsize = len(ret) + # allow size queries + if not value: + return retsize + + # do not truncate + if retsize > size: + return -errno.ERANGE + + # Does not add trailing 0 + buf = ctypes.create_string_buffer(ret, retsize) + ctypes.memmove(value, buf, retsize) + + return retsize + + def listxattr(self, path, namebuf, size): + attrs = self.operations('listxattr', path.decode(self.encoding)) or '' + ret = '\x00'.join(attrs).encode(self.encoding) + if len(ret) > 0: + ret += '\x00'.encode(self.encoding) + + retsize = len(ret) + # allow size queries + if not namebuf: + return retsize + + # do not truncate + if retsize > size: + return -errno.ERANGE + + buf = ctypes.create_string_buffer(ret, retsize) + ctypes.memmove(namebuf, buf, retsize) + + return retsize + + def removexattr(self, path, name): + return self.operations('removexattr', path.decode(self.encoding), + name.decode(self.encoding)) + + def opendir(self, path, fip): + # Ignore raw_fi + fip.contents.fh = self.operations('opendir', + path.decode(self.encoding)) + + return 0 + + def readdir(self, path, buf, filler, offset, fip): + # Ignore raw_fi + for item in self.operations('readdir', self._decode_optional_path(path), + fip.contents.fh): + + if isinstance(item, basestring): + name, st, offset = item, None, 0 + else: + name, attrs, offset = item + if attrs: + st = c_stat() + set_st_attrs(st, attrs, use_ns=self.use_ns) + else: + st = None + + if filler(buf, name.encode(self.encoding), st, offset) != 0: + break + + return 0 + + def releasedir(self, path, fip): + # Ignore raw_fi + return self.operations('releasedir', self._decode_optional_path(path), + fip.contents.fh) + + def fsyncdir(self, path, datasync, fip): + # Ignore raw_fi + return self.operations('fsyncdir', self._decode_optional_path(path), + datasync, fip.contents.fh) + + def init(self, conn): + return self.operations('init', '/') + + def destroy(self, private_data): + return self.operations('destroy', '/') + + def access(self, path, amode): + return self.operations('access', path.decode(self.encoding), amode) + + def create(self, path, mode, fip): + fi = fip.contents + path = path.decode(self.encoding) + + if self.raw_fi: + return self.operations('create', path, mode, fi) + else: + fi.fh = self.operations('create', path, mode) + return 0 + + def ftruncate(self, path, length, fip): + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('truncate', self._decode_optional_path(path), + length, fh) + + def fgetattr(self, path, buf, fip): + ctypes.memset(buf, 0, ctypes.sizeof(c_stat)) + + st = buf.contents + if not fip: + fh = fip + elif self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + attrs = self.operations('getattr', self._decode_optional_path(path), fh) + set_st_attrs(st, attrs, use_ns=self.use_ns) + return 0 + + def lock(self, path, fip, cmd, lock): + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('lock', self._decode_optional_path(path), fh, cmd, + lock) + + def utimens(self, path, buf): + if buf: + atime = time_of_timespec(buf.contents.actime, use_ns=self.use_ns) + mtime = time_of_timespec(buf.contents.modtime, use_ns=self.use_ns) + times = (atime, mtime) + else: + times = None + + return self.operations('utimens', path.decode(self.encoding), times) + + def bmap(self, path, blocksize, idx): + return self.operations('bmap', path.decode(self.encoding), blocksize, + idx) + + def ioctl(self, path, cmd, arg, fip, flags, data): + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('ioctl', path.decode(self.encoding), + cmd, arg, fh, flags, data) + +class Operations(object): + ''' + This class should be subclassed and passed as an argument to FUSE on + initialization. All operations should raise a FuseOSError exception on + error. + + When in doubt of what an operation should do, check the FUSE header file + or the corresponding system call man page. + ''' + + def __call__(self, op, *args): + if not hasattr(self, op): + raise FuseOSError(errno.EFAULT) + return getattr(self, op)(*args) + + def access(self, path, amode): + return 0 + + bmap = None + + def chmod(self, path, mode): + raise FuseOSError(errno.EROFS) + + def chown(self, path, uid, gid): + raise FuseOSError(errno.EROFS) + + def create(self, path, mode, fi=None): + ''' + When raw_fi is False (default case), fi is None and create should + return a numerical file handle. + + When raw_fi is True the file handle should be set directly by create + and return 0. + ''' + + raise FuseOSError(errno.EROFS) + + def destroy(self, path): + 'Called on filesystem destruction. Path is always /' + + pass + + def flush(self, path, fh): + return 0 + + def fsync(self, path, datasync, fh): + return 0 + + def fsyncdir(self, path, datasync, fh): + return 0 + + def getattr(self, path, fh=None): + ''' + Returns a dictionary with keys identical to the stat C structure of + stat(2). + + st_atime, st_mtime and st_ctime should be floats. + + NOTE: There is an incompatibility between Linux and Mac OS X + concerning st_nlink of directories. Mac OS X counts all files inside + the directory, while Linux counts only the subdirectories. + ''' + + if path != '/': + raise FuseOSError(errno.ENOENT) + return dict(st_mode=(S_IFDIR | 0o755), st_nlink=2) + + def getxattr(self, path, name, position=0): + raise FuseOSError(ENOTSUP) + + def init(self, path): + ''' + Called on filesystem initialization. (Path is always /) + + Use it instead of __init__ if you start threads on initialization. + ''' + + pass + + def ioctl(self, path, cmd, arg, fip, flags, data): + raise FuseOSError(errno.ENOTTY) + + def link(self, target, source): + 'creates a hard link `target -> source` (e.g. ln source target)' + + raise FuseOSError(errno.EROFS) + + def listxattr(self, path): + return [] + + lock = None + + def mkdir(self, path, mode): + raise FuseOSError(errno.EROFS) + + def mknod(self, path, mode, dev): + raise FuseOSError(errno.EROFS) + + def open(self, path, flags): + ''' + When raw_fi is False (default case), open should return a numerical + file handle. + + When raw_fi is True the signature of open becomes: + open(self, path, fi) + + and the file handle should be set directly. + ''' + + return 0 + + def opendir(self, path): + 'Returns a numerical file handle.' + + return 0 + + def read(self, path, size, offset, fh): + 'Returns a string containing the data requested.' + + raise FuseOSError(errno.EIO) + + def readdir(self, path, fh): + ''' + Can return either a list of names, or a list of (name, attrs, offset) + tuples. attrs is a dict as in getattr. + ''' + + return ['.', '..'] + + def readlink(self, path): + raise FuseOSError(errno.ENOENT) + + def release(self, path, fh): + return 0 + + def releasedir(self, path, fh): + return 0 + + def removexattr(self, path, name): + raise FuseOSError(ENOTSUP) + + def rename(self, old, new): + raise FuseOSError(errno.EROFS) + + def rmdir(self, path): + raise FuseOSError(errno.EROFS) + + def setxattr(self, path, name, value, options, position=0): + raise FuseOSError(ENOTSUP) + + def statfs(self, path): + ''' + Returns a dictionary with keys identical to the statvfs C structure of + statvfs(3). + + On Mac OS X f_bsize and f_frsize must be a power of 2 + (minimum 512). + ''' + + return {} + + def symlink(self, target, source): + 'creates a symlink `target -> source` (e.g. ln -s source target)' + + raise FuseOSError(errno.EROFS) + + def truncate(self, path, length, fh=None): + raise FuseOSError(errno.EROFS) + + def unlink(self, path): + raise FuseOSError(errno.EROFS) + + def utimens(self, path, times=None): + 'Times is a (atime, mtime) tuple. If None use current time.' + + return 0 + + def write(self, path, data, offset, fh): + raise FuseOSError(errno.EROFS) + + +class LoggingMixIn: + log = logging.getLogger('fuse.log-mixin') + + def __call__(self, op, path, *args): + self.log.debug('-> %s %s %s', op, path, repr(args)) + ret = '[Unhandled Exception]' + try: + ret = getattr(self, op)(path, *args) + return ret + except OSError as e: + ret = str(e) + raise + finally: + self.log.debug('<- %s %s', op, repr(ret)) diff --git a/main.py b/main.py index 9955093..04f7811 100644 --- a/main.py +++ b/main.py @@ -11,124 +11,390 @@ from Crypto.Util.Padding import pad, unpad import math from pprint import pprint import hashlib -import time import sys +import random +import time -api = Iota('https://nodes.thetangle.org:443', local_pow=True) +import msgpack +import asyncio +import copy -def genBundles(data, addrIter, lenPerTx = 2187, txPerBundle = 1): - msg = TryteString.from_bytes(data) - bundles = [] - nextAddr = addrIter.__next__() - for b in range(math.ceil(len(msg)/(lenPerTx*txPerBundle))): - bundleMsg = msg[lenPerTx*txPerBundle*b:][:lenPerTx*txPerBundle] - bundleTxs = [] - addr = nextAddr - print("[addr] "+str(addr.with_valid_checksum())) +from errno import ENOENT +from fuse import FUSE, FuseOSError, Operations, LoggingMixIn +import stat +import os + +class IotaFS_BlobStore(): + def __init__(self, api=None): + if api==None: + self.api = Iota('https://nodes.thetangle.org:443', local_pow=True) + else: + self.api = api + + def genBundles(self, data, addrIter, lenPerTx = 2187, txPerBundle = 1): + msg = TryteString.from_bytes(data) + bundles = [] nextAddr = addrIter.__next__() - for t in range(math.ceil(len(bundleMsg)/lenPerTx)): - txMsg = bundleMsg[lenPerTx*t:][:lenPerTx] - bundleTxs.append( - ProposedTransaction( - address = addr, - value = 0, - tag = Tag("IOTAFS"), - message = txMsg + for b in range(math.ceil(len(msg)/(lenPerTx*txPerBundle))): + bundleMsg = msg[lenPerTx*txPerBundle*b:][:lenPerTx*txPerBundle] + bundleTxs = [] + addr = nextAddr + print("[addr] "+str(addr.with_valid_checksum())) + nextAddr = addrIter.__next__() + for t in range(math.ceil(len(bundleMsg)/lenPerTx)): + txMsg = bundleMsg[lenPerTx*t:][:lenPerTx] + bundleTxs.append( + ProposedTransaction( + address = addr, + value = 0, + tag = Tag("IOTAFS"), + message = txMsg + ) + ) + bundles.append( + self.api.prepare_transfer( + transfers = bundleTxs, + inputs = [addr] + )['trytes'] + ) + return bundles + + def sendBundles(self, bundles): + bundleRets = [] + for i,bundle in enumerate(bundles): + print(str(int(i/len(bundles)*100))+"%") + bundleRets.append( + self.api.send_trytes( + trytes=bundle ) ) - bundles.append( - api.prepare_transfer( - transfers = bundleTxs, - inputs = [addr] - )['trytes'] - ) - return bundles + return bundleRets -def sendBundles(bundles): - bundleRets = [] - for i,bundle in enumerate(bundles): - print(str(int(i/len(bundles)*100))+"%") - bundleRets.append( - api.send_trytes( - trytes=bundle - ) - ) - return bundleRets + def uploadData(self, data, secret): + print("Uploading...") + m = hashlib.sha3_384() + m.update(secret) + m.update(data) + sHash = m.digest() + self.uploadDataRaw(data, sHash) + return sHash -def uploadData(data, secret): - print("Uploading...") - m = hashlib.sha3_384() - m.update(secret) - m.update(data) - sHash = m.digest() - trSeed = TryteString.from_bytes(sHash[16:])[:81] - cipher = AES.new(sHash[:16], AES.MODE_CBC, sHash[22:][:16]) - ct_bytes = cipher.encrypt(pad(data, AES.block_size)) - addrIter = AddressGenerator(Seed(trSeed)).create_iterator(start = 0, step = 1) - bundles = genBundles(ct_bytes, addrIter) - sendBundles(bundles) - return sHash + def uploadDataRaw(self, data, sHash): + trSeed = TryteString.from_bytes(sHash[16:])[:81] + cipher = AES.new(sHash[:16], AES.MODE_CBC, sHash[22:][:16]) + ct_bytes = cipher.encrypt(pad(data, AES.block_size)) + addrIter = AddressGenerator(Seed(trSeed)).create_iterator(start = 0, step = 1) + bundles = self.genBundles(ct_bytes, addrIter) + self.sendBundles(bundles) -def uploadTxt(txt, secret): - data = str.encode(txt) - return uploadData(data, secret) + def uploadTxt(self, txt, secret): + data = str.encode(txt) + return self.uploadData(data, secret) -def getData(sHash): - print("Downloading...") - trSeed = TryteString.from_bytes(sHash[16:])[:81] - cipher = AES.new(sHash[:16], AES.MODE_CBC, sHash[22:][:16]) - addrIter = AddressGenerator(trSeed).create_iterator(start=0, step=1) - tryteMsg = "" - for addr in addrIter: - print("[addr] "+str(addr.with_valid_checksum())) - txHash = api.find_transactions(tags=[Tag("IOTAFS")], addresses=[addr])["hashes"] - if len(txHash)==0: - break - bundles = api.get_bundles(txHash[0])["bundles"] - for bundle in bundles: - for tx in bundle.transactions: - tryteMsg+=str(tx.signature_message_fragment) - tryteStr = TryteString(tryteMsg.rstrip("9")) - try: - ct_bytes = tryteStr.as_bytes() - except TrytesDecodeError: - ct_bytes = (tryteStr+"9").as_bytes() - data = unpad(cipher.decrypt(ct_bytes), AES.block_size) - return data + def getData(self, sHash): + print("Downloading...") + trSeed = TryteString.from_bytes(sHash[16:])[:81] + cipher = AES.new(sHash[:16], AES.MODE_CBC, sHash[22:][:16]) + addrIter = AddressGenerator(trSeed).create_iterator(start=0, step=1) + tryteMsg = "" + for addr in addrIter: + print("[addr] "+str(addr.with_valid_checksum())) + txHash = self.api.find_transactions(tags=[Tag("IOTAFS")], addresses=[addr])["hashes"] + if len(txHash)==0: + break + bundles = self.api.get_bundles(txHash[0])["bundles"] + for bundle in bundles: + for tx in bundle.transactions: + tryteMsg+=str(tx.signature_message_fragment) + if tryteMsg == "": + return "" + tryteStr = TryteString(tryteMsg.rstrip("9")) + try: + ct_bytes = tryteStr.as_bytes() + except TrytesDecodeError: + ct_bytes = (tryteStr+"9").as_bytes() + data = unpad(cipher.decrypt(ct_bytes), AES.block_size) + return data -def getTxt(sHash): - return getData(sHash).decode("utf-8") + def getTxt(self, sHash): + return self.getData(sHash).decode("utf-8") -def getSHash(data, secret): - m = hashlib.sha3_384() - m.update(secret) - m.update(data) - return m.digest() + def getSHash(self, data, secret): + m = hashlib.sha3_384() + m.update(secret) + m.update(data) + return m.digest() -def test(secret): - with open("cat2.jpeg","rb") as f: - x = f.read() - sHash = uploadData(x,secret) - print(sHash.hex()) - #sHash = getSHash(x, "catSecret".encode()) - y = getData(sHash) - with open("res.jpeg","wb") as f: - f.write(y) - -if __name__=="__main__": - if len(sys.argv)>=2 and sys.argv[1]=="put": - print("Uploading '"+sys.argv[2]+"' using secret '"+" ".join(sys.argv[3:])+"'") - with open(sys.argv[2], "rb") as f: + def test(self, secret): + with open("cat2.jpeg","rb") as f: x = f.read() - sHash = uploadData(x, " ".join(sys.argv[3:]).encode()) - print("Stored at {"+sHash.hex()+"}") - print("Done.") - elif len(sys.argv)>=2 and sys.argv[1]=="get": - print("Downloading {"+sys.argv[2]+"} into '"+sys.argv[3]+"'") - with open(sys.argv[3], "wb") as f: - f.write(getData(bytearray.fromhex(sys.argv[2]))) - print("Done.") - else: - print("Syntax:") - print(" put [file] [secret]") - print(" get [hash] [file]") + sHash = self.uploadData(x,secret) + print(sHash.hex()) + #sHash = getSHash(x, "catSecret".encode()) + y = self.getData(sHash) + with open("res.jpeg","wb") as f: + f.write(y) + +class IotaFS(): + def __init__(self, token): + self.api = Iota('https://nodes.thetangle.org:443', local_pow=True) + self.blobStore = IotaFS_BlobStore(self.api) + #self.token = token + self.hashState = hashlib.sha3_384() + self.hashState.update(token.encode()) + self._fileTree = {} + self.lastBlockIncomplete = False + self.incompleteBlockRescanTimeout = 5 + self.chainDelimiter = "#CHAIN_DELIM#" + self.cache = {} + + self._fetchFileTree() + + def getFileTree(self, update=False): + if update: + self._fetchFileTree() + return copy.deepcopy(self._fileTree) + + async def fileTreeFetchLoop(self, interval=10): + while True: + self._fetchFileTree() + await asyncio.sleep(10) + + def _fetchFileTree(self): + print("[<] Fetching FileTree") + chain = "" + while True: + print("[<] Fetching FileTree-ChainBlock") + sHash = self.hashState.digest() + block = self._getBlob(sHash) + if block=="": + print("[-] Last Block Received") + break + self.hashState.update(block) + chain+=block + if chain=="": + print("[.] FileTree succesfully fetched: [EMPTY FILE TREE]") + return + if chain.endswith(self.chainDelimiter): + curRing = chain.split(self.chainDelimiter)[-1] + self.lastBlockIncomplete = False + else: + print("[-] Last Block was incomplete; refetching...") + self.lastBlockIncomplete = True + time.sleep(self.incompleteBlockRescanTimeout) + self._fetchFileTree() + + self._fileTree = msgpack.loads(curRing) + print("[.] FileTree succesfully fetched: ") + pprint(self._fileTree) + + def _mergeFileTrees(self, treeA, treeB): + # We update treeB with values from treeA (treeA has priority), except for deletions, + # which are always prioritized + + # fileTree = {fileA: sHash, fileB: sHash, dirA: {fileC: sHash}} + for key, value in treeA.items(): + if isinstance(value, dict): + # get node or create one + node = treeB.setdefault(key, {}) + self._mergeFileTrees(value, node) + else: + if key in treeA and treeA[key]=="#REMOVE#" or key in treeB and treeB[key]=="#REMOVE#": + treeB[key] = "#REMOVE#" + treeB[key] = value + + return treeB + + def upsertFileTree(self, newFileTree): + while self.lastBlockIncomplete: + time.sleep(1) + + self._fileTree = self._mergeFileTrees(newFileTree, self._fileTree) + + newRing = msgpack.dumps(self._fileTree)+self.chainDelimiter + sHash = self.hashState.digest() + self.blobStore.uploadDataRaw(newRing.encode(), sHash) + self.hashState.update(newRing) # For every link in the chain, we salt our hashState using the links data + + def _putBlob(self, data): + #TODO: Use secure random provider + return self.blobStore.uploadData(data, str(random.random()*999999999999).encode()) + + def _getBlob(self, sHash): + return self.blobStore.getData(sHash) + + def _fetchFile(self, sHash): + file = self._getBlob(sHash) + # file lastFetch lastAccess + self.cache[sHash] = [file, time.now(), time.now()] + return self.cache[sHash] + + def getFile(self, sHash): + if sHash in self.cache: + # TODO: maybe update if to old? + self.cache[sHash][2] = time.now() + return self.cache[sHash] + else: + return self._fetchFile(sHash) + + def putFile(self, file, path): + blablabla + + +class IotaFS_Fuse(LoggingMixIn, Operations): + def __init__(self, token): + self.fs = IotaFS(token) + self.fileTree = self.fs.getFileTree() + + def getSubtree(self, path): + subTree = self.fileTree + for elem in path.split("/"): + if elem not in subTree: + return False + subTree = subTree[elem] + return subTree + + def createFileObj(self, path, fileObj): + for elem in path.split("/")[:-1]: + if elem not in subTree: + return False + subTree = subTree[elem] + subTree[path.split("/")[-1]] = fileObj + + def subtreeIsFile(self, subtree): + return isinstance(subtree, str) + + def subtreeExists(self, subtree): + return not (subtree == False) + + #def chmod(self, path, mode): + # return self.sftp.chmod(path, mode) + + #def chown(self, path, uid, gid): + # return self.sftp.chown(path, uid, gid) + + #def create(self, path, mode): + # f = self.sftp.open(path, 'w') + # f.chmod(mode) + # f.close() + # return 0 + + #def destroy(self, path): + # self.sftp.close() + # self.client.close() + + def getattr(self, path, fh=None): + print("[#] GETATTR "+path) + subTree = self.getSubtree(path) + if not self.subtreeExists(subTree): + # File does not exist / is not a file + raise FuseOSError(ENOENT) + now = time.time() + st = {} + # mode decides access permissions and if file object is a directory (stat.S_IFDIR), file (stat.S_IFREG) or a special file + if self.subtreeIsFile(subTree): + st['st_mode'] = 777 | stat.S_IFREG + else: + st['st_mode'] = 777 | stat.S_IFDIR + st['st_ino'] = 0 + st['st_dev'] = 0 + st['st_nlink'] = 1 + st['st_uid'] = os.getuid() #file object's user id + st['st_gid'] = os.getgid() #file object's group id + if fh: + file, path, sHash, lastFetch, lastAccess = fh + st["st_size"] = len(file) + st['st_atime'] = lastAccess + st['st_mtime'] = lastFetch + st['st_ctime'] = 0 + else: + st['st_size'] = 0 # 0 Byte lol + st['st_atime'] = now #last access time in seconds + st['st_mtime'] = now #last modified time in seconds + st['st_ctime'] = 0 # very old file + # TODO: Actuall real value + block_size = 512 + st['st_blocks'] = (int) ((st['st_size'] + block_size-1) / block_size) + return st + + #def mkdir(self, path, mode): + # return self.sftp.mkdir(path, mode) + + def read(self, path2, size, offset, fh): + print("[#] WRITE "+path2) + file, path, sHash, lastFetch, lastAccess = fh + if path!=path2: + return FuseOSError(ENOENT) + + return file[offset : offset+size] + + def readdir(self, path, fh): + print("[#] READDIR "+path) + l = ['.', '..'] + for elem in self.getSubtree(path): + l.append(elem.encode('utf-8')) + return l + + #def rename(self, old, new): + # return self.sftp.rename(old, new) + + #def rmdir(self, path): + # return self.sftp.rmdir(path) + + def write(self, path2, data, offset, fh): + print("[#] WRITE "+path2) + file, path, sHash, lastFetch, lastAccess = fh + if path!=path2: + return FuseOSError(ENOENT) + + raw = data.encode() + file[:offset] + raw + file[offset+len(raw):] + return len(raw) + + def open(self, path, flags): + print("[#] OPEN "+path) + subTree = self.getSubtree(path) + if subTree == False: + pass + else: + if not self.subtreeIsFile(subTree): + # cannot open a dir + raise FuseOSError(ENOENT) + sHash = subTree + file, lastFetch, lastAccess = self.fs.getFile(sHash) + return (file, path, sHash, lastFetch, lastAccess) + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('token') + parser.add_argument('mount') + args = parser.parse_args() + + fuse = FUSE( + IotaFS_Fuse(args.token), + args.mount, + foreground=True, + nothreads=True, + allow_other=False) + +#if __name__=="__main__": +# iotaFS = IotaFS_BlobStore() +# +# if len(sys.argv)>=2 and sys.argv[1]=="put": +# print("Uploading '"+sys.argv[2]+"' using secret '"+" ".join(sys.argv[3:])+"'") +# with open(sys.argv[2], "rb") as f: +# x = f.read() +# sHash = iotaFS.uploadData(x, " ".join(sys.argv[3:]).encode()) +# print("Stored at {"+sHash.hex()+"}") +# print("Done.") +# elif len(sys.argv)>=2 and sys.argv[1]=="get": +# print("Downloading {"+sys.argv[2]+"} into '"+sys.argv[3]+"'") +# with open(sys.argv[3], "wb") as f: +# f.write(iotaFS.getData(bytearray.fromhex(sys.argv[2]))) +# print("Done.") +# else: +# print("Syntax:") +# print(" put [file] [secret]") +# print(" get [hash] [file]") +#