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/cpanel/ea-ruby24/src/passenger-release-6.0.20/src/agent/Watchdog
Viewing File: /opt/cpanel/ea-ruby24/src/passenger-release-6.0.20/src/agent/Watchdog/WatchdogMain.cpp
/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2010-2018 Phusion Holding B.V. * * "Passenger", "Phusion Passenger" and "Union Station" are registered * trademarks of Phusion Holding B.V. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ // Include ev++.h early to avoid macro clash on EV_ERROR. #include <ev++.h> #include <oxt/thread.hpp> #include <oxt/system_calls.hpp> #include <boost/function.hpp> #include <boost/bind/bind.hpp> #include <boost/make_shared.hpp> #include <boost/foreach.hpp> #include <boost/enable_shared_from_this.hpp> #include <string> #include <utility> #include <vector> #include <algorithm> #if !defined(sun) && !defined(__sun) #define HAVE_FLOCK #endif #ifdef __linux__ #include <sys/prctl.h> #endif #include <sys/select.h> #include <sys/types.h> #include <sys/time.h> #ifdef HAVE_FLOCK #include <sys/file.h> #endif #include <sys/resource.h> #include <unistd.h> #include <pwd.h> #include <grp.h> #include <fcntl.h> #include <cstdio> #include <cstdlib> #include <cstring> #include <cerrno> #include <jsoncpp/json.h> #include <Shared/Fundamentals/Initialization.h> #include <Shared/ApiServerUtils.h> #include <Core/OptionParser.h> #include <Watchdog/Config.h> #include <Watchdog/ApiServer.h> #include <JsonTools/Autocast.h> #include <Constants.h> #include <InstanceDirectory.h> #include <FileDescriptor.h> #include <FileTools/PathSecurityCheck.h> #include <SystemTools/UserDatabase.h> #include <SystemTools/ContainerHelpers.h> #include <RandomGenerator.h> #include <BackgroundEventLoop.h> #include <LoggingKit/LoggingKit.h> #include <Exceptions.h> #include <StaticString.h> #include <Hooks.h> #include <IOTools/IOUtils.h> #include <IOTools/MessageIO.h> #include <Utils.h> #include <Utils/Timer.h> #include <Utils/ScopeGuard.h> #include <StrIntTools/StrIntUtils.h> #include <Utils/OptionParsing.h> #include <Utils/VariantMap.h> using namespace std; using namespace boost; using namespace oxt; using namespace Passenger; using namespace Passenger::Agent::Fundamentals; enum OomFileType { OOM_ADJ, OOM_SCORE_ADJ }; #define REQUEST_SOCKET_PASSWORD_SIZE 64 class InstanceDirToucher; class AgentWatcher; /***** Working objects *****/ namespace Passenger { namespace Watchdog { struct WorkingObjects { RandomGenerator randomGenerator; EventFd errorEvent; EventFd exitEvent; uid_t defaultUid; gid_t defaultGid; InstanceDirectoryPtr instanceDir; int startupReportFile; int lockFile; vector<string> cleanupPidfiles; bool pidsCleanedUp; bool pidFileCleanedUp; string corePidFile; string fdPassingPassword; Json::Value extraConfigToPassToSubAgents; Json::Value controllerAddresses; Json::Value coreApiServerAddresses; Json::Value coreApiServerAuthorizations; Json::Value watchdogApiServerAddresses; Json::Value watchdogApiServerAuthorizations; int apiServerFds[SERVER_KIT_MAX_SERVER_ENDPOINTS]; BackgroundEventLoop *bgloop; ServerKit::Context *serverKitContext; ServerKit::Schema serverKitSchema; ApiServer::ApiServer *apiServer; WorkingObjects() : errorEvent(__FILE__, __LINE__, "WorkingObjects: errorEvent"), exitEvent(__FILE__, __LINE__, "WorkingObjects: exitEvent"), startupReportFile(-1), pidsCleanedUp(false), pidFileCleanedUp(false), extraConfigToPassToSubAgents(Json::objectValue), controllerAddresses(Json::arrayValue), coreApiServerAddresses(Json::arrayValue), coreApiServerAuthorizations(Json::arrayValue), watchdogApiServerAddresses(Json::arrayValue), watchdogApiServerAuthorizations(Json::arrayValue), bgloop(NULL), serverKitContext(NULL), apiServer(NULL) { for (unsigned int i = 0; i < SERVER_KIT_MAX_SERVER_ENDPOINTS; i++) { apiServerFds[i] = -1; } } }; typedef boost::shared_ptr<WorkingObjects> WorkingObjectsPtr; } // namespace Watchdog } // namespace Passenger using namespace Passenger::Watchdog; static WrapperRegistry::Registry *watchdogWrapperRegistry; static Schema *watchdogSchema; static ConfigKit::Store *watchdogConfig; static WorkingObjects *workingObjects; static void cleanup(const WorkingObjectsPtr &wo); #include "AgentWatcher.cpp" #include "InstanceDirToucher.cpp" #include "CoreWatcher.cpp" /***** Functions *****/ #if !BOOST_OS_MACOS struct WatchdogOomAdjustResult { struct Message { LoggingKit::Level level; string text; }; string oldScore; // LoggingKit has not been initialized yet when setOomScoreNeverKill() // is called, so we store the messages here and print them after // LoggingKit initialization. vector<Message> messages; }; static FILE * openOomAdjFileGetType(const char *mode, OomFileType &type, string &path) { FILE *f = fopen("/proc/self/oom_score_adj", mode); if (f == NULL) { f = fopen("/proc/self/oom_adj", mode); if (f == NULL) { return NULL; } else { type = OOM_ADJ; path = "/proc/self/oom_adj"; return f; } } else { type = OOM_SCORE_ADJ; path = "/proc/self/oom_score_adj"; return f; } } /** * Set the current process's OOM score to "never kill". */ static WatchdogOomAdjustResult setOomScoreNeverKill() { WatchdogOomAdjustResult result; FILE *f; string path; OomFileType type; int e; if (geteuid() != 0) { WatchdogOomAdjustResult::Message msg; msg.level = LoggingKit::DEBUG; msg.text = "Not adjusting Watchdog's OOM score because not running with root privileges"; result.messages.push_back(msg); return result; } f = openOomAdjFileGetType("r", type, path); if (f == NULL) { e = errno; P_ERROR("Error adjusting Watchdog's OOM score: error opening both" " /proc/self/oom_score_adj and /proc/self/oom_adj for reading: " << strerror(e) << " (errno=" << e << ")"); return result; } // mark if this is a legacy score so we won't try to write it as OOM_SCORE_ADJ if (type == OOM_ADJ) { result.oldScore.append("l"); } char buf[1024]; size_t bytesRead; while (true) { bytesRead = fread(buf, 1, sizeof(buf), f); if (bytesRead == 0 && feof(f)) { break; } else if (bytesRead == 0 && ferror(f)) { P_ERROR("Error adjusting Watchdog's OOM score: error reading " << path); fclose(f); result.oldScore.clear(); return result; } else { result.oldScore.append(buf, bytesRead); } } fclose(f); f = fopen(path.c_str(), "w"); if (f == NULL) { e = errno; WatchdogOomAdjustResult::Message msg; msg.level = LoggingKit::ERROR; msg.text = "Error adjusting Watchdog's OOM score: error opening " + path + " for writing: " + strerror(e) + " (errno=" + toString(e) + ")"; result.messages.push_back(msg); result.oldScore.clear(); return result; } if (type == OOM_SCORE_ADJ) { fprintf(f, "-1000\n"); } else { assert(type == OOM_ADJ); fprintf(f, "-17\n"); } e = fflush(f); if (e != 0) { e = errno; WatchdogOomAdjustResult::Message msg; if (autoDetectInContainer()) { msg.level = LoggingKit::INFO; msg.text = "Running in container, so couldn't adjust Watchdog's" " OOM score through " + path; } else { msg.level = LoggingKit::ERROR; msg.text = "Error adjusting Watchdog's OOM score: error writing to " + path + ": " + strerror(e) + " (errno=" + toString(e) + ")"; } result.messages.push_back(msg); } e = fclose(f); if (e == EOF) { e = errno; WatchdogOomAdjustResult::Message msg; msg.level = LoggingKit::ERROR; msg.text = "Error adjusting Watchdog's OOM score: error closing " + path + ": " + strerror(e) + " (errno=" + toString(e) + ")"; result.messages.push_back(msg); } return result; } static void printOomAdjustResultMessages(const WatchdogOomAdjustResult &result) { vector<WatchdogOomAdjustResult::Message>::const_iterator it; for (it = result.messages.begin(); it != result.messages.end(); it++) { P_LOG(LoggingKit::context, it->level, __FILE__, __LINE__, it->text); } } #endif static void terminationHandler(int signo) { ssize_t ret = write(workingObjects->exitEvent.writerFd(), "x", 1); (void) ret; // Don't care about the result. } /** * Wait until the starter process has exited or sent us an exit command, * or until one of the watcher threads encounter an error. If a thread * encountered an error then the error message will be printed. * * Returns whether this watchdog should exit gracefully, which is only the * case if the web server sent us an exit command and no thread encountered * an error. */ static bool waitForStarterProcessOrWatchers(const WorkingObjectsPtr &wo, vector<AgentWatcherPtr> &watchers) { TRACE_POINT(); fd_set fds; int max = -1, ret; char x; wo->bgloop->start("Main event loop", 0); struct sigaction action; action.sa_handler = terminationHandler; action.sa_flags = SA_RESTART; sigemptyset(&action.sa_mask); sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); FD_ZERO(&fds); if (feedbackFdAvailable()) { FD_SET(FEEDBACK_FD, &fds); max = std::max(max, FEEDBACK_FD); } FD_SET(wo->errorEvent.fd(), &fds); max = std::max(max, wo->errorEvent.fd()); FD_SET(wo->exitEvent.fd(), &fds); max = std::max(max, wo->exitEvent.fd()); UPDATE_TRACE_POINT(); ret = syscalls::select(max + 1, &fds, NULL, NULL, NULL); if (ret == -1) { int e = errno; P_ERROR("select() failed: " << strerror(e)); return false; } action.sa_handler = SIG_DFL; sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); P_DEBUG("Stopping API server"); wo->bgloop->stop(); for (unsigned int i = 0; i < SERVER_KIT_MAX_SERVER_ENDPOINTS; i++) { if (wo->apiServerFds[i] != -1) { syscalls::close(wo->apiServerFds[i]); } } if (FD_ISSET(wo->errorEvent.fd(), &fds)) { UPDATE_TRACE_POINT(); vector<AgentWatcherPtr>::const_iterator it; string message, backtrace, watcherName; for (it = watchers.begin(); it != watchers.end() && message.empty(); it++) { message = (*it)->getErrorMessage(); backtrace = (*it)->getErrorBacktrace(); watcherName = (*it)->name(); } if (!message.empty() && backtrace.empty()) { P_ERROR("Error in " << watcherName << " watcher:\n " << message); } else if (!message.empty() && !backtrace.empty()) { P_ERROR("Error in " << watcherName << " watcher:\n " << message << "\n" << backtrace); } return false; } else if (FD_ISSET(wo->exitEvent.fd(), &fds)) { return true; } else { UPDATE_TRACE_POINT(); assert(feedbackFdAvailable()); ret = syscalls::read(FEEDBACK_FD, &x, 1); return ret == 1 && x == 'c'; } } string relative(string filename){ string dir = filename.substr(filename.find_last_of('/')+1); return dir; } static vector<pid_t> readCleanupPids(const WorkingObjectsPtr &wo) { vector<pid_t> result; foreach (string filename, wo->cleanupPidfiles) { FILE *f = fopen(relative(filename).c_str(), "r"); if (f != NULL) { char buf[33]; size_t ret; ret = fread(buf, 1, 32, f); fclose(f); if (ret > 0) { buf[ret] = '\0'; result.push_back(atoi(buf)); } else { P_WARN("Cannot read cleanup PID file " << relative(filename).c_str() << " (" << filename << ")"); } } else { P_WARN("Cannot open cleanup PID file " << relative(filename).c_str() << " (" << filename << ")"); } } return result; } static void killCleanupPids(const vector<pid_t> &cleanupPids) { foreach (pid_t pid, cleanupPids) { P_DEBUG("Sending SIGTERM to cleanup PID " << pid); if(kill(pid, SIGTERM) == -1){ int e = errno; P_WARN("Failed to send SIGTERM to " << pid << ", error: " << e << " " << strerror(e)); } } } static void killCleanupPids(const WorkingObjectsPtr &wo) { if (!wo->pidsCleanedUp) { killCleanupPids(readCleanupPids(wo)); wo->pidsCleanedUp = true; } } static void deletePidFile(const WorkingObjectsPtr &wo) { Json::Value pidFile = watchdogConfig->get("watchdog_pid_file"); if (!pidFile.isNull() && !wo->pidFileCleanedUp && watchdogConfig->get("watchdog_pid_file_autodelete").asBool()) { syscalls::unlink(pidFile.asCString()); wo->pidFileCleanedUp = true; } } static void cleanupAgentsInBackground(const WorkingObjectsPtr &wo, vector<AgentWatcherPtr> &watchers, char *argv[]) { boost::this_thread::disable_interruption di; boost::this_thread::disable_syscall_interruption dsi; pid_t pid; int e; pid = fork(); if (pid == 0) { // Child try { vector<AgentWatcherPtr>::const_iterator it; Timer<SystemTime::GRAN_10MSEC> timer(false); fd_set fds, fds2; int max, agentProcessesDone; unsigned long long deadline = 30000; // miliseconds #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(sun) // Change process title. strcpy(argv[0], "PassengerWatchdog (cleaning up...)"); #endif P_DEBUG("Sending SIGTERM to all agent processes"); for (it = watchers.begin(); it != watchers.end(); it++) { (*it)->signalShutdown(); } max = 0; FD_ZERO(&fds); for (it = watchers.begin(); it != watchers.end(); it++) { FD_SET((*it)->getFeedbackFd(), &fds); if ((*it)->getFeedbackFd() > max) { max = (*it)->getFeedbackFd(); } } P_DEBUG("Waiting until all agent processes have exited..."); timer.start(); agentProcessesDone = 0; while (agentProcessesDone != -1 && agentProcessesDone < (int) watchers.size() && timer.elapsed() < deadline) { struct timeval timeout; #ifdef FD_COPY FD_COPY(&fds, &fds2); #else FD_ZERO(&fds2); for (it = watchers.begin(); it != watchers.end(); it++) { FD_SET((*it)->getFeedbackFd(), &fds2); } #endif timeout.tv_sec = 0; timeout.tv_usec = 10000; agentProcessesDone = syscalls::select(max + 1, &fds2, NULL, NULL, &timeout); if (agentProcessesDone > 0 && timer.elapsed() < deadline) { usleep(10000); } } if (agentProcessesDone == -1 || timer.elapsed() >= deadline) { // An error occurred or we've waited long enough. Kill all the // processes. P_WARN("Some " PROGRAM_NAME " agent processes did not exit " << "in time, forcefully shutting down all."); } else { P_DEBUG("All " PROGRAM_NAME " agent processes have exited. Forcing all subprocesses to shut down."); } P_DEBUG("Sending SIGKILL to all agent processes"); for (it = watchers.begin(); it != watchers.end(); it++) { (*it)->forceShutdown(); } cleanup(wo); _exit(0); } catch (const std::exception &e) { P_CRITICAL("An exception occurred during cleaning up: " << e.what()); _exit(1); } catch (...) { P_CRITICAL("An unknown exception occurred during cleaning up"); _exit(1); } } else if (pid == -1) { // Error e = errno; throw SystemException("fork() failed", e); } else { // Parent // Let child process handle cleanup. wo->instanceDir->detach(); } } static void forceAllAgentsShutdown(const WorkingObjectsPtr &wo, vector<AgentWatcherPtr> &watchers) { vector<AgentWatcherPtr>::iterator it; P_DEBUG("Sending SIGTERM to all agent processes"); for (it = watchers.begin(); it != watchers.end(); it++) { (*it)->signalShutdown(); } usleep(1000000); P_DEBUG("Sending SIGKILL to all agent processes"); for (it = watchers.begin(); it != watchers.end(); it++) { (*it)->forceShutdown(); } } static void runHookScriptAndThrowOnError(const char *name) { TRACE_POINT(); HookScriptOptions options; options.name = name; options.spec = watchdogConfig->get(string("hook_") + name).asString(); options.agentConfig = watchdogConfig->inspectEffectiveValues(); if (!runHookScripts(options)) { throw RuntimeException(string("Hook script ") + name + " failed"); } } static void usage() { printf("Usage: " AGENT_EXE " watchdog <OPTIONS...>\n"); printf("Runs the " PROGRAM_NAME " watchdog.\n\n"); printf("The watchdog runs and supervises various " PROGRAM_NAME " agent processes,\n"); printf("at this moment only the core (there was also a process called UstRouter but\n"); printf("it no longer exists). Arguments marked with \"[A]\", e.g.\n"); printf("--passenger-root and --log-level, are automatically passed to all supervised\n"); printf("agents, unless you explicitly override them by passing extra arguments to a\n"); printf("supervised agent specifically. You can pass arguments to a supervised agent by\n"); printf("wrapping those arguments between --BC/--EC and --BU/--EU.\n"); printf("\n"); printf(" Example 1: pass some arguments to the core.\n\n"); printf(" " SHORT_PROGRAM_NAME " watchdog --passenger-root /opt/passenger \\\n"); printf(" --BC --listen tcp://127.0.0.1:4000 /webapps/foo\n"); printf("\n"); printf(" Example 2: pass some arguments to the core, and some others to the\n"); printf(" UstRouter. The watchdog itself and the core will use logging\n"); printf(" level 3, while the UstRouter will use logging level 1.\n\n"); printf(" " SHORT_PROGRAM_NAME " watchdog --passenger-root /opt/passenger \\\n"); printf(" --BC --listen tcp://127.0.0.1:4000 /webapps/foo --EC \\\n"); printf(" --BU --log-level 1 --EU \\\n"); printf(" --log-level 3\n"); printf("\n"); printf("Required options:\n"); printf(" --passenger-root PATH The location to the " PROGRAM_NAME " source\n"); printf(" directory [A]\n"); printf("\n"); printf("Argument passing options (optional):\n"); printf(" --BC, --begin-core-args Signals the beginning of arguments to pass to the\n"); printf(" Passenger core\n"); printf(" --EC, --end-core-args Signals the end of arguments to pass to the\n"); printf(" Passenger core\n"); printf(" --BU, --begin-ust-router-args\n"); printf(" Signals the beginning of arguments to pass to the\n"); printf(" UstRouter\n"); printf(" --EU, --end-ust-router-args\n"); printf(" Signals the end of arguments to pass to the\n"); printf(" UstRouter\n"); printf("\n"); printf("Other options (optional):\n"); printf(" --api-listen ADDRESS Listen on the given address for API commands.\n"); printf(" The address must be formatted as tcp://IP:PORT for\n"); printf(" TCP sockets, or unix:PATH for Unix domain sockets.\n"); printf(" You can specify this option multiple times (up to\n"); printf(" %u times) to listen on multiple addresses.\n", SERVER_KIT_MAX_SERVER_ENDPOINTS - 1); printf(" --authorize [LEVEL]:USERNAME:PASSWORDFILE\n"); printf(" Enables authentication on the API server, through\n"); printf(" the given API account. LEVEL indicates the\n"); printf(" privilege level (see below). PASSWORDFILE must\n"); printf(" point to a file containing the password\n"); printf("\n"); printf(" --instance-registry-dir Directory to register instance into.\n"); printf(" Default: %s\n", getSystemTempDir()); printf("\n"); printf(" --spawn-dir Directory for spawn handshakes.\n"); printf(" Default: %s\n", getSystemTempDir()); printf("\n"); printf(" --no-user-switching Disables user switching support [A]\n"); printf(" --default-user NAME Default user to start apps as, when user\n"); printf(" switching is enabled. Default: " DEFAULT_WEB_APP_USER "\n"); printf(" --default-group NAME Default group to start apps as, when user\n"); printf(" switching is disabled. Default: the default\n"); printf(" user's primary group\n"); printf("\n"); printf(" --daemonize Daemonize into the background\n"); printf(" --user NAME Lower privilege to the given user\n"); printf(" --pid-file PATH Store the watchdog's PID in the given file. The\n"); printf(" file is deleted on exit\n"); printf(" --no-delete-pid-file Do not delete PID file on exit\n"); printf(" --log-file PATH Log to the given file.\n"); printf(" --log-level LEVEL Logging level. [A] Default: %d\n", DEFAULT_LOG_LEVEL); printf(" --disable-log-prefix Disables prefixing application logs with App PID stdout\n"); printf(" --report-file PATH Upon successful initialization, report instance\n"); printf(" information to the given file, in JSON format\n"); printf(" --cleanup-pidfile PATH Upon shutdown, kill the process specified by\n"); printf(" the given PID file\n"); printf("\n"); printf(" --ctl NAME=VALUE Set custom internal option\n"); printf("\n"); printf(" -h, --help Show this help\n"); printf("\n"); printf("[A] = Automatically passed to supervised agents\n"); printf("\n"); printf("API account privilege levels (ordered from most to least privileges):\n"); printf(" readonly Read-only access\n"); printf(" full Full access (default)\n"); } static void parseOptions(int argc, const char *argv[], ConfigKit::Store &config) { OptionParser p(usage); Json::Value updates(Json::objectValue); int i = 2; while (i < argc) { if (p.isValueFlag(argc, i, argv[i], '\0', "--passenger-root")) { updates["passenger_root"] = argv[i + 1]; i += 2; } else if (p.isFlag(argv[i], '\0', "--BC") || p.isFlag(argv[i], '\0', "--begin-core-args")) { i++; while (i < argc) { if (p.isFlag(argv[i], '\0', "--EC") || p.isFlag(argv[i], '\0', "--end-core-args")) { i++; break; } else if (p.isFlag(argv[i], '\0', "--BU") || p.isFlag(argv[i], '\0', "--begin-ust-router-args")) { break; } else { Json::Value coreUpdates(Json::objectValue); if (!parseCoreOption(argc, argv, i, coreUpdates)) { fprintf(stderr, "ERROR: unrecognized core argument %s. Please " "type '%s core --help' for usage.\n", argv[i], argv[0]); exit(1); } Json::Value::iterator it, end = coreUpdates.end(); for (it = coreUpdates.begin(); it != end; it++) { string translatedName = watchdogSchema->core.translator. reverseTranslateOne(it.name()); updates[translatedName] = *it; } } } } else if (p.isFlag(argv[i], '\0', "--BU") || p.isFlag(argv[i], '\0', "--begin-ust-router-args")) { i++; while (i < argc) { if (p.isFlag(argv[i], '\0', "--EU") || p.isFlag(argv[i], '\0', "--end-ust-router-args")) { i++; break; } else if (p.isFlag(argv[i], '\0', "--BC") || p.isFlag(argv[i], '\0', "--begin-core-args")) { break; } else { fprintf(stderr, "ERROR: unrecognized UstRouter argument %s. Please " "type '%s ust-router --help' for usage.\n", argv[i], argv[0]); exit(1); } } } else if (p.isValueFlag(argc, i, argv[i], '\0', "--api-listen")) { if (getSocketAddressType(argv[i + 1]) != SAT_UNKNOWN) { Json::Value &addresses = updates["watchdog_api_server_addresses"]; if (addresses.size() == SERVER_KIT_MAX_SERVER_ENDPOINTS - 1) { fprintf(stderr, "ERROR: you may specify up to %u --api-listen addresses.\n", SERVER_KIT_MAX_SERVER_ENDPOINTS - 1); exit(1); } addresses.append(argv[i + 1]); i += 2; } else { fprintf(stderr, "ERROR: invalid address format for --api-listen. The address " "must be formatted as tcp://IP:PORT for TCP sockets, or unix:PATH " "for Unix domain sockets.\n"); exit(1); } } else if (p.isValueFlag(argc, i, argv[i], '\0', "--authorize")) { vector<string> args; split(argv[i + 1], ':', args); if (args.size() < 2 || args.size() > 3) { fprintf(stderr, "ERROR: invalid format for --authorize. The syntax " "is \"[LEVEL:]USERNAME:PASSWORDFILE\".\n"); exit(1); } updates["watchdog_api_server_authorizations"].append(argv[i + 1]); i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--instance-registry-dir")) { updates["instance_registry_dir"] = argv[i + 1]; i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--spawn-dir")) { updates["spawn_dir"] = argv[i + 1]; i += 2; } else if (p.isFlag(argv[i], '\0', "--no-user-switching")) { updates["user_switching"] = false; i++; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--default-user")) { updates["default_user"] = argv[i + 1]; i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--default-group")) { updates["default_group"] = argv[i + 1]; i += 2; } else if (p.isFlag(argv[i], '\0', "--daemonize")) { updates["daemonize"] = true; i++; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--user")) { updates["user"] = argv[i + 1]; i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--pid-file")) { updates["watchdog_pid_file"] = argv[i + 1]; i += 2; } else if (p.isFlag(argv[i], '\0', "--no-delete-pid-file")) { updates["watchdog_pid_file_autodelete"] = false; i++; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--log-level")) { updates["log_level"] = argv[i + 1]; i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--report-file")) { updates["startup_report_file"] = argv[i + 1]; i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--cleanup-pidfile")) { updates["pidfiles_to_delete_on_exit"].append(argv[i + 1]); i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--log-file")) { updates["log_target"] = argv[i + 1]; i += 2; } else if (p.isFlag(argv[i], '\0', "--disable-log-prefix")) { updates["disable_log_prefix"] = true; i++; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--ctl")) { const char *value = strchr(argv[i + 1], '='); if (value == NULL) { fprintf(stderr, "ERROR: '%s' is not a valid --ctl parameter. " "It must be in the form of NAME=VALUE.\n", argv[i + 1]); exit(1); } string name(argv[i + 1], value - argv[i + 1]); value++; if (*value == '\0') { fprintf(stderr, "ERROR: '%s' is not a valid --ctl parameter. " "The value must be non-empty.\n", argv[i + 1]); exit(1); } updates[name] = autocastValueToJson(value); i += 2; } else if (p.isFlag(argv[i], 'h', "--help")) { usage(); exit(0); } else { fprintf(stderr, "ERROR: unrecognized argument %s. Please type " "'%s watchdog --help' for usage.\n", argv[i], argv[0]); exit(1); } } if (!updates.empty()) { vector<ConfigKit::Error> errors; if (!config.update(updates, errors)) { P_BUG("Unable to set initial configuration: " << ConfigKit::toString(errors) << "\n" "Raw initial configuration: " << updates.toStyledString()); } } } static void initializeBareEssentials(int argc, char *argv[], WorkingObjectsPtr &wo) { /* * Some Apache installations (like on OS X) redirect stdout to /dev/null, * so that only stderr is redirected to the log file. We therefore * forcefully redirect stdout to stderr so that everything ends up in the * same place. */ dup2(2, 1); /* * Most operating systems overcommit memory. We *know* that this watchdog process * doesn't use much memory; on OS X it uses about 200 KB of private RSS. If the * watchdog is killed by the system Out-Of-Memory Killer or then it's all over: * the system administrator will have to restart the web server for Phusion * Passenger to be usable again. So here we disable Linux's OOM killer * for this watchdog. Note that the OOM score is inherited by child processes * so we need to restore it after each fork(). */ #if !BOOST_OS_MACOS WatchdogOomAdjustResult oomAdjustResult = setOomScoreNeverKill(); #endif watchdogWrapperRegistry = new WrapperRegistry::Registry(); watchdogWrapperRegistry->finalize(); watchdogSchema = new Schema(watchdogWrapperRegistry); watchdogConfig = new ConfigKit::Store(*watchdogSchema); initializeAgent(argc, &argv, SHORT_PROGRAM_NAME " watchdog", *watchdogConfig, watchdogSchema->core.schema.loggingKit.translator, parseOptions, NULL, 2); // Start all sub-agents with this environment variable. setenv("PASSENGER_USE_FEEDBACK_FD", "true", 1); wo = boost::make_shared<WorkingObjects>(); workingObjects = wo.get(); #if !BOOST_OS_MACOS printOomAdjustResultMessages(oomAdjustResult); wo->extraConfigToPassToSubAgents["oom_score"] = oomAdjustResult.oldScore; #endif } static void maybeSetsid() { /* Become the session leader so that Apache can't kill the * watchdog with killpg() during shutdown, so that a * Ctrl-C only affects the web server, and so that * we can kill all of our subprocesses in a single killpg(). * * WatchdogLauncher.h already calls setsid() before exec()ing * the Watchdog, but Flying Passenger does not. */ if (watchdogConfig->get("setsid").asBool()) { setsid(); } } static void redirectStdinToNull() { int fd = open("/dev/null", O_RDONLY); if (fd != -1) { dup2(fd, 0); close(fd); } } static void maybeDaemonize() { pid_t pid; int e; if (watchdogConfig->get("daemonize").asBool()) { pid = fork(); if (pid == 0) { setsid(); redirectStdinToNull(); } else if (pid == -1) { e = errno; throw SystemException("Cannot fork", e); } else { _exit(0); } } } static void createPidFile() { TRACE_POINT(); Json::Value pidFile = watchdogConfig->get("watchdog_pid_file"); if (!pidFile.isNull()) { char pidStr[32]; snprintf(pidStr, sizeof(pidStr), "%lld", (long long) getpid()); int fd = syscalls::open(pidFile.asCString(), O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { int e = errno; throw FileSystemException("Cannot create PID file " + pidFile.asString(), e, pidFile.asString()); } UPDATE_TRACE_POINT(); FdGuard guard(fd, __FILE__, __LINE__); writeExact(fd, pidStr, strlen(pidStr)); } } static void openStartupReportFile(const WorkingObjectsPtr &wo) { TRACE_POINT(); Json::Value path = watchdogConfig->get("startup_report_file"); if (!path.isNull()) { int fd = syscalls::open(path.asCString(), O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd == -1) { int e = errno; throw FileSystemException("Cannot open report file " + path.asString(), e, path.asString()); } P_LOG_FILE_DESCRIPTOR_OPEN4(fd, __FILE__, __LINE__, "WorkingObjects: startupReportFile"); wo->startupReportFile = fd; } } static void chdirToTmpDir() { const Json::Value pidfiles = watchdogConfig->get("pidfiles_to_delete_on_exit"); if (!pidfiles.empty()) { string str = pidfiles[0].asString(); string dir = str.substr(0,str.find_last_of('/')); if (dir != "" && chdir(dir.c_str()) == -1) { throw RuntimeException("Cannot change working directory to " + dir); } } } static void lowerPrivilege() { TRACE_POINT(); string userName = watchdogConfig->get("user").asString(); if (geteuid() == 0 && !userName.empty()) { OsUser osUser; if (!lookupSystemUserByName(userName, osUser)) { throw NonExistentUserException("Operating system user '" + userName + "' does not exist"); } gid_t gid = osUser.pwd.pw_gid; string groupName = lookupSystemGroupnameByGid(gid); if (initgroups(userName.c_str(), gid) != 0) { int e = errno; throw SystemException("Unable to lower " SHORT_PROGRAM_NAME " watchdog's privilege " "to that of user '" + userName + "' and group '" + groupName + "': cannot set supplementary groups", e); } if (setgid(gid) != 0) { int e = errno; throw SystemException("Unable to lower " SHORT_PROGRAM_NAME " watchdog's privilege " "to that of user '" + userName + "' and group '" + groupName + "': cannot set group ID to " + toString(gid), e); } if (setuid(osUser.pwd.pw_uid) != 0) { int e = errno; throw SystemException("Unable to lower " SHORT_PROGRAM_NAME " watchdog's privilege " "to that of user '" + userName + "' and group '" + groupName + "': cannot set user ID to " + toString(osUser.pwd.pw_uid), e); } #ifdef __linux__ // When we change the uid, /proc/self/pid contents don't change owner, // causing us to lose access to our own /proc/self/pid files. // This prctl call changes those files' ownership. // References: // https://stackoverflow.com/questions/8337846/files-ownergroup-doesnt-change-at-location-proc-pid-after-setuid // http://man7.org/linux/man-pages/man5/proc.5.html (search for "dumpable") prctl(PR_SET_DUMPABLE, 1); #endif setenv("USER", osUser.pwd.pw_name, 1); setenv("HOME", osUser.pwd.pw_dir, 1); setenv("UID", toString(gid).c_str(), 1); } } static void lookupDefaultUidGid(uid_t &uid, gid_t &gid) { const string defaultUser = watchdogConfig->get("default_user").asString(); const string defaultGroup = watchdogConfig->get("default_group").asString(); OsUser osUser; if (!lookupSystemUserByName(defaultUser, osUser)) { throw NonExistentUserException("Default user '" + defaultUser + "' does not exist"); } uid = osUser.pwd.pw_uid; OsGroup osGroup; if (!lookupSystemGroupByName(defaultGroup, osGroup)) { throw NonExistentGroupException("Default group '" + defaultGroup + "' does not exist"); } gid = osGroup.grp.gr_gid; } static void warnIfInstanceDirVulnerable(const string &root) { TRACE_POINT(); if (geteuid() != 0) { return; // Passenger is not root, so no escalation. } vector<string> errors, checkErrors; if (isPathProbablySecureForRootUse(root, errors, checkErrors)) { if (!checkErrors.empty()) { string message = "WARNING: unable to perform privilege escalation vulnerability detection:\n"; foreach (string line, checkErrors) { message.append("\n - " + line); } P_WARN(message); } } else { string message = "WARNING: potential privilege escalation vulnerability detected. " \ PROGRAM_NAME " is running as root, and part(s) of the " SHORT_PROGRAM_NAME " instance directory (" + root + ") can be changed by non-root user(s):\n"; foreach (string line, errors) { message.append("\n - " + line); } foreach (string line, checkErrors) { message.append("\n - " + line); } message.append("\n\nPlease either fix up the permissions for the insecure paths, or use" \ " a different location for the instance dir that can only be modified by root."); P_WARN(message); } } static void initializeWorkingObjects(const WorkingObjectsPtr &wo, InstanceDirToucherPtr &instanceDirToucher, uid_t uidBeforeLoweringPrivilege) { TRACE_POINT(); Json::Value doc; Json::Value::iterator it, end; UPDATE_TRACE_POINT(); lookupDefaultUidGid(wo->defaultUid, wo->defaultGid); doc = watchdogConfig->get("pidfiles_to_delete_on_exit"); for (it = doc.begin(); it != doc.end(); it++) { wo->cleanupPidfiles.push_back(it->asString()); } UPDATE_TRACE_POINT(); InstanceDirectory::CreationOptions instanceOptions; instanceOptions.userSwitching = watchdogConfig->get("user_switching").asBool(); instanceOptions.originalUid = uidBeforeLoweringPrivilege; instanceOptions.defaultUid = wo->defaultUid; instanceOptions.defaultGid = wo->defaultGid; instanceOptions.properties["name"] = wo->randomGenerator.generateAsciiString(8); instanceOptions.properties["integration_mode"] = watchdogConfig->get("integration_mode").asString(); instanceOptions.properties["server_software"] = watchdogConfig->get("server_software").asString(); if (watchdogConfig->get("integration_mode").asString() == "standalone") { instanceOptions.properties["standalone_engine"] = watchdogConfig->get("standalone_engine").asString(); } // check if path is safe warnIfInstanceDirVulnerable(watchdogConfig->get("instance_registry_dir").asString()); warnIfInstanceDirVulnerable(watchdogConfig->get("spawn_dir").asString()); wo->instanceDir = boost::make_shared<InstanceDirectory>(instanceOptions, watchdogConfig->get("instance_registry_dir").asString()); wo->extraConfigToPassToSubAgents["instance_dir"] = wo->instanceDir->getPath(); instanceDirToucher = boost::make_shared<InstanceDirToucher>(wo); UPDATE_TRACE_POINT(); string lockFilePath = wo->instanceDir->getPath() + "/lock"; wo->lockFile = syscalls::open(lockFilePath.c_str(), O_RDONLY); if (wo->lockFile == -1) { int e = errno; throw FileSystemException("Cannot open " + lockFilePath + " for reading", e, lockFilePath); } P_LOG_FILE_DESCRIPTOR_OPEN4(wo->lockFile, __FILE__, __LINE__, "WorkingObjects: lock file"); createFile(wo->instanceDir->getPath() + "/watchdog.pid", toString(getpid())); UPDATE_TRACE_POINT(); string readOnlyAdminPassword = wo->randomGenerator.generateAsciiString(24); string fullAdminPassword = wo->randomGenerator.generateAsciiString(24); if (geteuid() == 0 && !watchdogConfig->get("user_switching").asBool()) { createFile(wo->instanceDir->getPath() + "/read_only_admin_password.txt", readOnlyAdminPassword, S_IRUSR, wo->defaultUid, wo->defaultGid); createFile(wo->instanceDir->getPath() + "/full_admin_password.txt", fullAdminPassword, S_IRUSR, wo->defaultUid, wo->defaultGid); } else { createFile(wo->instanceDir->getPath() + "/read_only_admin_password.txt", readOnlyAdminPassword, S_IRUSR | S_IWUSR); createFile(wo->instanceDir->getPath() + "/full_admin_password.txt", fullAdminPassword, S_IRUSR | S_IWUSR); } if (watchdogConfig->get("core_pid_file").isNull()) { wo->corePidFile = wo->instanceDir->getPath() + "/core.pid"; } else { wo->corePidFile = watchdogConfig->get("core_pid_file").asString(); } wo->fdPassingPassword = wo->randomGenerator.generateAsciiString(24); UPDATE_TRACE_POINT(); wo->controllerAddresses.append("unix:" + wo->instanceDir->getPath() + "/agents.s/core"); doc = watchdogConfig->get("controller_addresses"); for (it = doc.begin(); it != doc.end(); it++) { wo->controllerAddresses.append(*it); } wo->coreApiServerAddresses.append("unix:" + wo->instanceDir->getPath() + "/agents.s/core_api"); doc = watchdogConfig->get("core_api_server_addresses"); for (it = doc.begin(); it != doc.end(); it++) { wo->coreApiServerAddresses.append(*it); } UPDATE_TRACE_POINT(); wo->coreApiServerAuthorizations.append( "readonly:ro_admin:" + wo->instanceDir->getPath() + "/read_only_admin_password.txt"); wo->coreApiServerAuthorizations.append( "full:admin:" + wo->instanceDir->getPath() + "/full_admin_password.txt"); doc = watchdogConfig->get("core_api_server_authorizations"); for (it = doc.begin(); it != doc.end(); it++) { wo->coreApiServerAuthorizations.append(*it); } } static void initializeAgentWatchers(const WorkingObjectsPtr &wo, vector<AgentWatcherPtr> &watchers) { TRACE_POINT(); watchers.push_back(boost::make_shared<CoreWatcher>(wo)); } static void makeFileWorldReadableAndWritable(const string &path) { int ret; do { ret = chmod(path.c_str(), parseModeString("u=rw,g=rw,o=rw")); } while (ret == -1 && errno == EINTR); } static void initializeApiServer(const WorkingObjectsPtr &wo) { TRACE_POINT(); Json::Value doc; Json::Value::iterator it; string description; UPDATE_TRACE_POINT(); wo->watchdogApiServerAuthorizations.append( "readonly:ro_admin:" + wo->instanceDir->getPath() + "/read_only_admin_password.txt"); wo->watchdogApiServerAuthorizations.append( "full:admin:" + wo->instanceDir->getPath() + "/full_admin_password.txt"); doc = watchdogConfig->get("watchdog_api_server_authorizations"); for (it = doc.begin(); it != doc.end(); it++) { wo->watchdogApiServerAuthorizations.append(*it); } UPDATE_TRACE_POINT(); wo->watchdogApiServerAddresses.append( "unix:" + wo->instanceDir->getPath() + "/agents.s/watchdog_api"); doc = watchdogConfig->get("watchdog_api_server_addresses"); for (it = doc.begin(); it != doc.end(); it++) { wo->watchdogApiServerAddresses.append(*it); } UPDATE_TRACE_POINT(); for (unsigned int i = 0; i < wo->watchdogApiServerAddresses.size(); i++) { string address = wo->watchdogApiServerAddresses[i].asString(); P_DEBUG("API server will listen on " << address); wo->apiServerFds[i] = createServer(address, 0, true, __FILE__, __LINE__); if (getSocketAddressType(address) == SAT_UNIX) { makeFileWorldReadableAndWritable(parseUnixSocketAddress(address)); } } UPDATE_TRACE_POINT(); Json::Value contextConfig = watchdogConfig->inspectEffectiveValues(); wo->bgloop = new BackgroundEventLoop(true, true); wo->serverKitContext = new ServerKit::Context( watchdogSchema->apiServerKit.schema, contextConfig, watchdogSchema->apiServerKit.translator); wo->serverKitContext->libev = wo->bgloop->safe; wo->serverKitContext->libuv = wo->bgloop->libuv_loop; wo->serverKitContext->initialize(); UPDATE_TRACE_POINT(); Json::Value apiServerConfig = watchdogConfig->inspectEffectiveValues(); apiServerConfig["fd_passing_password"] = wo->fdPassingPassword; apiServerConfig["authorizations"] = wo->watchdogApiServerAuthorizations; wo->apiServer = new ApiServer::ApiServer( wo->serverKitContext, watchdogSchema->apiServer.schema, apiServerConfig, watchdogSchema->apiServer.translator); wo->apiServer->exitEvent = &wo->exitEvent; wo->apiServer->initialize(); for (unsigned int i = 0; i < wo->watchdogApiServerAddresses.size(); i++) { wo->apiServer->listen(wo->apiServerFds[i]); } } static void startAgents(const WorkingObjectsPtr &wo, vector<AgentWatcherPtr> &watchers) { TRACE_POINT(); foreach (AgentWatcherPtr watcher, watchers) { P_DEBUG("Starting agent: " << watcher->name()); try { watcher->start(); } catch (const std::exception &e) { if (feedbackFdAvailable()) { writeArrayMessage(FEEDBACK_FD, "Watchdog startup error", e.what(), NULL); } else { const oxt::tracable_exception *e2 = dynamic_cast<const oxt::tracable_exception *>(&e); if (e2 != NULL) { P_CRITICAL("ERROR: " << e2->what() << "\n" << e2->backtrace()); } else { P_CRITICAL("ERROR: " << e.what()); } } forceAllAgentsShutdown(wo, watchers); cleanup(wo); exit(1); } // Allow other exceptions to propagate and crash the watchdog. } } static void beginWatchingAgents(const WorkingObjectsPtr &wo, vector<AgentWatcherPtr> &watchers) { foreach (AgentWatcherPtr watcher, watchers) { try { watcher->beginWatching(); } catch (const std::exception &e) { writeArrayMessage(FEEDBACK_FD, "Watchdog startup error", e.what(), NULL); forceAllAgentsShutdown(wo, watchers); cleanup(wo); exit(1); } // Allow other exceptions to propagate and crash the watchdog. } } static void reportStartupResult(const WorkingObjectsPtr &wo, const vector<AgentWatcherPtr> &watchers) { TRACE_POINT(); Json::Value report; report["instance_dir"] = wo->instanceDir->getPath(); foreach (AgentWatcherPtr watcher, watchers) { watcher->reportAgentStartupResult(report); } if (feedbackFdAvailable()) { writeArrayMessage(FEEDBACK_FD, "Agents information", NULL); writeScalarMessage(FEEDBACK_FD, report.toStyledString()); } if (wo->startupReportFile != -1) { string str = report.toStyledString(); writeExact(wo->startupReportFile, str.data(), str.size()); close(wo->startupReportFile); P_LOG_FILE_DESCRIPTOR_CLOSE(wo->startupReportFile); wo->startupReportFile = -1; } } static void finalizeInstanceDir(const WorkingObjectsPtr &wo) { TRACE_POINT(); #ifdef HAVE_FLOCK if (flock(wo->lockFile, LOCK_EX) == -1) { int e = errno; throw SystemException("Cannot obtain exclusive lock on the " "instance directory lock file", e); } #endif wo->instanceDir->finalizeCreation(); } static void cleanup(const WorkingObjectsPtr &wo) { TRACE_POINT(); // We need to call destroy() explicitly because of circular references. if (wo->instanceDir != NULL && wo->instanceDir->isOwner()) { wo->instanceDir->destroy(); wo->instanceDir.reset(); } killCleanupPids(wo); deletePidFile(wo); } int watchdogMain(int argc, char *argv[]) { WorkingObjectsPtr wo; initializeBareEssentials(argc, argv, wo); P_NOTICE("Starting " SHORT_PROGRAM_NAME " watchdog..."); InstanceDirToucherPtr instanceDirToucher; vector<AgentWatcherPtr> watchers; uid_t uidBeforeLoweringPrivilege = geteuid(); try { TRACE_POINT(); maybeSetsid(); maybeDaemonize(); createPidFile(); openStartupReportFile(wo); chdirToTmpDir(); lowerPrivilege(); initializeWorkingObjects(wo, instanceDirToucher, uidBeforeLoweringPrivilege); initializeAgentWatchers(wo, watchers); initializeApiServer(wo); UPDATE_TRACE_POINT(); runHookScriptAndThrowOnError("before_watchdog_initialization"); } catch (const std::exception &e) { if (feedbackFdAvailable()) { writeArrayMessage(FEEDBACK_FD, "Watchdog startup error", e.what(), NULL); } else { const oxt::tracable_exception *e2 = dynamic_cast<const oxt::tracable_exception *>(&e); if (e2 != NULL) { P_CRITICAL("ERROR: " << e2->what() << "\n" << e2->backtrace()); } else { P_CRITICAL("ERROR: " << e.what()); } } if (wo != NULL) { cleanup(wo); } return 1; } // Allow other exceptions to propagate and crash the watchdog. try { TRACE_POINT(); startAgents(wo, watchers); beginWatchingAgents(wo, watchers); reportStartupResult(wo, watchers); finalizeInstanceDir(wo); P_INFO("All " PROGRAM_NAME " agents started!"); UPDATE_TRACE_POINT(); runHookScriptAndThrowOnError("after_watchdog_initialization"); UPDATE_TRACE_POINT(); boost::this_thread::disable_interruption di; boost::this_thread::disable_syscall_interruption dsi; bool shouldExitGracefully = waitForStarterProcessOrWatchers(wo, watchers); if (shouldExitGracefully) { /* Fork a child process which cleans up all the agent processes in * the background and exit this watchdog process so that we don't block * the web server. */ P_DEBUG("Web server exited gracefully; gracefully shutting down all agents..."); } else { P_DEBUG("Web server did not exit gracefully, forcing shutdown of all agents..."); } UPDATE_TRACE_POINT(); runHookScriptAndThrowOnError("before_watchdog_shutdown"); UPDATE_TRACE_POINT(); AgentWatcher::stopWatching(watchers); if (shouldExitGracefully) { UPDATE_TRACE_POINT(); cleanupAgentsInBackground(wo, watchers, argv); // Child process will call cleanup() } else { UPDATE_TRACE_POINT(); forceAllAgentsShutdown(wo, watchers); cleanup(wo); } UPDATE_TRACE_POINT(); runHookScriptAndThrowOnError("after_watchdog_shutdown"); return shouldExitGracefully ? 0 : 1; } catch (const tracable_exception &e) { P_CRITICAL("ERROR: " << e.what() << "\n" << e.backtrace()); cleanup(wo); return 1; } }