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/modules
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/disk.py
""" Module for managing disks and blockdevices """ import collections import decimal import logging import os import re import subprocess import salt.utils.decorators import salt.utils.decorators.path import salt.utils.path import salt.utils.platform from salt.exceptions import CommandExecutionError __func_alias__ = {"format_": "format"} log = logging.getLogger(__name__) def __virtual__(): """ Only work on POSIX-like systems """ if salt.utils.platform.is_windows(): return False, "This module doesn't work on Windows." return True def _parse_numbers(text): """ Convert a string to a number, allowing for a K|M|G|T postfix, 32.8K. Returns a decimal number if the string is a real number, or the string unchanged otherwise. """ if text.isdigit(): return decimal.Decimal(text) try: postPrefixes = { "K": "10E3", "M": "10E6", "G": "10E9", "T": "10E12", "P": "10E15", "E": "10E18", "Z": "10E21", "Y": "10E24", } if text[-1] in postPrefixes: v = decimal.Decimal(text[:-1]) v = v * decimal.Decimal(postPrefixes[text[-1]]) return v else: return decimal.Decimal(text) except ValueError: return text def _clean_flags(args, caller): """ Sanitize flags passed into df """ flags = "" if args is None: return flags allowed = ("a", "B", "h", "H", "i", "k", "l", "P", "t", "T", "x", "v") for flag in args: if flag in allowed: flags += flag else: raise CommandExecutionError(f"Invalid flag passed to {caller}") return flags def usage(args=None): """ Return usage information for volumes mounted on this minion .. versionchanged:: 2019.2.0 Default for SunOS changed to 1 kilobyte blocks CLI Example: .. code-block:: bash salt '*' disk.usage """ flags = _clean_flags(args, "disk.usage") if not os.path.isfile("/etc/mtab") and __grains__["kernel"] == "Linux": log.error("df cannot run without /etc/mtab") if __grains__.get("virtual_subtype") == "LXC": log.error( "df command failed and LXC detected. If you are running " "a Docker container, consider linking /proc/mounts to " "/etc/mtab or consider running Docker with -privileged" ) return {} if __grains__["kernel"] == "Linux": cmd = "df -P" elif __grains__["kernel"] == "OpenBSD" or __grains__["kernel"] == "AIX": cmd = "df -kP" elif __grains__["kernel"] == "SunOS": cmd = "df -k" else: cmd = "df" if flags: cmd += f" -{flags}" ret = {} out = __salt__["cmd.run"](cmd, python_shell=False).splitlines() oldline = None for line in out: if not line: continue if line.startswith("Filesystem"): continue if oldline: line = oldline + " " + line comps = line.split() if len(comps) == 1: oldline = line continue else: oldline = None while len(comps) >= 2 and not comps[1].isdigit(): comps[0] = f"{comps[0]} {comps[1]}" comps.pop(1) if len(comps) < 2: continue try: if __grains__["kernel"] == "Darwin": ret[comps[8]] = { "filesystem": comps[0], "512-blocks": comps[1], "used": comps[2], "available": comps[3], "capacity": comps[4], "iused": comps[5], "ifree": comps[6], "%iused": comps[7], } else: ret[comps[5]] = { "filesystem": comps[0], "1K-blocks": comps[1], "used": comps[2], "available": comps[3], "capacity": comps[4], } except IndexError: log.error("Problem parsing disk usage information") ret = {} return ret def inodeusage(args=None): """ Return inode usage information for volumes mounted on this minion CLI Example: .. code-block:: bash salt '*' disk.inodeusage """ flags = _clean_flags(args, "disk.inodeusage") if __grains__["kernel"] == "AIX": cmd = "df -i" else: cmd = "df -iP" if flags: cmd += f" -{flags}" ret = {} out = __salt__["cmd.run"](cmd, python_shell=False).splitlines() for line in out: if line.startswith("Filesystem"): continue comps = line.split() # Don't choke on empty lines if not comps: continue try: if __grains__["kernel"] == "OpenBSD": ret[comps[8]] = { "inodes": int(comps[5]) + int(comps[6]), "used": comps[5], "free": comps[6], "use": comps[7], "filesystem": comps[0], } elif __grains__["kernel"] == "AIX": ret[comps[6]] = { "inodes": comps[4], "used": comps[5], "free": comps[2], "use": comps[5], "filesystem": comps[0], } else: ret[comps[5]] = { "inodes": comps[1], "used": comps[2], "free": comps[3], "use": comps[4], "filesystem": comps[0], } except (IndexError, ValueError): log.error("Problem parsing inode usage information") ret = {} return ret def percent(args=None): """ Return partition information for volumes mounted on this minion CLI Example: .. code-block:: bash salt '*' disk.percent /var """ if __grains__["kernel"] == "Linux": cmd = "df -P" elif __grains__["kernel"] == "OpenBSD" or __grains__["kernel"] == "AIX": cmd = "df -kP" else: cmd = "df" ret = {} out = __salt__["cmd.run"](cmd, python_shell=False).splitlines() for line in out: if not line: continue if line.startswith("Filesystem"): continue comps = line.split() while len(comps) >= 2 and not comps[1].isdigit(): comps[0] = f"{comps[0]} {comps[1]}" comps.pop(1) if len(comps) < 2: continue try: if __grains__["kernel"] == "Darwin": ret[comps[8]] = comps[4] else: ret[comps[5]] = comps[4] except IndexError: log.error("Problem parsing disk usage information") ret = {} if args and args not in ret: log.error( "Problem parsing disk usage information: Partition '%s' does not exist!", args, ) ret = {} elif args: return ret[args] return ret @salt.utils.decorators.path.which("blkid") def blkid(device=None, token=None): """ Return block device attributes: UUID, LABEL, etc. This function only works on systems where blkid is available. device Device name from the system token Any valid token used for the search CLI Example: .. code-block:: bash salt '*' disk.blkid salt '*' disk.blkid /dev/sda salt '*' disk.blkid token='UUID=6a38ee5-7235-44e7-8b22-816a403bad5d' salt '*' disk.blkid token='TYPE=ext4' """ cmd = ["blkid"] if device: cmd.append(device) elif token: cmd.extend(["-t", token]) ret = {} blkid_result = __salt__["cmd.run_all"](cmd, python_shell=False) if blkid_result["retcode"] > 0: return ret for line in blkid_result["stdout"].splitlines(): if not line: continue comps = line.split() device = comps[0][:-1] info = {} device_attributes = re.split('"*"', line.partition(" ")[2]) for key, value in zip(*[iter(device_attributes)] * 2): key = key.strip("=").strip(" ") info[key] = value.strip('"') ret[device] = info return ret def tune(device, **kwargs): """ Set attributes for the specified device CLI Example: .. code-block:: bash salt '*' disk.tune /dev/sda1 read-ahead=1024 read-write=True Valid options are: ``read-ahead``, ``filesystem-read-ahead``, ``read-only``, ``read-write``. See the ``blockdev(8)`` manpage for a more complete description of these options. """ kwarg_map = { "read-ahead": "setra", "filesystem-read-ahead": "setfra", "read-only": "setro", "read-write": "setrw", } opts = "" args = [] for key in kwargs: if key in kwarg_map: switch = kwarg_map[key] if key != "read-write": args.append(switch.replace("set", "get")) else: args.append("getro") if kwargs[key] == "True" or kwargs[key] is True: opts += f"--{key} " else: opts += f"--{switch} {kwargs[key]} " cmd = f"blockdev {opts}{device}" out = __salt__["cmd.run"](cmd, python_shell=False).splitlines() return dump(device, args) def wipe(device): """ Remove the filesystem information CLI Example: .. code-block:: bash salt '*' disk.wipe /dev/sda1 """ cmd = f"wipefs -a {device}" try: out = __salt__["cmd.run_all"](cmd, python_shell=False) except subprocess.CalledProcessError as err: return False if out["retcode"] == 0: return True else: log.error("Error wiping device %s: %s", device, out["stderr"]) return False def dump(device, args=None): """ Return all contents of dumpe2fs for a specified device CLI Example: .. code-block:: bash salt '*' disk.dump /dev/sda1 """ cmd = ( "blockdev --getro --getsz --getss --getpbsz --getiomin --getioopt --getalignoff" " --getmaxsect --getsize --getsize64 --getra --getfra {}".format(device) ) ret = {} opts = [c[2:] for c in cmd.split() if c.startswith("--")] out = __salt__["cmd.run_all"](cmd, python_shell=False) if out["retcode"] == 0: lines = [line for line in out["stdout"].splitlines() if line] count = 0 for line in lines: ret[opts[count]] = line count = count + 1 if args: temp_ret = {} for arg in args: temp_ret[arg] = ret[arg] return temp_ret else: return ret else: return False def resize2fs(device): """ Resizes the filesystem. CLI Example: .. code-block:: bash salt '*' disk.resize2fs /dev/sda1 """ cmd = f"resize2fs {device}" try: out = __salt__["cmd.run_all"](cmd, python_shell=False) except subprocess.CalledProcessError as err: return False if out["retcode"] == 0: return True @salt.utils.decorators.path.which("sync") @salt.utils.decorators.path.which("mkfs") def format_( device, fs_type="ext4", inode_size=None, lazy_itable_init=None, fat=None, force=False, ): """ Format a filesystem onto a device .. versionadded:: 2016.11.0 device The device in which to create the new filesystem fs_type The type of filesystem to create inode_size Size of the inodes This option is only enabled for ext and xfs filesystems lazy_itable_init If enabled and the uninit_bg feature is enabled, the inode table will not be fully initialized by mke2fs. This speeds up filesystem initialization noticeably, but it requires the kernel to finish initializing the filesystem in the background when the filesystem is first mounted. If the option value is omitted, it defaults to 1 to enable lazy inode table zeroing. This option is only enabled for ext filesystems fat FAT size option. Can be 12, 16 or 32, and can only be used on fat or vfat filesystems. force Force mke2fs to create a filesystem, even if the specified device is not a partition on a block special device. This option is only enabled for ext and xfs filesystems This option is dangerous, use it with caution. CLI Example: .. code-block:: bash salt '*' disk.format /dev/sdX1 """ cmd = ["mkfs", "-t", str(fs_type)] if inode_size is not None: if fs_type[:3] == "ext": cmd.extend(["-i", str(inode_size)]) elif fs_type == "xfs": cmd.extend(["-i", f"size={inode_size}"]) if lazy_itable_init is not None: if fs_type[:3] == "ext": cmd.extend(["-E", f"lazy_itable_init={lazy_itable_init}"]) if fat is not None and fat in (12, 16, 32): if fs_type[-3:] == "fat": cmd.extend(["-F", fat]) if force: if fs_type[:3] == "ext": cmd.append("-F") elif fs_type == "xfs": cmd.append("-f") cmd.append(str(device)) mkfs_success = __salt__["cmd.retcode"](cmd, ignore_retcode=True) == 0 sync_success = __salt__["cmd.retcode"]("sync", ignore_retcode=True) == 0 return all([mkfs_success, sync_success]) @salt.utils.decorators.path.which_bin(["lsblk", "df"]) def fstype(device): """ Return the filesystem name of the specified device .. versionadded:: 2016.11.0 device The name of the device CLI Example: .. code-block:: bash salt '*' disk.fstype /dev/sdX1 """ if salt.utils.path.which("lsblk"): lsblk_out = __salt__["cmd.run"](f"lsblk -o fstype {device}").splitlines() if len(lsblk_out) > 1: fs_type = lsblk_out[1].strip() if fs_type: return fs_type if salt.utils.path.which("df"): # the fstype was not set on the block device, so inspect the filesystem # itself for its type if __grains__["kernel"] == "AIX" and os.path.isfile("/usr/sysv/bin/df"): df_out = __salt__["cmd.run"](f"/usr/sysv/bin/df -n {device}").split() if len(df_out) > 2: fs_type = df_out[2] if fs_type: return fs_type else: df_out = __salt__["cmd.run"](f"df -T {device}").splitlines() if len(df_out) > 1: fs_type = df_out[1] if fs_type: return fs_type return "" @salt.utils.decorators.path.which("hdparm") def _hdparm(args, failhard=True): """ Execute hdparm Fail hard when required return output when possible """ cmd = f"hdparm {args}" result = __salt__["cmd.run_all"](cmd) if result["retcode"] != 0: msg = "{}: {}".format(cmd, result["stderr"]) if failhard: raise CommandExecutionError(msg) else: log.warning(msg) return result["stdout"] @salt.utils.decorators.path.which("hdparm") def hdparms(disks, args=None): """ Retrieve all info's for all disks parse 'em into a nice dict (which, considering hdparms output, is quite a hassle) .. versionadded:: 2016.3.0 CLI Example: .. code-block:: bash salt '*' disk.hdparms /dev/sda """ all_parms = "aAbBcCdgHiJkMmNnQrRuW" if args is None: args = all_parms elif isinstance(args, (list, tuple)): args = "".join(args) if not isinstance(disks, (list, tuple)): disks = [disks] out = {} for disk in disks: if not disk.startswith("/dev"): disk = f"/dev/{disk}" disk_data = {} for line in _hdparm(f"-{args} {disk}", False).splitlines(): line = line.strip() if not line or line == disk + ":": continue if ":" in line: key, vals = line.split(":", 1) key = re.sub(r" is$", "", key) elif "=" in line: key, vals = line.split("=", 1) else: continue key = key.strip().lower().replace(" ", "_") vals = vals.strip() rvals = [] if re.match(r"[0-9]+ \(.*\)", vals): vals = vals.split(" ") rvals.append(int(vals[0])) rvals.append(vals[1].strip("()")) else: valdict = {} for val in re.split(r"[/,]", vals.strip()): val = val.strip() try: val = int(val) rvals.append(val) except Exception: # pylint: disable=broad-except if "=" in val: deep_key, val = val.split("=", 1) deep_key = deep_key.strip() val = val.strip() if val: valdict[deep_key] = val elif val: rvals.append(val) if valdict: rvals.append(valdict) if not rvals: continue elif len(rvals) == 1: rvals = rvals[0] disk_data[key] = rvals out[disk] = disk_data return out @salt.utils.decorators.path.which("hdparm") def hpa(disks, size=None): """ Get/set Host Protected Area settings T13 INCITS 346-2001 (1367D) defines the BEER (Boot Engineering Extension Record) and PARTIES (Protected Area Run Time Interface Extension Services), allowing for a Host Protected Area on a disk. It's often used by OEMS to hide parts of a disk, and for overprovisioning SSD's .. warning:: Setting the HPA might clobber your data, be very careful with this on active disks! .. versionadded:: 2016.3.0 CLI Example: .. code-block:: bash salt '*' disk.hpa /dev/sda salt '*' disk.hpa /dev/sda 5% salt '*' disk.hpa /dev/sda 10543256 """ hpa_data = {} for disk, data in hdparms(disks, "N").items(): visible, total, status = next(iter(data.values())) if visible == total or "disabled" in status: hpa_data[disk] = {"total": total} else: hpa_data[disk] = { "total": total, "visible": visible, "hidden": total - visible, } if size is None: return hpa_data for disk, data in hpa_data.items(): try: size = data["total"] - int(size) except Exception: # pylint: disable=broad-except if "%" in size: size = int(size.strip("%")) size = (100 - size) * data["total"] size /= 100 if size <= 0: size = data["total"] _hdparm(f"--yes-i-know-what-i-am-doing -Np{size} {disk}") def smart_attributes(dev, attributes=None, values=None): """ Fetch SMART attributes Providing attributes will deliver only requested attributes Providing values will deliver only requested values for attributes Default is the Backblaze recommended set (https://www.backblaze.com/blog/hard-drive-smart-stats/): (5,187,188,197,198) .. versionadded:: 2016.3.0 CLI Example: .. code-block:: bash salt '*' disk.smart_attributes /dev/sda salt '*' disk.smart_attributes /dev/sda attributes=(5,187,188,197,198) """ if not dev.startswith("/dev/"): dev = "/dev/" + dev cmd = f"smartctl --attributes {dev}" smart_result = __salt__["cmd.run_all"](cmd, output_loglevel="quiet") if smart_result["retcode"] != 0: raise CommandExecutionError(smart_result["stderr"]) smart_result = iter(smart_result["stdout"].splitlines()) fields = [] for line in smart_result: if line.startswith("ID#"): fields = re.split(r"\s+", line.strip()) fields = [key.lower() for key in fields[1:]] break if values is not None: fields = [field if field in values else "_" for field in fields] smart_attr = {} for line in smart_result: if not re.match(r"[\s]*\d", line): break line = re.split(r"\s+", line.strip(), maxsplit=len(fields)) attr = int(line[0]) if attributes is not None and attr not in attributes: continue data = dict(zip(fields, line[1:])) try: del data["_"] except Exception: # pylint: disable=broad-except pass for field in data: val = data[field] try: val = int(val) except Exception: # pylint: disable=broad-except try: val = [int(value) for value in val.split(" ")] except Exception: # pylint: disable=broad-except pass data[field] = val smart_attr[attr] = data return smart_attr @salt.utils.decorators.path.which("iostat") def iostat(interval=1, count=5, disks=None): """ Gather and return (averaged) IO stats. .. versionadded:: 2016.3.0 .. versionchanged:: 2016.11.4 Added support for AIX CLI Example: .. code-block:: bash salt '*' disk.iostat 1 5 disks=sda """ if salt.utils.platform.is_linux(): return _iostat_linux(interval, count, disks) elif salt.utils.platform.is_freebsd(): return _iostat_fbsd(interval, count, disks) elif salt.utils.platform.is_aix(): return _iostat_aix(interval, count, disks) def _iostats_dict(header, stats): """ Transpose collected data, average it, stomp it in dict using header Use Decimals so we can properly calc & round, convert to float 'caus' we can't transmit Decimals over 0mq """ stats = [ float((sum(stat) / len(stat)).quantize(decimal.Decimal(".01"))) for stat in zip(*stats) ] stats = dict(zip(header, stats)) return stats def _iostat_fbsd(interval, count, disks): """ Tested on FreeBSD, quite likely other BSD's only need small changes in cmd syntax """ if disks is None: iostat_cmd = f"iostat -xC -w {interval} -c {count} " elif isinstance(disks, str): iostat_cmd = f"iostat -x -w {interval} -c {count} {disks}" else: iostat_cmd = "iostat -x -w {} -c {} {}".format(interval, count, " ".join(disks)) sys_stats = [] dev_stats = collections.defaultdict(list) sys_header = [] dev_header = [] h_len = 1000 # randomly absurdly high ret = iter( __salt__["cmd.run_stdout"](iostat_cmd, output_loglevel="quiet").splitlines() ) for line in ret: if not line.startswith("device"): continue elif not dev_header: dev_header = line.split()[1:] while line is not False: line = next(ret, False) if not line or not line[0].isalnum(): break line = line.split() disk = line[0] stats = [decimal.Decimal(x) for x in line[1:]] # h_len will become smallest number of fields in stat lines if len(stats) < h_len: h_len = len(stats) dev_stats[disk].append(stats) iostats = {} # The header was longer than the smallest number of fields # Therefore the sys stats are hidden in there if h_len < len(dev_header): sys_header = dev_header[h_len:] dev_header = dev_header[0:h_len] for disk, stats in dev_stats.items(): if len(stats[0]) > h_len: sys_stats = [stat[h_len:] for stat in stats] dev_stats[disk] = [stat[0:h_len] for stat in stats] iostats["sys"] = _iostats_dict(sys_header, sys_stats) for disk, stats in dev_stats.items(): iostats[disk] = _iostats_dict(dev_header, stats) return iostats def _iostat_linux(interval, count, disks): if disks is None: iostat_cmd = f"iostat -x {interval} {count} " elif isinstance(disks, str): iostat_cmd = f"iostat -xd {interval} {count} {disks}" else: iostat_cmd = "iostat -xd {} {} {}".format(interval, count, " ".join(disks)) sys_stats = [] dev_stats = collections.defaultdict(list) sys_header = [] dev_header = [] ret = iter( __salt__["cmd.run_stdout"](iostat_cmd, output_loglevel="quiet").splitlines() ) for line in ret: if line.startswith("avg-cpu:"): if not sys_header: sys_header = tuple(line.split()[1:]) line = [decimal.Decimal(x) for x in next(ret).split()] sys_stats.append(line) elif line.startswith("Device:"): if not dev_header: dev_header = tuple(line.split()[1:]) while line is not False: line = next(ret, False) if not line or not line[0].isalnum(): break line = line.split() disk = line[0] stats = [decimal.Decimal(x) for x in line[1:]] dev_stats[disk].append(stats) iostats = {} if sys_header: iostats["sys"] = _iostats_dict(sys_header, sys_stats) for disk, stats in dev_stats.items(): iostats[disk] = _iostats_dict(dev_header, stats) return iostats def _iostat_aix(interval, count, disks): """ AIX support to gather and return (averaged) IO stats. """ log.debug("AIX disk iostat entry") if disks is None: iostat_cmd = f"iostat -dD {interval} {count} " elif isinstance(disks, str): iostat_cmd = f"iostat -dD {disks} {interval} {count}" else: iostat_cmd = "iostat -dD {} {} {}".format(" ".join(disks), interval, count) ret = {} procn = None fields = [] disk_name = "" disk_mode = "" dev_stats = collections.defaultdict(list) for line in __salt__["cmd.run"](iostat_cmd).splitlines(): # Note: iostat -dD is per-system # # root@l490vp031_pub:~/devtest# iostat -dD hdisk6 1 3 # # System configuration: lcpu=8 drives=1 paths=2 vdisks=2 # # hdisk6 xfer: %tm_act bps tps bread bwrtn # 0.0 0.0 0.0 0.0 0.0 # read: rps avgserv minserv maxserv timeouts fails # 0.0 0.0 0.0 0.0 0 0 # write: wps avgserv minserv maxserv timeouts fails # 0.0 0.0 0.0 0.0 0 0 # queue: avgtime mintime maxtime avgwqsz avgsqsz sqfull # 0.0 0.0 0.0 0.0 0.0 0.0 # -------------------------------------------------------------------------------- # # hdisk6 xfer: %tm_act bps tps bread bwrtn # 9.6 16.4K 4.0 16.4K 0.0 # read: rps avgserv minserv maxserv timeouts fails # 4.0 4.9 0.3 9.9 0 0 # write: wps avgserv minserv maxserv timeouts fails # 0.0 0.0 0.0 0.0 0 0 # queue: avgtime mintime maxtime avgwqsz avgsqsz sqfull # 0.0 0.0 0.0 0.0 0.0 0.0 # -------------------------------------------------------------------------------- # # hdisk6 xfer: %tm_act bps tps bread bwrtn # 0.0 0.0 0.0 0.0 0.0 # read: rps avgserv minserv maxserv timeouts fails # 0.0 0.0 0.3 9.9 0 0 # write: wps avgserv minserv maxserv timeouts fails # 0.0 0.0 0.0 0.0 0 0 # queue: avgtime mintime maxtime avgwqsz avgsqsz sqfull # 0.0 0.0 0.0 0.0 0.0 0.0 # -------------------------------------------------------------------------------- if not line or line.startswith("System") or line.startswith("-----------"): continue if not re.match(r"\s", line): # seen disk name dsk_comps = line.split(":") dsk_firsts = dsk_comps[0].split() disk_name = dsk_firsts[0] disk_mode = dsk_firsts[1] fields = dsk_comps[1].split() if disk_name not in dev_stats.keys(): dev_stats[disk_name] = [] procn = len(dev_stats[disk_name]) dev_stats[disk_name].append({}) dev_stats[disk_name][procn][disk_mode] = {} dev_stats[disk_name][procn][disk_mode]["fields"] = fields dev_stats[disk_name][procn][disk_mode]["stats"] = [] continue if ":" in line: comps = line.split(":") fields = comps[1].split() disk_mode = comps[0].lstrip() if disk_mode not in dev_stats[disk_name][0].keys(): dev_stats[disk_name][0][disk_mode] = {} dev_stats[disk_name][0][disk_mode]["fields"] = fields dev_stats[disk_name][0][disk_mode]["stats"] = [] else: line = line.split() stats = [_parse_numbers(x) for x in line[:]] dev_stats[disk_name][0][disk_mode]["stats"].append(stats) iostats = {} for disk, list_modes in dev_stats.items(): iostats[disk] = {} for modes in list_modes: for disk_mode in modes.keys(): fields = modes[disk_mode]["fields"] stats = modes[disk_mode]["stats"] iostats[disk][disk_mode] = _iostats_dict(fields, stats) return iostats def get_fstype_from_path(path): """ Return the filesystem type of the underlying device for a specified path. .. versionadded:: 3006.0 path The path for the function to evaluate. CLI Example: .. code-block:: bash salt '*' disk.get_fstype_from_path /root """ dev = __salt__["mount.get_device_from_path"](path) return fstype(dev)