PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/saltstack/salt/lib/python3.10/site-packages/salt/utils
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/utils/files.py
""" Functions for working with files """ import codecs import contextlib import errno import io import logging import os import re import shutil import stat import subprocess import tempfile import time import urllib.parse import salt.modules.selinux import salt.utils.path import salt.utils.platform import salt.utils.stringutils from salt.exceptions import CommandExecutionError, FileLockError, MinionError from salt.utils.decorators.jinja import jinja_filter try: import fcntl HAS_FCNTL = True except ImportError: # fcntl is not available on windows HAS_FCNTL = False log = logging.getLogger(__name__) LOCAL_PROTOS = ("", "file") REMOTE_PROTOS = ("http", "https", "ftp", "swift", "s3") VALID_PROTOS = ("salt", "file") + REMOTE_PROTOS TEMPFILE_PREFIX = "__salt.tmp." HASHES = { "sha512": 128, "sha384": 96, "sha256": 64, "sha224": 56, "sha1": 40, "md5": 32, } HASHES_REVMAP = {y: x for x, y in HASHES.items()} def __clean_tmp(tmp): """ Remove temporary files """ try: rm_rf(tmp) except Exception as exc: # pylint: disable=broad-except log.error( "Exception while removing temp directory: %s", exc, exc_info_on_loglevel=logging.DEBUG, ) def guess_archive_type(name): """ Guess an archive type (tar, zip, or rar) by its file extension """ name = name.lower() for ending in ( "tar", "tar.gz", "tgz", "tar.bz2", "tbz2", "tbz", "tar.xz", "txz", "tar.lzma", "tlz", ): if name.endswith("." + ending): return "tar" for ending in ("zip", "rar"): if name.endswith("." + ending): return ending return None def mkstemp(*args, **kwargs): """ Helper function which does exactly what ``tempfile.mkstemp()`` does but accepts another argument, ``close_fd``, which, by default, is true and closes the fd before returning the file path. Something commonly done throughout Salt's code. """ if "prefix" not in kwargs: kwargs["prefix"] = "__salt.tmp." close_fd = kwargs.pop("close_fd", True) fd_, f_path = tempfile.mkstemp(*args, **kwargs) if close_fd is False: return fd_, f_path os.close(fd_) del fd_ return f_path def recursive_copy(source, dest): """ Recursively copy the source directory to the destination, leaving files with the source does not explicitly overwrite. (identical to cp -r on a unix machine) """ for root, _, files in salt.utils.path.os_walk(source): path_from_source = root.replace(source, "").lstrip(os.sep) target_directory = os.path.join(dest, path_from_source) if not os.path.exists(target_directory): os.makedirs(target_directory) for name in files: file_path_from_source = os.path.join(source, path_from_source, name) target_path = os.path.join(target_directory, name) shutil.copyfile(file_path_from_source, target_path) def copyfile(source, dest, backup_mode="", cachedir=""): """ Copy files from a source to a destination in an atomic way, and if specified cache the file. """ if not os.path.isfile(source): raise OSError(f"[Errno 2] No such file or directory: {source}") if not os.path.isdir(os.path.dirname(dest)): raise OSError(f"[Errno 2] No such file or directory: {dest}") bname = os.path.basename(dest) dname = os.path.dirname(os.path.abspath(dest)) tgt = mkstemp(prefix=bname, dir=dname) shutil.copyfile(source, tgt) bkroot = "" if cachedir: bkroot = os.path.join(cachedir, "file_backup") if backup_mode == "minion" or backup_mode == "both" and bkroot: if os.path.exists(dest): backup_minion(dest, bkroot) if backup_mode == "master" or backup_mode == "both" and bkroot: # TODO, backup to master pass # Get current file stats to they can be replicated after the new file is # moved to the destination path. fstat = None if not salt.utils.platform.is_windows(): try: fstat = os.stat(dest) except OSError: pass # The move could fail if the dest has xattr protections, so delete the # temp file in this case try: shutil.move(tgt, dest) except Exception: # pylint: disable=broad-except __clean_tmp(tgt) raise if fstat is not None: os.chown(dest, fstat.st_uid, fstat.st_gid) os.chmod(dest, fstat.st_mode) # If SELINUX is available run a restorecon on the file rcon = salt.utils.path.which("restorecon") if rcon: policy = False try: policy = salt.modules.selinux.getenforce() except (ImportError, CommandExecutionError): pass if policy == "Enforcing": with fopen(os.devnull, "w") as dev_null: cmd = [rcon, dest] subprocess.call(cmd, stdout=dev_null, stderr=dev_null) if os.path.isfile(tgt): # The temp file failed to move __clean_tmp(tgt) def rename(src, dst): """ On Windows, os.rename() will fail with a WindowsError exception if a file exists at the destination path. This function checks for this error and if found, it deletes the destination path first. """ try: os.rename(src, dst) except OSError as exc: if exc.errno != errno.EEXIST: raise try: os.remove(dst) except OSError as exc: if exc.errno != errno.ENOENT: raise MinionError(f"Error: Unable to remove {dst}: {exc.strerror}") os.rename(src, dst) def process_read_exception(exc, path, ignore=None): """ Common code for raising exceptions when reading a file fails The ignore argument can be an iterable of integer error codes (or a single integer error code) that should be ignored. """ if ignore is not None: if isinstance(ignore, int): ignore = (ignore,) else: ignore = () if exc.errno in ignore: return if exc.errno == errno.ENOENT: raise CommandExecutionError(f"{path} does not exist") elif exc.errno == errno.EACCES: raise CommandExecutionError(f"Permission denied reading from {path}") else: raise CommandExecutionError( "Error {} encountered reading from {}: {}".format( exc.errno, path, exc.strerror ) ) @contextlib.contextmanager def wait_lock(path, lock_fn=None, timeout=5, sleep=0.1, time_start=None): """ Obtain a write lock. If one exists, wait for it to release first """ if not isinstance(path, str): raise FileLockError("path must be a string") if lock_fn is None: lock_fn = path + ".w" if time_start is None: time_start = time.time() obtained_lock = False def _raise_error(msg, race=False): """ Raise a FileLockError """ raise FileLockError(msg, time_start=time_start) try: if os.path.exists(lock_fn) and not os.path.isfile(lock_fn): _raise_error(f"lock_fn {lock_fn} exists and is not a file") open_flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY while time.time() - time_start < timeout: try: # Use os.open() to obtain filehandle so that we can force an # exception if the file already exists. Concept found here: # http://stackoverflow.com/a/10979569 fh_ = os.open(lock_fn, open_flags) except OSError as exc: if exc.errno != errno.EEXIST: _raise_error( "Error {} encountered obtaining file lock {}: {}".format( exc.errno, lock_fn, exc.strerror ) ) log.trace("Lock file %s exists, sleeping %f seconds", lock_fn, sleep) time.sleep(sleep) else: # Write the lock file with os.fdopen(fh_, "w"): pass # Lock successfully acquired log.trace("Write lock %s obtained", lock_fn) obtained_lock = True # Transfer control back to the code inside the with block yield # Exit the loop break else: _raise_error( "Timeout of {} seconds exceeded waiting for lock_fn {} " "to be released".format(timeout, lock_fn) ) except FileLockError: raise except Exception as exc: # pylint: disable=broad-except _raise_error(f"Error encountered obtaining file lock {lock_fn}: {exc}") finally: if obtained_lock: os.remove(lock_fn) log.trace("Write lock for %s (%s) released", path, lock_fn) def get_umask(): """ Returns the current umask """ ret = os.umask(0) # pylint: disable=blacklisted-function os.umask(ret) # pylint: disable=blacklisted-function return ret @contextlib.contextmanager def set_umask(mask): """ Temporarily set the umask and restore once the contextmanager exits """ if mask is None or salt.utils.platform.is_windows(): # Don't attempt on Windows, or if no mask was passed yield else: orig_mask = os.umask(mask) # pylint: disable=blacklisted-function try: yield finally: os.umask(orig_mask) # pylint: disable=blacklisted-function def fopen(*args, **kwargs): """ Wrapper around open() built-in to set CLOEXEC on the fd. This flag specifies that the file descriptor should be closed when an exec function is invoked; When a file descriptor is allocated (as with open or dup), this bit is initially cleared on the new file descriptor, meaning that descriptor will survive into the new program after exec. NB! We still have small race condition between open and fcntl. """ try: # Don't permit stdin/stdout/stderr to be opened. The boolean False # and True are treated by Python 3's open() as file descriptors 0 # and 1, respectively. if args[0] in (0, 1, 2): raise TypeError(f"{args[0]} is not a permitted file descriptor") except IndexError: pass binary = None if kwargs.pop("binary", None): if len(args) > 1: args = list(args) if "b" not in args[1]: args[1] = args[1].replace("t", "b") if "b" not in args[1]: args[1] += "b" elif kwargs.get("mode"): if "b" not in kwargs["mode"]: kwargs["mode"] = kwargs["mode"].replace("t", "b") if "b" not in kwargs["mode"]: kwargs["mode"] += "b" else: # the default is to read kwargs["mode"] = "rb" if "encoding" not in kwargs: # In Python 3, if text mode is used and the encoding # is not specified, set the encoding to 'utf-8'. binary = False if len(args) > 1: args = list(args) if "b" in args[1]: binary = True if kwargs.get("mode", None): if "b" in kwargs["mode"]: binary = True if not binary: kwargs["encoding"] = __salt_system_encoding__ if not binary and not kwargs.get("newline", None): kwargs["newline"] = "" # Workaround callers with bad buffering setting for binary files if kwargs.get("buffering") == 1 and "b" in kwargs.get("mode", ""): log.debug( "Line buffering (buffering=1) isn't supported in binary mode, " "the default buffer size will be used" ) kwargs["buffering"] = io.DEFAULT_BUFFER_SIZE f_handle = open( # pylint: disable=resource-leakage,unspecified-encoding *args, **kwargs ) if is_fcntl_available(): # modify the file descriptor on systems with fcntl # unix and unix-like systems only try: FD_CLOEXEC = fcntl.FD_CLOEXEC # pylint: disable=C0103 except AttributeError: FD_CLOEXEC = 1 # pylint: disable=C0103 old_flags = fcntl.fcntl(f_handle.fileno(), fcntl.F_GETFD) fcntl.fcntl(f_handle.fileno(), fcntl.F_SETFD, old_flags | FD_CLOEXEC) return f_handle @contextlib.contextmanager def flopen(*args, **kwargs): """ Shortcut for fopen with lock and context manager. """ filename, args = args[0], args[1:] writing = "wa" with fopen(filename, *args, **kwargs) as f_handle: try: if is_fcntl_available(check_sunos=True): lock_type = fcntl.LOCK_SH if args and any([write in args[0] for write in writing]): lock_type = fcntl.LOCK_EX fcntl.flock(f_handle.fileno(), lock_type) yield f_handle finally: if is_fcntl_available(check_sunos=True): fcntl.flock(f_handle.fileno(), fcntl.LOCK_UN) @contextlib.contextmanager def fpopen(*args, **kwargs): """ Shortcut for fopen with extra uid, gid, and mode options. Supported optional Keyword Arguments: mode Explicit mode to set. Mode is anything os.chmod would accept as input for mode. Works only on unix/unix-like systems. uid The uid to set, if not set, or it is None or -1 no changes are made. Same applies if the path is already owned by this uid. Must be int. Works only on unix/unix-like systems. gid The gid to set, if not set, or it is None or -1 no changes are made. Same applies if the path is already owned by this gid. Must be int. Works only on unix/unix-like systems. """ # Remove uid, gid and mode from kwargs if present uid = kwargs.pop("uid", -1) # -1 means no change to current uid gid = kwargs.pop("gid", -1) # -1 means no change to current gid mode = kwargs.pop("mode", None) with fopen(*args, **kwargs) as f_handle: path = args[0] d_stat = os.stat(path) if hasattr(os, "chown"): # if uid and gid are both -1 then go ahead with # no changes at all if (d_stat.st_uid != uid or d_stat.st_gid != gid) and [ i for i in (uid, gid) if i != -1 ]: os.chown(path, uid, gid) if mode is not None: mode_part = stat.S_IMODE(d_stat.st_mode) if mode_part != mode: os.chmod(path, (d_stat.st_mode ^ mode_part) | mode) yield f_handle def safe_walk(top, topdown=True, onerror=None, followlinks=True, _seen=None): """ A clone of the python os.walk function with some checks for recursive symlinks. Unlike os.walk this follows symlinks by default. """ if _seen is None: _seen = set() # We may not have read permission for top, in which case we can't # get a list of the files the directory contains. os.path.walk # always suppressed the exception then, rather than blow up for a # minor reason when (say) a thousand readable directories are still # left to visit. That logic is copied here. try: # Note that listdir and error are globals in this module due # to earlier import-*. names = os.listdir(top) except OSError as err: if onerror is not None: onerror(err) return if followlinks: status = os.stat(top) # st_ino is always 0 on some filesystems (FAT, NTFS); ignore them if status.st_ino != 0: node = (status.st_dev, status.st_ino) if node in _seen: return _seen.add(node) dirs, nondirs = [], [] for name in names: full_path = os.path.join(top, name) if os.path.isdir(full_path): dirs.append(name) else: nondirs.append(name) if topdown: yield top, dirs, nondirs for name in dirs: new_path = os.path.join(top, name) if followlinks or not os.path.islink(new_path): yield from safe_walk(new_path, topdown, onerror, followlinks, _seen) if not topdown: yield top, dirs, nondirs def safe_rm(tgt): """ Safely remove a file """ try: os.remove(tgt) except OSError: pass def rm_rf(path): """ Platform-independent recursive delete. Includes code from http://stackoverflow.com/a/2656405 """ def _onerror(func, path, exc_info): """ Error handler for `shutil.rmtree`. If the error is due to an access error (read only file) it attempts to add write permission and then retries. If the error is for another reason it re-raises the error. Usage : `shutil.rmtree(path, onerror=onerror)` """ if salt.utils.platform.is_windows() and not os.access(path, os.W_OK): # Is the error an access error ? os.chmod(path, stat.S_IWUSR) func(path) else: raise # pylint: disable=E0704 if os.path.islink(path) or not os.path.isdir(path): os.remove(path) else: if salt.utils.platform.is_windows(): try: path = salt.utils.stringutils.to_unicode(path) except TypeError: pass shutil.rmtree(path, onerror=_onerror) @jinja_filter("is_empty") def is_empty(filename): """ Is a file empty? """ try: return os.stat(filename).st_size == 0 except OSError: # Non-existent file or permission denied to the parent dir return False def is_fcntl_available(check_sunos=False): """ Simple function to check if the ``fcntl`` module is available or not. If ``check_sunos`` is passed as ``True`` an additional check to see if host is SunOS is also made. For additional information see: http://goo.gl/159FF8 """ if check_sunos and salt.utils.platform.is_sunos(): return False return HAS_FCNTL def safe_filename_leaf(file_basename): """ Input the basename of a file, without the directory tree, and returns a safe name to use i.e. only the required characters are converted by urllib.parse.quote If the input is a PY2 String, output a PY2 String. If input is Unicode output Unicode. For consistency all platforms are treated the same. Hard coded to utf8 as its ascii compatible windows is \\ / : * ? " < > | posix is / .. versionadded:: 2017.7.2 :codeauthor: Damon Atkins <https://github.com/damon-atkins> """ def _replace(re_obj): return urllib.parse.quote(re_obj.group(0), safe="") if not isinstance(file_basename, str): # the following string is not prefixed with u return re.sub( '[\\\\:/*?"<>|]', _replace, str(file_basename, "utf8").encode("ascii", "backslashreplace"), ) # the following string is prefixed with u return re.sub('[\\\\:/*?"<>|]', _replace, file_basename, flags=re.UNICODE) def safe_filepath(file_path_name, dir_sep=None): """ Input the full path and filename, splits on directory separator and calls safe_filename_leaf for each part of the path. dir_sep allows coder to force a directory separate to a particular character .. versionadded:: 2017.7.2 :codeauthor: Damon Atkins <https://github.com/damon-atkins> """ if not dir_sep: dir_sep = os.sep # Normally if file_path_name or dir_sep is Unicode then the output will be Unicode # This code ensure the output type is the same as file_path_name if not isinstance(file_path_name, str) and isinstance(dir_sep, str): dir_sep = dir_sep.encode("ascii") # This should not be executed under PY3 # splitdrive only set drive on windows platform (drive, path) = os.path.splitdrive(file_path_name) path = dir_sep.join( [safe_filename_leaf(file_section) for file_section in path.rsplit(dir_sep)] ) if drive: path = dir_sep.join([drive, path]) return path @jinja_filter("is_text_file") def is_text(fp_, blocksize=512): """ Uses heuristics to guess whether the given file is text or binary, by reading a single block of bytes from the file. If more than 30% of the chars in the block are non-text, or there are NUL ('\x00') bytes in the block, assume this is a binary file. """ def int2byte(x): return bytes((x,)) text_characters = b"".join(int2byte(i) for i in range(32, 127)) + b"\n\r\t\f\b" try: block = fp_.read(blocksize) except AttributeError: # This wasn't an open filehandle, so treat it as a file path and try to # open the file try: with fopen(fp_, "rb") as fp2_: block = fp2_.read(blocksize) except OSError: # Unable to open file, bail out and return false return False if b"\x00" in block: # Files with null bytes are binary return False elif not block: # An empty file is considered a valid text file return True try: block.decode("utf-8") return True except UnicodeDecodeError: pass nontext = block.translate(None, text_characters) return float(len(nontext)) / len(block) <= 0.30 @jinja_filter("is_bin_file") def is_binary(path): """ Detects if the file is a binary, returns bool. Returns True if the file is a bin, False if the file is not and None if the file is not available. """ if not os.path.isfile(path): return False try: with fopen(path, "rb") as fp_: try: data = fp_.read(2048) data = data.decode(__salt_system_encoding__) return salt.utils.stringutils.is_binary(data) except UnicodeDecodeError: return True except OSError: return False def remove(path): """ Runs os.remove(path) and suppresses the OSError if the file doesn't exist """ try: os.remove(path) except OSError as exc: if exc.errno != errno.ENOENT: raise @jinja_filter("list_files") def list_files(directory): """ Return a list of all files found under directory (and its subdirectories) """ ret = set() ret.add(directory) for root, dirs, files in safe_walk(directory): for name in files: ret.add(os.path.join(root, name)) for name in dirs: ret.add(os.path.join(root, name)) return list(ret) def st_mode_to_octal(mode): """ Convert the st_mode value from a stat(2) call (as returned from os.stat()) to an octal mode. """ try: return oct(mode)[-4:] except (TypeError, IndexError): return "" def normalize_mode(mode): """ Return a mode value, normalized to a string and containing a leading zero if it does not have one. Allow "keep" as a valid mode (used by file state/module to preserve mode from the Salt fileserver in file states). """ if mode is None: return None if not isinstance(mode, str): mode = str(mode) mode = mode.replace("0o", "0") # Strip any quotes any initial zeroes, then though zero-pad it up to 4. # This ensures that somethign like '00644' is normalized to '0644' return mode.strip('"').strip("'").lstrip("0").zfill(4) def human_size_to_bytes(human_size): """ Convert human-readable units to bytes """ size_exp_map = {"K": 1, "M": 2, "G": 3, "T": 4, "P": 5} human_size_str = str(human_size) match = re.match(r"^(\d+)([KMGTP])?$", human_size_str) if not match: raise ValueError( "Size must be all digits, with an optional unit type (K, M, G, T, or P)" ) size_num = int(match.group(1)) unit_multiplier = 1024 ** size_exp_map.get(match.group(2), 0) return size_num * unit_multiplier def backup_minion(path, bkroot): """ Backup a file on the minion """ dname, bname = os.path.split(path) if salt.utils.platform.is_windows(): src_dir = dname.replace(":", "_") else: src_dir = dname[1:] if not salt.utils.platform.is_windows(): fstat = os.stat(path) msecs = str(int(time.time() * 1000000))[-6:] if salt.utils.platform.is_windows(): # ':' is an illegal filesystem path character on Windows stamp = time.strftime("%a_%b_%d_%H-%M-%S_%Y") else: stamp = time.strftime("%a_%b_%d_%H:%M:%S_%Y") stamp = f"{stamp[:-4]}{msecs}_{stamp[-4:]}" bkpath = os.path.join(bkroot, src_dir, f"{bname}_{stamp}") if not os.path.isdir(os.path.dirname(bkpath)): os.makedirs(os.path.dirname(bkpath)) shutil.copyfile(path, bkpath) if not salt.utils.platform.is_windows(): os.chown(bkpath, fstat.st_uid, fstat.st_gid) os.chmod(bkpath, fstat.st_mode) def case_insensitive_filesystem(path=None): """ Detect case insensitivity on a system. Returns: bool: Flag to indicate case insensitivity .. versionadded:: 3004 """ with tempfile.NamedTemporaryFile(prefix="TmP", dir=path, delete=True) as tmp_file: return os.path.exists(tmp_file.name.lower()) def get_encoding(path): """ Detect a file's encoding using the following: - Check for Byte Order Marks (BOM) - Check for UTF-8 Markers - Check System Encoding - Check for ascii Args: path (str): The path to the file to check Returns: str: The encoding of the file Raises: CommandExecutionError: If the encoding cannot be detected """ def check_ascii(_data): # If all characters can be decoded to ASCII, then it's ASCII try: _data.decode("ASCII") log.debug("Found ASCII") except UnicodeDecodeError: return False else: return True def check_bom(_data): # Supported Python Codecs # https://docs.python.org/2/library/codecs.html # https://docs.python.org/3/library/codecs.html boms = [ ("UTF-32-BE", salt.utils.stringutils.to_bytes(codecs.BOM_UTF32_BE)), ("UTF-32-LE", salt.utils.stringutils.to_bytes(codecs.BOM_UTF32_LE)), ("UTF-16-BE", salt.utils.stringutils.to_bytes(codecs.BOM_UTF16_BE)), ("UTF-16-LE", salt.utils.stringutils.to_bytes(codecs.BOM_UTF16_LE)), ("UTF-8", salt.utils.stringutils.to_bytes(codecs.BOM_UTF8)), ("UTF-7", salt.utils.stringutils.to_bytes("\x2b\x2f\x76\x38\x2D")), ("UTF-7", salt.utils.stringutils.to_bytes("\x2b\x2f\x76\x38")), ("UTF-7", salt.utils.stringutils.to_bytes("\x2b\x2f\x76\x39")), ("UTF-7", salt.utils.stringutils.to_bytes("\x2b\x2f\x76\x2b")), ("UTF-7", salt.utils.stringutils.to_bytes("\x2b\x2f\x76\x2f")), ] for _encoding, bom in boms: if _data.startswith(bom): log.debug("Found BOM for %s", _encoding) return _encoding return False def check_utf8_markers(_data): try: decoded = _data.decode("UTF-8") except UnicodeDecodeError: return False else: return True def check_system_encoding(_data): try: _data.decode(__salt_system_encoding__) except UnicodeDecodeError: return False else: return True if not os.path.isfile(path): raise CommandExecutionError("Not a file") try: with fopen(path, "rb") as fp_: data = fp_.read(2048) except OSError: raise CommandExecutionError("Failed to open file") # Check for Unicode BOM encoding = check_bom(data) if encoding: return encoding # Check for UTF-8 markers if check_utf8_markers(data): return "UTF-8" # Check system encoding if check_system_encoding(data): return __salt_system_encoding__ # Check for ASCII first if check_ascii(data): return "ASCII" raise CommandExecutionError("Could not detect file encoding")