PNG  IHDRQgAMA a cHRMz&u0`:pQ<bKGDgmIDATxwUﹻ& ^CX(J I@ "% (** BX +*i"]j(IH{~R)[~>h{}gy)I$Ij .I$I$ʊy@}x.: $I$Ii}VZPC)I$IF ^0ʐJ$I$Q^}{"r=OzI$gRZeC.IOvH eKX $IMpxsk.쒷/&r[޳<v| .I~)@$updYRa$I |M.e JaֶpSYR6j>h%IRز if&uJ)M$I vLi=H;7UJ,],X$I1AҒJ$ XY XzI@GNҥRT)E@;]K*Mw;#5_wOn~\ DC&$(A5 RRFkvIR}l!RytRl;~^ǷJj اy뷦BZJr&ӥ8Pjw~vnv X^(I;4R=P[3]J,]ȏ~:3?[ a&e)`e*P[4]T=Cq6R[ ~ޤrXR Հg(t_HZ-Hg M$ãmL5R uk*`%C-E6/%[t X.{8P9Z.vkXŐKjgKZHg(aK9ڦmKjѺm_ \#$5,)-  61eJ,5m| r'= &ڡd%-]J on Xm|{ RҞe $eڧY XYrԮ-a7RK6h>n$5AVڴi*ֆK)mѦtmr1p| q:흺,)Oi*ֺK)ܬ֦K-5r3>0ԔHjJئEZj,%re~/z%jVMڸmrt)3]J,T K֦OvԒgii*bKiNO~%PW0=dii2tJ9Jݕ{7"I P9JKTbu,%r"6RKU}Ij2HKZXJ,妝 XYrP ެ24c%i^IK|.H,%rb:XRl1X4Pe/`x&P8Pj28Mzsx2r\zRPz4J}yP[g=L) .Q[6RjWgp FIH*-`IMRaK9TXcq*I y[jE>cw%gLRԕiFCj-ďa`#e~I j,%r,)?[gp FI˨mnWX#>mʔ XA DZf9,nKҲzIZXJ,L#kiPz4JZF,I,`61%2s $,VOϚ2/UFJfy7K> X+6 STXIeJILzMfKm LRaK9%|4p9LwJI!`NsiazĔ)%- XMq>pk$-$Q2x#N ؎-QR}ᶦHZډ)J,l#i@yn3LN`;nڔ XuX5pF)m|^0(>BHF9(cզEerJI rg7 4I@z0\JIi䵙RR0s;$s6eJ,`n 䂦0a)S)A 1eJ,堌#635RIgpNHuTH_SԕqVe ` &S)>p;S$魁eKIuX`I4춒o}`m$1":PI<[v9^\pTJjriRŭ P{#{R2,`)e-`mgj~1ϣLKam7&U\j/3mJ,`F;M'䱀 .KR#)yhTq;pcK9(q!w?uRR,n.yw*UXj#\]ɱ(qv2=RqfB#iJmmL<]Y͙#$5 uTU7ӦXR+q,`I}qL'`6Kͷ6r,]0S$- [RKR3oiRE|nӦXR.(i:LDLTJjY%o:)6rxzҒqTJjh㞦I.$YR.ʼnGZ\ֿf:%55 I˼!6dKxm4E"mG_ s? .e*?LRfK9%q#uh$)i3ULRfK9yxm܌bj84$i1U^@Wbm4uJ,ҪA>_Ij?1v32[gLRD96oTaR׿N7%L2 NT,`)7&ƝL*꽙yp_$M2#AS,`)7$rkTA29_Iye"|/0t)$n XT2`YJ;6Jx".e<`$) PI$5V4]29SRI>~=@j]lp2`K9Jaai^" Ԋ29ORI%:XV5]JmN9]H;1UC39NI%Xe78t)a;Oi Ҙ>Xt"~G>_mn:%|~ޅ_+]$o)@ǀ{hgN;IK6G&rp)T2i୦KJuv*T=TOSV>(~D>dm,I*Ɛ:R#ۙNI%D>G.n$o;+#RR!.eU˽TRI28t)1LWϚ>IJa3oFbu&:tJ*(F7y0ZR ^p'Ii L24x| XRI%ۄ>S1]Jy[zL$adB7.eh4%%누>WETf+3IR:I3Xה)3אOۦSRO'ٺ)S}"qOr[B7ϙ.edG)^ETR"RtRݜh0}LFVӦDB^k_JDj\=LS(Iv─aTeZ%eUAM-0;~˃@i|l @S4y72>sX-vA}ϛBI!ݎߨWl*)3{'Y|iSlEڻ(5KtSI$Uv02,~ԩ~x;P4ցCrO%tyn425:KMlD ^4JRxSهF_}شJTS6uj+ﷸk$eZO%G*^V2u3EMj3k%)okI]dT)URKDS 7~m@TJR~荪fT"֛L \sM -0T KfJz+nإKr L&j()[E&I ߴ>e FW_kJR|!O:5/2跌3T-'|zX ryp0JS ~^F>-2< `*%ZFP)bSn"L :)+pʷf(pO3TMW$~>@~ū:TAIsV1}S2<%ޟM?@iT ,Eūoz%i~g|`wS(]oȤ8)$ ntu`өe`6yPl IzMI{ʣzʨ )IZ2= ld:5+請M$-ї;U>_gsY$ÁN5WzWfIZ)-yuXIfp~S*IZdt;t>KūKR|$#LcԀ+2\;kJ`]YǔM1B)UbG"IRߊ<xܾӔJ0Z='Y嵤 Leveg)$znV-º^3Ւof#0Tfk^Zs[*I꯳3{)ˬW4Ւ4 OdpbZRS|*I 55#"&-IvT&/윚Ye:i$ 9{LkuRe[I~_\ؠ%>GL$iY8 9ܕ"S`kS.IlC;Ҏ4x&>u_0JLr<J2(^$5L s=MgV ~,Iju> 7r2)^=G$1:3G< `J3~&IR% 6Tx/rIj3O< ʔ&#f_yXJiގNSz; Tx(i8%#4 ~AS+IjerIUrIj362v885+IjAhK__5X%nV%Iͳ-y|7XV2v4fzo_68"S/I-qbf; LkF)KSM$ Ms>K WNV}^`-큧32ŒVؙGdu,^^m%6~Nn&͓3ŒVZMsRpfEW%IwdǀLm[7W&bIRL@Q|)* i ImsIMmKmyV`i$G+R 0tV'!V)֏28vU7͒vHꦼtxꗞT ;S}7Mf+fIRHNZUkUx5SAJㄌ9MqμAIRi|j5)o*^'<$TwI1hEU^c_j?Е$%d`z cyf,XO IJnTgA UXRD }{H}^S,P5V2\Xx`pZ|Yk:$e ~ @nWL.j+ϝYb퇪bZ BVu)u/IJ_ 1[p.p60bC >|X91P:N\!5qUB}5a5ja `ubcVxYt1N0Zzl4]7­gKj]?4ϻ *[bg$)+À*x쳀ogO$~,5 زUS9 lq3+5mgw@np1sso Ӻ=|N6 /g(Wv7U;zωM=wk,0uTg_`_P`uz?2yI!b`kĸSo+Qx%!\οe|އԁKS-s6pu_(ֿ$i++T8=eY; צP+phxWQv*|p1. ά. XRkIQYP,drZ | B%wP|S5`~́@i޾ E;Չaw{o'Q?%iL{u D?N1BD!owPHReFZ* k_-~{E9b-~P`fE{AܶBJAFO wx6Rox5 K5=WwehS8 (JClJ~ p+Fi;ŗo+:bD#g(C"wA^ r.F8L;dzdIHUX݆ϞXg )IFqem%I4dj&ppT{'{HOx( Rk6^C٫O.)3:s(۳(Z?~ٻ89zmT"PLtw䥈5&b<8GZ-Y&K?e8,`I6e(֍xb83 `rzXj)F=l($Ij 2*(F?h(/9ik:I`m#p3MgLaKjc/U#n5S# m(^)=y=đx8ŬI[U]~SцA4p$-F i(R,7Cx;X=cI>{Km\ o(Tv2vx2qiiDJN,Ҏ!1f 5quBj1!8 rDFd(!WQl,gSkL1Bxg''՞^ǘ;pQ P(c_ IRujg(Wz bs#P­rz> k c&nB=q+ؔXn#r5)co*Ũ+G?7< |PQӣ'G`uOd>%Mctz# Ԫڞ&7CaQ~N'-P.W`Oedp03C!IZcIAMPUۀ5J<\u~+{9(FbbyAeBhOSܳ1 bÈT#ŠyDžs,`5}DC-`̞%r&ڙa87QWWp6e7 Rϫ/oY ꇅ Nܶըtc!LA T7V4Jsū I-0Pxz7QNF_iZgúWkG83 0eWr9 X]㾮݁#Jˢ C}0=3ݱtBi]_ &{{[/o[~ \q鯜00٩|cD3=4B_b RYb$óBRsf&lLX#M*C_L܄:gx)WΘsGSbuL rF$9';\4Ɍq'n[%p.Q`u hNb`eCQyQ|l_C>Lb꟟3hSb #xNxSs^ 88|Mz)}:](vbۢamŖ࿥ 0)Q7@0=?^k(*J}3ibkFn HjB׻NO z x}7p 0tfDX.lwgȔhԾŲ }6g E |LkLZteu+=q\Iv0쮑)QٵpH8/2?Σo>Jvppho~f>%bMM}\//":PTc(v9v!gոQ )UfVG+! 35{=x\2+ki,y$~A1iC6#)vC5^>+gǵ@1Hy٪7u;p psϰu/S <aʸGu'tD1ԝI<pg|6j'p:tպhX{o(7v],*}6a_ wXRk,O]Lܳ~Vo45rp"N5k;m{rZbΦ${#)`(Ŵg,;j%6j.pyYT?}-kBDc3qA`NWQū20/^AZW%NQ MI.X#P#,^Ebc&?XR tAV|Y.1!؅⨉ccww>ivl(JT~ u`ٵDm q)+Ri x/x8cyFO!/*!/&,7<.N,YDŽ&ܑQF1Bz)FPʛ?5d 6`kQձ λc؎%582Y&nD_$Je4>a?! ͨ|ȎWZSsv8 j(I&yj Jb5m?HWp=g}G3#|I,5v珿] H~R3@B[☉9Ox~oMy=J;xUVoj bUsl_35t-(ՃɼRB7U!qc+x4H_Qo֮$[GO<4`&č\GOc[.[*Af%mG/ ňM/r W/Nw~B1U3J?P&Y )`ѓZ1p]^l“W#)lWZilUQu`-m|xĐ,_ƪ|9i:_{*(3Gѧ}UoD+>m_?VPۅ15&}2|/pIOʵ> GZ9cmíتmnz)yߐbD >e}:) r|@R5qVSA10C%E_'^8cR7O;6[eKePGϦX7jb}OTGO^jn*媓7nGMC t,k31Rb (vyܴʭ!iTh8~ZYZp(qsRL ?b}cŨʊGO^!rPJO15MJ[c&~Z`"ѓޔH1C&^|Ш|rʼ,AwĴ?b5)tLU)F| &g٣O]oqSUjy(x<Ϳ3 .FSkoYg2 \_#wj{u'rQ>o;%n|F*O_L"e9umDds?.fuuQbIWz |4\0 sb;OvxOSs; G%T4gFRurj(֍ڑb uԖKDu1MK{1^ q; C=6\8FR艇!%\YÔU| 88m)֓NcLve C6z;o&X x59:q61Z(T7>C?gcļxѐ Z oo-08jہ x,`' ҔOcRlf~`jj".Nv+sM_]Zk g( UOPyεx%pUh2(@il0ݽQXxppx-NS( WO+轾 nFߢ3M<;z)FBZjciu/QoF 7R¥ ZFLF~#ȣߨ^<쩡ݛкvџ))ME>ώx4m#!-m!L;vv#~Y[đKmx9.[,UFS CVkZ +ߟrY٧IZd/ioi$%͝ب_ֶX3ܫhNU ZZgk=]=bbJS[wjU()*I =ώ:}-蹞lUj:1}MWm=̛ _ ¾,8{__m{_PVK^n3esw5ӫh#$-q=A̟> ,^I}P^J$qY~Q[ Xq9{#&T.^GVj__RKpn,b=`żY@^՝;z{paVKkQXj/)y TIc&F;FBG7wg ZZDG!x r_tƢ!}i/V=M/#nB8 XxЫ ^@CR<{䤭YCN)eKOSƟa $&g[i3.C6xrOc8TI;o hH6P&L{@q6[ Gzp^71j(l`J}]e6X☉#͕ ׈$AB1Vjh㭦IRsqFBjwQ_7Xk>y"N=MB0 ,C #o6MRc0|$)ف"1!ixY<B9mx `,tA>)5ػQ?jQ?cn>YZe Tisvh# GMމȇp:ԴVuږ8ɼH]C.5C!UV;F`mbBk LTMvPʍϤj?ԯ/Qr1NB`9s"s TYsz &9S%U԰> {<ؿSMxB|H\3@!U| k']$U+> |HHMLޢ?V9iD!-@x TIî%6Z*9X@HMW#?nN ,oe6?tQwڱ.]-y':mW0#!J82qFjH -`ѓ&M0u Uγmxϵ^-_\])@0Rt.8/?ٰCY]x}=sD3ojަЫNuS%U}ԤwHH>ڗjܷ_3gN q7[q2la*ArǓԖ+p8/RGM ]jacd(JhWko6ڎbj]i5Bj3+3!\j1UZLsLTv8HHmup<>gKMJj0@H%,W΃7R) ">c, xixј^ aܖ>H[i.UIHc U1=yW\=S*GR~)AF=`&2h`DzT󑓶J+?W+}C%P:|0H܆}-<;OC[~o.$~i}~HQ TvXΈr=b}$vizL4:ȰT|4~*!oXQR6Lk+#t/g lԁߖ[Jڶ_N$k*". xsxX7jRVbAAʯKҎU3)zSNN _'s?f)6X!%ssAkʱ>qƷb hg %n ~p1REGMHH=BJiy[<5 ǁJҖgKR*倳e~HUy)Ag,K)`Vw6bRR:qL#\rclK/$sh*$ 6덤 KԖc 3Z9=Ɣ=o>X Ώ"1 )a`SJJ6k(<c e{%kϊP+SL'TcMJWRm ŏ"w)qc ef꒵i?b7b('"2r%~HUS1\<(`1Wx9=8HY9m:X18bgD1u ~|H;K-Uep,, C1 RV.MR5άh,tWO8WC$ XRVsQS]3GJ|12 [vM :k#~tH30Rf-HYݺ-`I9%lIDTm\ S{]9gOڒMNCV\G*2JRŨ;Rҏ^ڽ̱mq1Eu?To3I)y^#jJw^Ńj^vvlB_⋌P4x>0$c>K†Aļ9s_VjTt0l#m>E-,,x,-W)سo&96RE XR.6bXw+)GAEvL)͞K4$p=Ũi_ѱOjb HY/+@θH9޼]Nԥ%n{ &zjT? Ty) s^ULlb,PiTf^<À] 62R^V7)S!nllS6~͝V}-=%* ʻ>G DnK<y&>LPy7'r=Hj 9V`[c"*^8HpcO8bnU`4JȪAƋ#1_\ XϘHPRgik(~G~0DAA_2p|J묭a2\NCr]M_0 ^T%e#vD^%xy-n}-E\3aS%yN!r_{ )sAw ڼp1pEAk~v<:`'ӭ^5 ArXOI驻T (dk)_\ PuA*BY]yB"l\ey hH*tbK)3 IKZ򹞋XjN n *n>k]X_d!ryBH ]*R 0(#'7 %es9??ښFC,ՁQPjARJ\Ρw K#jahgw;2$l*) %Xq5!U᢯6Re] |0[__64ch&_}iL8KEgҎ7 M/\`|.p,~`a=BR?xܐrQ8K XR2M8f ?`sgWS%" Ԉ 7R%$ N}?QL1|-эټwIZ%pvL3Hk>,ImgW7{E xPHx73RA @RS CC !\ȟ5IXR^ZxHл$Q[ŝ40 (>+ _C >BRt<,TrT {O/H+˟Pl6 I B)/VC<6a2~(XwV4gnXR ϱ5ǀHٻ?tw똤Eyxp{#WK qG%5],(0ӈH HZ])ג=K1j&G(FbM@)%I` XRg ʔ KZG(vP,<`[ Kn^ SJRsAʠ5xՅF`0&RbV tx:EaUE/{fi2;.IAwW8/tTxAGOoN?G}l L(n`Zv?pB8K_gI+ܗ #i?ޙ.) p$utc ~DžfՈEo3l/)I-U?aԅ^jxArA ΧX}DmZ@QLےbTXGd.^|xKHR{|ΕW_h] IJ`[G9{).y) 0X YA1]qp?p_k+J*Y@HI>^?gt.06Rn ,` ?);p pSF9ZXLBJPWjgQ|&)7! HjQt<| ؅W5 x W HIzYoVMGP Hjn`+\(dNW)F+IrS[|/a`K|ͻ0Hj{R,Q=\ (F}\WR)AgSG`IsnAR=|8$}G(vC$)s FBJ?]_u XRvύ6z ŨG[36-T9HzpW̞ú Xg큽=7CufzI$)ki^qk-) 0H*N` QZkk]/tnnsI^Gu't=7$ Z;{8^jB% IItRQS7[ϭ3 $_OQJ`7!]W"W,)Iy W AJA;KWG`IY{8k$I$^%9.^(`N|LJ%@$I}ֽp=FB*xN=gI?Q{٥4B)mw $Igc~dZ@G9K X?7)aK%݅K$IZ-`IpC U6$I\0>!9k} Xa IIS0H$I H ?1R.Чj:4~Rw@p$IrA*u}WjWFPJ$I➓/6#! LӾ+ X36x8J |+L;v$Io4301R20M I$-E}@,pS^ޟR[/s¹'0H$IKyfŸfVOπFT*a$I>He~VY/3R/)>d$I>28`Cjw,n@FU*9ttf$I~<;=/4RD~@ X-ѕzἱI$: ԍR a@b X{+Qxuq$IЛzo /~3\8ڒ4BN7$IҀj V]n18H$IYFBj3̵̚ja pp $Is/3R Ӻ-Yj+L;.0ŔI$Av? #!5"aʄj}UKmɽH$IjCYs?h$IDl843.v}m7UiI=&=0Lg0$I4: embe` eQbm0u? $IT!Sƍ'-sv)s#C0:XB2a w I$zbww{."pPzO =Ɔ\[ o($Iaw]`E).Kvi:L*#gР7[$IyGPI=@R 4yR~̮´cg I$I/<tPͽ hDgo 94Z^k盇΄8I56^W$I^0̜N?4*H`237}g+hxoq)SJ@p|` $I%>-hO0eO>\ԣNߌZD6R=K ~n($I$y3D>o4b#px2$yڪtzW~a $I~?x'BwwpH$IZݑnC㧄Pc_9sO gwJ=l1:mKB>Ab<4Lp$Ib o1ZQ@85b̍ S'F,Fe,^I$IjEdù{l4 8Ys_s Z8.x m"+{~?q,Z D!I$ϻ'|XhB)=…']M>5 rgotԎ 獽PH$IjIPhh)n#cÔqA'ug5qwU&rF|1E%I$%]!'3AFD/;Ck_`9 v!ٴtPV;x`'*bQa w I$Ix5 FC3D_~A_#O݆DvV?<qw+I$I{=Z8".#RIYyjǪ=fDl9%M,a8$I$Ywi[7ݍFe$s1ՋBVA?`]#!oz4zjLJo8$I$%@3jAa4(o ;p,,dya=F9ً[LSPH$IJYЉ+3> 5"39aZ<ñh!{TpBGkj}Sp $IlvF.F$I z< '\K*qq.f<2Y!S"-\I$IYwčjF$ w9 \ߪB.1v!Ʊ?+r:^!I$BϹB H"B;L'G[ 4U#5>੐)|#o0aڱ$I>}k&1`U#V?YsV x>{t1[I~D&(I$I/{H0fw"q"y%4 IXyE~M3 8XψL}qE$I[> nD?~sf ]o΁ cT6"?'_Ἣ $I>~.f|'!N?⟩0G KkXZE]ޡ;/&?k OۘH$IRۀwXӨ<7@PnS04aӶp.:@\IWQJ6sS%I$e5ڑv`3:x';wq_vpgHyXZ 3gЂ7{{EuԹn±}$I$8t;b|591nءQ"P6O5i }iR̈́%Q̄p!I䮢]O{H$IRϻ9s֧ a=`- aB\X0"+5"C1Hb?߮3x3&gşggl_hZ^,`5?ߎvĸ%̀M!OZC2#0x LJ0 Gw$I$I}<{Eb+y;iI,`ܚF:5ܛA8-O-|8K7s|#Z8a&><a&/VtbtLʌI$I$I$I$I$I$IRjDD%tEXtdate:create2022-05-31T04:40:26+00:00!Î%tEXtdate:modify2022-05-31T04:40:26+00:00|{2IENDB`Mini Shell

HOME


Mini Shell 1.0
DIR:/opt/cpanel/ea-ruby27/src/passenger-release-6.0.23/src/agent/Watchdog/
Upload File :
Current File : //opt/cpanel/ea-ruby27/src/passenger-release-6.0.23/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/PathManip.h>
#include <FileTools/PathSecurityCheck.h>
#include <SystemTools/UserDatabase.h>
#include <SystemTools/ContainerHelpers.h>
#include <RandomGenerator.h>
#include <BackgroundEventLoop.h>
#include <LoggingKit/LoggingKit.h>
#include <MainFunctions.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
};

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';
	}
}

static vector<pid_t>
readCleanupPids(const WorkingObjectsPtr &wo) {
	vector<pid_t> result;

	foreach (string filename, wo->cleanupPidfiles) {
		FILE *f = fopen(extractBaseName(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 " << extractBaseName(filename).c_str() << " (" << filename << ")");
			}
		} else {
			P_WARN("Cannot open cleanup PID file " << extractBaseName(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;
	}
}