systemctl3.py 285 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479
  1. #! /usr/bin/python3
  2. ## type hints are provided in 'types/systemctl3.pyi'
  3. from __future__ import print_function
  4. __copyright__ = "(C) 2016-2020 Guido U. Draheim, licensed under the EUPL"
  5. __version__ = "1.5.4505"
  6. import logging
  7. logg = logging.getLogger("systemctl")
  8. from types import GeneratorType
  9. import re
  10. import fnmatch
  11. import shlex
  12. import collections
  13. import errno
  14. import os
  15. import sys
  16. import signal
  17. import time
  18. import socket
  19. import datetime
  20. import string
  21. import fcntl
  22. import select
  23. import hashlib
  24. import pwd
  25. import grp
  26. import threading
  27. if sys.version[0] == '3':
  28. basestring = str
  29. xrange = range
  30. DEBUG_AFTER = False
  31. DEBUG_STATUS = False
  32. DEBUG_BOOTTIME = False
  33. DEBUG_INITLOOP = False
  34. DEBUG_KILLALL = False
  35. DEBUG_FLOCK = False
  36. TestListen = False
  37. TestAccept = False
  38. def logg_debug_flock(format, *args):
  39. if DEBUG_FLOCK:
  40. logg.debug(format, *args) # pragma: no cover
  41. def logg_debug_after(format, *args):
  42. if DEBUG_AFTER:
  43. logg.debug(format, *args) # pragma: no cover
  44. NOT_A_PROBLEM = 0 # FOUND_OK
  45. NOT_OK = 1 # FOUND_ERROR
  46. NOT_ACTIVE = 2 # FOUND_INACTIVE
  47. NOT_FOUND = 4 # FOUND_UNKNOWN
  48. # defaults for options
  49. _extra_vars = []
  50. _force = False
  51. _full = False
  52. _log_lines = 0
  53. _no_pager = False
  54. _now = False
  55. _no_reload = False
  56. _no_legend = False
  57. _no_ask_password = False
  58. _preset_mode = "all"
  59. _quiet = False
  60. _root = ""
  61. _unit_type = None
  62. _unit_state = None
  63. _unit_property = None
  64. _what_kind = ""
  65. _show_all = False
  66. _user_mode = False
  67. # common default paths
  68. _system_folder1 = "/etc/systemd/system"
  69. _system_folder2 = "/run/systemd/system"
  70. _system_folder3 = "/var/run/systemd/system"
  71. _system_folder4 = "/usr/local/lib/systemd/system"
  72. _system_folder5 = "/usr/lib/systemd/system"
  73. _system_folder6 = "/lib/systemd/system"
  74. _system_folderX = None
  75. _user_folder1 = "{XDG_CONFIG_HOME}/systemd/user"
  76. _user_folder2 = "/etc/systemd/user"
  77. _user_folder3 = "{XDG_RUNTIME_DIR}/systemd/user"
  78. _user_folder4 = "/run/systemd/user"
  79. _user_folder5 = "/var/run/systemd/user"
  80. _user_folder6 = "{XDG_DATA_HOME}/systemd/user"
  81. _user_folder7 = "/usr/local/lib/systemd/user"
  82. _user_folder8 = "/usr/lib/systemd/user"
  83. _user_folder9 = "/lib/systemd/user"
  84. _user_folderX = None
  85. _init_folder1 = "/etc/init.d"
  86. _init_folder2 = "/run/init.d"
  87. _init_folder3 = "/var/run/init.d"
  88. _init_folderX = None
  89. _preset_folder1 = "/etc/systemd/system-preset"
  90. _preset_folder2 = "/run/systemd/system-preset"
  91. _preset_folder3 = "/var/run/systemd/system-preset"
  92. _preset_folder4 = "/usr/local/lib/systemd/system-preset"
  93. _preset_folder5 = "/usr/lib/systemd/system-preset"
  94. _preset_folder6 = "/lib/systemd/system-preset"
  95. _preset_folderX = None
  96. # standard paths
  97. _dev_null = "/dev/null"
  98. _dev_zero = "/dev/zero"
  99. _etc_hosts = "/etc/hosts"
  100. _rc3_boot_folder = "/etc/rc3.d"
  101. _rc3_init_folder = "/etc/init.d/rc3.d"
  102. _rc5_boot_folder = "/etc/rc5.d"
  103. _rc5_init_folder = "/etc/init.d/rc5.d"
  104. _proc_pid_stat = "/proc/{pid}/stat"
  105. _proc_pid_status = "/proc/{pid}/status"
  106. _proc_pid_cmdline= "/proc/{pid}/cmdline"
  107. _proc_pid_dir = "/proc"
  108. _proc_sys_uptime = "/proc/uptime"
  109. _proc_sys_stat = "/proc/stat"
  110. # default values
  111. SystemCompatibilityVersion = 219
  112. SysInitTarget = "sysinit.target"
  113. SysInitWait = 5 # max for target
  114. MinimumYield = 0.5
  115. MinimumTimeoutStartSec = 4
  116. MinimumTimeoutStopSec = 4
  117. DefaultTimeoutStartSec = 90 # official value
  118. DefaultTimeoutStopSec = 90 # official value
  119. DefaultTimeoutAbortSec = 3600 # officially it none (usually larget than StopSec)
  120. DefaultMaximumTimeout = 200 # overrides all other
  121. DefaultRestartSec = 0.1 # official value of 100ms
  122. DefaultStartLimitIntervalSec = 10 # official value
  123. DefaultStartLimitBurst = 5 # official value
  124. InitLoopSleep = 5
  125. MaxLockWait = 0 # equals DefaultMaximumTimeout
  126. DefaultPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  127. ResetLocale = ["LANG", "LANGUAGE", "LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE", "LC_MONETARY",
  128. "LC_MESSAGES", "LC_PAPER", "LC_NAME", "LC_ADDRESS", "LC_TELEPHONE", "LC_MEASUREMENT",
  129. "LC_IDENTIFICATION", "LC_ALL"]
  130. LocaleConf="/etc/locale.conf"
  131. DefaultListenBacklog=2
  132. ExitWhenNoMoreServices = False
  133. ExitWhenNoMoreProcs = False
  134. DefaultUnit = os.environ.get("SYSTEMD_DEFAULT_UNIT", "default.target") # systemd.exe --unit=default.target
  135. DefaultTarget = os.environ.get("SYSTEMD_DEFAULT_TARGET", "multi-user.target") # DefaultUnit fallback
  136. # LogLevel = os.environ.get("SYSTEMD_LOG_LEVEL", "info") # systemd.exe --log-level
  137. # LogTarget = os.environ.get("SYSTEMD_LOG_TARGET", "journal-or-kmsg") # systemd.exe --log-target
  138. # LogLocation = os.environ.get("SYSTEMD_LOG_LOCATION", "no") # systemd.exe --log-location
  139. # ShowStatus = os.environ.get("SYSTEMD_SHOW_STATUS", "auto") # systemd.exe --show-status
  140. DefaultStandardInput=os.environ.get("SYSTEMD_STANDARD_INPUT", "null")
  141. DefaultStandardOutput=os.environ.get("SYSTEMD_STANDARD_OUTPUT", "journal") # systemd.exe --default-standard-output
  142. DefaultStandardError=os.environ.get("SYSTEMD_STANDARD_ERROR", "inherit") # systemd.exe --default-standard-error
  143. EXEC_SPAWN = False
  144. EXEC_DUP2 = True
  145. REMOVE_LOCK_FILE = False
  146. BOOT_PID_MIN = 0
  147. BOOT_PID_MAX = -9
  148. PROC_MAX_DEPTH = 100
  149. EXPAND_VARS_MAXDEPTH = 20
  150. EXPAND_KEEP_VARS = True
  151. RESTART_FAILED_UNITS = True
  152. ACTIVE_IF_ENABLED=False
  153. TAIL_CMD = "/usr/bin/tail"
  154. LESS_CMD = "/usr/bin/less"
  155. CAT_CMD = "/usr/bin/cat"
  156. # The systemd default was NOTIFY_SOCKET="/var/run/systemd/notify"
  157. _notify_socket_folder = "{RUN}/systemd" # alias /run/systemd
  158. _journal_log_folder = "{LOG}/journal"
  159. SYSTEMCTL_DEBUG_LOG = "{LOG}/systemctl.debug.log"
  160. SYSTEMCTL_EXTRA_LOG = "{LOG}/systemctl.log"
  161. _default_targets = [ "poweroff.target", "rescue.target", "sysinit.target", "basic.target", "multi-user.target", "graphical.target", "reboot.target" ]
  162. _feature_targets = [ "network.target", "remote-fs.target", "local-fs.target", "timers.target", "nfs-client.target" ]
  163. _all_common_targets = [ "default.target" ] + _default_targets + _feature_targets
  164. # inside a docker we pretend the following
  165. _all_common_enabled = [ "default.target", "multi-user.target", "remote-fs.target" ]
  166. _all_common_disabled = [ "graphical.target", "resue.target", "nfs-client.target" ]
  167. target_requires = { "graphical.target": "multi-user.target", "multi-user.target": "basic.target", "basic.target": "sockets.target" }
  168. _runlevel_mappings = {} # the official list
  169. _runlevel_mappings["0"] = "poweroff.target"
  170. _runlevel_mappings["1"] = "rescue.target"
  171. _runlevel_mappings["2"] = "multi-user.target"
  172. _runlevel_mappings["3"] = "multi-user.target"
  173. _runlevel_mappings["4"] = "multi-user.target"
  174. _runlevel_mappings["5"] = "graphical.target"
  175. _runlevel_mappings["6"] = "reboot.target"
  176. _sysv_mappings = {} # by rule of thumb
  177. _sysv_mappings["$local_fs"] = "local-fs.target"
  178. _sysv_mappings["$network"] = "network.target"
  179. _sysv_mappings["$remote_fs"] = "remote-fs.target"
  180. _sysv_mappings["$timer"] = "timers.target"
  181. def strINET(value):
  182. if value == socket.SOCK_DGRAM:
  183. return "UDP"
  184. if value == socket.SOCK_STREAM:
  185. return "TCP"
  186. if value == socket.SOCK_RAW: # pragma: no cover
  187. return "RAW"
  188. if value == socket.SOCK_RDM: # pragma: no cover
  189. return "RDM"
  190. if value == socket.SOCK_SEQPACKET: # pragma: no cover
  191. return "SEQ"
  192. return "<?>" # pragma: no cover
  193. def strYes(value):
  194. if value is True:
  195. return "yes"
  196. if not value:
  197. return "no"
  198. return str(value)
  199. def strE(part):
  200. if not part:
  201. return ""
  202. return str(part)
  203. def strQ(part):
  204. if part is None:
  205. return ""
  206. if isinstance(part, int):
  207. return str(part)
  208. return "'%s'" % part
  209. def shell_cmd(cmd):
  210. return " ".join([strQ(part) for part in cmd])
  211. def to_intN(value, default = None):
  212. if not value:
  213. return default
  214. try:
  215. return int(value)
  216. except:
  217. return default
  218. def to_int(value, default = 0):
  219. try:
  220. return int(value)
  221. except:
  222. return default
  223. def to_list(value):
  224. if not value:
  225. return []
  226. if isinstance(value, list):
  227. return value
  228. if isinstance(value, tuple):
  229. return list(value)
  230. return str(value or "").split(",")
  231. def int_mode(value):
  232. try: return int(value, 8)
  233. except: return None # pragma: no cover
  234. def unit_of(module):
  235. if "." not in module:
  236. return module + ".service"
  237. return module
  238. def o22(part):
  239. if isinstance(part, basestring):
  240. if len(part) <= 22:
  241. return part
  242. return part[:5] + "..." + part[-14:]
  243. return part # pragma: no cover (is always str)
  244. def o44(part):
  245. if isinstance(part, basestring):
  246. if len(part) <= 44:
  247. return part
  248. return part[:10] + "..." + part[-31:]
  249. return part # pragma: no cover (is always str)
  250. def o77(part):
  251. if isinstance(part, basestring):
  252. if len(part) <= 77:
  253. return part
  254. return part[:20] + "..." + part[-54:]
  255. return part # pragma: no cover (is always str)
  256. def unit_name_escape(text):
  257. # https://www.freedesktop.org/software/systemd/man/systemd.unit.html#id-1.6
  258. esc = re.sub("([^a-z-AZ.-/])", lambda m: "\\x%02x" % ord(m.group(1)[0]), text)
  259. return esc.replace("/", "-")
  260. def unit_name_unescape(text):
  261. esc = text.replace("-", "/")
  262. return re.sub("\\\\x(..)", lambda m: "%c" % chr(int(m.group(1), 16)), esc)
  263. def is_good_root(root):
  264. if not root:
  265. return True
  266. return root.strip(os.path.sep).count(os.path.sep) > 1
  267. def os_path(root, path):
  268. if not root:
  269. return path
  270. if not path:
  271. return path
  272. if is_good_root(root) and path.startswith(root):
  273. return path
  274. while path.startswith(os.path.sep):
  275. path = path[1:]
  276. return os.path.join(root, path)
  277. def path_replace_extension(path, old, new):
  278. if path.endswith(old):
  279. path = path[:-len(old)]
  280. return path + new
  281. def get_PAGER():
  282. PAGER = os.environ.get("PAGER", "less")
  283. pager = os.environ.get("SYSTEMD_PAGER", "{PAGER}").format(**locals())
  284. options = os.environ.get("SYSTEMD_LESS", "FRSXMK") # see 'man timedatectl'
  285. if not pager: pager = "cat"
  286. if "less" in pager and options:
  287. return [ pager, "-" + options ]
  288. return [ pager ]
  289. def os_getlogin():
  290. """ NOT using os.getlogin() """
  291. return pwd.getpwuid(os.geteuid()).pw_name
  292. def get_runtime_dir():
  293. explicit = os.environ.get("XDG_RUNTIME_DIR", "")
  294. if explicit: return explicit
  295. user = os_getlogin()
  296. return "/tmp/run-"+user
  297. def get_RUN(root = False):
  298. tmp_var = get_TMP(root)
  299. if _root:
  300. tmp_var = _root
  301. if root:
  302. for p in ("/run", "/var/run", "{tmp_var}/run"):
  303. path = p.format(**locals())
  304. if os.path.isdir(path) and os.access(path, os.W_OK):
  305. return path
  306. os.makedirs(path) # "/tmp/run"
  307. return path
  308. else:
  309. uid = get_USER_ID(root)
  310. for p in ("/run/user/{uid}", "/var/run/user/{uid}", "{tmp_var}/run-{uid}"):
  311. path = p.format(**locals())
  312. if os.path.isdir(path) and os.access(path, os.W_OK):
  313. return path
  314. os.makedirs(path, 0o700) # "/tmp/run/user/{uid}"
  315. return path
  316. def get_PID_DIR(root = False):
  317. if root:
  318. return get_RUN(root)
  319. else:
  320. return os.path.join(get_RUN(root), "run") # compat with older systemctl.py
  321. def get_home():
  322. if False: # pragma: no cover
  323. explicit = os.environ.get("HOME", "") # >> On Unix, an initial ~ (tilde) is replaced by the
  324. if explicit: return explicit # environment variable HOME if it is set; otherwise
  325. uid = os.geteuid() # the current users home directory is looked up in the
  326. # # password directory through the built-in module pwd.
  327. return pwd.getpwuid(uid).pw_name # An initial ~user i looked up directly in the
  328. return os.path.expanduser("~") # password directory. << from docs(os.path.expanduser)
  329. def get_HOME(root = False):
  330. if root: return "/root"
  331. return get_home()
  332. def get_USER_ID(root = False):
  333. ID = 0
  334. if root: return ID
  335. return os.geteuid()
  336. def get_USER(root = False):
  337. if root: return "root"
  338. uid = os.geteuid()
  339. return pwd.getpwuid(uid).pw_name
  340. def get_GROUP_ID(root = False):
  341. ID = 0
  342. if root: return ID
  343. return os.getegid()
  344. def get_GROUP(root = False):
  345. if root: return "root"
  346. gid = os.getegid()
  347. return grp.getgrgid(gid).gr_name
  348. def get_TMP(root = False):
  349. TMP = "/tmp"
  350. if root: return TMP
  351. return os.environ.get("TMPDIR", os.environ.get("TEMP", os.environ.get("TMP", TMP)))
  352. def get_VARTMP(root = False):
  353. VARTMP = "/var/tmp"
  354. if root: return VARTMP
  355. return os.environ.get("TMPDIR", os.environ.get("TEMP", os.environ.get("TMP", VARTMP)))
  356. def get_SHELL(root = False):
  357. SHELL = "/bin/sh"
  358. if root: return SHELL
  359. return os.environ.get("SHELL", SHELL)
  360. def get_RUNTIME_DIR(root = False):
  361. RUN = "/run"
  362. if root: return RUN
  363. return os.environ.get("XDG_RUNTIME_DIR", get_runtime_dir())
  364. def get_CONFIG_HOME(root = False):
  365. CONFIG = "/etc"
  366. if root: return CONFIG
  367. HOME = get_HOME(root)
  368. return os.environ.get("XDG_CONFIG_HOME", HOME + "/.config")
  369. def get_CACHE_HOME(root = False):
  370. CACHE = "/var/cache"
  371. if root: return CACHE
  372. HOME = get_HOME(root)
  373. return os.environ.get("XDG_CACHE_HOME", HOME + "/.cache")
  374. def get_DATA_HOME(root = False):
  375. SHARE = "/usr/share"
  376. if root: return SHARE
  377. HOME = get_HOME(root)
  378. return os.environ.get("XDG_DATA_HOME", HOME + "/.local/share")
  379. def get_LOG_DIR(root = False):
  380. LOGDIR = "/var/log"
  381. if root: return LOGDIR
  382. CONFIG = get_CONFIG_HOME(root)
  383. return os.path.join(CONFIG, "log")
  384. def get_VARLIB_HOME(root = False):
  385. VARLIB = "/var/lib"
  386. if root: return VARLIB
  387. CONFIG = get_CONFIG_HOME(root)
  388. return CONFIG
  389. def expand_path(path, root = False):
  390. HOME = get_HOME(root)
  391. RUN = get_RUN(root)
  392. LOG = get_LOG_DIR(root)
  393. XDG_DATA_HOME=get_DATA_HOME(root)
  394. XDG_CONFIG_HOME=get_CONFIG_HOME(root)
  395. XDG_RUNTIME_DIR=get_RUNTIME_DIR(root)
  396. return os.path.expanduser(path.replace("${","{").format(**locals()))
  397. def shutil_chown(path, user, group):
  398. if user or group:
  399. uid, gid = -1, -1
  400. if user:
  401. uid = pwd.getpwnam(user).pw_uid
  402. gid = pwd.getpwnam(user).pw_gid
  403. if group:
  404. gid = grp.getgrnam(group).gr_gid
  405. os.chown(path, uid, gid)
  406. def shutil_fchown(fileno, user, group):
  407. if user or group:
  408. uid, gid = -1, -1
  409. if user:
  410. uid = pwd.getpwnam(user).pw_uid
  411. gid = pwd.getpwnam(user).pw_gid
  412. if group:
  413. gid = grp.getgrnam(group).gr_gid
  414. os.fchown(fileno, uid, gid)
  415. def shutil_setuid(user = None, group = None, xgroups = None):
  416. """ set fork-child uid/gid (returns pw-info env-settings)"""
  417. if group:
  418. gid = grp.getgrnam(group).gr_gid
  419. os.setgid(gid)
  420. logg.debug("setgid %s for %s", gid, strQ(group))
  421. groups = [ gid ]
  422. try:
  423. os.setgroups(groups)
  424. logg.debug("setgroups %s < (%s)", groups, group)
  425. except OSError as e: # pragma: no cover (it will occur in non-root mode anyway)
  426. logg.debug("setgroups %s < (%s) : %s", groups, group, e)
  427. if user:
  428. pw = pwd.getpwnam(user)
  429. gid = pw.pw_gid
  430. gname = grp.getgrgid(gid).gr_name
  431. if not group:
  432. os.setgid(gid)
  433. logg.debug("setgid %s for user %s", gid, strQ(user))
  434. groupnames = [g.gr_name for g in grp.getgrall() if user in g.gr_mem]
  435. groups = [g.gr_gid for g in grp.getgrall() if user in g.gr_mem]
  436. if xgroups:
  437. groups += [g.gr_gid for g in grp.getgrall() if g.gr_name in xgroups and g.gr_gid not in groups]
  438. if not groups:
  439. if group:
  440. gid = grp.getgrnam(group).gr_gid
  441. groups = [ gid ]
  442. try:
  443. os.setgroups(groups)
  444. logg.debug("setgroups %s > %s ", groups, groupnames)
  445. except OSError as e: # pragma: no cover (it will occur in non-root mode anyway)
  446. logg.debug("setgroups %s > %s : %s", groups, groupnames, e)
  447. uid = pw.pw_uid
  448. os.setuid(uid)
  449. logg.debug("setuid %s for user %s", uid, strQ(user))
  450. home = pw.pw_dir
  451. shell = pw.pw_shell
  452. logname = pw.pw_name
  453. return { "USER": user, "LOGNAME": logname, "HOME": home, "SHELL": shell }
  454. return {}
  455. def shutil_truncate(filename):
  456. """ truncates the file (or creates a new empty file)"""
  457. filedir = os.path.dirname(filename)
  458. if not os.path.isdir(filedir):
  459. os.makedirs(filedir)
  460. f = open(filename, "w")
  461. f.write("")
  462. f.close()
  463. # http://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid
  464. def pid_exists(pid):
  465. """Check whether pid exists in the current process table."""
  466. if pid is None: # pragma: no cover (is never null)
  467. return False
  468. return _pid_exists(int(pid))
  469. def _pid_exists(pid):
  470. """Check whether pid exists in the current process table.
  471. UNIX only.
  472. """
  473. if pid < 0:
  474. return False
  475. if pid == 0:
  476. # According to "man 2 kill" PID 0 refers to every process
  477. # in the process group of the calling process.
  478. # On certain systems 0 is a valid PID but we have no way
  479. # to know that in a portable fashion.
  480. raise ValueError('invalid PID 0')
  481. try:
  482. os.kill(pid, 0)
  483. except OSError as err:
  484. if err.errno == errno.ESRCH:
  485. # ESRCH == No such process
  486. return False
  487. elif err.errno == errno.EPERM:
  488. # EPERM clearly means there's a process to deny access to
  489. return True
  490. else:
  491. # According to "man 2 kill" possible error values are
  492. # (EINVAL, EPERM, ESRCH)
  493. raise
  494. else:
  495. return True
  496. def pid_zombie(pid):
  497. """ may be a pid exists but it is only a zombie """
  498. if pid is None:
  499. return False
  500. return _pid_zombie(int(pid))
  501. def _pid_zombie(pid):
  502. """ may be a pid exists but it is only a zombie """
  503. if pid < 0:
  504. return False
  505. if pid == 0:
  506. # According to "man 2 kill" PID 0 refers to every process
  507. # in the process group of the calling process.
  508. # On certain systems 0 is a valid PID but we have no way
  509. # to know that in a portable fashion.
  510. raise ValueError('invalid PID 0')
  511. check = _proc_pid_status.format(**locals())
  512. try:
  513. for line in open(check):
  514. if line.startswith("State:"):
  515. return "Z" in line
  516. except IOError as e:
  517. if e.errno != errno.ENOENT:
  518. logg.error("%s (%s): %s", check, e.errno, e)
  519. return False
  520. return False
  521. def checkstatus(cmd):
  522. if cmd.startswith("-"):
  523. return False, cmd[1:]
  524. else:
  525. return True, cmd
  526. # https://github.com/phusion/baseimage-docker/blob/rel-0.9.16/image/bin/my_init
  527. def ignore_signals_and_raise_keyboard_interrupt(signame):
  528. signal.signal(signal.SIGTERM, signal.SIG_IGN)
  529. signal.signal(signal.SIGINT, signal.SIG_IGN)
  530. raise KeyboardInterrupt(signame)
  531. _default_dict_type = collections.OrderedDict
  532. _default_conf_type = collections.OrderedDict
  533. class SystemctlConfData:
  534. """ A *.service files has a structure similar to an *.ini file so
  535. that data is structured in sections and values. Actually the
  536. values are lists - the raw data is in .getlist(). Otherwise
  537. .get() will return the first line that was encountered. """
  538. def __init__(self, defaults=None, dict_type=None, conf_type=None, allow_no_value=False):
  539. self._defaults = defaults or {}
  540. self._conf_type = conf_type or _default_conf_type
  541. self._dict_type = dict_type or _default_dict_type
  542. self._allow_no_value = allow_no_value
  543. self._conf = self._conf_type()
  544. self._files = []
  545. def defaults(self):
  546. return self._defaults
  547. def sections(self):
  548. return list(self._conf.keys())
  549. def add_section(self, section):
  550. if section not in self._conf:
  551. self._conf[section] = self._dict_type()
  552. def has_section(self, section):
  553. return section in self._conf
  554. def has_option(self, section, option):
  555. if section not in self._conf:
  556. return False
  557. return option in self._conf[section]
  558. def set(self, section, option, value):
  559. if section not in self._conf:
  560. self._conf[section] = self._dict_type()
  561. if value is None:
  562. self._conf[section][option] = []
  563. elif option not in self._conf[section]:
  564. self._conf[section][option] = [ value ]
  565. else:
  566. self._conf[section][option].append(value)
  567. def getstr(self, section, option, default = None, allow_no_value = False):
  568. done = self.get(section, option, strE(default), allow_no_value)
  569. if done is None: return strE(default)
  570. return done
  571. def get(self, section, option, default = None, allow_no_value = False):
  572. allow_no_value = allow_no_value or self._allow_no_value
  573. if section not in self._conf:
  574. if default is not None:
  575. return default
  576. if allow_no_value:
  577. return None
  578. logg.warning("section {} does not exist".format(section))
  579. logg.warning(" have {}".format(self.sections()))
  580. raise AttributeError("section {} does not exist".format(section))
  581. if option not in self._conf[section]:
  582. if default is not None:
  583. return default
  584. if allow_no_value:
  585. return None
  586. raise AttributeError("option {} in {} does not exist".format(option, section))
  587. if not self._conf[section][option]: # i.e. an empty list
  588. if default is not None:
  589. return default
  590. if allow_no_value:
  591. return None
  592. raise AttributeError("option {} in {} is None".format(option, section))
  593. return self._conf[section][option][0] # the first line in the list of configs
  594. def getlist(self, section, option, default = None, allow_no_value = False):
  595. allow_no_value = allow_no_value or self._allow_no_value
  596. if section not in self._conf:
  597. if default is not None:
  598. return default
  599. if allow_no_value:
  600. return []
  601. logg.warning("section {} does not exist".format(section))
  602. logg.warning(" have {}".format(self.sections()))
  603. raise AttributeError("section {} does not exist".format(section))
  604. if option not in self._conf[section]:
  605. if default is not None:
  606. return default
  607. if allow_no_value:
  608. return []
  609. raise AttributeError("option {} in {} does not exist".format(option, section))
  610. return self._conf[section][option] # returns a list, possibly empty
  611. def filenames(self):
  612. return self._files
  613. class SystemctlConfigParser(SystemctlConfData):
  614. """ A *.service files has a structure similar to an *.ini file but it is
  615. actually not like it. Settings may occur multiple times in each section
  616. and they create an implicit list. In reality all the settings are
  617. globally uniqute, so that an 'environment' can be printed without
  618. adding prefixes. Settings are continued with a backslash at the end
  619. of the line. """
  620. # def __init__(self, defaults=None, dict_type=None, allow_no_value=False):
  621. # SystemctlConfData.__init__(self, defaults, dict_type, allow_no_value)
  622. def read(self, filename):
  623. return self.read_sysd(filename)
  624. def read_sysd(self, filename):
  625. initscript = False
  626. initinfo = False
  627. section = "GLOBAL"
  628. nextline = False
  629. name, text = "", ""
  630. if os.path.isfile(filename):
  631. self._files.append(filename)
  632. for orig_line in open(filename):
  633. if nextline:
  634. text += orig_line
  635. if text.rstrip().endswith("\\") or text.rstrip().endswith("\\\n"):
  636. text = text.rstrip() + "\n"
  637. else:
  638. self.set(section, name, text)
  639. nextline = False
  640. continue
  641. line = orig_line.strip()
  642. if not line:
  643. continue
  644. if line.startswith("#"):
  645. continue
  646. if line.startswith(";"):
  647. continue
  648. if line.startswith(".include"):
  649. logg.error("the '.include' syntax is deprecated. Use x.service.d/ drop-in files!")
  650. includefile = re.sub(r'^\.include[ ]*', '', line).rstrip()
  651. if not os.path.isfile(includefile):
  652. raise Exception("tried to include file that doesn't exist: %s" % includefile)
  653. self.read_sysd(includefile)
  654. continue
  655. if line.startswith("["):
  656. x = line.find("]")
  657. if x > 0:
  658. section = line[1:x]
  659. self.add_section(section)
  660. continue
  661. m = re.match(r"(\w+) *=(.*)", line)
  662. if not m:
  663. logg.warning("bad ini line: %s", line)
  664. raise Exception("bad ini line")
  665. name, text = m.group(1), m.group(2).strip()
  666. if text.endswith("\\") or text.endswith("\\\n"):
  667. nextline = True
  668. text = text + "\n"
  669. else:
  670. # hint: an empty line shall reset the value-list
  671. self.set(section, name, text and text or None)
  672. return self
  673. def read_sysv(self, filename):
  674. """ an LSB header is scanned and converted to (almost)
  675. equivalent settings of a SystemD ini-style input """
  676. initscript = False
  677. initinfo = False
  678. section = "GLOBAL"
  679. if os.path.isfile(filename):
  680. self._files.append(filename)
  681. for orig_line in open(filename):
  682. line = orig_line.strip()
  683. if line.startswith("#"):
  684. if " BEGIN INIT INFO" in line:
  685. initinfo = True
  686. section = "init.d"
  687. if " END INIT INFO" in line:
  688. initinfo = False
  689. if initinfo:
  690. m = re.match(r"\S+\s*(\w[\w_-]*):(.*)", line)
  691. if m:
  692. key, val = m.group(1), m.group(2).strip()
  693. self.set(section, key, val)
  694. continue
  695. self.systemd_sysv_generator(filename)
  696. return self
  697. def systemd_sysv_generator(self, filename):
  698. """ see systemd-sysv-generator(8) """
  699. self.set("Unit", "SourcePath", filename)
  700. description = self.get("init.d", "Description", "")
  701. if description:
  702. self.set("Unit", "Description", description)
  703. check = self.get("init.d", "Required-Start","")
  704. if check:
  705. for item in check.split(" "):
  706. if item.strip() in _sysv_mappings:
  707. self.set("Unit", "Requires", _sysv_mappings[item.strip()])
  708. provides = self.get("init.d", "Provides", "")
  709. if provides:
  710. self.set("Install", "Alias", provides)
  711. # if already in multi-user.target then start it there.
  712. runlevels = self.getstr("init.d", "Default-Start","3 5")
  713. for item in runlevels.split(" "):
  714. if item.strip() in _runlevel_mappings:
  715. self.set("Install", "WantedBy", _runlevel_mappings[item.strip()])
  716. self.set("Service", "Restart", "no")
  717. self.set("Service", "TimeoutSec", strE(DefaultMaximumTimeout))
  718. self.set("Service", "KillMode", "process")
  719. self.set("Service", "GuessMainPID", "no")
  720. # self.set("Service", "RemainAfterExit", "yes")
  721. # self.set("Service", "SuccessExitStatus", "5 6")
  722. self.set("Service", "ExecStart", filename + " start")
  723. self.set("Service", "ExecStop", filename + " stop")
  724. if description: # LSB style initscript
  725. self.set("Service", "ExecReload", filename + " reload")
  726. self.set("Service", "Type", "forking") # not "sysv" anymore
  727. # UnitConfParser = ConfigParser.RawConfigParser
  728. UnitConfParser = SystemctlConfigParser
  729. class SystemctlSocket:
  730. def __init__(self, conf, sock, skip = False):
  731. self.conf = conf
  732. self.sock = sock
  733. self.skip = skip
  734. def fileno(self):
  735. return self.sock.fileno()
  736. def listen(self, backlog = None):
  737. if backlog is None:
  738. backlog = DefaultListenBacklog
  739. dgram = (self.sock.type == socket.SOCK_DGRAM)
  740. if not dgram and not self.skip:
  741. self.sock.listen(backlog)
  742. def name(self):
  743. return self.conf.name()
  744. def addr(self):
  745. stream = self.conf.get("Socket", "ListenStream", "")
  746. dgram = self.conf.get("Socket", "ListenDatagram", "")
  747. return stream or dgram
  748. def close(self):
  749. self.sock.close()
  750. class SystemctlConf:
  751. def __init__(self, data, module = None):
  752. self.data = data # UnitConfParser
  753. self.env = {}
  754. self.status = None
  755. self.masked = None
  756. self.module = module
  757. self.nonloaded_path = ""
  758. self.drop_in_files = {}
  759. self._root = _root
  760. self._user_mode = _user_mode
  761. def root_mode(self):
  762. return not self._user_mode
  763. def loaded(self):
  764. files = self.data.filenames()
  765. if self.masked:
  766. return "masked"
  767. if len(files):
  768. return "loaded"
  769. return ""
  770. def filename(self):
  771. """ returns the last filename that was parsed """
  772. files = self.data.filenames()
  773. if files:
  774. return files[0]
  775. return None
  776. def overrides(self):
  777. """ drop-in files are loaded alphabetically by name, not by full path """
  778. return [ self.drop_in_files[name] for name in sorted(self.drop_in_files) ]
  779. def name(self):
  780. """ the unit id or defaults to the file name """
  781. name = self.module or ""
  782. filename = self.filename()
  783. if filename:
  784. name = os.path.basename(filename)
  785. return self.module or name
  786. def set(self, section, name, value):
  787. return self.data.set(section, name, value)
  788. def get(self, section, name, default, allow_no_value = False):
  789. return self.data.getstr(section, name, default, allow_no_value)
  790. def getlist(self, section, name, default = None, allow_no_value = False):
  791. return self.data.getlist(section, name, default or [], allow_no_value)
  792. def getbool(self, section, name, default = None):
  793. value = self.data.get(section, name, default or "no")
  794. if value:
  795. if value[0] in "TtYy123456789":
  796. return True
  797. return False
  798. class PresetFile:
  799. def __init__(self):
  800. self._files = []
  801. self._lines = []
  802. def filename(self):
  803. """ returns the last filename that was parsed """
  804. if self._files:
  805. return self._files[-1]
  806. return None
  807. def read(self, filename):
  808. self._files.append(filename)
  809. for line in open(filename):
  810. self._lines.append(line.strip())
  811. return self
  812. def get_preset(self, unit):
  813. for line in self._lines:
  814. m = re.match(r"(enable|disable)\s+(\S+)", line)
  815. if m:
  816. status, pattern = m.group(1), m.group(2)
  817. if fnmatch.fnmatchcase(unit, pattern):
  818. logg.debug("%s %s => %s %s", status, pattern, unit, strQ(self.filename()))
  819. return status
  820. return None
  821. ## with waitlock(conf): self.start()
  822. class waitlock:
  823. def __init__(self, conf):
  824. self.conf = conf # currently unused
  825. self.opened = -1
  826. self.lockfolder = expand_path(_notify_socket_folder, conf.root_mode())
  827. try:
  828. folder = self.lockfolder
  829. if not os.path.isdir(folder):
  830. os.makedirs(folder)
  831. except Exception as e:
  832. logg.warning("oops, %s", e)
  833. def lockfile(self):
  834. unit = ""
  835. if self.conf:
  836. unit = self.conf.name()
  837. return os.path.join(self.lockfolder, str(unit or "global") + ".lock")
  838. def __enter__(self):
  839. try:
  840. lockfile = self.lockfile()
  841. lockname = os.path.basename(lockfile)
  842. self.opened = os.open(lockfile, os.O_RDWR | os.O_CREAT, 0o600)
  843. for attempt in xrange(int(MaxLockWait or DefaultMaximumTimeout)):
  844. try:
  845. logg_debug_flock("[%s] %s. trying %s _______ ", os.getpid(), attempt, lockname)
  846. fcntl.flock(self.opened, fcntl.LOCK_EX | fcntl.LOCK_NB)
  847. st = os.fstat(self.opened)
  848. if not st.st_nlink:
  849. logg_debug_flock("[%s] %s. %s got deleted, trying again", os.getpid(), attempt, lockname)
  850. os.close(self.opened)
  851. self.opened = os.open(lockfile, os.O_RDWR | os.O_CREAT, 0o600)
  852. continue
  853. content = "{ 'systemctl': %s, 'lock': '%s' }\n" % (os.getpid(), lockname)
  854. os.write(self.opened, content.encode("utf-8"))
  855. logg_debug_flock("[%s] %s. holding lock on %s", os.getpid(), attempt, lockname)
  856. return True
  857. except IOError as e:
  858. whom = os.read(self.opened, 4096)
  859. os.lseek(self.opened, 0, os.SEEK_SET)
  860. logg.info("[%s] %s. systemctl locked by %s", os.getpid(), attempt, whom.rstrip())
  861. time.sleep(1) # until MaxLockWait
  862. continue
  863. logg.error("[%s] not able to get the lock to %s", os.getpid(), lockname)
  864. except Exception as e:
  865. logg.warning("[%s] oops %s, %s", os.getpid(), str(type(e)), e)
  866. #TODO# raise Exception("no lock for %s", self.unit or "global")
  867. return False
  868. def __exit__(self, type, value, traceback):
  869. try:
  870. os.lseek(self.opened, 0, os.SEEK_SET)
  871. os.ftruncate(self.opened, 0)
  872. if REMOVE_LOCK_FILE: # an optional implementation
  873. lockfile = self.lockfile()
  874. lockname = os.path.basename(lockfile)
  875. os.unlink(lockfile) # ino is kept allocated because opened by this process
  876. logg.debug("[%s] lockfile removed for %s", os.getpid(), lockname)
  877. fcntl.flock(self.opened, fcntl.LOCK_UN)
  878. os.close(self.opened) # implies an unlock but that has happend like 6 seconds later
  879. self.opened = -1
  880. except Exception as e:
  881. logg.warning("oops, %s", e)
  882. waitpid_result = collections.namedtuple("waitpid", ["pid", "returncode", "signal" ])
  883. def must_have_failed(waitpid, cmd):
  884. # found to be needed on ubuntu:16.04 to match test result from ubuntu:18.04 and other distros
  885. # .... I have tracked it down that python's os.waitpid() returns an exitcode==0 even when the
  886. # .... underlying process has actually failed with an exitcode<>0. It is unknown where that
  887. # .... bug comes from but it seems a bit serious to trash some very basic unix functionality.
  888. # .... Essentially a parent process does not get the correct exitcode from its own children.
  889. if cmd and cmd[0] == "/bin/kill":
  890. pid = None
  891. for arg in cmd[1:]:
  892. if not arg.startswith("-"):
  893. pid = arg
  894. if pid is None: # unknown $MAINPID
  895. if not waitpid.returncode:
  896. logg.error("waitpid %s did return %s => correcting as 11", cmd, waitpid.returncode)
  897. waitpid = waitpid_result(waitpid.pid, 11, waitpid.signal)
  898. return waitpid
  899. def subprocess_waitpid(pid):
  900. run_pid, run_stat = os.waitpid(pid, 0)
  901. return waitpid_result(run_pid, os.WEXITSTATUS(run_stat), os.WTERMSIG(run_stat))
  902. def subprocess_testpid(pid):
  903. run_pid, run_stat = os.waitpid(pid, os.WNOHANG)
  904. if run_pid:
  905. return waitpid_result(run_pid, os.WEXITSTATUS(run_stat), os.WTERMSIG(run_stat))
  906. else:
  907. return waitpid_result(pid, None, 0)
  908. parse_result = collections.namedtuple("UnitName", ["fullname", "name", "prefix", "instance", "suffix", "component" ])
  909. def parse_unit(fullname): # -> object(prefix, instance, suffix, ...., name, component)
  910. name, suffix = fullname, ""
  911. has_suffix = fullname.rfind(".")
  912. if has_suffix > 0:
  913. name = fullname[:has_suffix]
  914. suffix = fullname[has_suffix+1:]
  915. prefix, instance = name, ""
  916. has_instance = name.find("@")
  917. if has_instance > 0:
  918. prefix = name[:has_instance]
  919. instance = name[has_instance+1:]
  920. component = ""
  921. has_component = prefix.rfind("-")
  922. if has_component > 0:
  923. component = prefix[has_component+1:]
  924. return parse_result(fullname, name, prefix, instance, suffix, component)
  925. def time_to_seconds(text, maximum):
  926. value = 0.
  927. for part in str(text).split(" "):
  928. item = part.strip()
  929. if item == "infinity":
  930. return maximum
  931. if item.endswith("m"):
  932. try: value += 60 * int(item[:-1])
  933. except: pass # pragma: no cover
  934. if item.endswith("min"):
  935. try: value += 60 * int(item[:-3])
  936. except: pass # pragma: no cover
  937. elif item.endswith("ms"):
  938. try: value += int(item[:-2]) / 1000.
  939. except: pass # pragma: no cover
  940. elif item.endswith("s"):
  941. try: value += int(item[:-1])
  942. except: pass # pragma: no cover
  943. elif item:
  944. try: value += int(item)
  945. except: pass # pragma: no cover
  946. if value > maximum:
  947. return maximum
  948. if not value and text.strip() == "0":
  949. return 0.
  950. if not value:
  951. return 1.
  952. return value
  953. def seconds_to_time(seconds):
  954. seconds = float(seconds)
  955. mins = int(int(seconds) / 60)
  956. secs = int(int(seconds) - (mins * 60))
  957. msecs = int(int(seconds * 1000) - (secs * 1000 + mins * 60000))
  958. if mins and secs and msecs:
  959. return "%smin %ss %sms" % (mins, secs, msecs)
  960. elif mins and secs:
  961. return "%smin %ss" % (mins, secs)
  962. elif secs and msecs:
  963. return "%ss %sms" % (secs, msecs)
  964. elif mins and msecs:
  965. return "%smin %sms" % (mins, msecs)
  966. elif mins:
  967. return "%smin" % (mins)
  968. else:
  969. return "%ss" % (secs)
  970. def getBefore(conf):
  971. result = []
  972. beforelist = conf.getlist("Unit", "Before", [])
  973. for befores in beforelist:
  974. for before in befores.split(" "):
  975. name = before.strip()
  976. if name and name not in result:
  977. result.append(name)
  978. return result
  979. def getAfter(conf):
  980. result = []
  981. afterlist = conf.getlist("Unit", "After", [])
  982. for afters in afterlist:
  983. for after in afters.split(" "):
  984. name = after.strip()
  985. if name and name not in result:
  986. result.append(name)
  987. return result
  988. def compareAfter(confA, confB):
  989. idA = confA.name()
  990. idB = confB.name()
  991. for after in getAfter(confA):
  992. if after == idB:
  993. logg.debug("%s After %s", idA, idB)
  994. return -1
  995. for after in getAfter(confB):
  996. if after == idA:
  997. logg.debug("%s After %s", idB, idA)
  998. return 1
  999. for before in getBefore(confA):
  1000. if before == idB:
  1001. logg.debug("%s Before %s", idA, idB)
  1002. return 1
  1003. for before in getBefore(confB):
  1004. if before == idA:
  1005. logg.debug("%s Before %s", idB, idA)
  1006. return -1
  1007. return 0
  1008. def conf_sortedAfter(conflist, cmp = compareAfter):
  1009. # the normal sorted() does only look at two items
  1010. # so if "A after C" and a list [A, B, C] then
  1011. # it will see "A = B" and "B = C" assuming that
  1012. # "A = C" and the list is already sorted.
  1013. #
  1014. # To make a totalsorted we have to create a marker
  1015. # that informs sorted() that also B has a relation.
  1016. # It only works when 'after' has a direction, so
  1017. # anything without 'before' is a 'after'. In that
  1018. # case we find that "B after C".
  1019. class SortTuple:
  1020. def __init__(self, rank, conf):
  1021. self.rank = rank
  1022. self.conf = conf
  1023. sortlist = [ SortTuple(0, conf) for conf in conflist]
  1024. for check in xrange(len(sortlist)): # maxrank = len(sortlist)
  1025. changed = 0
  1026. for A in xrange(len(sortlist)):
  1027. for B in xrange(len(sortlist)):
  1028. if A != B:
  1029. itemA = sortlist[A]
  1030. itemB = sortlist[B]
  1031. before = compareAfter(itemA.conf, itemB.conf)
  1032. if before > 0 and itemA.rank <= itemB.rank:
  1033. logg_debug_after(" %-30s before %s", itemA.conf.name(), itemB.conf.name())
  1034. itemA.rank = itemB.rank + 1
  1035. changed += 1
  1036. if before < 0 and itemB.rank <= itemA.rank:
  1037. logg_debug_after(" %-30s before %s", itemB.conf.name(), itemA.conf.name())
  1038. itemB.rank = itemA.rank + 1
  1039. changed += 1
  1040. if not changed:
  1041. logg_debug_after("done in check %s of %s", check, len(sortlist))
  1042. break
  1043. # because Requires is almost always the same as the After clauses
  1044. # we are mostly done in round 1 as the list is in required order
  1045. for conf in conflist:
  1046. logg_debug_after(".. %s", conf.name())
  1047. for item in sortlist:
  1048. logg_debug_after("(%s) %s", item.rank, item.conf.name())
  1049. sortedlist = sorted(sortlist, key = lambda item: -item.rank)
  1050. for item in sortedlist:
  1051. logg_debug_after("[%s] %s", item.rank, item.conf.name())
  1052. return [ item.conf for item in sortedlist ]
  1053. class SystemctlListenThread(threading.Thread):
  1054. def __init__(self, systemctl):
  1055. threading.Thread.__init__(self, name="listen")
  1056. self.systemctl = systemctl
  1057. self.stopped = threading.Event()
  1058. def stop(self):
  1059. self.stopped.set()
  1060. def run(self):
  1061. READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR
  1062. READ_WRITE = READ_ONLY | select.POLLOUT
  1063. me = os.getpid()
  1064. if DEBUG_INITLOOP: # pragma: no cover
  1065. logg.info("[%s] listen: new thread", me)
  1066. if not self.systemctl._sockets:
  1067. return
  1068. if DEBUG_INITLOOP: # pragma: no cover
  1069. logg.info("[%s] listen: start thread", me)
  1070. listen = select.poll()
  1071. for sock in self.systemctl._sockets.values():
  1072. listen.register(sock, READ_ONLY)
  1073. sock.listen()
  1074. logg.debug("[%s] listen: %s :%s", me, sock.name(), sock.addr())
  1075. timestamp = time.time()
  1076. while not self.stopped.is_set():
  1077. try:
  1078. sleep_sec = InitLoopSleep - (time.time() - timestamp)
  1079. if sleep_sec < MinimumYield:
  1080. sleep_sec = MinimumYield
  1081. sleeping = sleep_sec
  1082. while sleeping > 2:
  1083. time.sleep(1) # accept signals atleast every second
  1084. sleeping = InitLoopSleep - (time.time() - timestamp)
  1085. if sleeping < MinimumYield:
  1086. sleeping = MinimumYield
  1087. break
  1088. time.sleep(sleeping) # remainder waits less that 2 seconds
  1089. if DEBUG_INITLOOP: # pragma: no cover
  1090. logg.debug("[%s] listen: poll", me)
  1091. accepting = listen.poll(100) # milliseconds
  1092. if DEBUG_INITLOOP: # pragma: no cover
  1093. logg.debug("[%s] listen: poll (%s)", me, len(accepting))
  1094. for sock_fileno, event in accepting:
  1095. for sock in self.systemctl._sockets.values():
  1096. if sock.fileno() == sock_fileno:
  1097. if not self.stopped.is_set():
  1098. if self.systemctl.loop.acquire():
  1099. logg.debug("[%s] listen: accept %s :%s", me, sock.name(), sock_fileno)
  1100. self.systemctl.do_accept_socket_from(sock.conf, sock.sock)
  1101. except Exception as e:
  1102. logg.info("[%s] listen: interrupted - exception %s", me, e)
  1103. raise
  1104. for sock in self.systemctl._sockets.values():
  1105. try:
  1106. listen.unregister(sock)
  1107. sock.close()
  1108. except Exception as e:
  1109. logg.warning("[%s] listen: close socket: %s", me, e)
  1110. return
  1111. class Systemctl:
  1112. def __init__(self):
  1113. self.error = NOT_A_PROBLEM # program exitcode or process returncode
  1114. # from command line options or the defaults
  1115. self._extra_vars = _extra_vars
  1116. self._force = _force
  1117. self._full = _full
  1118. self._init = _init
  1119. self._no_ask_password = _no_ask_password
  1120. self._no_legend = _no_legend
  1121. self._now = _now
  1122. self._preset_mode = _preset_mode
  1123. self._quiet = _quiet
  1124. self._root = _root
  1125. self._show_all = _show_all
  1126. self._unit_property = _unit_property
  1127. self._unit_state = _unit_state
  1128. self._unit_type = _unit_type
  1129. # some common constants that may be changed
  1130. self._systemd_version = SystemCompatibilityVersion
  1131. self._journal_log_folder = _journal_log_folder
  1132. # and the actual internal runtime state
  1133. self._loaded_file_sysv = {} # /etc/init.d/name => config data
  1134. self._loaded_file_sysd = {} # /etc/systemd/system/name.service => config data
  1135. self._file_for_unit_sysv = None # name.service => /etc/init.d/name
  1136. self._file_for_unit_sysd = None # name.service => /etc/systemd/system/name.service
  1137. self._preset_file_list = None # /etc/systemd/system-preset/* => file content
  1138. self._default_target = DefaultTarget
  1139. self._sysinit_target = None # stores a UnitConf()
  1140. self.doExitWhenNoMoreProcs = ExitWhenNoMoreProcs or False
  1141. self.doExitWhenNoMoreServices = ExitWhenNoMoreServices or False
  1142. self._user_mode = _user_mode
  1143. self._user_getlogin = os_getlogin()
  1144. self._log_file = {} # init-loop
  1145. self._log_hold = {} # init-loop
  1146. self._boottime = None # cache self.get_boottime()
  1147. self._SYSTEMD_UNIT_PATH = None
  1148. self._SYSTEMD_SYSVINIT_PATH = None
  1149. self._SYSTEMD_PRESET_PATH = None
  1150. self._restarted_unit = {}
  1151. self._restart_failed_units = {}
  1152. self._sockets = {}
  1153. self.loop = threading.Lock()
  1154. def user(self):
  1155. return self._user_getlogin
  1156. def user_mode(self):
  1157. return self._user_mode
  1158. def user_folder(self):
  1159. for folder in self.user_folders():
  1160. if folder: return folder
  1161. raise Exception("did not find any systemd/user folder")
  1162. def system_folder(self):
  1163. for folder in self.system_folders():
  1164. if folder: return folder
  1165. raise Exception("did not find any systemd/system folder")
  1166. def preset_folders(self):
  1167. SYSTEMD_PRESET_PATH = self.get_SYSTEMD_PRESET_PATH()
  1168. for path in SYSTEMD_PRESET_PATH.split(":"):
  1169. if path.strip(): yield expand_path(path.strip())
  1170. if SYSTEMD_PRESET_PATH.endswith(":"):
  1171. if _preset_folder1: yield _preset_folder1
  1172. if _preset_folder2: yield _preset_folder2
  1173. if _preset_folder3: yield _preset_folder3
  1174. if _preset_folder4: yield _preset_folder4
  1175. if _preset_folder5: yield _preset_folder5
  1176. if _preset_folder6: yield _preset_folder6
  1177. if _preset_folderX: yield _preset_folderX
  1178. def init_folders(self):
  1179. SYSTEMD_SYSVINIT_PATH = self.get_SYSTEMD_SYSVINIT_PATH()
  1180. for path in SYSTEMD_SYSVINIT_PATH.split(":"):
  1181. if path.strip(): yield expand_path(path.strip())
  1182. if SYSTEMD_SYSVINIT_PATH.endswith(":"):
  1183. if _init_folder1: yield _init_folder1
  1184. if _init_folder2: yield _init_folder2
  1185. if _init_folder3: yield _init_folder3
  1186. if _init_folderX: yield _init_folderX
  1187. def user_folders(self):
  1188. SYSTEMD_UNIT_PATH = self.get_SYSTEMD_UNIT_PATH()
  1189. for path in SYSTEMD_UNIT_PATH.split(":"):
  1190. if path.strip(): yield expand_path(path.strip())
  1191. if SYSTEMD_UNIT_PATH.endswith(":"):
  1192. if _user_folder1: yield expand_path(_user_folder1)
  1193. if _user_folder2: yield expand_path(_user_folder2)
  1194. if _user_folder3: yield expand_path(_user_folder3)
  1195. if _user_folder4: yield expand_path(_user_folder4)
  1196. if _user_folder5: yield expand_path(_user_folder5)
  1197. if _user_folder6: yield expand_path(_user_folder6)
  1198. if _user_folder7: yield expand_path(_user_folder7)
  1199. if _user_folder8: yield expand_path(_user_folder8)
  1200. if _user_folder9: yield expand_path(_user_folder9)
  1201. if _user_folderX: yield expand_path(_user_folderX)
  1202. def system_folders(self):
  1203. SYSTEMD_UNIT_PATH = self.get_SYSTEMD_UNIT_PATH()
  1204. for path in SYSTEMD_UNIT_PATH.split(":"):
  1205. if path.strip(): yield expand_path(path.strip())
  1206. if SYSTEMD_UNIT_PATH.endswith(":"):
  1207. if _system_folder1: yield _system_folder1
  1208. if _system_folder2: yield _system_folder2
  1209. if _system_folder3: yield _system_folder3
  1210. if _system_folder4: yield _system_folder4
  1211. if _system_folder5: yield _system_folder5
  1212. if _system_folder6: yield _system_folder6
  1213. if _system_folderX: yield _system_folderX
  1214. def get_SYSTEMD_UNIT_PATH(self):
  1215. if self._SYSTEMD_UNIT_PATH is None:
  1216. self._SYSTEMD_UNIT_PATH = os.environ.get("SYSTEMD_UNIT_PATH", ":")
  1217. assert self._SYSTEMD_UNIT_PATH is not None
  1218. return self._SYSTEMD_UNIT_PATH
  1219. def get_SYSTEMD_SYSVINIT_PATH(self):
  1220. if self._SYSTEMD_SYSVINIT_PATH is None:
  1221. self._SYSTEMD_SYSVINIT_PATH = os.environ.get("SYSTEMD_SYSVINIT_PATH", ":")
  1222. assert self._SYSTEMD_SYSVINIT_PATH is not None
  1223. return self._SYSTEMD_SYSVINIT_PATH
  1224. def get_SYSTEMD_PRESET_PATH(self):
  1225. if self._SYSTEMD_PRESET_PATH is None:
  1226. self._SYSTEMD_PRESET_PATH = os.environ.get("SYSTEMD_PRESET_PATH", ":")
  1227. assert self._SYSTEMD_PRESET_PATH is not None
  1228. return self._SYSTEMD_PRESET_PATH
  1229. def sysd_folders(self):
  1230. """ if --user then these folders are preferred """
  1231. if self.user_mode():
  1232. for folder in self.user_folders():
  1233. yield folder
  1234. if True:
  1235. for folder in self.system_folders():
  1236. yield folder
  1237. def scan_unit_sysd_files(self, module = None): # -> [ unit-names,... ]
  1238. """ reads all unit files, returns the first filename for the unit given """
  1239. if self._file_for_unit_sysd is None:
  1240. self._file_for_unit_sysd = {}
  1241. for folder in self.sysd_folders():
  1242. if not folder:
  1243. continue
  1244. folder = os_path(self._root, folder)
  1245. if not os.path.isdir(folder):
  1246. continue
  1247. for name in os.listdir(folder):
  1248. path = os.path.join(folder, name)
  1249. if os.path.isdir(path):
  1250. continue
  1251. service_name = name
  1252. if service_name not in self._file_for_unit_sysd:
  1253. self._file_for_unit_sysd[service_name] = path
  1254. logg.debug("found %s sysd files", len(self._file_for_unit_sysd))
  1255. return list(self._file_for_unit_sysd.keys())
  1256. def scan_unit_sysv_files(self, module = None): # -> [ unit-names,... ]
  1257. """ reads all init.d files, returns the first filename when unit is a '.service' """
  1258. if self._file_for_unit_sysv is None:
  1259. self._file_for_unit_sysv = {}
  1260. for folder in self.init_folders():
  1261. if not folder:
  1262. continue
  1263. folder = os_path(self._root, folder)
  1264. if not os.path.isdir(folder):
  1265. continue
  1266. for name in os.listdir(folder):
  1267. path = os.path.join(folder, name)
  1268. if os.path.isdir(path):
  1269. continue
  1270. service_name = name + ".service" # simulate systemd
  1271. if service_name not in self._file_for_unit_sysv:
  1272. self._file_for_unit_sysv[service_name] = path
  1273. logg.debug("found %s sysv files", len(self._file_for_unit_sysv))
  1274. return list(self._file_for_unit_sysv.keys())
  1275. def unit_sysd_file(self, module = None): # -> filename?
  1276. """ file path for the given module (systemd) """
  1277. self.scan_unit_sysd_files()
  1278. assert self._file_for_unit_sysd is not None
  1279. if module and module in self._file_for_unit_sysd:
  1280. return self._file_for_unit_sysd[module]
  1281. if module and unit_of(module) in self._file_for_unit_sysd:
  1282. return self._file_for_unit_sysd[unit_of(module)]
  1283. return None
  1284. def unit_sysv_file(self, module = None): # -> filename?
  1285. """ file path for the given module (sysv) """
  1286. self.scan_unit_sysv_files()
  1287. assert self._file_for_unit_sysv is not None
  1288. if module and module in self._file_for_unit_sysv:
  1289. return self._file_for_unit_sysv[module]
  1290. if module and unit_of(module) in self._file_for_unit_sysv:
  1291. return self._file_for_unit_sysv[unit_of(module)]
  1292. return None
  1293. def unit_file(self, module = None): # -> filename?
  1294. """ file path for the given module (sysv or systemd) """
  1295. path = self.unit_sysd_file(module)
  1296. if path is not None: return path
  1297. path = self.unit_sysv_file(module)
  1298. if path is not None: return path
  1299. return None
  1300. def is_sysv_file(self, filename):
  1301. """ for routines that have a special treatment for init.d services """
  1302. self.unit_file() # scan all
  1303. assert self._file_for_unit_sysd is not None
  1304. assert self._file_for_unit_sysv is not None
  1305. if not filename: return None
  1306. if filename in self._file_for_unit_sysd.values(): return False
  1307. if filename in self._file_for_unit_sysv.values(): return True
  1308. return None # not True
  1309. def is_user_conf(self, conf):
  1310. if not conf: # pragma: no cover (is never null)
  1311. return False
  1312. filename = conf.nonloaded_path or conf.filename()
  1313. if filename and "/user/" in filename:
  1314. return True
  1315. return False
  1316. def not_user_conf(self, conf):
  1317. """ conf can not be started as user service (when --user)"""
  1318. if conf is None: # pragma: no cover (is never null)
  1319. return True
  1320. if not self.user_mode():
  1321. logg.debug("%s no --user mode >> accept", strQ(conf.filename()))
  1322. return False
  1323. if self.is_user_conf(conf):
  1324. logg.debug("%s is /user/ conf >> accept", strQ(conf.filename()))
  1325. return False
  1326. # to allow for 'docker run -u user' with system services
  1327. user = self.get_User(conf)
  1328. if user and user == self.user():
  1329. logg.debug("%s with User=%s >> accept", strQ(conf.filename()), user)
  1330. return False
  1331. return True
  1332. def find_drop_in_files(self, unit):
  1333. """ search for some.service.d/extra.conf files """
  1334. result = {}
  1335. basename_d = unit + ".d"
  1336. for folder in self.sysd_folders():
  1337. if not folder:
  1338. continue
  1339. folder = os_path(self._root, folder)
  1340. override_d = os_path(folder, basename_d)
  1341. if not os.path.isdir(override_d):
  1342. continue
  1343. for name in os.listdir(override_d):
  1344. path = os.path.join(override_d, name)
  1345. if os.path.isdir(path):
  1346. continue
  1347. if not path.endswith(".conf"):
  1348. continue
  1349. if name not in result:
  1350. result[name] = path
  1351. return result
  1352. def load_sysd_template_conf(self, module): # -> conf?
  1353. """ read the unit template with a UnitConfParser (systemd) """
  1354. if module and "@" in module:
  1355. unit = parse_unit(module)
  1356. service = "%s@.service" % unit.prefix
  1357. conf = self.load_sysd_unit_conf(service)
  1358. if conf:
  1359. conf.module = module
  1360. return conf
  1361. return None
  1362. def load_sysd_unit_conf(self, module): # -> conf?
  1363. """ read the unit file with a UnitConfParser (systemd) """
  1364. path = self.unit_sysd_file(module)
  1365. if not path: return None
  1366. assert self._loaded_file_sysd is not None
  1367. if path in self._loaded_file_sysd:
  1368. return self._loaded_file_sysd[path]
  1369. masked = None
  1370. if os.path.islink(path) and os.readlink(path).startswith("/dev"):
  1371. masked = os.readlink(path)
  1372. drop_in_files = {}
  1373. data = UnitConfParser()
  1374. if not masked:
  1375. data.read_sysd(path)
  1376. drop_in_files = self.find_drop_in_files(os.path.basename(path))
  1377. # load in alphabetic order, irrespective of location
  1378. for name in sorted(drop_in_files):
  1379. path = drop_in_files[name]
  1380. data.read_sysd(path)
  1381. conf = SystemctlConf(data, module)
  1382. conf.masked = masked
  1383. conf.nonloaded_path = path # if masked
  1384. conf.drop_in_files = drop_in_files
  1385. conf._root = self._root
  1386. self._loaded_file_sysd[path] = conf
  1387. return conf
  1388. def load_sysv_unit_conf(self, module): # -> conf?
  1389. """ read the unit file with a UnitConfParser (sysv) """
  1390. path = self.unit_sysv_file(module)
  1391. if not path: return None
  1392. assert self._loaded_file_sysv is not None
  1393. if path in self._loaded_file_sysv:
  1394. return self._loaded_file_sysv[path]
  1395. data = UnitConfParser()
  1396. data.read_sysv(path)
  1397. conf = SystemctlConf(data, module)
  1398. conf._root = self._root
  1399. self._loaded_file_sysv[path] = conf
  1400. return conf
  1401. def load_unit_conf(self, module): # -> conf | None(not-found)
  1402. """ read the unit file with a UnitConfParser (sysv or systemd) """
  1403. try:
  1404. conf = self.load_sysd_unit_conf(module)
  1405. if conf is not None:
  1406. return conf
  1407. conf = self.load_sysd_template_conf(module)
  1408. if conf is not None:
  1409. return conf
  1410. conf = self.load_sysv_unit_conf(module)
  1411. if conf is not None:
  1412. return conf
  1413. except Exception as e:
  1414. logg.warning("%s not loaded: %s", module, e)
  1415. return None
  1416. def default_unit_conf(self, module, description = None): # -> conf
  1417. """ a unit conf that can be printed to the user where
  1418. attributes are empty and loaded() is False """
  1419. data = UnitConfParser()
  1420. data.set("Unit", "Description", description or ("NOT-FOUND " + str(module)))
  1421. # assert(not data.loaded())
  1422. conf = SystemctlConf(data, module)
  1423. conf._root = self._root
  1424. return conf
  1425. def get_unit_conf(self, module): # -> conf (conf | default-conf)
  1426. """ accept that a unit does not exist
  1427. and return a unit conf that says 'not-loaded' """
  1428. conf = self.load_unit_conf(module)
  1429. if conf is not None:
  1430. return conf
  1431. return self.default_unit_conf(module)
  1432. def get_unit_type(self, module):
  1433. name, ext = os.path.splitext(module)
  1434. if ext in [".service", ".socket", ".target"]:
  1435. return ext[1:]
  1436. return None
  1437. def get_unit_section(self, module, default = "Service"):
  1438. return string.capwords(self.get_unit_type(module) or default)
  1439. def get_unit_section_from(self, conf, default = "Service"):
  1440. return self.get_unit_section(conf.name(), default)
  1441. def match_sysd_templates(self, modules = None, suffix=".service"): # -> generate[ unit ]
  1442. """ make a file glob on all known template units (systemd areas).
  1443. It returns no modules (!!) if no modules pattern were given.
  1444. The module string should contain an instance name already. """
  1445. modules = to_list(modules)
  1446. if not modules:
  1447. return
  1448. self.scan_unit_sysd_files()
  1449. assert self._file_for_unit_sysd is not None
  1450. for item in sorted(self._file_for_unit_sysd.keys()):
  1451. if "@" not in item:
  1452. continue
  1453. service_unit = parse_unit(item)
  1454. for module in modules:
  1455. if "@" not in module:
  1456. continue
  1457. module_unit = parse_unit(module)
  1458. if service_unit.prefix == module_unit.prefix:
  1459. yield "%s@%s.%s" % (service_unit.prefix, module_unit.instance, service_unit.suffix)
  1460. def match_sysd_units(self, modules = None, suffix=".service"): # -> generate[ unit ]
  1461. """ make a file glob on all known units (systemd areas).
  1462. It returns all modules if no modules pattern were given.
  1463. Also a single string as one module pattern may be given. """
  1464. modules = to_list(modules)
  1465. self.scan_unit_sysd_files()
  1466. assert self._file_for_unit_sysd is not None
  1467. for item in sorted(self._file_for_unit_sysd.keys()):
  1468. if not modules:
  1469. yield item
  1470. elif [ module for module in modules if fnmatch.fnmatchcase(item, module) ]:
  1471. yield item
  1472. elif [ module for module in modules if module+suffix == item ]:
  1473. yield item
  1474. def match_sysv_units(self, modules = None, suffix=".service"): # -> generate[ unit ]
  1475. """ make a file glob on all known units (sysv areas).
  1476. It returns all modules if no modules pattern were given.
  1477. Also a single string as one module pattern may be given. """
  1478. modules = to_list(modules)
  1479. self.scan_unit_sysv_files()
  1480. assert self._file_for_unit_sysv is not None
  1481. for item in sorted(self._file_for_unit_sysv.keys()):
  1482. if not modules:
  1483. yield item
  1484. elif [ module for module in modules if fnmatch.fnmatchcase(item, module) ]:
  1485. yield item
  1486. elif [ module for module in modules if module+suffix == item ]:
  1487. yield item
  1488. def match_units(self, modules = None, suffix=".service"): # -> [ units,.. ]
  1489. """ Helper for about any command with multiple units which can
  1490. actually be glob patterns on their respective unit name.
  1491. It returns all modules if no modules pattern were given.
  1492. Also a single string as one module pattern may be given. """
  1493. found = []
  1494. for unit in self.match_sysd_units(modules, suffix):
  1495. if unit not in found:
  1496. found.append(unit)
  1497. for unit in self.match_sysd_templates(modules, suffix):
  1498. if unit not in found:
  1499. found.append(unit)
  1500. for unit in self.match_sysv_units(modules, suffix):
  1501. if unit not in found:
  1502. found.append(unit)
  1503. return found
  1504. def list_service_unit_basics(self):
  1505. """ show all the basic loading state of services """
  1506. filename = self.unit_file() # scan all
  1507. assert self._file_for_unit_sysd is not None
  1508. assert self._file_for_unit_sysv is not None
  1509. result = []
  1510. for name, value in self._file_for_unit_sysd.items():
  1511. result += [ (name, "SysD", value) ]
  1512. for name, value in self._file_for_unit_sysv.items():
  1513. result += [ (name, "SysV", value) ]
  1514. return result
  1515. def list_service_units(self, *modules): # -> [ (unit,loaded+active+substate,description) ]
  1516. """ show all the service units """
  1517. result = {}
  1518. active = {}
  1519. substate = {}
  1520. description = {}
  1521. for unit in self.match_units(to_list(modules)):
  1522. result[unit] = "not-found"
  1523. active[unit] = "inactive"
  1524. substate[unit] = "dead"
  1525. description[unit] = ""
  1526. try:
  1527. conf = self.get_unit_conf(unit)
  1528. result[unit] = "loaded"
  1529. description[unit] = self.get_description_from(conf)
  1530. active[unit] = self.get_active_from(conf)
  1531. substate[unit] = self.get_substate_from(conf) or "unknown"
  1532. except Exception as e:
  1533. logg.warning("list-units: %s", e)
  1534. if self._unit_state:
  1535. if self._unit_state not in [ result[unit], active[unit], substate[unit] ]:
  1536. del result[unit]
  1537. return [ (unit, result[unit] + " " + active[unit] + " " + substate[unit], description[unit]) for unit in sorted(result) ]
  1538. def show_list_units(self, *modules): # -> [ (unit,loaded,description) ]
  1539. """ [PATTERN]... -- List loaded units.
  1540. If one or more PATTERNs are specified, only units matching one of
  1541. them are shown. NOTE: This is the default command."""
  1542. hint = "To show all installed unit files use 'systemctl list-unit-files'."
  1543. result = self.list_service_units(*modules)
  1544. if self._no_legend:
  1545. return result
  1546. found = "%s loaded units listed." % len(result)
  1547. return result + [ ("", "", ""), (found, "", ""), (hint, "", "") ]
  1548. def list_service_unit_files(self, *modules): # -> [ (unit,enabled) ]
  1549. """ show all the service units and the enabled status"""
  1550. logg.debug("list service unit files for %s", modules)
  1551. result = {}
  1552. enabled = {}
  1553. for unit in self.match_units(to_list(modules)):
  1554. if _unit_type and self.get_unit_type(unit) not in _unit_type.split(","):
  1555. continue
  1556. result[unit] = None
  1557. enabled[unit] = ""
  1558. try:
  1559. conf = self.get_unit_conf(unit)
  1560. if self.not_user_conf(conf):
  1561. result[unit] = None
  1562. continue
  1563. result[unit] = conf
  1564. enabled[unit] = self.enabled_from(conf)
  1565. except Exception as e:
  1566. logg.warning("list-units: %s", e)
  1567. return [ (unit, enabled[unit]) for unit in sorted(result) if result[unit] ]
  1568. def each_target_file(self):
  1569. folders = self.system_folders()
  1570. if self.user_mode():
  1571. folders = self.user_folders()
  1572. for folder1 in folders:
  1573. folder = os_path(self._root, folder1)
  1574. if not os.path.isdir(folder):
  1575. continue
  1576. for filename in os.listdir(folder):
  1577. if filename.endswith(".target"):
  1578. yield (filename, os.path.join(folder, filename))
  1579. def list_target_unit_files(self, *modules): # -> [ (unit,enabled) ]
  1580. """ show all the target units and the enabled status"""
  1581. enabled = {}
  1582. targets = {}
  1583. for target, filepath in self.each_target_file():
  1584. logg.info("target %s", filepath)
  1585. targets[target] = filepath
  1586. enabled[target] = "static"
  1587. for unit in _all_common_targets:
  1588. targets[unit] = None
  1589. enabled[unit] = "static"
  1590. if unit in _all_common_enabled:
  1591. enabled[unit] = "enabled"
  1592. if unit in _all_common_disabled:
  1593. enabled[unit] = "disabled"
  1594. return [ (unit, enabled[unit]) for unit in sorted(targets) ]
  1595. def show_list_unit_files(self, *modules): # -> [ (unit,enabled) ]
  1596. """[PATTERN]... -- List installed unit files
  1597. List installed unit files and their enablement state (as reported
  1598. by is-enabled). If one or more PATTERNs are specified, only units
  1599. whose filename (just the last component of the path) matches one of
  1600. them are shown. This command reacts to limitations of --type being
  1601. --type=service or --type=target (and --now for some basics)."""
  1602. result = []
  1603. if self._now:
  1604. basics = self.list_service_unit_basics()
  1605. result = [ (name, sysv + " " + filename) for name, sysv, filename in basics ]
  1606. elif self._unit_type == "target":
  1607. result = self.list_target_unit_files()
  1608. elif self._unit_type == "service":
  1609. result = self.list_service_unit_files()
  1610. elif self._unit_type:
  1611. logg.warning("unsupported unit --type=%s", self._unit_type)
  1612. else:
  1613. result = self.list_target_unit_files()
  1614. result += self.list_service_unit_files(*modules)
  1615. if self._no_legend:
  1616. return result
  1617. found = "%s unit files listed." % len(result)
  1618. return [ ("UNIT FILE", "STATE") ] + result + [ ("", ""), (found, "") ]
  1619. ##
  1620. ##
  1621. def get_description(self, unit, default = None):
  1622. return self.get_description_from(self.load_unit_conf(unit))
  1623. def get_description_from(self, conf, default = None): # -> text
  1624. """ Unit.Description could be empty sometimes """
  1625. if not conf: return default or ""
  1626. description = conf.get("Unit", "Description", default or "")
  1627. return self.expand_special(description, conf)
  1628. def read_pid_file(self, pid_file, default = None):
  1629. pid = default
  1630. if not pid_file:
  1631. return default
  1632. if not os.path.isfile(pid_file):
  1633. return default
  1634. if self.truncate_old(pid_file):
  1635. return default
  1636. try:
  1637. # some pid-files from applications contain multiple lines
  1638. for line in open(pid_file):
  1639. if line.strip():
  1640. pid = to_intN(line.strip())
  1641. break
  1642. except Exception as e:
  1643. logg.warning("bad read of pid file '%s': %s", pid_file, e)
  1644. return pid
  1645. def wait_pid_file(self, pid_file, timeout = None): # -> pid?
  1646. """ wait some seconds for the pid file to appear and return the pid """
  1647. timeout = int(timeout or (DefaultTimeoutStartSec/2))
  1648. timeout = max(timeout, (MinimumTimeoutStartSec))
  1649. dirpath = os.path.dirname(os.path.abspath(pid_file))
  1650. for x in xrange(timeout):
  1651. if not os.path.isdir(dirpath):
  1652. time.sleep(1) # until TimeoutStartSec/2
  1653. continue
  1654. pid = self.read_pid_file(pid_file)
  1655. if not pid:
  1656. time.sleep(1) # until TimeoutStartSec/2
  1657. continue
  1658. if not pid_exists(pid):
  1659. time.sleep(1) # until TimeoutStartSec/2
  1660. continue
  1661. return pid
  1662. return None
  1663. def test_pid_file(self, unit): # -> text
  1664. """ support for the testsuite.py """
  1665. conf = self.get_unit_conf(unit)
  1666. return self.pid_file_from(conf) or self.get_status_file_from(conf)
  1667. def pid_file_from(self, conf, default = ""):
  1668. """ get the specified pid file path (not a computed default) """
  1669. pid_file = self.get_pid_file(conf) or default
  1670. return os_path(self._root, self.expand_special(pid_file, conf))
  1671. def get_pid_file(self, conf, default = None):
  1672. return conf.get("Service", "PIDFile", default)
  1673. def read_mainpid_from(self, conf, default = None):
  1674. """ MAINPID is either the PIDFile content written from the application
  1675. or it is the value in the status file written by this systemctl.py code """
  1676. pid_file = self.pid_file_from(conf)
  1677. if pid_file:
  1678. return self.read_pid_file(pid_file, default)
  1679. status = self.read_status_from(conf)
  1680. if "MainPID" in status:
  1681. return to_intN(status["MainPID"], default)
  1682. return default
  1683. def clean_pid_file_from(self, conf):
  1684. pid_file = self.pid_file_from(conf)
  1685. if pid_file and os.path.isfile(pid_file):
  1686. try:
  1687. os.remove(pid_file)
  1688. except OSError as e:
  1689. logg.warning("while rm %s: %s", pid_file, e)
  1690. self.write_status_from(conf, MainPID=None)
  1691. def get_status_file(self, unit): # for testing
  1692. conf = self.get_unit_conf(unit)
  1693. return self.get_status_file_from(conf)
  1694. def get_status_file_from(self, conf, default = None):
  1695. status_file = self.get_StatusFile(conf)
  1696. # this not a real setting, but do the expand_special anyway
  1697. return os_path(self._root, self.expand_special(status_file, conf))
  1698. def get_StatusFile(self, conf, default = None): # -> text
  1699. """ file where to store a status mark """
  1700. status_file = conf.get("Service", "StatusFile", default)
  1701. if status_file:
  1702. return status_file
  1703. root = conf.root_mode()
  1704. folder = get_PID_DIR(root)
  1705. name = "%s.status" % conf.name()
  1706. return os.path.join(folder, name)
  1707. def clean_status_from(self, conf):
  1708. status_file = self.get_status_file_from(conf)
  1709. if os.path.exists(status_file):
  1710. os.remove(status_file)
  1711. conf.status = {}
  1712. def write_status_from(self, conf, **status): # -> bool(written)
  1713. """ if a status_file is known then path is created and the
  1714. give status is written as the only content. """
  1715. status_file = self.get_status_file_from(conf)
  1716. # if not status_file: return False
  1717. dirpath = os.path.dirname(os.path.abspath(status_file))
  1718. if not os.path.isdir(dirpath):
  1719. os.makedirs(dirpath)
  1720. if conf.status is None:
  1721. conf.status = self.read_status_from(conf)
  1722. if True:
  1723. for key in sorted(status.keys()):
  1724. value = status[key]
  1725. if key.upper() == "AS": key = "ActiveState"
  1726. if key.upper() == "EXIT": key = "ExecMainCode"
  1727. if value is None:
  1728. try: del conf.status[key]
  1729. except KeyError: pass
  1730. else:
  1731. conf.status[key] = strE(value)
  1732. try:
  1733. with open(status_file, "w") as f:
  1734. for key in sorted(conf.status):
  1735. value = conf.status[key]
  1736. if key == "MainPID" and str(value) == "0":
  1737. logg.warning("ignore writing MainPID=0")
  1738. continue
  1739. content = "{}={}\n".format(key, str(value))
  1740. logg.debug("writing to %s\n\t%s", status_file, content.strip())
  1741. f.write(content)
  1742. except IOError as e:
  1743. logg.error("writing STATUS %s: %s\n\t to status file %s", status, e, status_file)
  1744. return True
  1745. def read_status_from(self, conf):
  1746. status_file = self.get_status_file_from(conf)
  1747. status = {}
  1748. # if not status_file: return status
  1749. if not os.path.isfile(status_file):
  1750. if DEBUG_STATUS: logg.debug("no status file: %s\n returning %s", status_file, status)
  1751. return status
  1752. if self.truncate_old(status_file):
  1753. if DEBUG_STATUS: logg.debug("old status file: %s\n returning %s", status_file, status)
  1754. return status
  1755. try:
  1756. if DEBUG_STATUS: logg.debug("reading %s", status_file)
  1757. for line in open(status_file):
  1758. if line.strip():
  1759. m = re.match(r"(\w+)[:=](.*)", line)
  1760. if m:
  1761. key, value = m.group(1), m.group(2)
  1762. if key.strip():
  1763. status[key.strip()] = value.strip()
  1764. else: #pragma: no cover
  1765. logg.warning("ignored %s", line.strip())
  1766. except:
  1767. logg.warning("bad read of status file '%s'", status_file)
  1768. return status
  1769. def get_status_from(self, conf, name, default = None):
  1770. if conf.status is None:
  1771. conf.status = self.read_status_from(conf)
  1772. return conf.status.get(name, default)
  1773. def set_status_from(self, conf, name, value):
  1774. if conf.status is None:
  1775. conf.status = self.read_status_from(conf)
  1776. if value is None:
  1777. try: del conf.status[name]
  1778. except KeyError: pass
  1779. else:
  1780. conf.status[name] = value
  1781. #
  1782. def get_boottime(self):
  1783. """ detects the boot time of the container - in general the start time of PID 1 """
  1784. if self._boottime is None:
  1785. self._boottime = self.get_boottime_from_proc()
  1786. assert self._boottime is not None
  1787. return self._boottime
  1788. def get_boottime_from_proc(self):
  1789. """ detects the latest boot time by looking at the start time of available process"""
  1790. pid1 = BOOT_PID_MIN or 0
  1791. pid_max = BOOT_PID_MAX
  1792. if pid_max < 0:
  1793. pid_max = pid1 - pid_max
  1794. for pid in xrange(pid1, pid_max):
  1795. proc = _proc_pid_stat.format(**locals())
  1796. try:
  1797. if os.path.exists(proc):
  1798. # return os.path.getmtime(proc) # did sometimes change
  1799. return self.path_proc_started(proc)
  1800. except Exception as e: # pragma: no cover
  1801. logg.warning("boottime - could not access %s: %s", proc, e)
  1802. if DEBUG_BOOTTIME:
  1803. logg.debug(" boottime from the oldest entry in /proc [nothing in %s..%s]", pid1, pid_max)
  1804. return self.get_boottime_from_old_proc()
  1805. def get_boottime_from_old_proc(self):
  1806. booted = time.time()
  1807. for pid in os.listdir(_proc_pid_dir):
  1808. proc = _proc_pid_stat.format(**locals())
  1809. try:
  1810. if os.path.exists(proc):
  1811. # ctime = os.path.getmtime(proc)
  1812. ctime = self.path_proc_started(proc)
  1813. if ctime < booted:
  1814. booted = ctime
  1815. except Exception as e: # pragma: no cover
  1816. logg.warning("could not access %s: %s", proc, e)
  1817. return booted
  1818. # Use uptime, time process running in ticks, and current time to determine process boot time
  1819. # You can't use the modified timestamp of the status file because it isn't static.
  1820. # ... using clock ticks it is known to be a linear time on Linux
  1821. def path_proc_started(self, proc):
  1822. #get time process started after boot in clock ticks
  1823. with open(proc) as file_stat:
  1824. data_stat = file_stat.readline()
  1825. file_stat.close()
  1826. stat_data = data_stat.split()
  1827. started_ticks = stat_data[21]
  1828. # man proc(5): "(22) starttime = The time the process started after system boot."
  1829. # ".. the value is expressed in clock ticks (divide by sysconf(_SC_CLK_TCK))."
  1830. # NOTE: for containers the start time is related to the boot time of host system.
  1831. clkTickInt = os.sysconf_names['SC_CLK_TCK']
  1832. clockTicksPerSec = os.sysconf(clkTickInt)
  1833. started_secs = float(started_ticks) / clockTicksPerSec
  1834. if DEBUG_BOOTTIME:
  1835. logg.debug(" BOOT .. Proc started time: %.3f (%s)", started_secs, proc)
  1836. # this value is the start time from the host system
  1837. # Variant 1:
  1838. system_uptime = _proc_sys_uptime
  1839. with open(system_uptime,"rb") as file_uptime:
  1840. data_uptime = file_uptime.readline()
  1841. file_uptime.close()
  1842. uptime_data = data_uptime.decode().split()
  1843. uptime_secs = float(uptime_data[0])
  1844. if DEBUG_BOOTTIME:
  1845. logg.debug(" BOOT 1. System uptime secs: %.3f (%s)", uptime_secs, system_uptime)
  1846. #get time now
  1847. now = time.time()
  1848. started_time = now - (uptime_secs - started_secs)
  1849. if DEBUG_BOOTTIME:
  1850. logg.debug(" BOOT 1. Proc has been running since: %s" % (datetime.datetime.fromtimestamp(started_time)))
  1851. # Variant 2:
  1852. system_stat = _proc_sys_stat
  1853. system_btime = 0.
  1854. with open(system_stat,"rb") as f:
  1855. for line in f:
  1856. assert isinstance(line, bytes)
  1857. if line.startswith(b"btime"):
  1858. system_btime = float(line.decode().split()[1])
  1859. f.closed
  1860. if DEBUG_BOOTTIME:
  1861. logg.debug(" BOOT 2. System btime secs: %.3f (%s)", system_btime, system_stat)
  1862. started_btime = system_btime + started_secs
  1863. if DEBUG_BOOTTIME:
  1864. logg.debug(" BOOT 2. Proc has been running since: %s" % (datetime.datetime.fromtimestamp(started_btime)))
  1865. # return started_time
  1866. return started_btime
  1867. def get_filetime(self, filename):
  1868. return os.path.getmtime(filename)
  1869. def truncate_old(self, filename):
  1870. filetime = self.get_filetime(filename)
  1871. boottime = self.get_boottime()
  1872. if filetime >= boottime:
  1873. if DEBUG_BOOTTIME:
  1874. logg.debug(" file time: %s (%s)", datetime.datetime.fromtimestamp(filetime), o22(filename))
  1875. logg.debug(" boot time: %s (%s)", datetime.datetime.fromtimestamp(boottime), "status modified later")
  1876. return False # OK
  1877. if DEBUG_BOOTTIME:
  1878. logg.info(" file time: %s (%s)", datetime.datetime.fromtimestamp(filetime), o22(filename))
  1879. logg.info(" boot time: %s (%s)", datetime.datetime.fromtimestamp(boottime), "status TRUNCATED NOW")
  1880. try:
  1881. shutil_truncate(filename)
  1882. except Exception as e:
  1883. logg.warning("while truncating: %s", e)
  1884. return True # truncated
  1885. def getsize(self, filename):
  1886. if filename is None: # pragma: no cover (is never null)
  1887. return 0
  1888. if not os.path.isfile(filename):
  1889. return 0
  1890. if self.truncate_old(filename):
  1891. return 0
  1892. try:
  1893. return os.path.getsize(filename)
  1894. except Exception as e:
  1895. logg.warning("while reading file size: %s\n of %s", e, filename)
  1896. return 0
  1897. #
  1898. def read_env_file(self, env_file): # -> generate[ (name,value) ]
  1899. """ EnvironmentFile=<name> is being scanned """
  1900. if env_file.startswith("-"):
  1901. env_file = env_file[1:]
  1902. if not os.path.isfile(os_path(self._root, env_file)):
  1903. return
  1904. try:
  1905. for real_line in open(os_path(self._root, env_file)):
  1906. line = real_line.strip()
  1907. if not line or line.startswith("#"):
  1908. continue
  1909. m = re.match(r"(?:export +)?([\w_]+)[=]'([^']*)'", line)
  1910. if m:
  1911. yield m.group(1), m.group(2)
  1912. continue
  1913. m = re.match(r'(?:export +)?([\w_]+)[=]"([^"]*)"', line)
  1914. if m:
  1915. yield m.group(1), m.group(2)
  1916. continue
  1917. m = re.match(r'(?:export +)?([\w_]+)[=](.*)', line)
  1918. if m:
  1919. yield m.group(1), m.group(2)
  1920. continue
  1921. except Exception as e:
  1922. logg.info("while reading %s: %s", env_file, e)
  1923. def read_env_part(self, env_part): # -> generate[ (name, value) ]
  1924. """ Environment=<name>=<value> is being scanned """
  1925. ## systemd Environment= spec says it is a space-seperated list of
  1926. ## assignments. In order to use a space or an equals sign in a value
  1927. ## one should enclose the whole assignment with double quotes:
  1928. ## Environment="VAR1=word word" VAR2=word3 "VAR3=$word 5 6"
  1929. ## and the $word is not expanded by other environment variables.
  1930. try:
  1931. for real_line in env_part.split("\n"):
  1932. line = real_line.strip()
  1933. for found in re.finditer(r'\s*("[\w_]+=[^"]*"|[\w_]+=\S*)', line):
  1934. part = found.group(1)
  1935. if part.startswith('"'):
  1936. part = part[1:-1]
  1937. name, value = part.split("=", 1)
  1938. yield name, value
  1939. except Exception as e:
  1940. logg.info("while reading %s: %s", env_part, e)
  1941. def show_environment(self, unit):
  1942. """ [UNIT]. -- show environment parts """
  1943. conf = self.load_unit_conf(unit)
  1944. if conf is None:
  1945. logg.error("Unit %s could not be found.", unit)
  1946. return False
  1947. if _unit_property:
  1948. return conf.getlist("Service", _unit_property)
  1949. return self.get_env(conf)
  1950. def extra_vars(self):
  1951. return self._extra_vars # from command line
  1952. def get_env(self, conf):
  1953. env = os.environ.copy()
  1954. for env_part in conf.getlist("Service", "Environment", []):
  1955. for name, value in self.read_env_part(self.expand_special(env_part, conf)):
  1956. env[name] = value # a '$word' is not special here (lazy expansion)
  1957. for env_file in conf.getlist("Service", "EnvironmentFile", []):
  1958. for name, value in self.read_env_file(self.expand_special(env_file, conf)):
  1959. env[name] = self.expand_env(value, env) # but nonlazy expansion here
  1960. logg.debug("extra-vars %s", self.extra_vars())
  1961. for extra in self.extra_vars():
  1962. if extra.startswith("@"):
  1963. for name, value in self.read_env_file(extra[1:]):
  1964. logg.info("override %s=%s", name, value)
  1965. env[name] = self.expand_env(value, env)
  1966. else:
  1967. for name, value in self.read_env_part(extra):
  1968. logg.info("override %s=%s", name, value)
  1969. env[name] = value # a '$word' is not special here
  1970. return env
  1971. def expand_env(self, cmd, env):
  1972. def get_env1(m):
  1973. name = m.group(1)
  1974. if name in env:
  1975. return env[name]
  1976. namevar = "$%s" % name
  1977. logg.debug("can not expand %s", namevar)
  1978. return (EXPAND_KEEP_VARS and namevar or "")
  1979. def get_env2(m):
  1980. name = m.group(1)
  1981. if name in env:
  1982. return env[name]
  1983. namevar = "${%s}" % name
  1984. logg.debug("can not expand %s", namevar)
  1985. return (EXPAND_KEEP_VARS and namevar or "")
  1986. #
  1987. maxdepth = EXPAND_VARS_MAXDEPTH
  1988. expanded = re.sub("[$](\w+)", lambda m: get_env1(m), cmd.replace("\\\n",""))
  1989. for depth in xrange(maxdepth):
  1990. new_text = re.sub("[$][{](\w+)[}]", lambda m: get_env2(m), expanded)
  1991. if new_text == expanded:
  1992. return expanded
  1993. expanded = new_text
  1994. logg.error("shell variable expansion exceeded maxdepth %s", maxdepth)
  1995. return expanded
  1996. def expand_special(self, cmd, conf):
  1997. """ expand %i %t and similar special vars. They are being expanded
  1998. before any other expand_env takes place which handles shell-style
  1999. $HOME references. """
  2000. def xx(arg): return unit_name_unescape(arg)
  2001. def yy(arg): return arg
  2002. def get_confs(conf):
  2003. confs={ "%": "%" }
  2004. if conf is None: # pragma: no cover (is never null)
  2005. return confs
  2006. unit = parse_unit(conf.name())
  2007. #
  2008. root = conf.root_mode()
  2009. VARTMP = get_VARTMP(root) # $TMPDIR # "/var/tmp"
  2010. TMP = get_TMP(root) # $TMPDIR # "/tmp"
  2011. RUN = get_RUNTIME_DIR(root) # $XDG_RUNTIME_DIR # "/run"
  2012. ETC = get_CONFIG_HOME(root) # $XDG_CONFIG_HOME # "/etc"
  2013. DAT = get_VARLIB_HOME(root) # $XDG_CONFIG_HOME # "/var/lib"
  2014. LOG = get_LOG_DIR(root) # $XDG_CONFIG_HOME/log # "/var/log"
  2015. CACHE = get_CACHE_HOME(root) # $XDG_CACHE_HOME # "/var/cache"
  2016. HOME = get_HOME(root) # $HOME or ~ # "/root"
  2017. USER = get_USER(root) # geteuid().pw_name # "root"
  2018. USER_ID = get_USER_ID(root) # geteuid() # 0
  2019. GROUP = get_GROUP(root) # getegid().gr_name # "root"
  2020. GROUP_ID = get_GROUP_ID(root) # getegid() # 0
  2021. SHELL = get_SHELL(root) # $SHELL # "/bin/sh"
  2022. # confs["b"] = boot_ID
  2023. confs["C"] = os_path(self._root, CACHE) # Cache directory root
  2024. confs["E"] = os_path(self._root, ETC) # Configuration directory root
  2025. confs["F"] = strE(conf.filename()) # EXTRA
  2026. confs["f"] = "/%s" % xx(unit.instance or unit.prefix)
  2027. confs["h"] = HOME # User home directory
  2028. # confs["H"] = host_NAME
  2029. confs["i"] = yy(unit.instance)
  2030. confs["I"] = xx(unit.instance) # same as %i but escaping undone
  2031. confs["j"] = yy(unit.component) # final component of the prefix
  2032. confs["J"] = xx(unit.component) # unescaped final component
  2033. confs["L"] = os_path(self._root, LOG)
  2034. # confs["m"] = machine_ID
  2035. confs["n"] = yy(unit.fullname) # Full unit name
  2036. confs["N"] = yy(unit.name) # Same as "%n", but with the type suffix removed.
  2037. confs["p"] = yy(unit.prefix) # before the first "@" or same as %n
  2038. confs["P"] = xx(unit.prefix) # same as %p but escaping undone
  2039. confs["s"] = SHELL
  2040. confs["S"] = os_path(self._root, DAT)
  2041. confs["t"] = os_path(self._root, RUN)
  2042. confs["T"] = os_path(self._root, TMP)
  2043. confs["g"] = GROUP
  2044. confs["G"] = str(GROUP_ID)
  2045. confs["u"] = USER
  2046. confs["U"] = str(USER_ID)
  2047. confs["V"] = os_path(self._root, VARTMP)
  2048. return confs
  2049. def get_conf1(m):
  2050. confs = get_confs(conf)
  2051. if m.group(1) in confs:
  2052. return confs[m.group(1)]
  2053. logg.warning("can not expand %%%s", m.group(1))
  2054. return ""
  2055. result = ""
  2056. if cmd:
  2057. result = re.sub("[%](.)", lambda m: get_conf1(m), cmd)
  2058. #++# logg.info("expanded => %s", result)
  2059. return result
  2060. ExecMode = collections.namedtuple("ExecMode", ["check"])
  2061. def exec_newcmd(self, cmd, env, conf):
  2062. check, cmd = checkstatus(cmd)
  2063. mode = Systemctl.ExecMode(check)
  2064. newcmd = self.exec_cmd(cmd, env, conf)
  2065. return mode, newcmd
  2066. def exec_cmd(self, cmd, env, conf):
  2067. """ expand ExecCmd statements including %i and $MAINPID """
  2068. cmd2 = cmd.replace("\\\n","")
  2069. # according to documentation, when bar="one two" then the expansion
  2070. # of '$bar' is ["one","two"] and '${bar}' becomes ["one two"]. We
  2071. # tackle that by expand $bar before shlex, and the rest thereafter.
  2072. def get_env1(m):
  2073. if m.group(1) in env:
  2074. return env[m.group(1)]
  2075. logg.debug("can not expand $%s", m.group(1))
  2076. return "" # empty string
  2077. def get_env2(m):
  2078. if m.group(1) in env:
  2079. return env[m.group(1)]
  2080. logg.debug("can not expand ${%s}", m.group(1))
  2081. return "" # empty string
  2082. cmd3 = re.sub("[$](\w+)", lambda m: get_env1(m), cmd2)
  2083. newcmd = []
  2084. for part in shlex.split(cmd3):
  2085. # newcmd += [ re.sub("[$][{](\w+)[}]", lambda m: get_env2(m), part) ]
  2086. newcmd += [ re.sub("[$][{](\w+)[}]", lambda m: get_env2(m), self.expand_special(part, conf)) ]
  2087. return newcmd
  2088. def remove_service_directories(self, conf, section = "Service"):
  2089. ok = True
  2090. nameRuntimeDirectory = self.get_RuntimeDirectory(conf, section)
  2091. keepRuntimeDirectory = self.get_RuntimeDirectoryPreserve(conf, section)
  2092. if not keepRuntimeDirectory:
  2093. root = conf.root_mode()
  2094. for name in nameRuntimeDirectory.split(" "):
  2095. if not name.strip(): continue
  2096. RUN = get_RUNTIME_DIR(root)
  2097. path = os.path.join(RUN, name)
  2098. dirpath = os_path(self._root, path)
  2099. ok = self.do_rm_tree(dirpath) and ok
  2100. if RUN == "/run":
  2101. for var_run in ("/var/run", "/tmp/run"):
  2102. if os.path.isdir(var_run):
  2103. var_path = os.path.join(var_run, name)
  2104. var_dirpath = os_path(self._root, var_path)
  2105. self.do_rm_tree(var_dirpath)
  2106. if not ok:
  2107. logg.debug("could not fully remove service directory %s", path)
  2108. return ok
  2109. def do_rm_tree(self, path):
  2110. ok = True
  2111. if os.path.isdir(path):
  2112. for dirpath, dirnames, filenames in os.walk(path, topdown=False):
  2113. for item in filenames:
  2114. filepath = os.path.join(dirpath, item)
  2115. try:
  2116. os.remove(filepath)
  2117. except Exception as e: # pragma: no cover
  2118. logg.debug("not removed file: %s (%s)", filepath, e)
  2119. ok = False
  2120. for item in dirnames:
  2121. dir_path = os.path.join(dirpath, item)
  2122. try:
  2123. os.rmdir(dir_path)
  2124. except Exception as e: # pragma: no cover
  2125. logg.debug("not removed dir: %s (%s)", dir_path, e)
  2126. ok = False
  2127. try:
  2128. os.rmdir(path)
  2129. except Exception as e:
  2130. logg.debug("not removed top dir: %s (%s)", path, e)
  2131. ok = False # pragma: no cover
  2132. logg.debug("%s rm_tree %s", ok and "done" or "fail", path)
  2133. return ok
  2134. def get_RuntimeDirectoryPreserve(self, conf, section = "Service"):
  2135. return conf.getbool(section, "RuntimeDirectoryPreserve", "no")
  2136. def get_RuntimeDirectory(self, conf, section = "Service"):
  2137. return self.expand_special(conf.get(section, "RuntimeDirectory", ""), conf)
  2138. def get_StateDirectory(self, conf, section = "Service"):
  2139. return self.expand_special(conf.get(section, "StateDirectory", ""), conf)
  2140. def get_CacheDirectory(self, conf, section = "Service"):
  2141. return self.expand_special(conf.get(section, "CacheDirectory", ""), conf)
  2142. def get_LogsDirectory(self, conf, section = "Service"):
  2143. return self.expand_special(conf.get(section, "LogsDirectory", ""), conf)
  2144. def get_ConfigurationDirectory(self, conf, section = "Service"):
  2145. return self.expand_special(conf.get(section, "ConfigurationDirectory", ""), conf)
  2146. def get_RuntimeDirectoryMode(self, conf, section = "Service"):
  2147. return conf.get(section, "RuntimeDirectoryMode", "")
  2148. def get_StateDirectoryMode(self, conf, section = "Service"):
  2149. return conf.get(section, "StateDirectoryMode", "")
  2150. def get_CacheDirectoryMode(self, conf, section = "Service"):
  2151. return conf.get(section, "CacheDirectoryMode", "")
  2152. def get_LogsDirectoryMode(self, conf, section = "Service"):
  2153. return conf.get(section, "LogsDirectoryMode", "")
  2154. def get_ConfigurationDirectoryMode(self, conf, section = "Service"):
  2155. return conf.get(section, "ConfigurationDirectoryMode", "")
  2156. def clean_service_directories(self, conf, which = ""):
  2157. ok = True
  2158. section = self.get_unit_section_from(conf)
  2159. nameRuntimeDirectory = self.get_RuntimeDirectory(conf, section)
  2160. nameStateDirectory = self.get_StateDirectory(conf, section)
  2161. nameCacheDirectory = self.get_CacheDirectory(conf, section)
  2162. nameLogsDirectory = self.get_LogsDirectory(conf, section)
  2163. nameConfigurationDirectory = self.get_ConfigurationDirectory(conf, section)
  2164. root = conf.root_mode()
  2165. for name in nameRuntimeDirectory.split(" "):
  2166. if not name.strip(): continue
  2167. RUN = get_RUNTIME_DIR(root)
  2168. path = os.path.join(RUN, name)
  2169. if which in ["all", "runtime", ""]:
  2170. dirpath = os_path(self._root, path)
  2171. ok = self.do_rm_tree(dirpath) and ok
  2172. if RUN == "/run":
  2173. for var_run in ("/var/run", "/tmp/run"):
  2174. var_path = os.path.join(var_run, name)
  2175. var_dirpath = os_path(self._root, var_path)
  2176. self.do_rm_tree(var_dirpath)
  2177. for name in nameStateDirectory.split(" "):
  2178. if not name.strip(): continue
  2179. DAT = get_VARLIB_HOME(root)
  2180. path = os.path.join(DAT, name)
  2181. if which in ["all", "state"]:
  2182. dirpath = os_path(self._root, path)
  2183. ok = self.do_rm_tree(dirpath) and ok
  2184. for name in nameCacheDirectory.split(" "):
  2185. if not name.strip(): continue
  2186. CACHE = get_CACHE_HOME(root)
  2187. path = os.path.join(CACHE, name)
  2188. if which in ["all", "cache", ""]:
  2189. dirpath = os_path(self._root, path)
  2190. ok = self.do_rm_tree(dirpath) and ok
  2191. for name in nameLogsDirectory.split(" "):
  2192. if not name.strip(): continue
  2193. LOGS = get_LOG_DIR(root)
  2194. path = os.path.join(LOGS, name)
  2195. if which in ["all", "logs"]:
  2196. dirpath = os_path(self._root, path)
  2197. ok = self.do_rm_tree(dirpath) and ok
  2198. for name in nameConfigurationDirectory.split(" "):
  2199. if not name.strip(): continue
  2200. CONFIG = get_CONFIG_HOME(root)
  2201. path = os.path.join(CONFIG, name)
  2202. if which in ["all", "configuration", ""]:
  2203. dirpath = os_path(self._root, path)
  2204. ok = self.do_rm_tree(dirpath) and ok
  2205. return ok
  2206. def env_service_directories(self, conf):
  2207. envs = {}
  2208. section = self.get_unit_section_from(conf)
  2209. nameRuntimeDirectory = self.get_RuntimeDirectory(conf, section)
  2210. nameStateDirectory = self.get_StateDirectory(conf, section)
  2211. nameCacheDirectory = self.get_CacheDirectory(conf, section)
  2212. nameLogsDirectory = self.get_LogsDirectory(conf, section)
  2213. nameConfigurationDirectory = self.get_ConfigurationDirectory(conf, section)
  2214. root = conf.root_mode()
  2215. for name in nameRuntimeDirectory.split(" "):
  2216. if not name.strip(): continue
  2217. RUN = get_RUNTIME_DIR(root)
  2218. path = os.path.join(RUN, name)
  2219. envs["RUNTIME_DIRECTORY"] = path
  2220. for name in nameStateDirectory.split(" "):
  2221. if not name.strip(): continue
  2222. DAT = get_VARLIB_HOME(root)
  2223. path = os.path.join(DAT, name)
  2224. envs["STATE_DIRECTORY"] = path
  2225. for name in nameCacheDirectory.split(" "):
  2226. if not name.strip(): continue
  2227. CACHE = get_CACHE_HOME(root)
  2228. path = os.path.join(CACHE, name)
  2229. envs["CACHE_DIRECTORY"] = path
  2230. for name in nameLogsDirectory.split(" "):
  2231. if not name.strip(): continue
  2232. LOGS = get_LOG_DIR(root)
  2233. path = os.path.join(LOGS, name)
  2234. envs["LOGS_DIRECTORY"] = path
  2235. for name in nameConfigurationDirectory.split(" "):
  2236. if not name.strip(): continue
  2237. CONFIG = get_CONFIG_HOME(root)
  2238. path = os.path.join(CONFIG, name)
  2239. envs["CONFIGURATION_DIRECTORY"] = path
  2240. return envs
  2241. def create_service_directories(self, conf):
  2242. envs = {}
  2243. section = self.get_unit_section_from(conf)
  2244. nameRuntimeDirectory = self.get_RuntimeDirectory(conf, section)
  2245. modeRuntimeDirectory = self.get_RuntimeDirectoryMode(conf, section)
  2246. nameStateDirectory = self.get_StateDirectory(conf, section)
  2247. modeStateDirectory = self.get_StateDirectoryMode(conf, section)
  2248. nameCacheDirectory = self.get_CacheDirectory(conf, section)
  2249. modeCacheDirectory = self.get_CacheDirectoryMode(conf, section)
  2250. nameLogsDirectory = self.get_LogsDirectory(conf, section)
  2251. modeLogsDirectory = self.get_LogsDirectoryMode(conf, section)
  2252. nameConfigurationDirectory = self.get_ConfigurationDirectory(conf, section)
  2253. modeConfigurationDirectory = self.get_ConfigurationDirectoryMode(conf, section)
  2254. root = conf.root_mode()
  2255. user = self.get_User(conf)
  2256. group = self.get_Group(conf)
  2257. for name in nameRuntimeDirectory.split(" "):
  2258. if not name.strip(): continue
  2259. RUN = get_RUNTIME_DIR(root)
  2260. path = os.path.join(RUN, name)
  2261. logg.debug("RuntimeDirectory %s", path)
  2262. self.make_service_directory(path, modeRuntimeDirectory)
  2263. self.chown_service_directory(path, user, group)
  2264. envs["RUNTIME_DIRECTORY"] = path
  2265. if RUN == "/run":
  2266. for var_run in ("/var/run", "/tmp/run"):
  2267. if os.path.isdir(var_run):
  2268. var_path = os.path.join(var_run, name)
  2269. var_dirpath = os_path(self._root, var_path)
  2270. if os.path.isdir(var_dirpath):
  2271. if not os.path.islink(var_dirpath):
  2272. logg.debug("not a symlink: %s", var_dirpath)
  2273. continue
  2274. dirpath = os_path(self._root, path)
  2275. basepath = os.path.dirname(var_dirpath)
  2276. if not os.path.isdir(basepath):
  2277. os.makedirs(basepath)
  2278. try:
  2279. os.symlink(dirpath, var_dirpath)
  2280. except Exception as e:
  2281. logg.debug("var symlink %s\n\t%s", var_dirpath, e)
  2282. for name in nameStateDirectory.split(" "):
  2283. if not name.strip(): continue
  2284. DAT = get_VARLIB_HOME(root)
  2285. path = os.path.join(DAT, name)
  2286. logg.debug("StateDirectory %s", path)
  2287. self.make_service_directory(path, modeStateDirectory)
  2288. self.chown_service_directory(path, user, group)
  2289. envs["STATE_DIRECTORY"] = path
  2290. for name in nameCacheDirectory.split(" "):
  2291. if not name.strip(): continue
  2292. CACHE = get_CACHE_HOME(root)
  2293. path = os.path.join(CACHE, name)
  2294. logg.debug("CacheDirectory %s", path)
  2295. self.make_service_directory(path, modeCacheDirectory)
  2296. self.chown_service_directory(path, user, group)
  2297. envs["CACHE_DIRECTORY"] = path
  2298. for name in nameLogsDirectory.split(" "):
  2299. if not name.strip(): continue
  2300. LOGS = get_LOG_DIR(root)
  2301. path = os.path.join(LOGS, name)
  2302. logg.debug("LogsDirectory %s", path)
  2303. self.make_service_directory(path, modeLogsDirectory)
  2304. self.chown_service_directory(path, user, group)
  2305. envs["LOGS_DIRECTORY"] = path
  2306. for name in nameConfigurationDirectory.split(" "):
  2307. if not name.strip(): continue
  2308. CONFIG = get_CONFIG_HOME(root)
  2309. path = os.path.join(CONFIG, name)
  2310. logg.debug("ConfigurationDirectory %s", path)
  2311. self.make_service_directory(path, modeConfigurationDirectory)
  2312. # not done according the standard
  2313. # self.chown_service_directory(path, user, group)
  2314. envs["CONFIGURATION_DIRECTORY"] = path
  2315. return envs
  2316. def make_service_directory(self, path, mode):
  2317. ok = True
  2318. dirpath = os_path(self._root, path)
  2319. if not os.path.isdir(dirpath):
  2320. try:
  2321. os.makedirs(dirpath)
  2322. logg.info("created directory path: %s", dirpath)
  2323. except Exception as e: # pragma: no cover
  2324. logg.debug("errors directory path: %s\n\t%s", dirpath, e)
  2325. ok = False
  2326. filemode = int_mode(mode)
  2327. if filemode:
  2328. try:
  2329. os.chmod(dirpath, filemode)
  2330. except Exception as e: # pragma: no cover
  2331. logg.debug("errors directory path: %s\n\t%s", dirpath, e)
  2332. ok = False
  2333. else:
  2334. logg.debug("path did already exist: %s", dirpath)
  2335. if not ok:
  2336. logg.debug("could not fully create service directory %s", path)
  2337. return ok
  2338. def chown_service_directory(self, path, user, group):
  2339. # the standard defines an optimization so that if the parent
  2340. # directory does have the correct user and group then there
  2341. # is no other chown on files and subdirectories to be done.
  2342. dirpath = os_path(self._root, path)
  2343. if not os.path.isdir(dirpath):
  2344. logg.debug("chown did not find %s", dirpath)
  2345. return True
  2346. if user or group:
  2347. st = os.stat(dirpath)
  2348. st_user = pwd.getpwuid(st.st_uid).pw_name
  2349. st_group = grp.getgrgid(st.st_gid).gr_name
  2350. change = False
  2351. if user and (user.strip() != st_user and user.strip() != str(st.st_uid)):
  2352. change = True
  2353. if group and (group.strip() != st_group and group.strip() != str(st.st_gid)):
  2354. change = True
  2355. if change:
  2356. logg.debug("do chown %s", dirpath)
  2357. try:
  2358. ok = self.do_chown_tree(dirpath, user, group)
  2359. logg.info("changed %s:%s %s", user, group, ok)
  2360. return ok
  2361. except Exception as e:
  2362. logg.info("oops %s\n\t%s", dirpath, e)
  2363. else:
  2364. logg.debug("untouched %s", dirpath)
  2365. return True
  2366. def do_chown_tree(self, path, user, group):
  2367. ok = True
  2368. uid, gid = -1, -1
  2369. if user:
  2370. uid = pwd.getpwnam(user).pw_uid
  2371. gid = pwd.getpwnam(user).pw_gid
  2372. if group:
  2373. gid = grp.getgrnam(group).gr_gid
  2374. for dirpath, dirnames, filenames in os.walk(path, topdown=False):
  2375. for item in filenames:
  2376. filepath = os.path.join(dirpath, item)
  2377. try:
  2378. os.chown(filepath, uid, gid)
  2379. except Exception as e: # pragma: no cover
  2380. logg.debug("could not set %s:%s on %s\n\t%s", user, group, filepath, e)
  2381. ok = False
  2382. for item in dirnames:
  2383. dir_path = os.path.join(dirpath, item)
  2384. try:
  2385. os.chown(dir_path, uid, gid)
  2386. except Exception as e: # pragma: no cover
  2387. logg.debug("could not set %s:%s on %s\n\t%s", user, group, dir_path, e)
  2388. ok = False
  2389. try:
  2390. os.chown(path, uid, gid)
  2391. except Exception as e: # pragma: no cover
  2392. logg.debug("could not set %s:%s on %s\n\t%s", user, group, path, e)
  2393. ok = False
  2394. if not ok:
  2395. logg.debug("could not chown %s:%s service directory %s", user, group, path)
  2396. return ok
  2397. def clean_modules(self, *modules):
  2398. """ [UNIT]... -- remove the state directories
  2399. /// it recognizes --what=all or any of configuration, state, cache, logs, runtime
  2400. while an empty value (the default) removes cache and runtime directories"""
  2401. found_all = True
  2402. units = []
  2403. for module in modules:
  2404. matched = self.match_units(to_list(module))
  2405. if not matched:
  2406. logg.error("Unit %s not found.", unit_of(module))
  2407. self.error |= NOT_FOUND
  2408. found_all = False
  2409. continue
  2410. for unit in matched:
  2411. if unit not in units:
  2412. units += [ unit ]
  2413. lines = _log_lines
  2414. follow = _force
  2415. ok = self.clean_units(units)
  2416. return ok and found_all
  2417. def clean_units(self, units, what = ""):
  2418. if not what:
  2419. what = _what_kind
  2420. ok = True
  2421. for unit in units:
  2422. ok = self.clean_unit(unit, what) and ok
  2423. return ok
  2424. def clean_unit(self, unit, what = ""):
  2425. conf = self.load_unit_conf(unit)
  2426. if not conf: return False
  2427. return self.clean_unit_from(conf, what)
  2428. def clean_unit_from(self, conf, what):
  2429. if self.is_active_from(conf):
  2430. logg.warning("can not clean active unit: %s", conf.name())
  2431. return False
  2432. return self.clean_service_directories(conf, what)
  2433. def log_modules(self, *modules):
  2434. """ [UNIT]... -- start 'less' on the log files for the services
  2435. /// use '-f' to follow and '-n lines' to limit output using 'tail',
  2436. using '--no-pager' just does a full 'cat'"""
  2437. found_all = True
  2438. units = []
  2439. for module in modules:
  2440. matched = self.match_units(to_list(module))
  2441. if not matched:
  2442. logg.error("Unit %s not found.", unit_of(module))
  2443. self.error |= NOT_FOUND
  2444. found_all = False
  2445. continue
  2446. for unit in matched:
  2447. if unit not in units:
  2448. units += [ unit ]
  2449. lines = _log_lines
  2450. follow = _force
  2451. result = self.log_units(units, lines, follow)
  2452. if result:
  2453. self.error = result
  2454. return False
  2455. return found_all
  2456. def log_units(self, units, lines = None, follow = False):
  2457. result = 0
  2458. for unit in self.sortedAfter(units):
  2459. exitcode = self.log_unit(unit, lines, follow)
  2460. if exitcode < 0:
  2461. return exitcode
  2462. if exitcode > result:
  2463. result = exitcode
  2464. return result
  2465. def log_unit(self, unit, lines = None, follow = False):
  2466. conf = self.load_unit_conf(unit)
  2467. if not conf: return -1
  2468. return self.log_unit_from(conf, lines, follow)
  2469. def log_unit_from(self, conf, lines = None, follow = False):
  2470. log_path = self.get_journal_log_from(conf)
  2471. if follow:
  2472. cmd = [ TAIL_CMD, "-n", str(lines or 10), "-F", log_path ]
  2473. logg.debug("journalctl %s -> %s", conf.name(), cmd)
  2474. return os.spawnvp(os.P_WAIT, cmd[0], cmd) # type: ignore
  2475. elif lines:
  2476. cmd = [ TAIL_CMD, "-n", str(lines or 10), log_path ]
  2477. logg.debug("journalctl %s -> %s", conf.name(), cmd)
  2478. return os.spawnvp(os.P_WAIT, cmd[0], cmd) # type: ignore
  2479. elif _no_pager:
  2480. cmd = [ CAT_CMD, log_path ]
  2481. logg.debug("journalctl %s -> %s", conf.name(), cmd)
  2482. return os.spawnvp(os.P_WAIT, cmd[0], cmd) # type: ignore
  2483. else:
  2484. cmd = [ LESS_CMD, log_path ]
  2485. logg.debug("journalctl %s -> %s", conf.name(), cmd)
  2486. return os.spawnvp(os.P_WAIT, cmd[0], cmd) # type: ignore
  2487. def get_journal_log_from(self, conf):
  2488. return os_path(self._root, self.get_journal_log(conf))
  2489. def get_journal_log(self, conf):
  2490. """ /var/log/zzz.service.log or /var/log/default.unit.log """
  2491. filename = os.path.basename(strE(conf.filename()))
  2492. unitname = (conf.name() or "default")+".unit"
  2493. name = filename or unitname
  2494. log_folder = expand_path(self._journal_log_folder, conf.root_mode())
  2495. log_file = name.replace(os.path.sep,".") + ".log"
  2496. if log_file.startswith("."):
  2497. log_file = "dot."+log_file
  2498. return os.path.join(log_folder, log_file)
  2499. def open_journal_log(self, conf):
  2500. log_file = self.get_journal_log_from(conf)
  2501. log_folder = os.path.dirname(log_file)
  2502. if not os.path.isdir(log_folder):
  2503. os.makedirs(log_folder)
  2504. return open(os.path.join(log_file), "a")
  2505. def get_WorkingDirectory(self, conf):
  2506. return conf.get("Service", "WorkingDirectory", "")
  2507. def chdir_workingdir(self, conf):
  2508. """ if specified then change the working directory """
  2509. # the original systemd will start in '/' even if User= is given
  2510. if self._root:
  2511. os.chdir(self._root)
  2512. workingdir = self.get_WorkingDirectory(conf)
  2513. if workingdir:
  2514. ignore = False
  2515. if workingdir.startswith("-"):
  2516. workingdir = workingdir[1:]
  2517. ignore = True
  2518. into = os_path(self._root, self.expand_special(workingdir, conf))
  2519. try:
  2520. logg.debug("chdir workingdir '%s'", into)
  2521. os.chdir(into)
  2522. return False
  2523. except Exception as e:
  2524. if not ignore:
  2525. logg.error("chdir workingdir '%s': %s", into, e)
  2526. return into
  2527. else:
  2528. logg.debug("chdir workingdir '%s': %s", into, e)
  2529. return None
  2530. return None
  2531. NotifySocket = collections.namedtuple("NotifySocket", ["socket", "socketfile" ])
  2532. def get_notify_socket_from(self, conf, socketfile = None, debug = False):
  2533. """ creates a notify-socket for the (non-privileged) user """
  2534. notify_socket_folder = expand_path(_notify_socket_folder, conf.root_mode())
  2535. notify_folder = os_path(self._root, notify_socket_folder)
  2536. notify_name = "notify." + str(conf.name() or "systemctl")
  2537. notify_socket = os.path.join(notify_folder, notify_name)
  2538. socketfile = socketfile or notify_socket
  2539. if len(socketfile) > 100:
  2540. # occurs during testsuite.py for ~user/test.tmp/root path
  2541. if debug:
  2542. logg.debug("https://unix.stackexchange.com/questions/367008/%s",
  2543. "why-is-socket-path-length-limited-to-a-hundred-chars")
  2544. logg.debug("old notify socketfile (%s) = %s", len(socketfile), socketfile)
  2545. notify_name44 = o44(notify_name)
  2546. notify_name77 = o77(notify_name)
  2547. socketfile = os.path.join(notify_folder, notify_name77)
  2548. if len(socketfile) > 100:
  2549. socketfile = os.path.join(notify_folder, notify_name44)
  2550. pref = "zz.%i.%s" % (get_USER_ID(),o22(os.path.basename(notify_socket_folder)))
  2551. if len(socketfile) > 100:
  2552. socketfile = os.path.join(get_TMP(), pref, notify_name)
  2553. if len(socketfile) > 100:
  2554. socketfile = os.path.join(get_TMP(), pref, notify_name77)
  2555. if len(socketfile) > 100: # pragma: no cover
  2556. socketfile = os.path.join(get_TMP(), pref, notify_name44)
  2557. if len(socketfile) > 100: # pragma: no cover
  2558. socketfile = os.path.join(get_TMP(), notify_name44)
  2559. if debug:
  2560. logg.info("new notify socketfile (%s) = %s", len(socketfile), socketfile)
  2561. return socketfile
  2562. def notify_socket_from(self, conf, socketfile = None):
  2563. socketfile = self.get_notify_socket_from(conf, socketfile, debug=True)
  2564. try:
  2565. if not os.path.isdir(os.path.dirname(socketfile)):
  2566. os.makedirs(os.path.dirname(socketfile))
  2567. if os.path.exists(socketfile):
  2568. os.unlink(socketfile)
  2569. except Exception as e:
  2570. logg.warning("error %s: %s", socketfile, e)
  2571. sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
  2572. sock.bind(socketfile)
  2573. os.chmod(socketfile, 0o777) # the service my run under some User=setting
  2574. return Systemctl.NotifySocket(sock, socketfile)
  2575. def read_notify_socket(self, notify, timeout):
  2576. notify.socket.settimeout(timeout or DefaultMaximumTimeout)
  2577. result = ""
  2578. try:
  2579. result, client_address = notify.socket.recvfrom(4096)
  2580. assert isinstance(result, bytes)
  2581. if result:
  2582. result = result.decode("utf-8")
  2583. result_txt = result.replace("\n","|")
  2584. result_len = len(result)
  2585. logg.debug("read_notify_socket(%s):%s", result_len, result_txt)
  2586. except socket.timeout as e:
  2587. if timeout > 2:
  2588. logg.debug("socket.timeout %s", e)
  2589. return result
  2590. def wait_notify_socket(self, notify, timeout, pid = None, pid_file = None):
  2591. if not os.path.exists(notify.socketfile):
  2592. logg.info("no $NOTIFY_SOCKET exists")
  2593. return {}
  2594. #
  2595. lapseTimeout = max(3, int(timeout / 100))
  2596. mainpidTimeout = lapseTimeout # Apache sends READY before MAINPID
  2597. status = ""
  2598. logg.info("wait $NOTIFY_SOCKET, timeout %s (lapse %s)", timeout, lapseTimeout)
  2599. waiting = " ---"
  2600. results = {}
  2601. for attempt in xrange(int(timeout)+1):
  2602. if pid and not self.is_active_pid(pid):
  2603. logg.info("seen dead PID %s", pid)
  2604. return results
  2605. if not attempt: # first one
  2606. time.sleep(1) # until TimeoutStartSec
  2607. continue
  2608. result = self.read_notify_socket(notify, 1) # sleep max 1 second
  2609. for line in result.splitlines():
  2610. # for name, value in self.read_env_part(line)
  2611. if "=" not in line:
  2612. continue
  2613. name, value = line.split("=", 1)
  2614. results[name] = value
  2615. if name in ["STATUS", "ACTIVESTATE", "MAINPID", "READY"]:
  2616. hint="seen notify %s " % (waiting)
  2617. logg.debug("%s :%s=%s", hint, name, value)
  2618. if status != results.get("STATUS",""):
  2619. mainpidTimeout = lapseTimeout
  2620. status = results.get("STATUS", "")
  2621. if "READY" not in results:
  2622. time.sleep(1) # until TimeoutStart
  2623. continue
  2624. if "MAINPID" not in results and not pid_file:
  2625. mainpidTimeout -= 1
  2626. if mainpidTimeout > 0:
  2627. waiting = "%4i" % (-mainpidTimeout)
  2628. time.sleep(1) # until TimeoutStart
  2629. continue
  2630. break # READY and MAINPID
  2631. if "READY" not in results:
  2632. logg.info(".... timeout while waiting for 'READY=1' status on $NOTIFY_SOCKET")
  2633. elif "MAINPID" not in results:
  2634. logg.info(".... seen 'READY=1' but no MAINPID update status on $NOTIFY_SOCKET")
  2635. logg.debug("notify = %s", results)
  2636. try:
  2637. notify.socket.close()
  2638. except Exception as e:
  2639. logg.debug("socket.close %s", e)
  2640. return results
  2641. def start_modules(self, *modules):
  2642. """ [UNIT]... -- start these units
  2643. /// SPECIAL: with --now or --init it will
  2644. run the init-loop and stop the units afterwards """
  2645. found_all = True
  2646. units = []
  2647. for module in modules:
  2648. matched = self.match_units(to_list(module))
  2649. if not matched:
  2650. logg.error("Unit %s not found.", unit_of(module))
  2651. self.error |= NOT_FOUND
  2652. found_all = False
  2653. continue
  2654. for unit in matched:
  2655. if unit not in units:
  2656. units += [ unit ]
  2657. init = self._now or self._init
  2658. return self.start_units(units, init) and found_all
  2659. def start_units(self, units, init = None):
  2660. """ fails if any unit does not start
  2661. /// SPECIAL: may run the init-loop and
  2662. stop the named units afterwards """
  2663. self.wait_system()
  2664. done = True
  2665. started_units = []
  2666. for unit in self.sortedAfter(units):
  2667. started_units.append(unit)
  2668. if not self.start_unit(unit):
  2669. done = False
  2670. if init:
  2671. logg.info("init-loop start")
  2672. sig = self.init_loop_until_stop(started_units)
  2673. logg.info("init-loop %s", sig)
  2674. for unit in reversed(started_units):
  2675. self.stop_unit(unit)
  2676. return done
  2677. def start_unit(self, unit):
  2678. conf = self.load_unit_conf(unit)
  2679. if conf is None:
  2680. logg.debug("unit could not be loaded (%s)", unit)
  2681. logg.error("Unit %s not found.", unit)
  2682. return False
  2683. if self.not_user_conf(conf):
  2684. logg.error("Unit %s not for --user mode", unit)
  2685. return False
  2686. return self.start_unit_from(conf)
  2687. def get_TimeoutStartSec(self, conf):
  2688. timeout = conf.get("Service", "TimeoutSec", strE(DefaultTimeoutStartSec))
  2689. timeout = conf.get("Service", "TimeoutStartSec", timeout)
  2690. return time_to_seconds(timeout, DefaultMaximumTimeout)
  2691. def get_SocketTimeoutSec(self, conf):
  2692. timeout = conf.get("Socket", "TimeoutSec", strE(DefaultTimeoutStartSec))
  2693. return time_to_seconds(timeout, DefaultMaximumTimeout)
  2694. def get_RemainAfterExit(self, conf):
  2695. return conf.getbool("Service", "RemainAfterExit", "no")
  2696. def start_unit_from(self, conf):
  2697. if not conf: return False
  2698. if self.syntax_check(conf) > 100: return False
  2699. with waitlock(conf):
  2700. logg.debug(" start unit %s => %s", conf.name(), strQ(conf.filename()))
  2701. return self.do_start_unit_from(conf)
  2702. def do_start_unit_from(self, conf):
  2703. if conf.name().endswith(".service"):
  2704. return self.do_start_service_from(conf)
  2705. elif conf.name().endswith(".socket"):
  2706. return self.do_start_socket_from(conf)
  2707. elif conf.name().endswith(".target"):
  2708. return self.do_start_target_from(conf)
  2709. else:
  2710. logg.error("start not implemented for unit type: %s", conf.name())
  2711. return False
  2712. def do_start_service_from(self, conf):
  2713. timeout = self.get_TimeoutStartSec(conf)
  2714. doRemainAfterExit = self.get_RemainAfterExit(conf)
  2715. runs = conf.get("Service", "Type", "simple").lower()
  2716. env = self.get_env(conf)
  2717. if not self._quiet:
  2718. okee = self.exec_check_unit(conf, env, "Service", "Exec") # all...
  2719. if not okee and _no_reload: return False
  2720. service_directories = self.create_service_directories(conf)
  2721. env.update(service_directories) # atleast sshd did check for /run/sshd
  2722. # for StopPost on failure:
  2723. returncode = 0
  2724. service_result = "success"
  2725. if True:
  2726. if runs in [ "simple", "forking", "notify", "idle" ]:
  2727. env["MAINPID"] = strE(self.read_mainpid_from(conf))
  2728. for cmd in conf.getlist("Service", "ExecStartPre", []):
  2729. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  2730. logg.info(" pre-start %s", shell_cmd(newcmd))
  2731. forkpid = os.fork()
  2732. if not forkpid:
  2733. self.execve_from(conf, newcmd, env) # pragma: no cover
  2734. run = subprocess_waitpid(forkpid)
  2735. logg.debug(" pre-start done (%s) <-%s>",
  2736. run.returncode or "OK", run.signal or "")
  2737. if run.returncode and exe.check:
  2738. logg.error("the ExecStartPre control process exited with error code")
  2739. active = "failed"
  2740. self.write_status_from(conf, AS=active )
  2741. if _what_kind not in ["none", "keep"]:
  2742. self.remove_service_directories(conf) # cleanup that /run/sshd
  2743. return False
  2744. if runs in [ "oneshot" ]:
  2745. status_file = self.get_status_file_from(conf)
  2746. if self.get_status_from(conf, "ActiveState", "unknown") == "active":
  2747. logg.warning("the service was already up once")
  2748. return True
  2749. for cmd in conf.getlist("Service", "ExecStart", []):
  2750. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  2751. logg.info("%s start %s", runs, shell_cmd(newcmd))
  2752. forkpid = os.fork()
  2753. if not forkpid: # pragma: no cover
  2754. os.setsid() # detach child process from parent
  2755. self.execve_from(conf, newcmd, env)
  2756. run = subprocess_waitpid(forkpid)
  2757. if run.returncode and exe.check:
  2758. returncode = run.returncode
  2759. service_result = "failed"
  2760. logg.error("%s start %s (%s) <-%s>", runs, service_result,
  2761. run.returncode or "OK", run.signal or "")
  2762. break
  2763. logg.info("%s start done (%s) <-%s>", runs,
  2764. run.returncode or "OK", run.signal or "")
  2765. if True:
  2766. self.set_status_from(conf, "ExecMainCode", strE(returncode))
  2767. active = returncode and "failed" or "active"
  2768. self.write_status_from(conf, AS=active)
  2769. elif runs in [ "simple", "idle" ]:
  2770. status_file = self.get_status_file_from(conf)
  2771. pid = self.read_mainpid_from(conf)
  2772. if self.is_active_pid(pid):
  2773. logg.warning("the service is already running on PID %s", pid)
  2774. return True
  2775. if doRemainAfterExit:
  2776. logg.debug("%s RemainAfterExit -> AS=active", runs)
  2777. self.write_status_from(conf, AS="active")
  2778. cmdlist = conf.getlist("Service", "ExecStart", [])
  2779. for idx, cmd in enumerate(cmdlist):
  2780. logg.debug("ExecStart[%s]: %s", idx, cmd)
  2781. for cmd in cmdlist:
  2782. pid = self.read_mainpid_from(conf)
  2783. env["MAINPID"] = strE(pid)
  2784. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  2785. logg.info("%s start %s", runs, shell_cmd(newcmd))
  2786. forkpid = os.fork()
  2787. if not forkpid: # pragma: no cover
  2788. os.setsid() # detach child process from parent
  2789. self.execve_from(conf, newcmd, env)
  2790. self.write_status_from(conf, MainPID=forkpid)
  2791. logg.info("%s started PID %s", runs, forkpid)
  2792. env["MAINPID"] = strE(forkpid)
  2793. time.sleep(MinimumYield)
  2794. run = subprocess_testpid(forkpid)
  2795. if run.returncode is not None:
  2796. logg.info("%s stopped PID %s (%s) <-%s>", runs, run.pid,
  2797. run.returncode or "OK", run.signal or "")
  2798. if doRemainAfterExit:
  2799. self.set_status_from(conf, "ExecMainCode", strE(run.returncode))
  2800. active = run.returncode and "failed" or "active"
  2801. self.write_status_from(conf, AS=active)
  2802. if run.returncode and exe.check:
  2803. service_result = "failed"
  2804. break
  2805. elif runs in [ "notify" ]:
  2806. # "notify" is the same as "simple" but we create a $NOTIFY_SOCKET
  2807. # and wait for startup completion by checking the socket messages
  2808. pid_file = self.pid_file_from(conf)
  2809. pid = self.read_mainpid_from(conf)
  2810. if self.is_active_pid(pid):
  2811. logg.error("the service is already running on PID %s", pid)
  2812. return False
  2813. notify = self.notify_socket_from(conf)
  2814. if notify:
  2815. env["NOTIFY_SOCKET"] = notify.socketfile
  2816. logg.debug("use NOTIFY_SOCKET=%s", notify.socketfile)
  2817. if doRemainAfterExit:
  2818. logg.debug("%s RemainAfterExit -> AS=active", runs)
  2819. self.write_status_from(conf, AS="active")
  2820. cmdlist = conf.getlist("Service", "ExecStart", [])
  2821. for idx, cmd in enumerate(cmdlist):
  2822. logg.debug("ExecStart[%s]: %s", idx, cmd)
  2823. mainpid = None
  2824. for cmd in cmdlist:
  2825. mainpid = self.read_mainpid_from(conf)
  2826. env["MAINPID"] = strE(mainpid)
  2827. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  2828. logg.info("%s start %s", runs, shell_cmd(newcmd))
  2829. forkpid = os.fork()
  2830. if not forkpid: # pragma: no cover
  2831. os.setsid() # detach child process from parent
  2832. self.execve_from(conf, newcmd, env)
  2833. # via NOTIFY # self.write_status_from(conf, MainPID=forkpid)
  2834. logg.info("%s started PID %s", runs, forkpid)
  2835. mainpid = forkpid
  2836. self.write_status_from(conf, MainPID=mainpid)
  2837. env["MAINPID"] = strE(mainpid)
  2838. time.sleep(MinimumYield)
  2839. run = subprocess_testpid(forkpid)
  2840. if run.returncode is not None:
  2841. logg.info("%s stopped PID %s (%s) <-%s>", runs, run.pid,
  2842. run.returncode or "OK", run.signal or "")
  2843. if doRemainAfterExit:
  2844. self.set_status_from(conf, "ExecMainCode", strE(run.returncode))
  2845. active = run.returncode and "failed" or "active"
  2846. self.write_status_from(conf, AS=active)
  2847. if run.returncode and exe.check:
  2848. service_result = "failed"
  2849. break
  2850. if service_result in [ "success" ] and mainpid:
  2851. logg.debug("okay, wating on socket for %ss", timeout)
  2852. results = self.wait_notify_socket(notify, timeout, mainpid, pid_file)
  2853. if "MAINPID" in results:
  2854. new_pid = to_intN(results["MAINPID"])
  2855. if new_pid and new_pid != mainpid:
  2856. logg.info("NEW PID %s from sd_notify (was PID %s)", new_pid, mainpid)
  2857. self.write_status_from(conf, MainPID=new_pid)
  2858. mainpid = new_pid
  2859. logg.info("%s start done %s", runs, mainpid)
  2860. pid = self.read_mainpid_from(conf)
  2861. if pid:
  2862. env["MAINPID"] = strE(pid)
  2863. else:
  2864. service_result = "timeout" # "could not start service"
  2865. elif runs in [ "forking" ]:
  2866. pid_file = self.pid_file_from(conf)
  2867. for cmd in conf.getlist("Service", "ExecStart", []):
  2868. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  2869. if not newcmd: continue
  2870. logg.info("%s start %s", runs, shell_cmd(newcmd))
  2871. forkpid = os.fork()
  2872. if not forkpid: # pragma: no cover
  2873. os.setsid() # detach child process from parent
  2874. self.execve_from(conf, newcmd, env)
  2875. logg.info("%s started PID %s", runs, forkpid)
  2876. run = subprocess_waitpid(forkpid)
  2877. if run.returncode and exe.check:
  2878. returncode = run.returncode
  2879. service_result = "failed"
  2880. logg.info("%s stopped PID %s (%s) <-%s>", runs, run.pid,
  2881. run.returncode or "OK", run.signal or "")
  2882. if pid_file and service_result in [ "success" ]:
  2883. pid = self.wait_pid_file(pid_file) # application PIDFile
  2884. logg.info("%s start done PID %s [%s]", runs, pid, pid_file)
  2885. if pid:
  2886. env["MAINPID"] = strE(pid)
  2887. if not pid_file:
  2888. time.sleep(MinimumTimeoutStartSec)
  2889. logg.warning("No PIDFile for forking %s", strQ(conf.filename()))
  2890. status_file = self.get_status_file_from(conf)
  2891. self.set_status_from(conf, "ExecMainCode", strE(returncode))
  2892. active = returncode and "failed" or "active"
  2893. self.write_status_from(conf, AS=active)
  2894. else:
  2895. logg.error("unsupported run type '%s'", runs)
  2896. return False
  2897. # POST sequence
  2898. if not self.is_active_from(conf):
  2899. logg.warning("%s start not active", runs)
  2900. # according to the systemd documentation, a failed start-sequence
  2901. # should execute the ExecStopPost sequence allowing some cleanup.
  2902. env["SERVICE_RESULT"] = service_result
  2903. for cmd in conf.getlist("Service", "ExecStopPost", []):
  2904. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  2905. logg.info("post-fail %s", shell_cmd(newcmd))
  2906. forkpid = os.fork()
  2907. if not forkpid:
  2908. self.execve_from(conf, newcmd, env) # pragma: no cover
  2909. run = subprocess_waitpid(forkpid)
  2910. logg.debug("post-fail done (%s) <-%s>",
  2911. run.returncode or "OK", run.signal or "")
  2912. if _what_kind not in ["none", "keep"]:
  2913. self.remove_service_directories(conf)
  2914. return False
  2915. else:
  2916. for cmd in conf.getlist("Service", "ExecStartPost", []):
  2917. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  2918. logg.info("post-start %s", shell_cmd(newcmd))
  2919. forkpid = os.fork()
  2920. if not forkpid:
  2921. self.execve_from(conf, newcmd, env) # pragma: no cover
  2922. run = subprocess_waitpid(forkpid)
  2923. logg.debug("post-start done (%s) <-%s>",
  2924. run.returncode or "OK", run.signal or "")
  2925. return True
  2926. def listen_modules(self, *modules):
  2927. """ [UNIT]... -- listen socket units"""
  2928. found_all = True
  2929. units = []
  2930. for module in modules:
  2931. matched = self.match_units(to_list(module))
  2932. if not matched:
  2933. logg.error("Unit %s not found.", unit_of(module))
  2934. self.error |= NOT_FOUND
  2935. found_all = False
  2936. continue
  2937. for unit in matched:
  2938. if unit not in units:
  2939. units += [ unit ]
  2940. return self.listen_units(units) and found_all
  2941. def listen_units(self, units):
  2942. """ fails if any socket does not start """
  2943. self.wait_system()
  2944. done = True
  2945. started_units = []
  2946. active_units = []
  2947. for unit in self.sortedAfter(units):
  2948. started_units.append(unit)
  2949. if not self.listen_unit(unit):
  2950. done = False
  2951. else:
  2952. active_units.append(unit)
  2953. if active_units:
  2954. logg.info("init-loop start")
  2955. sig = self.init_loop_until_stop(started_units)
  2956. logg.info("init-loop %s", sig)
  2957. for unit in reversed(started_units):
  2958. pass # self.stop_unit(unit)
  2959. return done
  2960. def listen_unit(self, unit):
  2961. conf = self.load_unit_conf(unit)
  2962. if conf is None:
  2963. logg.debug("unit could not be loaded (%s)", unit)
  2964. logg.error("Unit %s not found.", unit)
  2965. return False
  2966. if self.not_user_conf(conf):
  2967. logg.error("Unit %s not for --user mode", unit)
  2968. return False
  2969. return self.listen_unit_from(conf)
  2970. def listen_unit_from(self, conf):
  2971. if not conf: return False
  2972. with waitlock(conf):
  2973. logg.debug(" listen unit %s => %s", conf.name(), strQ(conf.filename()))
  2974. return self.do_listen_unit_from(conf)
  2975. def do_listen_unit_from(self, conf):
  2976. if conf.name().endswith(".socket"):
  2977. return self.do_start_socket_from(conf)
  2978. else:
  2979. logg.error("listen not implemented for unit type: %s", conf.name())
  2980. return False
  2981. def do_accept_socket_from(self, conf, sock):
  2982. logg.debug("%s: accepting %s", conf.name(), sock.fileno())
  2983. service_unit = self.get_socket_service_from(conf)
  2984. service_conf = self.load_unit_conf(service_unit)
  2985. if service_conf is None or TestAccept: #pragma: no cover
  2986. if sock.type == socket.SOCK_STREAM:
  2987. conn, addr = sock.accept()
  2988. data = conn.recv(1024)
  2989. logg.debug("%s: '%s'", conf.name(), data)
  2990. conn.send(b"ERROR: "+data.upper())
  2991. conn.close()
  2992. return False
  2993. if sock.type == socket.SOCK_DGRAM:
  2994. data, sender = sock.recvfrom(1024)
  2995. logg.debug("%s: '%s'", conf.name(), data)
  2996. sock.sendto(b"ERROR: "+data.upper(), sender)
  2997. return False
  2998. logg.error("can not accept socket type %s", strINET(sock.type))
  2999. return False
  3000. return self.do_start_service_from(service_conf)
  3001. def get_socket_service_from(self, conf):
  3002. socket_unit = conf.name()
  3003. accept = conf.getbool("Socket", "Accept", "no")
  3004. service_type = accept and "@.service" or ".service"
  3005. service_name = path_replace_extension(socket_unit, ".socket", service_type)
  3006. service_unit = conf.get("Socket", "Service", service_name)
  3007. logg.debug("socket %s -> service %s", socket_unit, service_unit)
  3008. return service_unit
  3009. def do_start_socket_from(self, conf):
  3010. runs = "socket"
  3011. timeout = self.get_SocketTimeoutSec(conf)
  3012. accept = conf.getbool("Socket", "Accept", "no")
  3013. stream = conf.get("Socket", "ListenStream", "")
  3014. service_unit = self.get_socket_service_from(conf)
  3015. service_conf = self.load_unit_conf(service_unit)
  3016. if service_conf is None:
  3017. logg.debug("unit could not be loaded (%s)", service_unit)
  3018. logg.error("Unit %s not found.", service_unit)
  3019. return False
  3020. env = self.get_env(conf)
  3021. if not self._quiet:
  3022. okee = self.exec_check_unit(conf, env, "Socket", "Exec") # all...
  3023. if not okee and _no_reload: return False
  3024. if True:
  3025. for cmd in conf.getlist("Socket", "ExecStartPre", []):
  3026. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  3027. logg.info(" pre-start %s", shell_cmd(newcmd))
  3028. forkpid = os.fork()
  3029. if not forkpid:
  3030. self.execve_from(conf, newcmd, env) # pragma: no cover
  3031. run = subprocess_waitpid(forkpid)
  3032. logg.debug(" pre-start done (%s) <-%s>",
  3033. run.returncode or "OK", run.signal or "")
  3034. if run.returncode and exe.check:
  3035. logg.error("the ExecStartPre control process exited with error code")
  3036. active = "failed"
  3037. self.write_status_from(conf, AS=active )
  3038. return False
  3039. # service_directories = self.create_service_directories(conf)
  3040. # env.update(service_directories)
  3041. listening=False
  3042. if not accept:
  3043. sock = self.create_socket(conf)
  3044. if sock and TestListen:
  3045. listening=True
  3046. self._sockets[conf.name()] = SystemctlSocket(conf, sock)
  3047. service_result = "success"
  3048. state = sock and "active" or "failed"
  3049. self.write_status_from(conf, AS=state)
  3050. if not listening:
  3051. # we do not listen but have the service started right away
  3052. done = self.do_start_service_from(service_conf)
  3053. service_result = done and "success" or "failed"
  3054. if not self.is_active_from(service_conf):
  3055. service_result = "failed"
  3056. state = service_result
  3057. if service_result in ["success"]:
  3058. state = "active"
  3059. self.write_status_from(conf, AS=state)
  3060. # POST sequence
  3061. if service_result in ["failed"]:
  3062. # according to the systemd documentation, a failed start-sequence
  3063. # should execute the ExecStopPost sequence allowing some cleanup.
  3064. env["SERVICE_RESULT"] = service_result
  3065. for cmd in conf.getlist("Socket", "ExecStopPost", []):
  3066. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  3067. logg.info("post-fail %s", shell_cmd(newcmd))
  3068. forkpid = os.fork()
  3069. if not forkpid:
  3070. self.execve_from(conf, newcmd, env) # pragma: no cover
  3071. run = subprocess_waitpid(forkpid)
  3072. logg.debug("post-fail done (%s) <-%s>",
  3073. run.returncode or "OK", run.signal or "")
  3074. return False
  3075. else:
  3076. for cmd in conf.getlist("Socket", "ExecStartPost", []):
  3077. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  3078. logg.info("post-start %s", shell_cmd(newcmd))
  3079. forkpid = os.fork()
  3080. if not forkpid:
  3081. self.execve_from(conf, newcmd, env) # pragma: no cover
  3082. run = subprocess_waitpid(forkpid)
  3083. logg.debug("post-start done (%s) <-%s>",
  3084. run.returncode or "OK", run.signal or "")
  3085. return True
  3086. def create_socket(self, conf):
  3087. unsupported = ["ListenUSBFunction", "ListenMessageQueue", "ListenNetlink"]
  3088. unsupported += [ "ListenSpecial", "ListenFIFO", "ListenSequentialPacket"]
  3089. for item in unsupported:
  3090. if conf.get("Socket", item, ""):
  3091. logg.warning("%s: %s sockets are not implemented", conf.name(), item)
  3092. self.error |= NOT_OK
  3093. return None
  3094. vListenDatagram = conf.get("Socket", "ListenDatagram", "")
  3095. vListenStream = conf.get("Socket", "ListenStream", "")
  3096. address = vListenStream or vListenDatagram
  3097. m = re.match(r"(/.*)", address)
  3098. if m:
  3099. path = m.group(1)
  3100. sock = self.create_unix_socket(conf, path, not vListenStream)
  3101. self.set_status_from(conf, "path", path)
  3102. return sock
  3103. m = re.match(r"(\d+[.]\d*[.]\d*[.]\d+):(\d+)", address)
  3104. if m:
  3105. addr, port = m.group(1), m.group(2)
  3106. sock = self.create_port_ipv4_socket(conf, addr, port, not vListenStream)
  3107. self.set_status_from(conf, "port", port)
  3108. self.set_status_from(conf, "addr", addr)
  3109. return sock
  3110. m = re.match(r"\[([0-9a-fA-F:]*)\]:(\d+)", address)
  3111. if m:
  3112. addr, port = m.group(1), m.group(2)
  3113. sock = self.create_port_ipv6_socket(conf, addr, port, not vListenStream)
  3114. self.set_status_from(conf, "port", port)
  3115. self.set_status_from(conf, "addr", addr)
  3116. return sock
  3117. m = re.match(r"(\d+)$", address)
  3118. if m:
  3119. port = m.group(1)
  3120. sock = self.create_port_socket(conf, port, not vListenStream)
  3121. self.set_status_from(conf, "port", port)
  3122. return sock
  3123. if re.match("@.*", address):
  3124. logg.warning("%s: abstract namespace socket not implemented (%s)", conf.name(), address)
  3125. return None
  3126. if re.match("vsock:.*", address):
  3127. logg.warning("%s: virtual machine socket not implemented (%s)", conf.name(), address)
  3128. return None
  3129. logg.error("%s: unknown socket address type (%s)", conf.name(), address)
  3130. return None
  3131. def create_unix_socket(self, conf, path, dgram):
  3132. sock_stream = dgram and socket.SOCK_DGRAM or socket.SOCK_STREAM
  3133. sock = socket.socket(socket.AF_UNIX, sock_stream)
  3134. try:
  3135. dirmode = conf.get("Socket", "DirectoryMode", "0755")
  3136. mode = conf.get("Socket", "SocketMode", "0666")
  3137. user = conf.get("Socket", "SocketUser", "")
  3138. group = conf.get("Socket", "SocketGroup", "")
  3139. symlinks = conf.getlist("Socket", "SymLinks", [])
  3140. dirpath = os.path.dirname(path)
  3141. if not os.path.isdir(dirpath):
  3142. os.makedirs(dirpath, int(dirmode, 8))
  3143. if os.path.exists(path):
  3144. os.unlink(path)
  3145. sock.bind(path)
  3146. os.fchmod(sock.fileno(), int(mode, 8))
  3147. shutil_fchown(sock.fileno(), user, group)
  3148. if symlinks:
  3149. logg.warning("%s: symlinks for socket not implemented (%s)", conf.name(), path)
  3150. except Exception as e:
  3151. logg.error("%s: create socket failed [%s]: %s", conf.name(), path, e)
  3152. sock.close()
  3153. return None
  3154. return sock
  3155. def create_port_socket(self, conf, port, dgram):
  3156. inet = dgram and socket.SOCK_DGRAM or socket.SOCK_STREAM
  3157. sock = socket.socket(socket.AF_INET, inet)
  3158. try:
  3159. sock.bind(('', int(port)))
  3160. logg.info("%s: bound socket at %s %s:%s", conf.name(), strINET(inet), "*", port)
  3161. except Exception as e:
  3162. logg.error("%s: create socket failed (%s:%s): %s", conf.name(), "*", port, e)
  3163. sock.close()
  3164. return None
  3165. return sock
  3166. def create_port_ipv4_socket(self, conf, addr, port, dgram):
  3167. inet = dgram and socket.SOCK_DGRAM or socket.SOCK_STREAM
  3168. sock = socket.socket(socket.AF_INET, inet)
  3169. try:
  3170. sock.bind((addr, int(port)))
  3171. logg.info("%s: bound socket at %s %s:%s", conf.name(), strINET(inet), addr, port)
  3172. except Exception as e:
  3173. logg.error("%s: create socket failed (%s:%s): %s", conf.name(), addr, port, e)
  3174. sock.close()
  3175. return None
  3176. return sock
  3177. def create_port_ipv6_socket(self, conf, addr, port, dgram):
  3178. inet = dgram and socket.SOCK_DGRAM or socket.SOCK_STREAM
  3179. sock = socket.socket(socket.AF_INET6, inet)
  3180. try:
  3181. sock.bind((addr, int(port)))
  3182. logg.info("%s: bound socket at %s [%s]:%s", conf.name(), strINET(inet), addr, port)
  3183. except Exception as e:
  3184. logg.error("%s: create socket failed ([%s]:%s): %s", conf.name(), addr, port, e)
  3185. sock.close()
  3186. return None
  3187. return sock
  3188. def extend_exec_env(self, env):
  3189. env = env.copy()
  3190. # implant DefaultPath into $PATH
  3191. path = env.get("PATH", DefaultPath)
  3192. parts = path.split(os.pathsep)
  3193. for part in DefaultPath.split(os.pathsep):
  3194. if part and part not in parts:
  3195. parts.append(part)
  3196. env["PATH"] = str(os.pathsep).join(parts)
  3197. # reset locale to system default
  3198. for name in ResetLocale:
  3199. if name in env:
  3200. del env[name]
  3201. locale = {}
  3202. path = env.get("LOCALE_CONF", LocaleConf)
  3203. parts = path.split(os.pathsep)
  3204. for part in parts:
  3205. if os.path.isfile(part):
  3206. for var, val in self.read_env_file("-"+part):
  3207. locale[var] = val
  3208. env[var] = val
  3209. if "LANG" not in locale:
  3210. env["LANG"] = locale.get("LANGUAGE", locale.get("LC_CTYPE", "C"))
  3211. return env
  3212. def expand_list(self, group_lines, conf):
  3213. result = []
  3214. for line in group_lines:
  3215. for item in line.split():
  3216. if item:
  3217. result.append(self.expand_special(item, conf))
  3218. return result
  3219. def get_User(self, conf):
  3220. return self.expand_special(conf.get("Service", "User", ""), conf)
  3221. def get_Group(self, conf):
  3222. return self.expand_special(conf.get("Service", "Group", ""), conf)
  3223. def get_SupplementaryGroups(self, conf):
  3224. return self.expand_list(conf.getlist("Service", "SupplementaryGroups", []), conf)
  3225. def skip_journal_log(self, conf):
  3226. if self.get_unit_type(conf.name()) not in [ "service" ]:
  3227. return True
  3228. std_out = conf.get("Service", "StandardOutput", DefaultStandardOutput)
  3229. std_err = conf.get("Service", "StandardError", DefaultStandardError)
  3230. out, err = False, False
  3231. if std_out in ["null"]: out = True
  3232. if std_out.startswith("file:"): out = True
  3233. if std_err in ["inherit"]: std_err = std_out
  3234. if std_err in ["null"]: err = True
  3235. if std_err.startswith("file:"): err = True
  3236. if std_err.startswith("append:"): err = True
  3237. return out and err
  3238. def dup2_journal_log(self, conf):
  3239. msg = ""
  3240. std_inp = conf.get("Service", "StandardInput", DefaultStandardInput)
  3241. std_out = conf.get("Service", "StandardOutput", DefaultStandardOutput)
  3242. std_err = conf.get("Service", "StandardError", DefaultStandardError)
  3243. inp, out, err = None, None, None
  3244. if std_inp in ["null"]:
  3245. inp = open(_dev_null, "r")
  3246. elif std_inp.startswith("file:"):
  3247. fname = std_inp[len("file:"):]
  3248. if os.path.exists(fname):
  3249. inp = open(fname, "r")
  3250. else:
  3251. inp = open(_dev_zero, "r")
  3252. else:
  3253. inp = open(_dev_zero, "r")
  3254. assert inp is not None
  3255. try:
  3256. if std_out in ["null"]:
  3257. out = open(_dev_null, "w")
  3258. elif std_out.startswith("file:"):
  3259. fname = std_out[len("file:"):]
  3260. fdir = os.path.dirname(fname)
  3261. if not os.path.exists(fdir):
  3262. os.makedirs(fdir)
  3263. out = open(fname, "w")
  3264. elif std_out.startswith("append:"):
  3265. fname = std_out[len("append:"):]
  3266. fdir = os.path.dirname(fname)
  3267. if not os.path.exists(fdir):
  3268. os.makedirs(fdir)
  3269. out = open(fname, "a")
  3270. except Exception as e:
  3271. msg += "\n%s: %s" % (fname, e)
  3272. if out is None:
  3273. out = self.open_journal_log(conf)
  3274. err = out
  3275. assert out is not None
  3276. try:
  3277. if std_err in ["inherit"]:
  3278. err = out
  3279. elif std_err in ["null"]:
  3280. err = open(_dev_null, "w")
  3281. elif std_err.startswith("file:"):
  3282. fname = std_err[len("file:"):]
  3283. fdir = os.path.dirname(fname)
  3284. if not os.path.exists(fdir):
  3285. os.makedirs(fdir)
  3286. err = open(fname, "w")
  3287. elif std_err.startswith("append:"):
  3288. fname = std_err[len("append:"):]
  3289. fdir = os.path.dirname(fname)
  3290. if not os.path.exists(fdir):
  3291. os.makedirs(fdir)
  3292. err = open(fname, "a")
  3293. except Exception as e:
  3294. msg += "\n%s: %s" % (fname, e)
  3295. if err is None:
  3296. err = self.open_journal_log(conf)
  3297. assert err is not None
  3298. if msg:
  3299. err.write("ERROR:")
  3300. err.write(msg.strip())
  3301. err.write("\n")
  3302. if EXEC_DUP2:
  3303. os.dup2(inp.fileno(), sys.stdin.fileno())
  3304. os.dup2(out.fileno(), sys.stdout.fileno())
  3305. os.dup2(err.fileno(), sys.stderr.fileno())
  3306. def execve_from(self, conf, cmd, env):
  3307. """ this code is commonly run in a child process // returns exit-code"""
  3308. runs = conf.get("Service", "Type", "simple").lower()
  3309. # logg.debug("%s process for %s => %s", runs, strE(conf.name()), strQ(conf.filename()))
  3310. self.dup2_journal_log(conf)
  3311. #
  3312. runuser = self.get_User(conf)
  3313. rungroup = self.get_Group(conf)
  3314. xgroups = self.get_SupplementaryGroups(conf)
  3315. envs = shutil_setuid(runuser, rungroup, xgroups)
  3316. badpath = self.chdir_workingdir(conf) # some dirs need setuid before
  3317. if badpath:
  3318. logg.error("(%s): bad workingdir: '%s'", shell_cmd(cmd), badpath)
  3319. sys.exit(1)
  3320. env = self.extend_exec_env(env)
  3321. env.update(envs) # set $HOME to ~$USER
  3322. try:
  3323. if EXEC_SPAWN:
  3324. cmd_args = [ arg for arg in cmd ] # satisfy mypy
  3325. exitcode = os.spawnvpe(os.P_WAIT, cmd[0], cmd_args, env)
  3326. sys.exit(exitcode)
  3327. else: # pragma: no cover
  3328. os.execve(cmd[0], cmd, env)
  3329. sys.exit(11) # pragma: no cover (can not be reached / bug like mypy#8401)
  3330. except Exception as e:
  3331. logg.error("(%s): %s", shell_cmd(cmd), e)
  3332. sys.exit(1)
  3333. def test_start_unit(self, unit):
  3334. """ helper function to test the code that is normally forked off """
  3335. conf = self.load_unit_conf(unit)
  3336. if not conf: return None
  3337. env = self.get_env(conf)
  3338. for cmd in conf.getlist("Service", "ExecStart", []):
  3339. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  3340. self.execve_from(conf, newcmd, env)
  3341. return None
  3342. def stop_modules(self, *modules):
  3343. """ [UNIT]... -- stop these units """
  3344. found_all = True
  3345. units = []
  3346. for module in modules:
  3347. matched = self.match_units(to_list(module))
  3348. if not matched:
  3349. logg.error("Unit %s not found.", unit_of(module))
  3350. self.error |= NOT_FOUND
  3351. found_all = False
  3352. continue
  3353. for unit in matched:
  3354. if unit not in units:
  3355. units += [ unit ]
  3356. return self.stop_units(units) and found_all
  3357. def stop_units(self, units):
  3358. """ fails if any unit fails to stop """
  3359. self.wait_system()
  3360. done = True
  3361. for unit in self.sortedBefore(units):
  3362. if not self.stop_unit(unit):
  3363. done = False
  3364. return done
  3365. def stop_unit(self, unit):
  3366. conf = self.load_unit_conf(unit)
  3367. if conf is None:
  3368. logg.error("Unit %s not found.", unit)
  3369. return False
  3370. if self.not_user_conf(conf):
  3371. logg.error("Unit %s not for --user mode", unit)
  3372. return False
  3373. return self.stop_unit_from(conf)
  3374. def get_TimeoutStopSec(self, conf):
  3375. timeout = conf.get("Service", "TimeoutSec", strE(DefaultTimeoutStartSec))
  3376. timeout = conf.get("Service", "TimeoutStopSec", timeout)
  3377. return time_to_seconds(timeout, DefaultMaximumTimeout)
  3378. def stop_unit_from(self, conf):
  3379. if not conf: return False
  3380. if self.syntax_check(conf) > 100: return False
  3381. with waitlock(conf):
  3382. logg.info(" stop unit %s => %s", conf.name(), strQ(conf.filename()))
  3383. return self.do_stop_unit_from(conf)
  3384. def do_stop_unit_from(self, conf):
  3385. if conf.name().endswith(".service"):
  3386. return self.do_stop_service_from(conf)
  3387. elif conf.name().endswith(".socket"):
  3388. return self.do_stop_socket_from(conf)
  3389. elif conf.name().endswith(".target"):
  3390. return self.do_stop_target_from(conf)
  3391. else:
  3392. logg.error("stop not implemented for unit type: %s", conf.name())
  3393. return False
  3394. def do_stop_service_from(self, conf):
  3395. timeout = self.get_TimeoutStopSec(conf)
  3396. runs = conf.get("Service", "Type", "simple").lower()
  3397. env = self.get_env(conf)
  3398. if not self._quiet:
  3399. okee = self.exec_check_unit(conf, env, "Service", "ExecStop")
  3400. if not okee and _no_reload: return False
  3401. service_directories = self.env_service_directories(conf)
  3402. env.update(service_directories)
  3403. returncode = 0
  3404. service_result = "success"
  3405. if runs in [ "oneshot" ]:
  3406. status_file = self.get_status_file_from(conf)
  3407. if self.get_status_from(conf, "ActiveState", "unknown") == "inactive":
  3408. logg.warning("the service is already down once")
  3409. return True
  3410. for cmd in conf.getlist("Service", "ExecStop", []):
  3411. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  3412. logg.info("%s stop %s", runs, shell_cmd(newcmd))
  3413. forkpid = os.fork()
  3414. if not forkpid:
  3415. self.execve_from(conf, newcmd, env) # pragma: no cover
  3416. run = subprocess_waitpid(forkpid)
  3417. if run.returncode and exe.check:
  3418. returncode = run.returncode
  3419. service_result = "failed"
  3420. break
  3421. if True:
  3422. if returncode:
  3423. self.set_status_from(conf, "ExecStopCode", strE(returncode))
  3424. self.write_status_from(conf, AS="failed")
  3425. else:
  3426. self.clean_status_from(conf) # "inactive"
  3427. ### fallback Stop => Kill for ["simple","notify","forking"]
  3428. elif not conf.getlist("Service", "ExecStop", []):
  3429. logg.info("no ExecStop => systemctl kill")
  3430. if True:
  3431. self.do_kill_unit_from(conf)
  3432. self.clean_pid_file_from(conf)
  3433. self.clean_status_from(conf) # "inactive"
  3434. elif runs in [ "simple", "notify", "idle" ]:
  3435. status_file = self.get_status_file_from(conf)
  3436. size = os.path.exists(status_file) and os.path.getsize(status_file)
  3437. logg.info("STATUS %s %s", status_file, size)
  3438. pid = 0
  3439. for cmd in conf.getlist("Service", "ExecStop", []):
  3440. env["MAINPID"] = strE(self.read_mainpid_from(conf))
  3441. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  3442. logg.info("%s stop %s", runs, shell_cmd(newcmd))
  3443. forkpid = os.fork()
  3444. if not forkpid:
  3445. self.execve_from(conf, newcmd, env) # pragma: no cover
  3446. run = subprocess_waitpid(forkpid)
  3447. run = must_have_failed(run, newcmd) # TODO: a workaround
  3448. # self.write_status_from(conf, MainPID=run.pid) # no ExecStop
  3449. if run.returncode and exe.check:
  3450. returncode = run.returncode
  3451. service_result = "failed"
  3452. break
  3453. pid = to_intN(env.get("MAINPID"))
  3454. if pid:
  3455. if self.wait_vanished_pid(pid, timeout):
  3456. self.clean_pid_file_from(conf)
  3457. self.clean_status_from(conf) # "inactive"
  3458. else:
  3459. logg.info("%s sleep as no PID was found on Stop", runs)
  3460. time.sleep(MinimumTimeoutStopSec)
  3461. pid = self.read_mainpid_from(conf)
  3462. if not pid or not pid_exists(pid) or pid_zombie(pid):
  3463. self.clean_pid_file_from(conf)
  3464. self.clean_status_from(conf) # "inactive"
  3465. elif runs in [ "forking" ]:
  3466. status_file = self.get_status_file_from(conf)
  3467. pid_file = self.pid_file_from(conf)
  3468. for cmd in conf.getlist("Service", "ExecStop", []):
  3469. # active = self.is_active_from(conf)
  3470. if pid_file:
  3471. new_pid = self.read_mainpid_from(conf)
  3472. if new_pid:
  3473. env["MAINPID"] = strE(new_pid)
  3474. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  3475. logg.info("fork stop %s", shell_cmd(newcmd))
  3476. forkpid = os.fork()
  3477. if not forkpid:
  3478. self.execve_from(conf, newcmd, env) # pragma: no cover
  3479. run = subprocess_waitpid(forkpid)
  3480. if run.returncode and exe.check:
  3481. returncode = run.returncode
  3482. service_result = "failed"
  3483. break
  3484. pid = to_intN(env.get("MAINPID"))
  3485. if pid:
  3486. if self.wait_vanished_pid(pid, timeout):
  3487. self.clean_pid_file_from(conf)
  3488. else:
  3489. logg.info("%s sleep as no PID was found on Stop", runs)
  3490. time.sleep(MinimumTimeoutStopSec)
  3491. pid = self.read_mainpid_from(conf)
  3492. if not pid or not pid_exists(pid) or pid_zombie(pid):
  3493. self.clean_pid_file_from(conf)
  3494. if returncode:
  3495. if os.path.isfile(status_file):
  3496. self.set_status_from(conf, "ExecStopCode", strE(returncode))
  3497. self.write_status_from(conf, AS="failed")
  3498. else:
  3499. self.clean_status_from(conf) # "inactive"
  3500. else:
  3501. logg.error("unsupported run type '%s'", runs)
  3502. return False
  3503. # POST sequence
  3504. if not self.is_active_from(conf):
  3505. env["SERVICE_RESULT"] = service_result
  3506. for cmd in conf.getlist("Service", "ExecStopPost", []):
  3507. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  3508. logg.info("post-stop %s", shell_cmd(newcmd))
  3509. forkpid = os.fork()
  3510. if not forkpid:
  3511. self.execve_from(conf, newcmd, env) # pragma: no cover
  3512. run = subprocess_waitpid(forkpid)
  3513. logg.debug("post-stop done (%s) <-%s>",
  3514. run.returncode or "OK", run.signal or "")
  3515. if _what_kind not in ["none", "keep"]:
  3516. self.remove_service_directories(conf)
  3517. return service_result == "success"
  3518. def do_stop_socket_from(self, conf):
  3519. runs = "socket"
  3520. timeout = self.get_SocketTimeoutSec(conf)
  3521. accept = conf.getbool("Socket", "Accept", "no")
  3522. service_unit = self.get_socket_service_from(conf)
  3523. service_conf = self.load_unit_conf(service_unit)
  3524. if service_conf is None:
  3525. logg.debug("unit could not be loaded (%s)", service_unit)
  3526. logg.error("Unit %s not found.", service_unit)
  3527. return False
  3528. env = self.get_env(conf)
  3529. if not self._quiet:
  3530. okee = self.exec_check_unit(conf, env, "Socket", "ExecStop")
  3531. if not okee and _no_reload: return False
  3532. if not accept:
  3533. # we do not listen but have the service started right away
  3534. done = self.do_stop_service_from(service_conf)
  3535. service_result = done and "success" or "failed"
  3536. else:
  3537. done = self.do_stop_service_from(service_conf)
  3538. service_result = done and "success" or "failed"
  3539. # service_directories = self.env_service_directories(conf)
  3540. # env.update(service_directories)
  3541. # POST sequence
  3542. if not self.is_active_from(conf):
  3543. env["SERVICE_RESULT"] = service_result
  3544. for cmd in conf.getlist("Socket", "ExecStopPost", []):
  3545. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  3546. logg.info("post-stop %s", shell_cmd(newcmd))
  3547. forkpid = os.fork()
  3548. if not forkpid:
  3549. self.execve_from(conf, newcmd, env) # pragma: no cover
  3550. run = subprocess_waitpid(forkpid)
  3551. logg.debug("post-stop done (%s) <-%s>",
  3552. run.returncode or "OK", run.signal or "")
  3553. return service_result == "success"
  3554. def wait_vanished_pid(self, pid, timeout):
  3555. if not pid:
  3556. return True
  3557. if not self.is_active_pid(pid):
  3558. return True
  3559. logg.info("wait for PID %s to vanish (%ss)", pid, timeout)
  3560. for x in xrange(int(timeout)):
  3561. time.sleep(1) # until TimeoutStopSec
  3562. if not self.is_active_pid(pid):
  3563. logg.info("wait for PID %s is done (%s.)", pid, x)
  3564. return True
  3565. logg.info("wait for PID %s failed (%s.)", pid, timeout)
  3566. return False
  3567. def reload_modules(self, *modules):
  3568. """ [UNIT]... -- reload these units """
  3569. self.wait_system()
  3570. found_all = True
  3571. units = []
  3572. for module in modules:
  3573. matched = self.match_units(to_list(module))
  3574. if not matched:
  3575. logg.error("Unit %s not found.", unit_of(module))
  3576. self.error |= NOT_FOUND
  3577. found_all = False
  3578. continue
  3579. for unit in matched:
  3580. if unit not in units:
  3581. units += [ unit ]
  3582. return self.reload_units(units) and found_all
  3583. def reload_units(self, units):
  3584. """ fails if any unit fails to reload """
  3585. self.wait_system()
  3586. done = True
  3587. for unit in self.sortedAfter(units):
  3588. if not self.reload_unit(unit):
  3589. done = False
  3590. return done
  3591. def reload_unit(self, unit):
  3592. conf = self.load_unit_conf(unit)
  3593. if conf is None:
  3594. logg.error("Unit %s not found.", unit)
  3595. return False
  3596. if self.not_user_conf(conf):
  3597. logg.error("Unit %s not for --user mode", unit)
  3598. return False
  3599. return self.reload_unit_from(conf)
  3600. def reload_unit_from(self, conf):
  3601. if not conf: return False
  3602. if self.syntax_check(conf) > 100: return False
  3603. with waitlock(conf):
  3604. logg.info(" reload unit %s => %s", conf.name(), strQ(conf.filename()))
  3605. return self.do_reload_unit_from(conf)
  3606. def do_reload_unit_from(self, conf):
  3607. if conf.name().endswith(".service"):
  3608. return self.do_reload_service_from(conf)
  3609. elif conf.name().endswith(".socket"):
  3610. service_unit = self.get_socket_service_from(conf)
  3611. service_conf = self.load_unit_conf(service_unit)
  3612. if service_conf:
  3613. return self.do_reload_service_from(service_conf)
  3614. else:
  3615. logg.error("no %s found for unit type: %s", service_unit, conf.name())
  3616. return False
  3617. elif conf.name().endswith(".target"):
  3618. return self.do_reload_target_from(conf)
  3619. else:
  3620. logg.error("reload not implemented for unit type: %s", conf.name())
  3621. return False
  3622. def do_reload_service_from(self, conf):
  3623. runs = conf.get("Service", "Type", "simple").lower()
  3624. env = self.get_env(conf)
  3625. if not self._quiet:
  3626. okee = self.exec_check_unit(conf, env, "Service", "ExecReload")
  3627. if not okee and _no_reload: return False
  3628. initscript = conf.filename()
  3629. if self.is_sysv_file(initscript):
  3630. status_file = self.get_status_file_from(conf)
  3631. if initscript:
  3632. newcmd = [initscript, "reload"]
  3633. env["SYSTEMCTL_SKIP_REDIRECT"] = "yes"
  3634. logg.info("%s reload %s", runs, shell_cmd(newcmd))
  3635. forkpid = os.fork()
  3636. if not forkpid:
  3637. self.execve_from(conf, newcmd, env) # pragma: nocover
  3638. run = subprocess_waitpid(forkpid)
  3639. self.set_status_from(conf, "ExecReloadCode", run.returncode)
  3640. if run.returncode:
  3641. self.write_status_from(conf, AS="failed")
  3642. return False
  3643. else:
  3644. self.write_status_from(conf, AS="active")
  3645. return True
  3646. service_directories = self.env_service_directories(conf)
  3647. env.update(service_directories)
  3648. if runs in [ "simple", "notify", "forking", "idle" ]:
  3649. if not self.is_active_from(conf):
  3650. logg.info("no reload on inactive service %s", conf.name())
  3651. return True
  3652. for cmd in conf.getlist("Service", "ExecReload", []):
  3653. env["MAINPID"] = strE(self.read_mainpid_from(conf))
  3654. exe, newcmd = self.exec_newcmd(cmd, env, conf)
  3655. logg.info("%s reload %s", runs, shell_cmd(newcmd))
  3656. forkpid = os.fork()
  3657. if not forkpid:
  3658. self.execve_from(conf, newcmd, env) # pragma: no cover
  3659. run = subprocess_waitpid(forkpid)
  3660. if run.returncode and exe.check:
  3661. logg.error("Job for %s failed because the control process exited with error code. (%s)",
  3662. conf.name(), run.returncode)
  3663. return False
  3664. time.sleep(MinimumYield)
  3665. return True
  3666. elif runs in [ "oneshot" ]:
  3667. logg.debug("ignored run type '%s' for reload", runs)
  3668. return True
  3669. else:
  3670. logg.error("unsupported run type '%s'", runs)
  3671. return False
  3672. def restart_modules(self, *modules):
  3673. """ [UNIT]... -- restart these units """
  3674. found_all = True
  3675. units = []
  3676. for module in modules:
  3677. matched = self.match_units(to_list(module))
  3678. if not matched:
  3679. logg.error("Unit %s not found.", unit_of(module))
  3680. self.error |= NOT_FOUND
  3681. found_all = False
  3682. continue
  3683. for unit in matched:
  3684. if unit not in units:
  3685. units += [ unit ]
  3686. return self.restart_units(units) and found_all
  3687. def restart_units(self, units):
  3688. """ fails if any unit fails to restart """
  3689. self.wait_system()
  3690. done = True
  3691. for unit in self.sortedAfter(units):
  3692. if not self.restart_unit(unit):
  3693. done = False
  3694. return done
  3695. def restart_unit(self, unit):
  3696. conf = self.load_unit_conf(unit)
  3697. if conf is None:
  3698. logg.error("Unit %s not found.", unit)
  3699. return False
  3700. if self.not_user_conf(conf):
  3701. logg.error("Unit %s not for --user mode", unit)
  3702. return False
  3703. return self.restart_unit_from(conf)
  3704. def restart_unit_from(self, conf):
  3705. if not conf: return False
  3706. if self.syntax_check(conf) > 100: return False
  3707. with waitlock(conf):
  3708. if conf.name().endswith(".service"):
  3709. logg.info(" restart service %s => %s", conf.name(), strQ(conf.filename()))
  3710. if not self.is_active_from(conf):
  3711. return self.do_start_unit_from(conf)
  3712. else:
  3713. return self.do_restart_unit_from(conf)
  3714. else:
  3715. return self.do_restart_unit_from(conf)
  3716. def do_restart_unit_from(self, conf):
  3717. logg.info("(restart) => stop/start %s", conf.name())
  3718. self.do_stop_unit_from(conf)
  3719. return self.do_start_unit_from(conf)
  3720. def try_restart_modules(self, *modules):
  3721. """ [UNIT]... -- try-restart these units """
  3722. found_all = True
  3723. units = []
  3724. for module in modules:
  3725. matched = self.match_units(to_list(module))
  3726. if not matched:
  3727. logg.error("Unit %s not found.", unit_of(module))
  3728. self.error |= NOT_FOUND
  3729. found_all = False
  3730. continue
  3731. for unit in matched:
  3732. if unit not in units:
  3733. units += [ unit ]
  3734. return self.try_restart_units(units) and found_all
  3735. def try_restart_units(self, units):
  3736. """ fails if any module fails to try-restart """
  3737. self.wait_system()
  3738. done = True
  3739. for unit in self.sortedAfter(units):
  3740. if not self.try_restart_unit(unit):
  3741. done = False
  3742. return done
  3743. def try_restart_unit(self, unit):
  3744. """ only do 'restart' if 'active' """
  3745. conf = self.load_unit_conf(unit)
  3746. if conf is None:
  3747. logg.error("Unit %s not found.", unit)
  3748. return False
  3749. if self.not_user_conf(conf):
  3750. logg.error("Unit %s not for --user mode", unit)
  3751. return False
  3752. with waitlock(conf):
  3753. logg.info(" try-restart unit %s => %s", conf.name(), strQ(conf.filename()))
  3754. if self.is_active_from(conf):
  3755. return self.do_restart_unit_from(conf)
  3756. return True
  3757. def reload_or_restart_modules(self, *modules):
  3758. """ [UNIT]... -- reload-or-restart these units """
  3759. found_all = True
  3760. units = []
  3761. for module in modules:
  3762. matched = self.match_units(to_list(module))
  3763. if not matched:
  3764. logg.error("Unit %s not found.", unit_of(module))
  3765. self.error |= NOT_FOUND
  3766. found_all = False
  3767. continue
  3768. for unit in matched:
  3769. if unit not in units:
  3770. units += [ unit ]
  3771. return self.reload_or_restart_units(units) and found_all
  3772. def reload_or_restart_units(self, units):
  3773. """ fails if any unit does not reload-or-restart """
  3774. self.wait_system()
  3775. done = True
  3776. for unit in self.sortedAfter(units):
  3777. if not self.reload_or_restart_unit(unit):
  3778. done = False
  3779. return done
  3780. def reload_or_restart_unit(self, unit):
  3781. """ do 'reload' if specified, otherwise do 'restart' """
  3782. conf = self.load_unit_conf(unit)
  3783. if conf is None:
  3784. logg.error("Unit %s not found.", unit)
  3785. return False
  3786. if self.not_user_conf(conf):
  3787. logg.error("Unit %s not for --user mode", unit)
  3788. return False
  3789. return self.reload_or_restart_unit_from(conf)
  3790. def reload_or_restart_unit_from(self, conf):
  3791. """ do 'reload' if specified, otherwise do 'restart' """
  3792. if not conf: return False
  3793. with waitlock(conf):
  3794. logg.info(" reload-or-restart unit %s => %s", conf.name(), strQ(conf.filename()))
  3795. return self.do_reload_or_restart_unit_from(conf)
  3796. def do_reload_or_restart_unit_from(self, conf):
  3797. if not self.is_active_from(conf):
  3798. # try: self.stop_unit_from(conf)
  3799. # except Exception as e: pass
  3800. return self.do_start_unit_from(conf)
  3801. elif conf.getlist("Service", "ExecReload", []):
  3802. logg.info("found service to have ExecReload -> 'reload'")
  3803. return self.do_reload_unit_from(conf)
  3804. else:
  3805. logg.info("found service without ExecReload -> 'restart'")
  3806. return self.do_restart_unit_from(conf)
  3807. def reload_or_try_restart_modules(self, *modules):
  3808. """ [UNIT]... -- reload-or-try-restart these units """
  3809. found_all = True
  3810. units = []
  3811. for module in modules:
  3812. matched = self.match_units(to_list(module))
  3813. if not matched:
  3814. logg.error("Unit %s not found.", unit_of(module))
  3815. self.error |= NOT_FOUND
  3816. found_all = False
  3817. continue
  3818. for unit in matched:
  3819. if unit not in units:
  3820. units += [ unit ]
  3821. return self.reload_or_try_restart_units(units) and found_all
  3822. def reload_or_try_restart_units(self, units):
  3823. """ fails if any unit fails to reload-or-try-restart """
  3824. self.wait_system()
  3825. done = True
  3826. for unit in self.sortedAfter(units):
  3827. if not self.reload_or_try_restart_unit(unit):
  3828. done = False
  3829. return done
  3830. def reload_or_try_restart_unit(self, unit):
  3831. conf = self.load_unit_conf(unit)
  3832. if conf is None:
  3833. logg.error("Unit %s not found.", unit)
  3834. return False
  3835. if self.not_user_conf(conf):
  3836. logg.error("Unit %s not for --user mode", unit)
  3837. return False
  3838. return self.reload_or_try_restart_unit_from(conf)
  3839. def reload_or_try_restart_unit_from(self, conf):
  3840. with waitlock(conf):
  3841. logg.info(" reload-or-try-restart unit %s => %s", conf.name(), strQ(conf.filename()))
  3842. return self.do_reload_or_try_restart_unit_from(conf)
  3843. def do_reload_or_try_restart_unit_from(self, conf):
  3844. if conf.getlist("Service", "ExecReload", []):
  3845. return self.do_reload_unit_from(conf)
  3846. elif not self.is_active_from(conf):
  3847. return True
  3848. else:
  3849. return self.do_restart_unit_from(conf)
  3850. def kill_modules(self, *modules):
  3851. """ [UNIT]... -- kill these units """
  3852. found_all = True
  3853. units = []
  3854. for module in modules:
  3855. matched = self.match_units(to_list(module))
  3856. if not matched:
  3857. logg.error("Unit %s not found.", unit_of(module))
  3858. # self.error |= NOT_FOUND
  3859. found_all = False
  3860. continue
  3861. for unit in matched:
  3862. if unit not in units:
  3863. units += [ unit ]
  3864. return self.kill_units(units) and found_all
  3865. def kill_units(self, units):
  3866. """ fails if any unit could not be killed """
  3867. self.wait_system()
  3868. done = True
  3869. for unit in self.sortedBefore(units):
  3870. if not self.kill_unit(unit):
  3871. done = False
  3872. return done
  3873. def kill_unit(self, unit):
  3874. conf = self.load_unit_conf(unit)
  3875. if conf is None:
  3876. logg.error("Unit %s not found.", unit)
  3877. return False
  3878. if self.not_user_conf(conf):
  3879. logg.error("Unit %s not for --user mode", unit)
  3880. return False
  3881. return self.kill_unit_from(conf)
  3882. def kill_unit_from(self, conf):
  3883. if not conf: return False
  3884. with waitlock(conf):
  3885. logg.info(" kill unit %s => %s", conf.name(), strQ(conf.filename()))
  3886. return self.do_kill_unit_from(conf)
  3887. def do_kill_unit_from(self, conf):
  3888. started = time.time()
  3889. doSendSIGKILL = self.get_SendSIGKILL(conf)
  3890. doSendSIGHUP = self.get_SendSIGHUP(conf)
  3891. useKillMode = self.get_KillMode(conf)
  3892. useKillSignal = self.get_KillSignal(conf)
  3893. kill_signal = getattr(signal, useKillSignal)
  3894. timeout = self.get_TimeoutStopSec(conf)
  3895. status_file = self.get_status_file_from(conf)
  3896. size = os.path.exists(status_file) and os.path.getsize(status_file)
  3897. logg.info("STATUS %s %s", status_file, size)
  3898. mainpid = self.read_mainpid_from(conf)
  3899. self.clean_status_from(conf) # clear RemainAfterExit and TimeoutStartSec
  3900. if not mainpid:
  3901. if useKillMode in ["control-group"]:
  3902. logg.warning("no main PID %s", strQ(conf.filename()))
  3903. logg.warning("and there is no control-group here")
  3904. else:
  3905. logg.info("no main PID %s", strQ(conf.filename()))
  3906. return False
  3907. if not pid_exists(mainpid) or pid_zombie(mainpid):
  3908. logg.debug("ignoring children when mainpid is already dead")
  3909. # because we list child processes, not processes in control-group
  3910. return True
  3911. pidlist = self.pidlist_of(mainpid) # here
  3912. if pid_exists(mainpid):
  3913. logg.info("stop kill PID %s", mainpid)
  3914. self._kill_pid(mainpid, kill_signal)
  3915. if useKillMode in ["control-group"]:
  3916. if len(pidlist) > 1:
  3917. logg.info("stop control-group PIDs %s", pidlist)
  3918. for pid in pidlist:
  3919. if pid != mainpid:
  3920. self._kill_pid(pid, kill_signal)
  3921. if doSendSIGHUP:
  3922. logg.info("stop SendSIGHUP to PIDs %s", pidlist)
  3923. for pid in pidlist:
  3924. self._kill_pid(pid, signal.SIGHUP)
  3925. # wait for the processes to have exited
  3926. while True:
  3927. dead = True
  3928. for pid in pidlist:
  3929. if pid_exists(pid) and not pid_zombie(pid):
  3930. dead = False
  3931. break
  3932. if dead:
  3933. break
  3934. if time.time() > started + timeout:
  3935. logg.info("service PIDs not stopped after %s", timeout)
  3936. break
  3937. time.sleep(1) # until TimeoutStopSec
  3938. if dead or not doSendSIGKILL:
  3939. logg.info("done kill PID %s %s", mainpid, dead and "OK")
  3940. return dead
  3941. if useKillMode in [ "control-group", "mixed" ]:
  3942. logg.info("hard kill PIDs %s", pidlist)
  3943. for pid in pidlist:
  3944. if pid != mainpid:
  3945. self._kill_pid(pid, signal.SIGKILL)
  3946. time.sleep(MinimumYield)
  3947. # useKillMode in [ "control-group", "mixed", "process" ]
  3948. if pid_exists(mainpid):
  3949. logg.info("hard kill PID %s", mainpid)
  3950. self._kill_pid(mainpid, signal.SIGKILL)
  3951. time.sleep(MinimumYield)
  3952. dead = not pid_exists(mainpid) or pid_zombie(mainpid)
  3953. logg.info("done hard kill PID %s %s", mainpid, dead and "OK")
  3954. return dead
  3955. def _kill_pid(self, pid, kill_signal = None):
  3956. try:
  3957. sig = kill_signal or signal.SIGTERM
  3958. os.kill(pid, sig)
  3959. except OSError as e:
  3960. if e.errno == errno.ESRCH or e.errno == errno.ENOENT:
  3961. logg.debug("kill PID %s => No such process", pid)
  3962. return True
  3963. else:
  3964. logg.error("kill PID %s => %s", pid, str(e))
  3965. return False
  3966. return not pid_exists(pid) or pid_zombie(pid)
  3967. def is_active_modules(self, *modules):
  3968. """ [UNIT].. -- check if these units are in active state
  3969. implements True if all is-active = True """
  3970. # systemctl returns multiple lines, one for each argument
  3971. # "active" when is_active
  3972. # "inactive" when not is_active
  3973. # "unknown" when not enabled
  3974. # The return code is set to
  3975. # 0 when "active"
  3976. # 1 when unit is not found
  3977. # 3 when any "inactive" or "unknown"
  3978. # However: # TODO! BUG in original systemctl!
  3979. # documentation says " exit code 0 if at least one is active"
  3980. # and "Unless --quiet is specified, print the unit state"
  3981. units = []
  3982. results = []
  3983. for module in modules:
  3984. units = self.match_units(to_list(module))
  3985. if not units:
  3986. logg.error("Unit %s not found.", unit_of(module))
  3987. # self.error |= NOT_FOUND
  3988. self.error |= NOT_ACTIVE
  3989. results += [ "inactive" ]
  3990. continue
  3991. for unit in units:
  3992. active = self.get_active_unit(unit)
  3993. enabled = self.enabled_unit(unit)
  3994. if enabled != "enabled" and ACTIVE_IF_ENABLED:
  3995. active = "inactive" # "unknown"
  3996. results += [ active ]
  3997. break
  3998. ## how it should work:
  3999. status = "active" in results
  4000. ## how 'systemctl' works:
  4001. non_active = [ result for result in results if result != "active" ]
  4002. if non_active:
  4003. self.error |= NOT_ACTIVE
  4004. if non_active:
  4005. self.error |= NOT_OK # status
  4006. if _quiet:
  4007. return []
  4008. return results
  4009. def is_active_from(self, conf):
  4010. """ used in try-restart/other commands to check if needed. """
  4011. if not conf: return False
  4012. return self.get_active_from(conf) == "active"
  4013. def active_pid_from(self, conf):
  4014. if not conf: return False
  4015. pid = self.read_mainpid_from(conf)
  4016. return self.is_active_pid(pid)
  4017. def is_active_pid(self, pid):
  4018. """ returns pid if the pid is still an active process """
  4019. if pid and pid_exists(pid) and not pid_zombie(pid):
  4020. return pid # usually a string (not null)
  4021. return None
  4022. def get_active_unit(self, unit):
  4023. """ returns 'active' 'inactive' 'failed' 'unknown' """
  4024. conf = self.load_unit_conf(unit)
  4025. if not conf:
  4026. logg.warning("Unit %s not found.", unit)
  4027. return "unknown"
  4028. else:
  4029. return self.get_active_from(conf)
  4030. def get_active_from(self, conf):
  4031. if conf.name().endswith(".service"):
  4032. return self.get_active_service_from(conf)
  4033. elif conf.name().endswith(".socket"):
  4034. service_unit = self.get_socket_service_from(conf)
  4035. service_conf = self.load_unit_conf(service_unit)
  4036. return self.get_active_service_from(service_conf)
  4037. elif conf.name().endswith(".target"):
  4038. return self.get_active_target_from(conf)
  4039. else:
  4040. logg.debug("is-active not implemented for unit type: %s", conf.name())
  4041. return "unknown" # TODO: "inactive" ?
  4042. def get_active_service_from(self, conf):
  4043. """ returns 'active' 'inactive' 'failed' 'unknown' """
  4044. # used in try-restart/other commands to check if needed.
  4045. if not conf: return "unknown"
  4046. pid_file = self.pid_file_from(conf)
  4047. if pid_file: # application PIDFile
  4048. if not os.path.exists(pid_file):
  4049. return "inactive"
  4050. status_file = self.get_status_file_from(conf)
  4051. if self.getsize(status_file):
  4052. state = self.get_status_from(conf, "ActiveState", "")
  4053. if state:
  4054. if DEBUG_STATUS:
  4055. logg.info("get_status_from %s => %s", conf.name(), state)
  4056. return state
  4057. pid = self.read_mainpid_from(conf)
  4058. if DEBUG_STATUS:
  4059. logg.debug("pid_file '%s' => PID %s", pid_file or status_file, strE(pid))
  4060. if pid:
  4061. if not pid_exists(pid) or pid_zombie(pid):
  4062. return "failed"
  4063. return "active"
  4064. else:
  4065. return "inactive"
  4066. def get_active_target_from(self, conf):
  4067. """ returns 'active' 'inactive' 'failed' 'unknown' """
  4068. return self.get_active_target(conf.name())
  4069. def get_active_target(self, target):
  4070. """ returns 'active' 'inactive' 'failed' 'unknown' """
  4071. if target in self.get_active_target_list():
  4072. status = self.is_system_running()
  4073. if status in [ "running" ]:
  4074. return "active"
  4075. return "inactive"
  4076. else:
  4077. services = self.target_default_services(target)
  4078. result = "active"
  4079. for service in services:
  4080. conf = self.load_unit_conf(service)
  4081. if conf:
  4082. state = self.get_active_from(conf)
  4083. if state in ["failed"]:
  4084. result = state
  4085. elif state not in ["active"]:
  4086. result = state
  4087. return result
  4088. def get_active_target_list(self):
  4089. current_target = self.get_default_target()
  4090. target_list = self.get_target_list(current_target)
  4091. target_list += [ DefaultUnit ] # upper end
  4092. target_list += [ SysInitTarget ] # lower end
  4093. return target_list
  4094. def get_substate_from(self, conf):
  4095. """ returns 'running' 'exited' 'dead' 'failed' 'plugged' 'mounted' """
  4096. if not conf: return None
  4097. pid_file = self.pid_file_from(conf)
  4098. if pid_file:
  4099. if not os.path.exists(pid_file):
  4100. return "dead"
  4101. status_file = self.get_status_file_from(conf)
  4102. if self.getsize(status_file):
  4103. state = self.get_status_from(conf, "ActiveState", "")
  4104. if state:
  4105. if state in [ "active" ]:
  4106. return self.get_status_from(conf, "SubState", "running")
  4107. else:
  4108. return self.get_status_from(conf, "SubState", "dead")
  4109. pid = self.read_mainpid_from(conf)
  4110. if DEBUG_STATUS:
  4111. logg.debug("pid_file '%s' => PID %s", pid_file or status_file, strE(pid))
  4112. if pid:
  4113. if not pid_exists(pid) or pid_zombie(pid):
  4114. return "failed"
  4115. return "running"
  4116. else:
  4117. return "dead"
  4118. def is_failed_modules(self, *modules):
  4119. """ [UNIT]... -- check if these units are in failes state
  4120. implements True if any is-active = True """
  4121. units = []
  4122. results = []
  4123. for module in modules:
  4124. units = self.match_units(to_list(module))
  4125. if not units:
  4126. logg.error("Unit %s not found.", unit_of(module))
  4127. # self.error |= NOT_FOUND
  4128. results += [ "inactive" ]
  4129. continue
  4130. for unit in units:
  4131. active = self.get_active_unit(unit)
  4132. enabled = self.enabled_unit(unit)
  4133. if enabled != "enabled" and ACTIVE_IF_ENABLED:
  4134. active = "inactive"
  4135. results += [ active ]
  4136. break
  4137. if "failed" in results:
  4138. self.error = 0
  4139. else:
  4140. self.error |= NOT_OK
  4141. if _quiet:
  4142. return []
  4143. return results
  4144. def is_failed_from(self, conf):
  4145. if conf is None: return True
  4146. return self.get_active_from(conf) == "failed"
  4147. def reset_failed_modules(self, *modules):
  4148. """ [UNIT]... -- Reset failed state for all, one, or more units """
  4149. units = []
  4150. status = True
  4151. for module in modules:
  4152. units = self.match_units(to_list(module))
  4153. if not units:
  4154. logg.error("Unit %s not found.", unit_of(module))
  4155. # self.error |= NOT_FOUND
  4156. return False
  4157. for unit in units:
  4158. if not self.reset_failed_unit(unit):
  4159. logg.error("Unit %s could not be reset.", unit_of(module))
  4160. status = False
  4161. break
  4162. return status
  4163. def reset_failed_unit(self, unit):
  4164. conf = self.load_unit_conf(unit)
  4165. if not conf:
  4166. logg.warning("Unit %s not found.", unit)
  4167. return False
  4168. if self.not_user_conf(conf):
  4169. logg.error("Unit %s not for --user mode", unit)
  4170. return False
  4171. return self.reset_failed_from(conf)
  4172. def reset_failed_from(self, conf):
  4173. if conf is None: return True
  4174. if not self.is_failed_from(conf): return False
  4175. done = False
  4176. status_file = self.get_status_file_from(conf)
  4177. if status_file and os.path.exists(status_file):
  4178. try:
  4179. os.remove(status_file)
  4180. done = True
  4181. logg.debug("done rm %s", status_file)
  4182. except Exception as e:
  4183. logg.error("while rm %s: %s", status_file, e)
  4184. pid_file = self.pid_file_from(conf)
  4185. if pid_file and os.path.exists(pid_file):
  4186. try:
  4187. os.remove(pid_file)
  4188. done = True
  4189. logg.debug("done rm %s", pid_file)
  4190. except Exception as e:
  4191. logg.error("while rm %s: %s", pid_file, e)
  4192. return done
  4193. def status_modules(self, *modules):
  4194. """ [UNIT]... check the status of these units.
  4195. """
  4196. found_all = True
  4197. units = []
  4198. for module in modules:
  4199. matched = self.match_units(to_list(module))
  4200. if not matched:
  4201. logg.error("Unit %s could not be found.", unit_of(module))
  4202. self.error |= NOT_FOUND
  4203. found_all = False
  4204. continue
  4205. for unit in matched:
  4206. if unit not in units:
  4207. units += [ unit ]
  4208. result = self.status_units(units)
  4209. # if not found_all:
  4210. # self.error |= NOT_OK | NOT_ACTIVE # 3
  4211. # # same as (dead) # original behaviour
  4212. return result
  4213. def status_units(self, units):
  4214. """ concatenates the status output of all units
  4215. and the last non-successful statuscode """
  4216. status = 0
  4217. result = ""
  4218. for unit in units:
  4219. status1, result1 = self.status_unit(unit)
  4220. if status1: status = status1
  4221. if result: result += "\n\n"
  4222. result += result1
  4223. if status:
  4224. self.error |= NOT_OK | NOT_ACTIVE # 3
  4225. return result
  4226. def status_unit(self, unit):
  4227. conf = self.get_unit_conf(unit)
  4228. result = "%s - %s" % (unit, self.get_description_from(conf))
  4229. loaded = conf.loaded()
  4230. if loaded:
  4231. filename = str(conf.filename())
  4232. enabled = self.enabled_from(conf)
  4233. result += "\n Loaded: {loaded} ({filename}, {enabled})".format(**locals())
  4234. for path in conf.overrides():
  4235. result += "\n Drop-In: {path}".format(**locals())
  4236. else:
  4237. result += "\n Loaded: failed"
  4238. return 3, result
  4239. active = self.get_active_from(conf)
  4240. substate = self.get_substate_from(conf)
  4241. result += "\n Active: {} ({})".format(active, substate)
  4242. if active == "active":
  4243. return 0, result
  4244. else:
  4245. return 3, result
  4246. def cat_modules(self, *modules):
  4247. """ [UNIT]... show the *.system file for these"
  4248. """
  4249. found_all = True
  4250. units = []
  4251. for module in modules:
  4252. matched = self.match_units(to_list(module))
  4253. if not matched:
  4254. logg.error("Unit %s could not be found.", unit_of(module))
  4255. # self.error |= NOT_FOUND
  4256. found_all = False
  4257. continue
  4258. for unit in matched:
  4259. if unit not in units:
  4260. units += [ unit ]
  4261. result = self.cat_units(units)
  4262. if not found_all:
  4263. self.error |= NOT_OK
  4264. return result
  4265. def cat_units(self, units):
  4266. done = True
  4267. result = ""
  4268. for unit in units:
  4269. text = self.cat_unit(unit)
  4270. if not text:
  4271. done = False
  4272. else:
  4273. if result:
  4274. result += "\n\n"
  4275. result += text
  4276. if not done:
  4277. self.error = NOT_OK
  4278. return result
  4279. def cat_unit(self, unit):
  4280. try:
  4281. unit_file = self.unit_file(unit)
  4282. if unit_file:
  4283. return open(unit_file).read()
  4284. logg.error("No files found for %s", unit)
  4285. except Exception as e:
  4286. print("Unit {} is not-loaded: {}".format(unit, e))
  4287. self.error |= NOT_OK
  4288. return None
  4289. ##
  4290. ##
  4291. def load_preset_files(self, module = None): # -> [ preset-file-names,... ]
  4292. """ reads all preset files, returns the scanned files """
  4293. if self._preset_file_list is None:
  4294. self._preset_file_list = {}
  4295. assert self._preset_file_list is not None
  4296. for folder in self.preset_folders():
  4297. if not folder:
  4298. continue
  4299. if self._root:
  4300. folder = os_path(self._root, folder)
  4301. if not os.path.isdir(folder):
  4302. continue
  4303. for name in os.listdir(folder):
  4304. if not name.endswith(".preset"):
  4305. continue
  4306. if name not in self._preset_file_list:
  4307. path = os.path.join(folder, name)
  4308. if os.path.isdir(path):
  4309. continue
  4310. preset = PresetFile().read(path)
  4311. self._preset_file_list[name] = preset
  4312. logg.debug("found %s preset files", len(self._preset_file_list))
  4313. return sorted(self._preset_file_list.keys())
  4314. def get_preset_of_unit(self, unit):
  4315. """ [UNIT] check the *.preset of this unit
  4316. """
  4317. self.load_preset_files()
  4318. assert self._preset_file_list is not None
  4319. for filename in sorted(self._preset_file_list.keys()):
  4320. preset = self._preset_file_list[filename]
  4321. status = preset.get_preset(unit)
  4322. if status:
  4323. return status
  4324. return None
  4325. def preset_modules(self, *modules):
  4326. """ [UNIT]... -- set 'enabled' when in *.preset
  4327. """
  4328. if self.user_mode():
  4329. logg.warning("preset makes no sense in --user mode")
  4330. return True
  4331. found_all = True
  4332. units = []
  4333. for module in modules:
  4334. matched = self.match_units(to_list(module))
  4335. if not matched:
  4336. logg.error("Unit %s could not be found.", unit_of(module))
  4337. found_all = False
  4338. continue
  4339. for unit in matched:
  4340. if unit not in units:
  4341. units += [ unit ]
  4342. return self.preset_units(units) and found_all
  4343. def preset_units(self, units):
  4344. """ fails if any unit could not be changed """
  4345. self.wait_system()
  4346. fails = 0
  4347. found = 0
  4348. for unit in units:
  4349. status = self.get_preset_of_unit(unit)
  4350. if not status: continue
  4351. found += 1
  4352. if status.startswith("enable"):
  4353. if self._preset_mode == "disable": continue
  4354. logg.info("preset enable %s", unit)
  4355. if not self.enable_unit(unit):
  4356. logg.warning("failed to enable %s", unit)
  4357. fails += 1
  4358. if status.startswith("disable"):
  4359. if self._preset_mode == "enable": continue
  4360. logg.info("preset disable %s", unit)
  4361. if not self.disable_unit(unit):
  4362. logg.warning("failed to disable %s", unit)
  4363. fails += 1
  4364. return not fails and not not found
  4365. def system_preset_all(self, *modules):
  4366. """ 'preset' all services
  4367. enable or disable services according to *.preset files
  4368. """
  4369. if self.user_mode():
  4370. logg.warning("preset-all makes no sense in --user mode")
  4371. return True
  4372. found_all = True
  4373. units = self.match_units() # TODO: how to handle module arguments
  4374. return self.preset_units(units) and found_all
  4375. def wanted_from(self, conf, default = None):
  4376. if not conf: return default
  4377. return conf.get("Install", "WantedBy", default, True)
  4378. def enablefolders(self, wanted):
  4379. if self.user_mode():
  4380. for folder in self.user_folders():
  4381. yield self.default_enablefolder(wanted, folder)
  4382. if True:
  4383. for folder in self.system_folders():
  4384. yield self.default_enablefolder(wanted, folder)
  4385. def enablefolder(self, wanted):
  4386. if self.user_mode():
  4387. user_folder = self.user_folder()
  4388. return self.default_enablefolder(wanted, user_folder)
  4389. else:
  4390. return self.default_enablefolder(wanted)
  4391. def default_enablefolder(self, wanted, basefolder = None):
  4392. basefolder = basefolder or self.system_folder()
  4393. if not wanted:
  4394. return wanted
  4395. if not wanted.endswith(".wants"):
  4396. wanted = wanted + ".wants"
  4397. return os.path.join(basefolder, wanted)
  4398. def enable_modules(self, *modules):
  4399. """ [UNIT]... -- enable these units """
  4400. found_all = True
  4401. units = []
  4402. for module in modules:
  4403. matched = self.match_units(to_list(module))
  4404. if not matched:
  4405. logg.error("Unit %s not found.", unit_of(module))
  4406. # self.error |= NOT_FOUND
  4407. found_all = False
  4408. continue
  4409. for unit in matched:
  4410. logg.info("matched %s", unit) #++
  4411. if unit not in units:
  4412. units += [ unit ]
  4413. return self.enable_units(units) and found_all
  4414. def enable_units(self, units):
  4415. self.wait_system()
  4416. done = True
  4417. for unit in units:
  4418. if not self.enable_unit(unit):
  4419. done = False
  4420. elif self._now:
  4421. self.start_unit(unit)
  4422. return done
  4423. def enable_unit(self, unit):
  4424. conf = self.load_unit_conf(unit)
  4425. if conf is None:
  4426. logg.error("Unit %s not found.", unit)
  4427. return False
  4428. unit_file = conf.filename()
  4429. if unit_file is None:
  4430. logg.error("Unit file %s not found.", unit)
  4431. return False
  4432. if self.is_sysv_file(unit_file):
  4433. if self.user_mode():
  4434. logg.error("Initscript %s not for --user mode", unit)
  4435. return False
  4436. return self.enable_unit_sysv(unit_file)
  4437. if self.not_user_conf(conf):
  4438. logg.error("Unit %s not for --user mode", unit)
  4439. return False
  4440. return self.enable_unit_from(conf)
  4441. def enable_unit_from(self, conf):
  4442. wanted = self.wanted_from(conf)
  4443. if not wanted and not self._force:
  4444. logg.debug("%s has no target", conf.name())
  4445. return False # "static" is-enabled
  4446. target = wanted or self.get_default_target()
  4447. folder = self.enablefolder(target)
  4448. if self._root:
  4449. folder = os_path(self._root, folder)
  4450. if not os.path.isdir(folder):
  4451. os.makedirs(folder)
  4452. source = conf.filename()
  4453. if not source: # pragma: no cover (was checked before)
  4454. logg.debug("%s has no real file", conf.name())
  4455. return False
  4456. symlink = os.path.join(folder, conf.name())
  4457. if True:
  4458. _f = self._force and "-f" or ""
  4459. logg.info("ln -s {_f} '{source}' '{symlink}'".format(**locals()))
  4460. if self._force and os.path.islink(symlink):
  4461. os.remove(target)
  4462. if not os.path.islink(symlink):
  4463. os.symlink(source, symlink)
  4464. return True
  4465. def rc3_root_folder(self):
  4466. old_folder = os_path(self._root, _rc3_boot_folder)
  4467. new_folder = os_path(self._root, _rc3_init_folder)
  4468. if os.path.isdir(old_folder): # pragma: no cover
  4469. return old_folder
  4470. return new_folder
  4471. def rc5_root_folder(self):
  4472. old_folder = os_path(self._root, _rc5_boot_folder)
  4473. new_folder = os_path(self._root, _rc5_init_folder)
  4474. if os.path.isdir(old_folder): # pragma: no cover
  4475. return old_folder
  4476. return new_folder
  4477. def enable_unit_sysv(self, unit_file):
  4478. # a "multi-user.target"/rc3 is also started in /rc5
  4479. rc3 = self._enable_unit_sysv(unit_file, self.rc3_root_folder())
  4480. rc5 = self._enable_unit_sysv(unit_file, self.rc5_root_folder())
  4481. return rc3 and rc5
  4482. def _enable_unit_sysv(self, unit_file, rc_folder):
  4483. name = os.path.basename(unit_file)
  4484. nameS = "S50"+name
  4485. nameK = "K50"+name
  4486. if not os.path.isdir(rc_folder):
  4487. os.makedirs(rc_folder)
  4488. # do not double existing entries
  4489. for found in os.listdir(rc_folder):
  4490. m = re.match(r"S\d\d(.*)", found)
  4491. if m and m.group(1) == name:
  4492. nameS = found
  4493. m = re.match(r"K\d\d(.*)", found)
  4494. if m and m.group(1) == name:
  4495. nameK = found
  4496. target = os.path.join(rc_folder, nameS)
  4497. if not os.path.exists(target):
  4498. os.symlink(unit_file, target)
  4499. target = os.path.join(rc_folder, nameK)
  4500. if not os.path.exists(target):
  4501. os.symlink(unit_file, target)
  4502. return True
  4503. def disable_modules(self, *modules):
  4504. """ [UNIT]... -- disable these units """
  4505. found_all = True
  4506. units = []
  4507. for module in modules:
  4508. matched = self.match_units(to_list(module))
  4509. if not matched:
  4510. logg.error("Unit %s not found.", unit_of(module))
  4511. # self.error |= NOT_FOUND
  4512. found_all = False
  4513. continue
  4514. for unit in matched:
  4515. if unit not in units:
  4516. units += [ unit ]
  4517. return self.disable_units(units) and found_all
  4518. def disable_units(self, units):
  4519. self.wait_system()
  4520. done = True
  4521. for unit in units:
  4522. if not self.disable_unit(unit):
  4523. done = False
  4524. return done
  4525. def disable_unit(self, unit):
  4526. conf = self.load_unit_conf(unit)
  4527. if conf is None:
  4528. logg.error("Unit %s not found.", unit)
  4529. return False
  4530. unit_file = conf.filename()
  4531. if unit_file is None:
  4532. logg.error("Unit file %s not found.", unit)
  4533. return False
  4534. if self.is_sysv_file(unit_file):
  4535. if self.user_mode():
  4536. logg.error("Initscript %s not for --user mode", unit)
  4537. return False
  4538. return self.disable_unit_sysv(unit_file)
  4539. if self.not_user_conf(conf):
  4540. logg.error("Unit %s not for --user mode", unit)
  4541. return False
  4542. return self.disable_unit_from(conf)
  4543. def disable_unit_from(self, conf):
  4544. wanted = self.wanted_from(conf)
  4545. if not wanted and not self._force:
  4546. logg.debug("%s has no target", conf.name())
  4547. return False # "static" is-enabled
  4548. target = wanted or self.get_default_target()
  4549. for folder in self.enablefolders(target):
  4550. if self._root:
  4551. folder = os_path(self._root, folder)
  4552. symlink = os.path.join(folder, conf.name())
  4553. if os.path.exists(symlink):
  4554. try:
  4555. _f = self._force and "-f" or ""
  4556. logg.info("rm {_f} '{symlink}'".format(**locals()))
  4557. if os.path.islink(symlink) or self._force:
  4558. os.remove(symlink)
  4559. except IOError as e:
  4560. logg.error("disable %s: %s", symlink, e)
  4561. except OSError as e:
  4562. logg.error("disable %s: %s", symlink, e)
  4563. return True
  4564. def disable_unit_sysv(self, unit_file):
  4565. rc3 = self._disable_unit_sysv(unit_file, self.rc3_root_folder())
  4566. rc5 = self._disable_unit_sysv(unit_file, self.rc5_root_folder())
  4567. return rc3 and rc5
  4568. def _disable_unit_sysv(self, unit_file, rc_folder):
  4569. # a "multi-user.target"/rc3 is also started in /rc5
  4570. name = os.path.basename(unit_file)
  4571. nameS = "S50"+name
  4572. nameK = "K50"+name
  4573. # do not forget the existing entries
  4574. for found in os.listdir(rc_folder):
  4575. m = re.match(r"S\d\d(.*)", found)
  4576. if m and m.group(1) == name:
  4577. nameS = found
  4578. m = re.match(r"K\d\d(.*)", found)
  4579. if m and m.group(1) == name:
  4580. nameK = found
  4581. target = os.path.join(rc_folder, nameS)
  4582. if os.path.exists(target):
  4583. os.unlink(target)
  4584. target = os.path.join(rc_folder, nameK)
  4585. if os.path.exists(target):
  4586. os.unlink(target)
  4587. return True
  4588. def is_enabled_sysv(self, unit_file):
  4589. name = os.path.basename(unit_file)
  4590. target = os.path.join(self.rc3_root_folder(), "S50%s" % name)
  4591. if os.path.exists(target):
  4592. return True
  4593. return False
  4594. def is_enabled_modules(self, *modules):
  4595. """ [UNIT]... -- check if these units are enabled
  4596. returns True if any of them is enabled."""
  4597. found_all = True
  4598. units = []
  4599. for module in modules:
  4600. matched = self.match_units(to_list(module))
  4601. if not matched:
  4602. logg.error("Unit %s not found.", unit_of(module))
  4603. # self.error |= NOT_FOUND
  4604. found_all = False
  4605. continue
  4606. for unit in matched:
  4607. if unit not in units:
  4608. units += [ unit ]
  4609. return self.is_enabled_units(units) # and found_all
  4610. def is_enabled_units(self, units):
  4611. """ true if any is enabled, and a list of infos """
  4612. result = False
  4613. infos = []
  4614. for unit in units:
  4615. infos += [ self.enabled_unit(unit) ]
  4616. if self.is_enabled(unit):
  4617. result = True
  4618. if not result:
  4619. self.error |= NOT_OK
  4620. return infos
  4621. def is_enabled(self, unit):
  4622. conf = self.load_unit_conf(unit)
  4623. if conf is None:
  4624. logg.error("Unit %s not found.", unit)
  4625. return False
  4626. unit_file = conf.filename()
  4627. if not unit_file:
  4628. logg.error("Unit %s not found.", unit)
  4629. return False
  4630. if self.is_sysv_file(unit_file):
  4631. return self.is_enabled_sysv(unit_file)
  4632. state = self.get_enabled_from(conf)
  4633. if state in ["enabled", "static"]:
  4634. return True
  4635. return False # ["disabled", "masked"]
  4636. def enabled_unit(self, unit):
  4637. conf = self.get_unit_conf(unit)
  4638. return self.enabled_from(conf)
  4639. def enabled_from(self, conf):
  4640. unit_file = strE(conf.filename())
  4641. if self.is_sysv_file(unit_file):
  4642. state = self.is_enabled_sysv(unit_file)
  4643. if state:
  4644. return "enabled"
  4645. return "disabled"
  4646. return self.get_enabled_from(conf)
  4647. def get_enabled_from(self, conf):
  4648. if conf.masked:
  4649. return "masked"
  4650. wanted = self.wanted_from(conf)
  4651. target = wanted or self.get_default_target()
  4652. for folder in self.enablefolders(target):
  4653. if self._root:
  4654. folder = os_path(self._root, folder)
  4655. target = os.path.join(folder, conf.name())
  4656. if os.path.isfile(target):
  4657. return "enabled"
  4658. if not wanted:
  4659. return "static"
  4660. return "disabled"
  4661. def mask_modules(self, *modules):
  4662. """ [UNIT]... -- mask non-startable units """
  4663. found_all = True
  4664. units = []
  4665. for module in modules:
  4666. matched = self.match_units(to_list(module))
  4667. if not matched:
  4668. logg.error("Unit %s not found.", unit_of(module))
  4669. self.error |= NOT_FOUND
  4670. found_all = False
  4671. continue
  4672. for unit in matched:
  4673. if unit not in units:
  4674. units += [ unit ]
  4675. return self.mask_units(units) and found_all
  4676. def mask_units(self, units):
  4677. self.wait_system()
  4678. done = True
  4679. for unit in units:
  4680. if not self.mask_unit(unit):
  4681. done = False
  4682. return done
  4683. def mask_unit(self, unit):
  4684. unit_file = self.unit_file(unit)
  4685. if not unit_file:
  4686. logg.error("Unit %s not found.", unit)
  4687. return False
  4688. if self.is_sysv_file(unit_file):
  4689. logg.error("Initscript %s can not be masked", unit)
  4690. return False
  4691. conf = self.get_unit_conf(unit)
  4692. if self.not_user_conf(conf):
  4693. logg.error("Unit %s not for --user mode", unit)
  4694. return False
  4695. folder = self.mask_folder()
  4696. if self._root:
  4697. folder = os_path(self._root, folder)
  4698. if not os.path.isdir(folder):
  4699. os.makedirs(folder)
  4700. target = os.path.join(folder, os.path.basename(unit_file))
  4701. dev_null = _dev_null
  4702. if True:
  4703. _f = self._force and "-f" or ""
  4704. logg.debug("ln -s {_f} {dev_null} '{target}'".format(**locals()))
  4705. if self._force and os.path.islink(target):
  4706. os.remove(target)
  4707. if not os.path.exists(target):
  4708. os.symlink(dev_null, target)
  4709. logg.info("Created symlink {target} -> {dev_null}".format(**locals()))
  4710. return True
  4711. elif os.path.islink(target):
  4712. logg.debug("mask symlink does already exist: %s", target)
  4713. return True
  4714. else:
  4715. logg.error("mask target does already exist: %s", target)
  4716. return False
  4717. def mask_folder(self):
  4718. for folder in self.mask_folders():
  4719. if folder: return folder
  4720. raise Exception("did not find any systemd/system folder")
  4721. def mask_folders(self):
  4722. if self.user_mode():
  4723. for folder in self.user_folders():
  4724. yield folder
  4725. if True:
  4726. for folder in self.system_folders():
  4727. yield folder
  4728. def unmask_modules(self, *modules):
  4729. """ [UNIT]... -- unmask non-startable units """
  4730. found_all = True
  4731. units = []
  4732. for module in modules:
  4733. matched = self.match_units(to_list(module))
  4734. if not matched:
  4735. logg.error("Unit %s not found.", unit_of(module))
  4736. self.error |= NOT_FOUND
  4737. found_all = False
  4738. continue
  4739. for unit in matched:
  4740. if unit not in units:
  4741. units += [ unit ]
  4742. return self.unmask_units(units) and found_all
  4743. def unmask_units(self, units):
  4744. self.wait_system()
  4745. done = True
  4746. for unit in units:
  4747. if not self.unmask_unit(unit):
  4748. done = False
  4749. return done
  4750. def unmask_unit(self, unit):
  4751. unit_file = self.unit_file(unit)
  4752. if not unit_file:
  4753. logg.error("Unit %s not found.", unit)
  4754. return False
  4755. if self.is_sysv_file(unit_file):
  4756. logg.error("Initscript %s can not be un/masked", unit)
  4757. return False
  4758. conf = self.get_unit_conf(unit)
  4759. if self.not_user_conf(conf):
  4760. logg.error("Unit %s not for --user mode", unit)
  4761. return False
  4762. folder = self.mask_folder()
  4763. if self._root:
  4764. folder = os_path(self._root, folder)
  4765. target = os.path.join(folder, os.path.basename(unit_file))
  4766. if True:
  4767. _f = self._force and "-f" or ""
  4768. logg.info("rm {_f} '{target}'".format(**locals()))
  4769. if os.path.islink(target):
  4770. os.remove(target)
  4771. return True
  4772. elif not os.path.exists(target):
  4773. logg.debug("Symlink did not exist anymore: %s", target)
  4774. return True
  4775. else:
  4776. logg.warning("target is not a symlink: %s", target)
  4777. return True
  4778. def list_dependencies_modules(self, *modules):
  4779. """ [UNIT]... show the dependency tree"
  4780. """
  4781. found_all = True
  4782. units = []
  4783. for module in modules:
  4784. matched = self.match_units(to_list(module))
  4785. if not matched:
  4786. logg.error("Unit %s could not be found.", unit_of(module))
  4787. found_all = False
  4788. continue
  4789. for unit in matched:
  4790. if unit not in units:
  4791. units += [ unit ]
  4792. return self.list_dependencies_units(units) # and found_all
  4793. def list_dependencies_units(self, units):
  4794. if self._now:
  4795. return self.list_start_dependencies_units(units)
  4796. result = []
  4797. for unit in units:
  4798. if result:
  4799. result += [ "", "" ]
  4800. result += self.list_dependencies_unit(unit)
  4801. return result
  4802. def list_dependencies_unit(self, unit):
  4803. result = []
  4804. for line in self.list_dependencies(unit, ""):
  4805. result += [ line ]
  4806. return result
  4807. def list_dependencies(self, unit, indent = None, mark = None, loop = []):
  4808. mapping = {}
  4809. mapping["Requires"] = "required to start"
  4810. mapping["Wants"] = "wanted to start"
  4811. mapping["Requisite"] = "required started"
  4812. mapping["Bindsto"] = "binds to start"
  4813. mapping["PartOf"] = "part of started"
  4814. mapping[".requires"] = ".required to start"
  4815. mapping[".wants"] = ".wanted to start"
  4816. mapping["PropagateReloadTo"] = "(to be reloaded as well)"
  4817. mapping["Conflicts"] = "(to be stopped on conflict)"
  4818. restrict = ["Requires", "Requisite", "ConsistsOf", "Wants",
  4819. "BindsTo", ".requires", ".wants"]
  4820. indent = indent or ""
  4821. mark = mark or ""
  4822. deps = self.get_dependencies_unit(unit)
  4823. conf = self.get_unit_conf(unit)
  4824. if not conf.loaded():
  4825. if not self._show_all:
  4826. return
  4827. yield "%s(%s): %s" % (indent, unit, mark)
  4828. else:
  4829. yield "%s%s: %s" % (indent, unit, mark)
  4830. for stop_recursion in [ "Conflict", "conflict", "reloaded", "Propagate" ]:
  4831. if stop_recursion in mark:
  4832. return
  4833. for dep in deps:
  4834. if dep in loop:
  4835. logg.debug("detected loop at %s", dep)
  4836. continue
  4837. new_loop = loop + list(deps.keys())
  4838. new_indent = indent + "| "
  4839. new_mark = deps[dep]
  4840. if not self._show_all:
  4841. if new_mark not in restrict:
  4842. continue
  4843. if new_mark in mapping:
  4844. new_mark = mapping[new_mark]
  4845. restrict = ["Requires", "Wants", "Requisite", "BindsTo", "PartOf", "ConsistsOf",
  4846. ".requires", ".wants"]
  4847. for line in self.list_dependencies(dep, new_indent, new_mark, new_loop):
  4848. yield line
  4849. def get_dependencies_unit(self, unit, styles = None):
  4850. styles = styles or [ "Requires", "Wants", "Requisite", "BindsTo", "PartOf", "ConsistsOf",
  4851. ".requires", ".wants", "PropagateReloadTo", "Conflicts", ]
  4852. conf = self.get_unit_conf(unit)
  4853. deps = {}
  4854. for style in styles:
  4855. if style.startswith("."):
  4856. for folder in self.sysd_folders():
  4857. if not folder:
  4858. continue
  4859. require_path = os.path.join(folder, unit + style)
  4860. if self._root:
  4861. require_path = os_path(self._root, require_path)
  4862. if os.path.isdir(require_path):
  4863. for required in os.listdir(require_path):
  4864. if required not in deps:
  4865. deps[required] = style
  4866. else:
  4867. for requirelist in conf.getlist("Unit", style, []):
  4868. for required in requirelist.strip().split(" "):
  4869. deps[required.strip()] = style
  4870. return deps
  4871. def get_required_dependencies(self, unit, styles = None):
  4872. styles = styles or [ "Requires", "Wants", "Requisite", "BindsTo",
  4873. ".requires", ".wants" ]
  4874. return self.get_dependencies_unit(unit, styles)
  4875. def get_start_dependencies(self, unit, styles = None): # pragma: no cover
  4876. """ the list of services to be started as well / TODO: unused """
  4877. styles = styles or ["Requires", "Wants", "Requisite", "BindsTo", "PartOf", "ConsistsOf",
  4878. ".requires", ".wants"]
  4879. deps = {}
  4880. unit_deps = self.get_dependencies_unit(unit)
  4881. for dep_unit, dep_style in unit_deps.items():
  4882. if dep_style in styles:
  4883. if dep_unit in deps:
  4884. if dep_style not in deps[dep_unit]:
  4885. deps[dep_unit].append( dep_style)
  4886. else:
  4887. deps[dep_unit] = [ dep_style ]
  4888. next_deps = self.get_start_dependencies(dep_unit)
  4889. for dep, styles in next_deps.items():
  4890. for style in styles:
  4891. if dep in deps:
  4892. if style not in deps[dep]:
  4893. deps[dep].append(style)
  4894. else:
  4895. deps[dep] = [ style ]
  4896. return deps
  4897. def list_start_dependencies_units(self, units):
  4898. unit_order = []
  4899. deps = {}
  4900. for unit in units:
  4901. unit_order.append(unit)
  4902. # unit_deps = self.get_start_dependencies(unit) # TODO
  4903. unit_deps = self.get_dependencies_unit(unit)
  4904. for dep_unit, styles in unit_deps.items():
  4905. dep_styles = to_list(styles)
  4906. for dep_style in dep_styles:
  4907. if dep_unit in deps:
  4908. if dep_style not in deps[dep_unit]:
  4909. deps[dep_unit].append( dep_style)
  4910. else:
  4911. deps[dep_unit] = [ dep_style ]
  4912. deps_conf = []
  4913. for dep in deps:
  4914. if dep in unit_order:
  4915. continue
  4916. conf = self.get_unit_conf(dep)
  4917. if conf.loaded():
  4918. deps_conf.append(conf)
  4919. for unit in unit_order:
  4920. deps[unit] = [ "Requested" ]
  4921. conf = self.get_unit_conf(unit)
  4922. if conf.loaded():
  4923. deps_conf.append(conf)
  4924. result = []
  4925. sortlist = conf_sortedAfter(deps_conf, cmp=compareAfter)
  4926. for item in sortlist:
  4927. line = (item.name(), "(%s)" % (" ".join(deps[item.name()])))
  4928. result.append(line)
  4929. return result
  4930. def sortedAfter(self, unitlist):
  4931. """ get correct start order for the unit list (ignoring masked units) """
  4932. conflist = [ self.get_unit_conf(unit) for unit in unitlist ]
  4933. if True:
  4934. conflist = []
  4935. for unit in unitlist:
  4936. conf = self.get_unit_conf(unit)
  4937. if conf.masked:
  4938. logg.debug("ignoring masked unit %s", unit)
  4939. continue
  4940. conflist.append(conf)
  4941. sortlist = conf_sortedAfter(conflist)
  4942. return [ item.name() for item in sortlist ]
  4943. def sortedBefore(self, unitlist):
  4944. """ get correct start order for the unit list (ignoring masked units) """
  4945. conflist = [ self.get_unit_conf(unit) for unit in unitlist ]
  4946. if True:
  4947. conflist = []
  4948. for unit in unitlist:
  4949. conf = self.get_unit_conf(unit)
  4950. if conf.masked:
  4951. logg.debug("ignoring masked unit %s", unit)
  4952. continue
  4953. conflist.append(conf)
  4954. sortlist = conf_sortedAfter(reversed(conflist))
  4955. return [ item.name() for item in reversed(sortlist) ]
  4956. def system_daemon_reload(self):
  4957. """ reload does will only check the service files here.
  4958. The returncode will tell the number of warnings,
  4959. and it is over 100 if it can not continue even
  4960. for the relaxed systemctl.py style of execution. """
  4961. errors = 0
  4962. for unit in self.match_units():
  4963. try:
  4964. conf = self.get_unit_conf(unit)
  4965. except Exception as e:
  4966. logg.error("%s: can not read unit file %s\n\t%s",
  4967. unit, strQ(conf.filename()), e)
  4968. continue
  4969. errors += self.syntax_check(conf)
  4970. if errors:
  4971. logg.warning(" (%s) found %s problems", errors, errors % 100)
  4972. return True # errors
  4973. def syntax_check(self, conf):
  4974. filename = conf.filename()
  4975. if filename and filename.endswith(".service"):
  4976. return self.syntax_check_service(conf)
  4977. return 0
  4978. def syntax_check_service(self, conf):
  4979. unit = conf.name()
  4980. if not conf.data.has_section("Service"):
  4981. logg.error(" %s: a .service file without [Service] section", unit)
  4982. return 101
  4983. errors = 0
  4984. haveType = conf.get("Service", "Type", "simple")
  4985. haveExecStart = conf.getlist("Service", "ExecStart", [])
  4986. haveExecStop = conf.getlist("Service", "ExecStop", [])
  4987. haveExecReload = conf.getlist("Service", "ExecReload", [])
  4988. usedExecStart = []
  4989. usedExecStop = []
  4990. usedExecReload = []
  4991. if haveType not in [ "simple", "forking", "notify", "oneshot", "dbus", "idle"]:
  4992. logg.error(" %s: Failed to parse service type, ignoring: %s", unit, haveType)
  4993. errors += 100
  4994. for line in haveExecStart:
  4995. if not line.startswith("/") and not line.startswith("-/"):
  4996. logg.error(" %s: Executable path is not absolute, ignoring: %s", unit, line.strip())
  4997. errors += 1
  4998. usedExecStart.append(line)
  4999. for line in haveExecStop:
  5000. if not line.startswith("/") and not line.startswith("-/"):
  5001. logg.error(" %s: Executable path is not absolute, ignoring: %s", unit, line.strip())
  5002. errors += 1
  5003. usedExecStop.append(line)
  5004. for line in haveExecReload:
  5005. if not line.startswith("/") and not line.startswith("-/"):
  5006. logg.error(" %s: Executable path is not absolute, ignoring: %s", unit, line.strip())
  5007. errors += 1
  5008. usedExecReload.append(line)
  5009. if haveType in ["simple", "notify", "forking", "idle"]:
  5010. if not usedExecStart and not usedExecStop:
  5011. logg.error(" %s: Service lacks both ExecStart and ExecStop= setting. Refusing.", unit)
  5012. errors += 101
  5013. elif not usedExecStart and haveType != "oneshot":
  5014. logg.error(" %s: Service has no ExecStart= setting, which is only allowed for Type=oneshot services. Refusing.", unit)
  5015. errors += 101
  5016. if len(usedExecStart) > 1 and haveType != "oneshot":
  5017. logg.error(" %s: there may be only one ExecStart statement (unless for 'oneshot' services)."
  5018. + "\n\t\t\tYou can use ExecStartPre / ExecStartPost to add additional commands.", unit)
  5019. errors += 1
  5020. if len(usedExecStop) > 1 and haveType != "oneshot":
  5021. logg.info(" %s: there should be only one ExecStop statement (unless for 'oneshot' services)."
  5022. + "\n\t\t\tYou can use ExecStopPost to add additional commands (also executed on failed Start)", unit)
  5023. if len(usedExecReload) > 1:
  5024. logg.info(" %s: there should be only one ExecReload statement."
  5025. + "\n\t\t\tUse ' ; ' for multiple commands (ExecReloadPost or ExedReloadPre do not exist)", unit)
  5026. if len(usedExecReload) > 0 and "/bin/kill " in usedExecReload[0]:
  5027. logg.warning(" %s: the use of /bin/kill is not recommended for ExecReload as it is asychronous."
  5028. + "\n\t\t\tThat means all the dependencies will perform the reload simultanously / out of order.", unit)
  5029. if conf.getlist("Service", "ExecRestart", []): #pragma: no cover
  5030. logg.error(" %s: there no such thing as an ExecRestart (ignored)", unit)
  5031. if conf.getlist("Service", "ExecRestartPre", []): #pragma: no cover
  5032. logg.error(" %s: there no such thing as an ExecRestartPre (ignored)", unit)
  5033. if conf.getlist("Service", "ExecRestartPost", []): #pragma: no cover
  5034. logg.error(" %s: there no such thing as an ExecRestartPost (ignored)", unit)
  5035. if conf.getlist("Service", "ExecReloadPre", []): #pragma: no cover
  5036. logg.error(" %s: there no such thing as an ExecReloadPre (ignored)", unit)
  5037. if conf.getlist("Service", "ExecReloadPost", []): #pragma: no cover
  5038. logg.error(" %s: there no such thing as an ExecReloadPost (ignored)", unit)
  5039. if conf.getlist("Service", "ExecStopPre", []): #pragma: no cover
  5040. logg.error(" %s: there no such thing as an ExecStopPre (ignored)", unit)
  5041. for env_file in conf.getlist("Service", "EnvironmentFile", []):
  5042. if env_file.startswith("-"): continue
  5043. if not os.path.isfile(os_path(self._root, self.expand_special(env_file, conf))):
  5044. logg.error(" %s: Failed to load environment files: %s", unit, env_file)
  5045. errors += 101
  5046. return errors
  5047. def exec_check_unit(self, conf, env, section = "Service", exectype = ""):
  5048. if conf is None: # pragma: no cover (is never null)
  5049. return True
  5050. if not conf.data.has_section(section):
  5051. return True #pragma: no cover
  5052. haveType = conf.get(section, "Type", "simple")
  5053. if self.is_sysv_file(conf.filename()):
  5054. return True # we don't care about that
  5055. unit = conf.name()
  5056. abspath = 0
  5057. notexists = 0
  5058. badusers = 0
  5059. badgroups = 0
  5060. for execs in [ "ExecStartPre", "ExecStart", "ExecStartPost", "ExecStop", "ExecStopPost", "ExecReload" ]:
  5061. if not execs.startswith(exectype):
  5062. continue
  5063. for cmd in conf.getlist(section, execs, []):
  5064. mode, newcmd = self.exec_newcmd(cmd, env, conf)
  5065. if not newcmd:
  5066. continue
  5067. exe = newcmd[0]
  5068. if not exe:
  5069. continue
  5070. if exe[0] != "/":
  5071. logg.error(" %s: Exec is not an absolute path: %s=%s", unit, execs, cmd)
  5072. abspath += 1
  5073. if not os.path.isfile(exe):
  5074. logg.error(" %s: Exec command does not exist: (%s) %s", unit, execs, exe)
  5075. if mode.check:
  5076. notexists += 1
  5077. newexe1 = os.path.join("/usr/bin", exe)
  5078. newexe2 = os.path.join("/bin", exe)
  5079. if os.path.exists(newexe1):
  5080. logg.error(" %s: but this does exist: %s %s", unit, " " * len(execs), newexe1)
  5081. elif os.path.exists(newexe2):
  5082. logg.error(" %s: but this does exist: %s %s", unit, " " * len(execs), newexe2)
  5083. users = [ conf.get(section, "User", ""), conf.get(section, "SocketUser", "") ]
  5084. groups = [ conf.get(section, "Group", ""), conf.get(section, "SocketGroup", "") ] + conf.getlist(section, "SupplementaryGroups")
  5085. for user in users:
  5086. if user:
  5087. try: pwd.getpwnam(self.expand_special(user, conf))
  5088. except Exception as e:
  5089. logg.error(" %s: User does not exist: %s (%s)", unit, user, getattr(e, "__doc__", ""))
  5090. badusers += 1
  5091. for group in groups:
  5092. if group:
  5093. try: grp.getgrnam(self.expand_special(group, conf))
  5094. except Exception as e:
  5095. logg.error(" %s: Group does not exist: %s (%s)", unit, group, getattr(e, "__doc__", ""))
  5096. badgroups += 1
  5097. tmpproblems = 0
  5098. for setting in ("RootDirectory", "RootImage", "BindPaths", "BindReadOnlyPaths",
  5099. "ReadWritePaths", "ReadOnlyPaths", "TemporaryFileSystem"):
  5100. setting_value = conf.get(section, setting, "")
  5101. if setting_value:
  5102. logg.info("%s: %s private directory remounts ignored: %s=%s", unit, section, setting, setting_value)
  5103. tmpproblems += 1
  5104. for setting in ("PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", "DynamicUser",
  5105. "ProtectSystem", "ProjectHome", "ProtectHostname", "PrivateMounts", "MountAPIVFS"):
  5106. setting_yes = conf.getbool(section, setting, "no")
  5107. if setting_yes:
  5108. logg.info("%s: %s private directory option is ignored: %s=yes", unit, section, setting)
  5109. tmpproblems += 1
  5110. if not abspath and not notexists and not badusers and not badgroups:
  5111. return True
  5112. if True:
  5113. filename = strE(conf.filename())
  5114. if len(filename) > 44: filename = o44(filename)
  5115. logg.error(" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
  5116. if abspath:
  5117. logg.error(" The SystemD ExecXY commands must always be absolute paths by definition.")
  5118. time.sleep(1)
  5119. if notexists:
  5120. logg.error(" Oops, %s executable paths were not found in the current environment. Refusing.", notexists)
  5121. time.sleep(1)
  5122. if badusers or badgroups:
  5123. logg.error(" Oops, %s user names and %s group names were not found. Refusing.", badusers, badgroups)
  5124. time.sleep(1)
  5125. if tmpproblems:
  5126. logg.info(" Note, %s private directory settings are ignored. The application should not depend on it.", tmpproblems)
  5127. time.sleep(1)
  5128. logg.error(" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
  5129. return False
  5130. def show_modules(self, *modules):
  5131. """ [PATTERN]... -- Show properties of one or more units
  5132. Show properties of one or more units (or the manager itself).
  5133. If no argument is specified, properties of the manager will be
  5134. shown. If a unit name is specified, properties of the unit is
  5135. shown. By default, empty properties are suppressed. Use --all to
  5136. show those too. To select specific properties to show, use
  5137. --property=. This command is intended to be used whenever
  5138. computer-parsable output is required. Use status if you are looking
  5139. for formatted human-readable output.
  5140. /
  5141. NOTE: only a subset of properties is implemented """
  5142. notfound = []
  5143. found_all = True
  5144. units = []
  5145. for module in modules:
  5146. matched = self.match_units(to_list(module))
  5147. if not matched:
  5148. logg.error("Unit %s could not be found.", unit_of(module))
  5149. units += [ module ]
  5150. # self.error |= NOT_FOUND
  5151. found_all = False
  5152. continue
  5153. for unit in matched:
  5154. if unit not in units:
  5155. units += [ unit ]
  5156. return self.show_units(units) + notfound # and found_all
  5157. def show_units(self, units):
  5158. logg.debug("show --property=%s", self._unit_property)
  5159. result = []
  5160. for unit in units:
  5161. if result: result += [ "" ]
  5162. for var, value in self.show_unit_items(unit):
  5163. if self._unit_property:
  5164. if self._unit_property != var:
  5165. continue
  5166. else:
  5167. if not value and not self._show_all:
  5168. continue
  5169. result += [ "%s=%s" % (var, value) ]
  5170. return result
  5171. def show_unit_items(self, unit):
  5172. """ [UNIT]... -- show properties of a unit.
  5173. """
  5174. logg.info("try read unit %s", unit)
  5175. conf = self.get_unit_conf(unit)
  5176. for entry in self.each_unit_items(unit, conf):
  5177. yield entry
  5178. def each_unit_items(self, unit, conf):
  5179. loaded = conf.loaded()
  5180. if not loaded:
  5181. loaded = "not-loaded"
  5182. if "NOT-FOUND" in self.get_description_from(conf):
  5183. loaded = "not-found"
  5184. names = { unit: 1, conf.name(): 1 }
  5185. yield "Id", conf.name()
  5186. yield "Names", " ".join(sorted(names.keys()))
  5187. yield "Description", self.get_description_from(conf) # conf.get("Unit", "Description")
  5188. yield "PIDFile", self.get_pid_file(conf) # not self.pid_file_from w/o default location
  5189. yield "PIDFilePath", self.pid_file_from(conf)
  5190. yield "MainPID", strE(self.active_pid_from(conf)) # status["MainPID"] or PIDFile-read
  5191. yield "SubState", self.get_substate_from(conf) or "unknown" # status["SubState"] or notify-result
  5192. yield "ActiveState", self.get_active_from(conf) or "unknown" # status["ActiveState"]
  5193. yield "LoadState", loaded
  5194. yield "UnitFileState", self.enabled_from(conf)
  5195. yield "StatusFile", self.get_StatusFile(conf)
  5196. yield "StatusFilePath", self.get_status_file_from(conf)
  5197. yield "JournalFile", self.get_journal_log(conf)
  5198. yield "JournalFilePath", self.get_journal_log_from(conf)
  5199. yield "NotifySocket", self.get_notify_socket_from(conf)
  5200. yield "User", self.get_User(conf) or ""
  5201. yield "Group", self.get_Group(conf) or ""
  5202. yield "SupplementaryGroups", " ".join(self.get_SupplementaryGroups(conf))
  5203. yield "TimeoutStartUSec", seconds_to_time(self.get_TimeoutStartSec(conf))
  5204. yield "TimeoutStopUSec", seconds_to_time(self.get_TimeoutStopSec(conf))
  5205. yield "NeedDaemonReload", "no"
  5206. yield "SendSIGKILL", strYes(self.get_SendSIGKILL(conf))
  5207. yield "SendSIGHUP", strYes(self.get_SendSIGHUP(conf))
  5208. yield "KillMode", strE(self.get_KillMode(conf))
  5209. yield "KillSignal", strE(self.get_KillSignal(conf))
  5210. yield "StartLimitBurst", strE(self.get_StartLimitBurst(conf))
  5211. yield "StartLimitIntervalSec", seconds_to_time(self.get_StartLimitIntervalSec(conf))
  5212. yield "RestartSec", seconds_to_time(self.get_RestartSec(conf))
  5213. yield "RemainAfterExit", strYes(self.get_RemainAfterExit(conf))
  5214. yield "WorkingDirectory", strE(self.get_WorkingDirectory(conf))
  5215. env_parts = []
  5216. for env_part in conf.getlist("Service", "Environment", []):
  5217. env_parts.append(self.expand_special(env_part, conf))
  5218. if env_parts:
  5219. yield "Environment", " ".join(env_parts)
  5220. env_files = []
  5221. for env_file in conf.getlist("Service", "EnvironmentFile", []):
  5222. env_files.append(self.expand_special(env_file, conf))
  5223. if env_files:
  5224. yield "EnvironmentFile", " ".join(env_files)
  5225. def get_SendSIGKILL(self, conf):
  5226. return conf.getbool("Service", "SendSIGKILL", "yes")
  5227. def get_SendSIGHUP(self, conf):
  5228. return conf.getbool("Service", "SendSIGHUP", "no")
  5229. def get_KillMode(self, conf):
  5230. return conf.get("Service", "KillMode", "control-group")
  5231. def get_KillSignal(self, conf):
  5232. return conf.get("Service", "KillSignal", "SIGTERM")
  5233. #
  5234. igno_centos = [ "netconsole", "network" ]
  5235. igno_opensuse = [ "raw", "pppoe", "*.local", "boot.*", "rpmconf*", "postfix*" ]
  5236. igno_ubuntu = [ "mount*", "umount*", "ondemand", "*.local" ]
  5237. igno_always = [ "network*", "dbus*", "systemd-*", "kdump*" ]
  5238. igno_always += [ "purge-kernels.service", "after-local.service", "dm-event.*" ] # as on opensuse
  5239. igno_targets = [ "remote-fs.target" ]
  5240. def _ignored_unit(self, unit, ignore_list):
  5241. for ignore in ignore_list:
  5242. if fnmatch.fnmatchcase(unit, ignore):
  5243. return True # ignore
  5244. if fnmatch.fnmatchcase(unit, ignore+".service"):
  5245. return True # ignore
  5246. return False
  5247. def default_services_modules(self, *modules):
  5248. """ show the default services
  5249. This is used internally to know the list of service to be started in the 'get-default'
  5250. target runlevel when the container is started through default initialisation. It will
  5251. ignore a number of services - use '--all' to show a longer list of services and
  5252. use '--all --force' if not even a minimal filter shall be used.
  5253. """
  5254. results = []
  5255. targets = modules or [ self.get_default_target() ]
  5256. for target in targets:
  5257. units = self.target_default_services(target)
  5258. logg.debug(" %s # %s", " ".join(units), target)
  5259. for unit in units:
  5260. if unit not in results:
  5261. results.append(unit)
  5262. return results
  5263. def target_default_services(self, target = None, sysv = "S"):
  5264. """ get the default services for a target - this will ignore a number of services,
  5265. use '--all' and --force' to get more services.
  5266. """
  5267. igno = self.igno_centos + self.igno_opensuse + self.igno_ubuntu + self.igno_always
  5268. if self._show_all:
  5269. igno = self.igno_always
  5270. if self._force:
  5271. igno = []
  5272. logg.debug("ignored services filter for default.target:\n\t%s", igno)
  5273. default_target = target or self.get_default_target()
  5274. return self.enabled_target_services(default_target, sysv, igno)
  5275. def enabled_target_services(self, target, sysv = "S", igno = []):
  5276. units = []
  5277. if self.user_mode():
  5278. targetlist = self.get_target_list(target)
  5279. logg.debug("check for %s user services : %s", target, targetlist)
  5280. for targets in targetlist:
  5281. for unit in self.enabled_target_user_local_units(targets, ".target", igno):
  5282. if unit not in units:
  5283. units.append(unit)
  5284. for targets in targetlist:
  5285. for unit in self.required_target_units(targets, ".socket", igno):
  5286. if unit not in units:
  5287. units.append(unit)
  5288. for targets in targetlist:
  5289. for unit in self.enabled_target_user_local_units(targets, ".socket", igno):
  5290. if unit not in units:
  5291. units.append(unit)
  5292. for targets in targetlist:
  5293. for unit in self.required_target_units(targets, ".service", igno):
  5294. if unit not in units:
  5295. units.append(unit)
  5296. for targets in targetlist:
  5297. for unit in self.enabled_target_user_local_units(targets, ".service", igno):
  5298. if unit not in units:
  5299. units.append(unit)
  5300. for targets in targetlist:
  5301. for unit in self.enabled_target_user_system_units(targets, ".service", igno):
  5302. if unit not in units:
  5303. units.append(unit)
  5304. else:
  5305. targetlist = self.get_target_list(target)
  5306. logg.debug("check for %s system services: %s", target, targetlist)
  5307. for targets in targetlist:
  5308. for unit in self.enabled_target_configured_system_units(targets, ".target", igno + self.igno_targets):
  5309. if unit not in units:
  5310. units.append(unit)
  5311. for targets in targetlist:
  5312. for unit in self.required_target_units(targets, ".socket", igno):
  5313. if unit not in units:
  5314. units.append(unit)
  5315. for targets in targetlist:
  5316. for unit in self.enabled_target_installed_system_units(targets, ".socket", igno):
  5317. if unit not in units:
  5318. units.append(unit)
  5319. for targets in targetlist:
  5320. for unit in self.required_target_units(targets, ".service", igno):
  5321. if unit not in units:
  5322. units.append(unit)
  5323. for targets in targetlist:
  5324. for unit in self.enabled_target_installed_system_units(targets, ".service", igno):
  5325. if unit not in units:
  5326. units.append(unit)
  5327. for targets in targetlist:
  5328. for unit in self.enabled_target_sysv_units(targets, sysv, igno):
  5329. if unit not in units:
  5330. units.append(unit)
  5331. return units
  5332. def enabled_target_user_local_units(self, target, unit_kind = ".service", igno = []):
  5333. units = []
  5334. for basefolder in self.user_folders():
  5335. if not basefolder:
  5336. continue
  5337. folder = self.default_enablefolder(target, basefolder)
  5338. if self._root:
  5339. folder = os_path(self._root, folder)
  5340. if os.path.isdir(folder):
  5341. for unit in sorted(os.listdir(folder)):
  5342. path = os.path.join(folder, unit)
  5343. if os.path.isdir(path): continue
  5344. if self._ignored_unit(unit, igno):
  5345. continue # ignore
  5346. if unit.endswith(unit_kind):
  5347. units.append(unit)
  5348. return units
  5349. def enabled_target_user_system_units(self, target, unit_kind = ".service", igno = []):
  5350. units = []
  5351. for basefolder in self.system_folders():
  5352. if not basefolder:
  5353. continue
  5354. folder = self.default_enablefolder(target, basefolder)
  5355. if self._root:
  5356. folder = os_path(self._root, folder)
  5357. if os.path.isdir(folder):
  5358. for unit in sorted(os.listdir(folder)):
  5359. path = os.path.join(folder, unit)
  5360. if os.path.isdir(path): continue
  5361. if self._ignored_unit(unit, igno):
  5362. continue # ignore
  5363. if unit.endswith(unit_kind):
  5364. conf = self.load_unit_conf(unit)
  5365. if conf is None:
  5366. pass
  5367. elif self.not_user_conf(conf):
  5368. pass
  5369. else:
  5370. units.append(unit)
  5371. return units
  5372. def enabled_target_installed_system_units(self, target, unit_type = ".service", igno = []):
  5373. units = []
  5374. for basefolder in self.system_folders():
  5375. if not basefolder:
  5376. continue
  5377. folder = self.default_enablefolder(target, basefolder)
  5378. if self._root:
  5379. folder = os_path(self._root, folder)
  5380. if os.path.isdir(folder):
  5381. for unit in sorted(os.listdir(folder)):
  5382. path = os.path.join(folder, unit)
  5383. if os.path.isdir(path): continue
  5384. if self._ignored_unit(unit, igno):
  5385. continue # ignore
  5386. if unit.endswith(unit_type):
  5387. units.append(unit)
  5388. return units
  5389. def enabled_target_configured_system_units(self, target, unit_type = ".service", igno = []):
  5390. units = []
  5391. if True:
  5392. folder = self.default_enablefolder(target)
  5393. if self._root:
  5394. folder = os_path(self._root, folder)
  5395. if os.path.isdir(folder):
  5396. for unit in sorted(os.listdir(folder)):
  5397. path = os.path.join(folder, unit)
  5398. if os.path.isdir(path): continue
  5399. if self._ignored_unit(unit, igno):
  5400. continue # ignore
  5401. if unit.endswith(unit_type):
  5402. units.append(unit)
  5403. return units
  5404. def enabled_target_sysv_units(self, target, sysv = "S", igno = []):
  5405. units = []
  5406. folders = []
  5407. if target in [ "multi-user.target", DefaultUnit ]:
  5408. folders += [ self.rc3_root_folder() ]
  5409. if target in [ "graphical.target" ]:
  5410. folders += [ self.rc5_root_folder() ]
  5411. for folder in folders:
  5412. if not os.path.isdir(folder):
  5413. logg.warning("non-existant %s", folder)
  5414. continue
  5415. for unit in sorted(os.listdir(folder)):
  5416. path = os.path.join(folder, unit)
  5417. if os.path.isdir(path): continue
  5418. m = re.match(sysv+r"\d\d(.*)", unit)
  5419. if m:
  5420. service = m.group(1)
  5421. unit = service + ".service"
  5422. if self._ignored_unit(unit, igno):
  5423. continue # ignore
  5424. units.append(unit)
  5425. return units
  5426. def required_target_units(self, target, unit_type, igno):
  5427. units = []
  5428. deps = self.get_required_dependencies(target)
  5429. for unit in sorted(deps):
  5430. if self._ignored_unit(unit, igno):
  5431. continue # ignore
  5432. if unit.endswith(unit_type):
  5433. if unit not in units:
  5434. units.append(unit)
  5435. return units
  5436. def get_target_conf(self, module): # -> conf (conf | default-conf)
  5437. """ accept that a unit does not exist
  5438. and return a unit conf that says 'not-loaded' """
  5439. conf = self.load_unit_conf(module)
  5440. if conf is not None:
  5441. return conf
  5442. target_conf = self.default_unit_conf(module)
  5443. if module in target_requires:
  5444. target_conf.set("Unit", "Requires", target_requires[module])
  5445. return target_conf
  5446. def get_target_list(self, module):
  5447. """ the Requires= in target units are only accepted if known """
  5448. target = module
  5449. if "." not in target: target += ".target"
  5450. targets = [ target ]
  5451. conf = self.get_target_conf(module)
  5452. requires = conf.get("Unit", "Requires", "")
  5453. while requires in target_requires:
  5454. targets = [ requires ] + targets
  5455. requires = target_requires[requires]
  5456. logg.debug("the %s requires %s", module, targets)
  5457. return targets
  5458. def system_default(self, arg = True):
  5459. """ start units for default system level
  5460. This will go through the enabled services in the default 'multi-user.target'.
  5461. However some services are ignored as being known to be installation garbage
  5462. from unintended services. Use '--all' so start all of the installed services
  5463. and with '--all --force' even those services that are otherwise wrong.
  5464. /// SPECIAL: with --now or --init the init-loop is run and afterwards
  5465. a system_halt is performed with the enabled services to be stopped."""
  5466. self.sysinit_status(SubState = "initializing")
  5467. logg.info("system default requested - %s", arg)
  5468. init = self._now or self._init
  5469. return self.start_system_default(init = init)
  5470. def start_system_default(self, init = False):
  5471. """ detect the default.target services and start them.
  5472. When --init is given then the init-loop is run and
  5473. the services are stopped again by 'systemctl halt'."""
  5474. target = self.get_default_target()
  5475. services = self.start_target_system(target, init)
  5476. logg.info("%s system is up", target)
  5477. if init:
  5478. logg.info("init-loop start")
  5479. sig = self.init_loop_until_stop(services)
  5480. logg.info("init-loop %s", sig)
  5481. self.stop_system_default()
  5482. return not not services
  5483. def start_target_system(self, target, init = False):
  5484. services = self.target_default_services(target, "S")
  5485. self.sysinit_status(SubState = "starting")
  5486. self.start_units(services)
  5487. return services
  5488. def do_start_target_from(self, conf):
  5489. target = conf.name()
  5490. # services = self.start_target_system(target)
  5491. services = self.target_default_services(target, "S")
  5492. units = [service for service in services if not self.is_running_unit(service)]
  5493. logg.debug("start %s is starting %s from %s", target, units, services)
  5494. return self.start_units(units)
  5495. def stop_system_default(self):
  5496. """ detect the default.target services and stop them.
  5497. This is commonly run through 'systemctl halt' or
  5498. at the end of a 'systemctl --init default' loop."""
  5499. target = self.get_default_target()
  5500. services = self.stop_target_system(target)
  5501. logg.info("%s system is down", target)
  5502. return not not services
  5503. def stop_target_system(self, target):
  5504. services = self.target_default_services(target, "K")
  5505. self.sysinit_status(SubState = "stopping")
  5506. self.stop_units(services)
  5507. return services
  5508. def do_stop_target_from(self, conf):
  5509. target = conf.name()
  5510. # services = self.stop_target_system(target)
  5511. services = self.target_default_services(target, "K")
  5512. units = [service for service in services if self.is_running_unit(service)]
  5513. logg.debug("stop %s is stopping %s from %s", target, units, services)
  5514. return self.stop_units(units)
  5515. def do_reload_target_from(self, conf):
  5516. target = conf.name()
  5517. return self.reload_target_system(target)
  5518. def reload_target_system(self, target):
  5519. services = self.target_default_services(target, "S")
  5520. units = [service for service in services if self.is_running_unit(service)]
  5521. return self.reload_units(units)
  5522. def system_halt(self, arg = True):
  5523. """ stop units from default system level """
  5524. logg.info("system halt requested - %s", arg)
  5525. done = self.stop_system_default()
  5526. try:
  5527. os.kill(1, signal.SIGQUIT) # exit init-loop on no_more_procs
  5528. except Exception as e:
  5529. logg.warning("SIGQUIT to init-loop on PID-1: %s", e)
  5530. return done
  5531. def system_get_default(self):
  5532. """ get current default run-level"""
  5533. return self.get_default_target()
  5534. def get_targets_folder(self):
  5535. return os_path(self._root, self.mask_folder())
  5536. def get_default_target_file(self):
  5537. targets_folder = self.get_targets_folder()
  5538. return os.path.join(targets_folder, DefaultUnit)
  5539. def get_default_target(self, default_target = None):
  5540. """ get current default run-level"""
  5541. current = default_target or self._default_target
  5542. default_target_file = self.get_default_target_file()
  5543. if os.path.islink(default_target_file):
  5544. current = os.path.basename(os.readlink(default_target_file))
  5545. return current
  5546. def set_default_modules(self, *modules):
  5547. """ set current default run-level"""
  5548. if not modules:
  5549. logg.debug(".. no runlevel given")
  5550. self.error |= NOT_OK
  5551. return "Too few arguments"
  5552. current = self.get_default_target()
  5553. default_target_file = self.get_default_target_file()
  5554. msg = ""
  5555. for module in modules:
  5556. if module == current:
  5557. continue
  5558. targetfile = None
  5559. for targetname, targetpath in self.each_target_file():
  5560. if targetname == module:
  5561. targetfile = targetpath
  5562. if not targetfile:
  5563. self.error |= NOT_OK | NOT_ACTIVE # 3
  5564. msg = "No such runlevel %s" % (module)
  5565. continue
  5566. #
  5567. if os.path.islink(default_target_file):
  5568. os.unlink(default_target_file)
  5569. if not os.path.isdir(os.path.dirname(default_target_file)):
  5570. os.makedirs(os.path.dirname(default_target_file))
  5571. os.symlink(targetfile, default_target_file)
  5572. msg = "Created symlink from %s -> %s" % (default_target_file, targetfile)
  5573. logg.debug("%s", msg)
  5574. return msg
  5575. def init_modules(self, *modules):
  5576. """ [UNIT*] -- init loop: '--init default' or '--init start UNIT*'
  5577. The systemctl init service will start the enabled 'default' services,
  5578. and then wait for any zombies to be reaped. When a SIGINT is received
  5579. then a clean shutdown of the enabled services is ensured. A Control-C in
  5580. in interactive mode will also run 'stop' on all the enabled services. //
  5581. When a UNIT name is given then only that one is started instead of the
  5582. services in the 'default.target'. Using 'init UNIT' is better than
  5583. '--init start UNIT' because the UNIT is also stopped cleanly even when
  5584. it was never enabled in the system.
  5585. /// SPECIAL: when using --now then only the init-loop is started,
  5586. with the reap-zombies function and waiting for an interrupt.
  5587. (and no unit is started/stoppped wether given or not).
  5588. """
  5589. if self._now:
  5590. result = self.init_loop_until_stop([])
  5591. return not not result
  5592. if not modules:
  5593. # like 'systemctl --init default'
  5594. if self._now or self._show_all:
  5595. logg.debug("init default --now --all => no_more_procs")
  5596. self.doExitWhenNoMoreProcs = True
  5597. return self.start_system_default(init = True)
  5598. #
  5599. # otherwise quit when all the init-services have died
  5600. self.doExitWhenNoMoreServices = True
  5601. if self._now or self._show_all:
  5602. logg.debug("init services --now --all => no_more_procs")
  5603. self.doExitWhenNoMoreProcs = True
  5604. found_all = True
  5605. units = []
  5606. for module in modules:
  5607. matched = self.match_units(to_list(module))
  5608. if not matched:
  5609. logg.error("Unit %s could not be found.", unit_of(module))
  5610. found_all = False
  5611. continue
  5612. for unit in matched:
  5613. if unit not in units:
  5614. units += [ unit ]
  5615. logg.info("init %s -> start %s", ",".join(modules), ",".join(units))
  5616. done = self.start_units(units, init = True)
  5617. logg.info("-- init is done")
  5618. return done # and found_all
  5619. def start_log_files(self, units):
  5620. self._log_file = {}
  5621. self._log_hold = {}
  5622. for unit in units:
  5623. conf = self.load_unit_conf(unit)
  5624. if not conf: continue
  5625. if self.skip_journal_log(conf): continue
  5626. log_path = self.get_journal_log_from(conf)
  5627. try:
  5628. opened = os.open(log_path, os.O_RDONLY | os.O_NONBLOCK)
  5629. self._log_file[unit] = opened
  5630. self._log_hold[unit] = b""
  5631. except Exception as e:
  5632. logg.error("can not open %s log: %s\n\t%s", unit, log_path, e)
  5633. def read_log_files(self, units):
  5634. BUFSIZE=8192
  5635. for unit in units:
  5636. if unit in self._log_file:
  5637. new_text = b""
  5638. while True:
  5639. buf = os.read(self._log_file[unit], BUFSIZE)
  5640. if not buf: break
  5641. new_text += buf
  5642. continue
  5643. text = self._log_hold[unit] + new_text
  5644. if not text: continue
  5645. lines = text.split(b"\n")
  5646. if not text.endswith(b"\n"):
  5647. self._log_hold[unit] = lines[-1]
  5648. lines = lines[:-1]
  5649. for line in lines:
  5650. prefix = unit.encode("utf-8")
  5651. content = prefix+b": "+line+b"\n"
  5652. os.write(1, content)
  5653. try: os.fsync(1)
  5654. except: pass
  5655. def stop_log_files(self, units):
  5656. for unit in units:
  5657. try:
  5658. if unit in self._log_file:
  5659. if self._log_file[unit]:
  5660. os.close(self._log_file[unit])
  5661. except Exception as e:
  5662. logg.error("can not close log: %s\n\t%s", unit, e)
  5663. self._log_file = {}
  5664. self._log_hold = {}
  5665. def get_StartLimitBurst(self, conf):
  5666. defaults = DefaultStartLimitBurst
  5667. return to_int(conf.get("Service", "StartLimitBurst", strE(defaults)), defaults) # 5
  5668. def get_StartLimitIntervalSec(self, conf, maximum = None):
  5669. maximum = maximum or 999
  5670. defaults = DefaultStartLimitIntervalSec
  5671. interval = conf.get("Service", "StartLimitIntervalSec", strE(defaults)) # 10s
  5672. return time_to_seconds(interval, maximum)
  5673. def get_RestartSec(self, conf, maximum = None):
  5674. maximum = maximum or DefaultStartLimitIntervalSec
  5675. delay = conf.get("Service", "RestartSec", strE(DefaultRestartSec))
  5676. return time_to_seconds(delay, maximum)
  5677. def restart_failed_units(self, units, maximum = None):
  5678. """ This function will retart failed units.
  5679. /
  5680. NOTE that with standard settings the LimitBurst implementation has no effect. If
  5681. the InitLoopSleep is ticking at the Default of 5sec and the LimitBurst Default
  5682. is 5x within a Default 10secs time frame then within those 10sec only 2 loop
  5683. rounds have come here checking for possible restarts. You can directly shorten
  5684. the interval ('-c InitLoopSleep=1') or have it indirectly shorter from the
  5685. service descriptor's RestartSec ("RestartSec=2s").
  5686. """
  5687. global InitLoopSleep
  5688. me = os.getpid()
  5689. maximum = maximum or DefaultStartLimitIntervalSec
  5690. restartDelay = MinimumYield
  5691. for unit in units:
  5692. now = time.time()
  5693. try:
  5694. conf = self.load_unit_conf(unit)
  5695. if not conf: continue
  5696. restartPolicy = conf.get("Service", "Restart", "no")
  5697. if restartPolicy in ["no", "on-success"]:
  5698. logg.debug("[%s] [%s] Current NoCheck (Restart=%s)", me, unit, restartPolicy)
  5699. continue
  5700. restartSec = self.get_RestartSec(conf)
  5701. if restartSec == 0:
  5702. if InitLoopSleep > 1:
  5703. logg.warning("[%s] set InitLoopSleep from %ss to 1 (caused by RestartSec=0!)",
  5704. unit, InitLoopSleep)
  5705. InitLoopSleep = 1
  5706. elif restartSec > 0.9 and restartSec < InitLoopSleep:
  5707. restartSleep = int(restartSec + 0.2)
  5708. if restartSleep < InitLoopSleep:
  5709. logg.warning("[%s] set InitLoopSleep from %ss to %s (caused by RestartSec=%.3fs)",
  5710. unit, InitLoopSleep, restartSleep, restartSec)
  5711. InitLoopSleep = restartSleep
  5712. isUnitState = self.get_active_from(conf)
  5713. isUnitFailed = isUnitState in ["failed"]
  5714. logg.debug("[%s] [%s] Current Status: %s (%s)", me, unit, isUnitState, isUnitFailed)
  5715. if not isUnitFailed:
  5716. if unit in self._restart_failed_units:
  5717. del self._restart_failed_units[unit]
  5718. continue
  5719. limitBurst = self.get_StartLimitBurst(conf)
  5720. limitSecs = self.get_StartLimitIntervalSec(conf)
  5721. if limitBurst > 1 and limitSecs >= 1:
  5722. try:
  5723. if unit not in self._restarted_unit:
  5724. self._restarted_unit[unit] = []
  5725. # we want to register restarts from now on
  5726. restarted = self._restarted_unit[unit]
  5727. logg.debug("[%s] [%s] Current limitSecs=%ss limitBurst=%sx (restarted %sx)",
  5728. me, unit, limitSecs, limitBurst, len(restarted))
  5729. oldest = 0.
  5730. interval = 0.
  5731. if len(restarted) >= limitBurst:
  5732. logg.debug("[%s] [%s] restarted %s",
  5733. me, unit, [ "%.3fs" % (t - now) for t in restarted ])
  5734. while len(restarted):
  5735. oldest = restarted[0]
  5736. interval = time.time() - oldest
  5737. if interval > limitSecs:
  5738. restarted = restarted[1:]
  5739. continue
  5740. break
  5741. self._restarted_unit[unit] = restarted
  5742. logg.debug("[%s] [%s] ratelimit %s",
  5743. me, unit, [ "%.3fs" % (t - now) for t in restarted ])
  5744. # all values in restarted have a time below limitSecs
  5745. if len(restarted) >= limitBurst:
  5746. logg.info("[%s] [%s] Blocking Restart - oldest %s is %s ago (allowed %s)",
  5747. me, unit, oldest, interval, limitSecs)
  5748. self.write_status_from(conf, AS="error")
  5749. unit = "" # dropped out
  5750. continue
  5751. except Exception as e:
  5752. logg.error("[%s] burst exception %s", unit, e)
  5753. if unit: # not dropped out
  5754. if unit not in self._restart_failed_units:
  5755. self._restart_failed_units[unit] = now + restartSec
  5756. logg.debug("[%s] [%s] restart scheduled in %+.3fs",
  5757. me, unit, (self._restart_failed_units[unit] - now))
  5758. except Exception as e:
  5759. logg.error("[%s] [%s] An error ocurred while restart checking: %s", me, unit, e)
  5760. if not self._restart_failed_units:
  5761. self.error |= NOT_OK
  5762. return []
  5763. # NOTE: this function is only called from InitLoop when "running"
  5764. # let's check if any of the restart_units has its restartSec expired
  5765. now = time.time()
  5766. restart_done = []
  5767. logg.debug("[%s] Restart checking %s",
  5768. me, [ "%+.3fs" % (t - now) for t in self._restart_failed_units.values() ])
  5769. for unit in sorted(self._restart_failed_units):
  5770. restartAt = self._restart_failed_units[unit]
  5771. if restartAt > now:
  5772. continue
  5773. restart_done.append(unit)
  5774. try:
  5775. conf = self.load_unit_conf(unit)
  5776. if not conf: continue
  5777. isUnitState = self.get_active_from(conf)
  5778. isUnitFailed = isUnitState in ["failed"]
  5779. logg.debug("[%s] [%s] Restart Status: %s (%s)", me, unit, isUnitState, isUnitFailed)
  5780. if isUnitFailed:
  5781. logg.debug("[%s] [%s] --- restarting failed unit...", me, unit)
  5782. self.restart_unit(unit)
  5783. logg.debug("[%s] [%s] --- has been restarted.", me, unit)
  5784. if unit in self._restarted_unit:
  5785. self._restarted_unit[unit].append(time.time())
  5786. except Exception as e:
  5787. logg.error("[%s] [%s] An error ocurred while restarting: %s", me, unit, e)
  5788. for unit in restart_done:
  5789. if unit in self._restart_failed_units:
  5790. del self._restart_failed_units[unit]
  5791. logg.debug("[%s] Restart remaining %s",
  5792. me, [ "%+.3fs" % (t - now) for t in self._restart_failed_units.values() ])
  5793. return restart_done
  5794. def init_loop_until_stop(self, units):
  5795. """ this is the init-loop - it checks for any zombies to be reaped and
  5796. waits for an interrupt. When a SIGTERM /SIGINT /Control-C signal
  5797. is received then the signal name is returned. Any other signal will
  5798. just raise an Exception like one would normally expect. As a special
  5799. the 'systemctl halt' emits SIGQUIT which puts it into no_more_procs mode."""
  5800. signal.signal(signal.SIGQUIT, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt("SIGQUIT"))
  5801. signal.signal(signal.SIGINT, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt("SIGINT"))
  5802. signal.signal(signal.SIGTERM, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt("SIGTERM"))
  5803. #
  5804. self.start_log_files(units)
  5805. logg.debug("start listen")
  5806. listen = SystemctlListenThread(self)
  5807. logg.debug("starts listen")
  5808. listen.start()
  5809. logg.debug("started listen")
  5810. self.sysinit_status(ActiveState = "active", SubState = "running")
  5811. timestamp = time.time()
  5812. result = None
  5813. while True:
  5814. try:
  5815. if DEBUG_INITLOOP: # pragma: no cover
  5816. logg.debug("DONE InitLoop (sleep %ss)", InitLoopSleep)
  5817. sleep_sec = InitLoopSleep - (time.time() - timestamp)
  5818. if sleep_sec < MinimumYield:
  5819. sleep_sec = MinimumYield
  5820. sleeping = sleep_sec
  5821. while sleeping > 2:
  5822. time.sleep(1) # accept signals atleast every second
  5823. sleeping = InitLoopSleep - (time.time() - timestamp)
  5824. if sleeping < MinimumYield:
  5825. sleeping = MinimumYield
  5826. break
  5827. time.sleep(sleeping) # remainder waits less that 2 seconds
  5828. timestamp = time.time()
  5829. self.loop.acquire()
  5830. if DEBUG_INITLOOP: # pragma: no cover
  5831. logg.debug("NEXT InitLoop (after %ss)", sleep_sec)
  5832. self.read_log_files(units)
  5833. if DEBUG_INITLOOP: # pragma: no cover
  5834. logg.debug("reap zombies - check current processes")
  5835. running = self.system_reap_zombies()
  5836. if DEBUG_INITLOOP: # pragma: no cover
  5837. logg.debug("reap zombies - init-loop found %s running procs", running)
  5838. if self.doExitWhenNoMoreServices:
  5839. active = False
  5840. for unit in units:
  5841. conf = self.load_unit_conf(unit)
  5842. if not conf: continue
  5843. if self.is_active_from(conf):
  5844. active = True
  5845. if not active:
  5846. logg.info("no more services - exit init-loop")
  5847. break
  5848. if self.doExitWhenNoMoreProcs:
  5849. if not running:
  5850. logg.info("no more procs - exit init-loop")
  5851. break
  5852. if RESTART_FAILED_UNITS:
  5853. self.restart_failed_units(units)
  5854. self.loop.release()
  5855. except KeyboardInterrupt as e:
  5856. if e.args and e.args[0] == "SIGQUIT":
  5857. # the original systemd puts a coredump on that signal.
  5858. logg.info("SIGQUIT - switch to no more procs check")
  5859. self.doExitWhenNoMoreProcs = True
  5860. continue
  5861. signal.signal(signal.SIGTERM, signal.SIG_DFL)
  5862. signal.signal(signal.SIGINT, signal.SIG_DFL)
  5863. logg.info("interrupted - exit init-loop")
  5864. result = str(e) or "STOPPED"
  5865. break
  5866. except Exception as e:
  5867. logg.info("interrupted - exception %s", e)
  5868. raise
  5869. self.sysinit_status(ActiveState = None, SubState = "degraded")
  5870. try: self.loop.release()
  5871. except: pass
  5872. listen.stop()
  5873. listen.join(2)
  5874. self.read_log_files(units)
  5875. self.read_log_files(units)
  5876. self.stop_log_files(units)
  5877. logg.debug("done - init loop")
  5878. return result
  5879. def system_reap_zombies(self):
  5880. """ check to reap children """
  5881. selfpid = os.getpid()
  5882. running = 0
  5883. for pid_entry in os.listdir(_proc_pid_dir):
  5884. pid = to_intN(pid_entry)
  5885. if pid is None:
  5886. continue
  5887. if pid == selfpid:
  5888. continue
  5889. proc_status = _proc_pid_status.format(**locals())
  5890. if os.path.isfile(proc_status):
  5891. zombie = False
  5892. ppid = -1
  5893. try:
  5894. for line in open(proc_status):
  5895. m = re.match(r"State:\s*Z.*", line)
  5896. if m: zombie = True
  5897. m = re.match(r"PPid:\s*(\d+)", line)
  5898. if m: ppid = int(m.group(1))
  5899. except IOError as e:
  5900. logg.warning("%s : %s", proc_status, e)
  5901. continue
  5902. if zombie and ppid == os.getpid():
  5903. logg.info("reap zombie %s", pid)
  5904. try: os.waitpid(pid, os.WNOHANG)
  5905. except OSError as e:
  5906. logg.warning("reap zombie %s: %s", e.strerror)
  5907. if os.path.isfile(proc_status):
  5908. if pid > 1:
  5909. running += 1
  5910. return running # except PID 0 and PID 1
  5911. def sysinit_status(self, **status):
  5912. conf = self.sysinit_target()
  5913. self.write_status_from(conf, **status)
  5914. def sysinit_target(self):
  5915. if not self._sysinit_target:
  5916. self._sysinit_target = self.default_unit_conf(SysInitTarget, "System Initialization")
  5917. assert self._sysinit_target is not None
  5918. return self._sysinit_target
  5919. def is_system_running(self):
  5920. conf = self.sysinit_target()
  5921. if not self.is_running_unit_from(conf):
  5922. time.sleep(MinimumYield)
  5923. if not self.is_running_unit_from(conf):
  5924. return "offline"
  5925. status = self.read_status_from(conf)
  5926. return status.get("SubState", "unknown")
  5927. def system_is_system_running(self):
  5928. state = self.is_system_running()
  5929. if state not in [ "running" ]:
  5930. self.error |= NOT_OK # 1
  5931. if self._quiet:
  5932. return None
  5933. return state
  5934. def wait_system(self, target = None):
  5935. target = target or SysInitTarget
  5936. for attempt in xrange(int(SysInitWait)):
  5937. state = self.is_system_running()
  5938. if "init" in state:
  5939. if target in [ SysInitTarget, "basic.target" ]:
  5940. logg.info("system not initialized - wait %s", target)
  5941. time.sleep(1)
  5942. continue
  5943. if "start" in state or "stop" in state:
  5944. if target in [ "basic.target" ]:
  5945. logg.info("system not running - wait %s", target)
  5946. time.sleep(1)
  5947. continue
  5948. if "running" not in state:
  5949. logg.info("system is %s", state)
  5950. break
  5951. def is_running_unit_from(self, conf):
  5952. status_file = self.get_status_file_from(conf)
  5953. return self.getsize(status_file) > 0
  5954. def is_running_unit(self, unit):
  5955. conf = self.get_unit_conf(unit)
  5956. return self.is_running_unit_from(conf)
  5957. def pidlist_of(self, pid):
  5958. if not pid:
  5959. return []
  5960. pidlist = [ pid ]
  5961. pids = [ pid ]
  5962. for depth in xrange(PROC_MAX_DEPTH):
  5963. for pid_entry in os.listdir(_proc_pid_dir):
  5964. pid = to_intN(pid_entry)
  5965. if pid is None:
  5966. continue
  5967. proc_status = _proc_pid_status.format(**locals())
  5968. if os.path.isfile(proc_status):
  5969. try:
  5970. for line in open(proc_status):
  5971. if line.startswith("PPid:"):
  5972. ppid_text = line[len("PPid:"):].strip()
  5973. try: ppid = int(ppid_text)
  5974. except: continue
  5975. if ppid in pidlist and pid not in pids:
  5976. pids += [ pid ]
  5977. except IOError as e:
  5978. logg.warning("%s : %s", proc_status, e)
  5979. continue
  5980. if len(pids) != len(pidlist):
  5981. pidlist = pids[:]
  5982. continue
  5983. return pids
  5984. def echo(self, *targets):
  5985. line = " ".join(*targets)
  5986. logg.info(" == echo == %s", line)
  5987. return line
  5988. def killall(self, *targets):
  5989. mapping = {}
  5990. mapping[":3"] = signal.SIGQUIT
  5991. mapping[":QUIT"] = signal.SIGQUIT
  5992. mapping[":6"] = signal.SIGABRT
  5993. mapping[":ABRT"] = signal.SIGABRT
  5994. mapping[":9"] = signal.SIGKILL
  5995. mapping[":KILL"] = signal.SIGKILL
  5996. sig = signal.SIGTERM
  5997. for target in targets:
  5998. if target.startswith(":"):
  5999. if target in mapping:
  6000. sig = mapping[target]
  6001. else: # pragma: no cover
  6002. logg.error("unsupported %s", target)
  6003. continue
  6004. for pid_entry in os.listdir(_proc_pid_dir):
  6005. pid = to_intN(pid_entry)
  6006. if pid:
  6007. try:
  6008. cmdline = _proc_pid_cmdline.format(**locals())
  6009. cmd = open(cmdline).read().split("\0")
  6010. if DEBUG_KILLALL: logg.debug("cmdline %s", cmd)
  6011. found = None
  6012. cmd_exe = os.path.basename(cmd[0])
  6013. if DEBUG_KILLALL: logg.debug("cmd.exe '%s'", cmd_exe)
  6014. if fnmatch.fnmatchcase(cmd_exe, target): found = "exe"
  6015. if len(cmd) > 1 and cmd_exe.startswith("python"):
  6016. X = 1
  6017. while cmd[X].startswith("-"): X += 1 # atleast '-u' unbuffered
  6018. cmd_arg = os.path.basename(cmd[X])
  6019. if DEBUG_KILLALL: logg.debug("cmd.arg '%s'", cmd_arg)
  6020. if fnmatch.fnmatchcase(cmd_arg, target): found = "arg"
  6021. if cmd_exe.startswith("coverage") or cmd_arg.startswith("coverage"):
  6022. x = cmd.index("--")
  6023. if x > 0 and x+1 < len(cmd):
  6024. cmd_run = os.path.basename(cmd[x+1])
  6025. if DEBUG_KILLALL: logg.debug("cmd.run '%s'", cmd_run)
  6026. if fnmatch.fnmatchcase(cmd_run, target): found = "run"
  6027. if found:
  6028. if DEBUG_KILLALL: logg.debug("%s found %s %s", found, pid, [ c for c in cmd ])
  6029. if pid != os.getpid():
  6030. logg.debug(" kill -%s %s # %s", sig, pid, target)
  6031. os.kill(pid, sig)
  6032. except Exception as e:
  6033. logg.error("kill -%s %s : %s", sig, pid, e)
  6034. return True
  6035. def force_ipv4(self, *args):
  6036. """ only ipv4 localhost in /etc/hosts """
  6037. logg.debug("checking hosts sysconf for '::1 localhost'")
  6038. lines = []
  6039. sysconf_hosts = os_path(self._root, _etc_hosts)
  6040. for line in open(sysconf_hosts):
  6041. if "::1" in line:
  6042. newline = re.sub("\\slocalhost\\s", " ", line)
  6043. if line != newline:
  6044. logg.info("%s: '%s' => '%s'", _etc_hosts, line.rstrip(), newline.rstrip())
  6045. line = newline
  6046. lines.append(line)
  6047. f = open(sysconf_hosts, "w")
  6048. for line in lines:
  6049. f.write(line)
  6050. f.close()
  6051. def force_ipv6(self, *args):
  6052. """ only ipv4 localhost in /etc/hosts """
  6053. logg.debug("checking hosts sysconf for '127.0.0.1 localhost'")
  6054. lines = []
  6055. sysconf_hosts = os_path(self._root, _etc_hosts)
  6056. for line in open(sysconf_hosts):
  6057. if "127.0.0.1" in line:
  6058. newline = re.sub("\\slocalhost\\s", " ", line)
  6059. if line != newline:
  6060. logg.info("%s: '%s' => '%s'", _etc_hosts, line.rstrip(), newline.rstrip())
  6061. line = newline
  6062. lines.append(line)
  6063. f = open(sysconf_hosts, "w")
  6064. for line in lines:
  6065. f.write(line)
  6066. f.close()
  6067. def show_help(self, *args):
  6068. """[command] -- show this help
  6069. """
  6070. lines = []
  6071. okay = True
  6072. prog = os.path.basename(sys.argv[0])
  6073. if not args:
  6074. argz = {}
  6075. for name in dir(self):
  6076. arg = None
  6077. if name.startswith("system_"):
  6078. arg = name[len("system_"):].replace("_","-")
  6079. if name.startswith("show_"):
  6080. arg = name[len("show_"):].replace("_","-")
  6081. if name.endswith("_of_unit"):
  6082. arg = name[:-len("_of_unit")].replace("_","-")
  6083. if name.endswith("_modules"):
  6084. arg = name[:-len("_modules")].replace("_","-")
  6085. if arg:
  6086. argz[arg] = name
  6087. lines.append("%s command [options]..." % prog)
  6088. lines.append("")
  6089. lines.append("Commands:")
  6090. for arg in sorted(argz):
  6091. name = argz[arg]
  6092. method = getattr(self, name)
  6093. doc = "..."
  6094. doctext = getattr(method, "__doc__")
  6095. if doctext:
  6096. doc = doctext
  6097. elif not self._show_all:
  6098. continue # pragma: no cover
  6099. firstline = doc.split("\n")[0]
  6100. doc_text = firstline.strip()
  6101. if "--" not in firstline:
  6102. doc_text = "-- " + doc_text
  6103. lines.append(" %s %s" % (arg, firstline.strip()))
  6104. return lines
  6105. for arg in args:
  6106. arg = arg.replace("-","_")
  6107. func1 = getattr(self.__class__, arg+"_modules", None)
  6108. func2 = getattr(self.__class__, arg+"_of_unit", None)
  6109. func3 = getattr(self.__class__, "show_"+arg, None)
  6110. func4 = getattr(self.__class__, "system_"+arg, None)
  6111. func5 = None
  6112. if arg.startswith("__"):
  6113. func5 = getattr(self.__class__, arg[2:], None)
  6114. func = func1 or func2 or func3 or func4 or func5
  6115. if func is None:
  6116. print("error: no such command '%s'" % arg)
  6117. okay = False
  6118. else:
  6119. doc_text = "..."
  6120. doc = getattr(func, "__doc__", None)
  6121. if doc:
  6122. doc_text = doc.replace("\n","\n\n", 1).strip()
  6123. if "--" not in doc_text:
  6124. doc_text = "-- " + doc_text
  6125. else:
  6126. func_name = arg # FIXME
  6127. logg.debug("__doc__ of %s is none", func_name)
  6128. if not self._show_all: continue
  6129. lines.append("%s %s %s" % (prog, arg, doc_text))
  6130. if not okay:
  6131. self.show_help()
  6132. self.error |= NOT_OK
  6133. return []
  6134. return lines
  6135. def systemd_version(self):
  6136. """ the version line for systemd compatibility """
  6137. return "systemd %s\n - via systemctl.py %s" % (self._systemd_version, __version__)
  6138. def systemd_features(self):
  6139. """ the info line for systemd features """
  6140. features1 = "-PAM -AUDIT -SELINUX -IMA -APPARMOR -SMACK"
  6141. features2 = " +SYSVINIT -UTMP -LIBCRYPTSETUP -GCRYPT -GNUTLS"
  6142. features3 = " -ACL -XZ -LZ4 -SECCOMP -BLKID -ELFUTILS -KMOD -IDN"
  6143. return features1+features2+features3
  6144. def systems_version(self):
  6145. return [ self.systemd_version(), self.systemd_features() ]
  6146. def test_float(self):
  6147. return 0. # "Unknown result type"
  6148. def print_result(result):
  6149. # logg_info = logg.info
  6150. # logg_debug = logg.debug
  6151. def logg_info(*msg): pass
  6152. def logg_debug(*msg): pass
  6153. exitcode = 0
  6154. if result is None:
  6155. logg_info("EXEC END None")
  6156. elif result is True:
  6157. logg_info("EXEC END True")
  6158. exitcode = 0
  6159. elif result is False:
  6160. logg_info("EXEC END False")
  6161. exitcode = NOT_OK # the only case that exitcode gets set
  6162. elif isinstance(result, int):
  6163. logg_info("EXEC END %s", result)
  6164. # exitcode = result # we do not do that anymore
  6165. elif isinstance(result, basestring):
  6166. print(result)
  6167. result1 = result.split("\n")[0][:-20]
  6168. if result == result1:
  6169. logg_info("EXEC END '%s'", result)
  6170. else:
  6171. logg_info("EXEC END '%s...'", result1)
  6172. logg_debug(" END '%s'", result)
  6173. elif isinstance(result, list) or isinstance(result, GeneratorType):
  6174. shown = 0
  6175. for element in result:
  6176. if isinstance(element, tuple):
  6177. print("\t".join([ str(elem) for elem in element] ))
  6178. else:
  6179. print(element)
  6180. shown += 1
  6181. logg_info("EXEC END %s items", shown)
  6182. logg_debug(" END %s", result)
  6183. elif isinstance(result, dict):
  6184. shown = 0
  6185. for key in sorted(result.keys()):
  6186. element = result[key]
  6187. if isinstance(element, tuple):
  6188. print(key,"=","\t".join([ str(elem) for elem in element]))
  6189. else:
  6190. print("%s=%s" % (key,element))
  6191. shown += 1
  6192. logg_info("EXEC END %s items", shown)
  6193. logg_debug(" END %s", result)
  6194. else:
  6195. logg.warning("EXEC END Unknown result type %s", str(type(result)))
  6196. return exitcode
  6197. if __name__ == "__main__":
  6198. import optparse
  6199. _o = optparse.OptionParser("%prog [options] command [name...]",
  6200. epilog="use 'help' command for more information")
  6201. _o.add_option("--version", action="store_true",
  6202. help="Show package version")
  6203. _o.add_option("--system", action="store_true", default=False,
  6204. help="Connect to system manager (default)") # overrides --user
  6205. _o.add_option("--user", action="store_true", default=_user_mode,
  6206. help="Connect to user service manager")
  6207. # _o.add_option("-H", "--host", metavar="[USER@]HOST",
  6208. # help="Operate on remote host*")
  6209. # _o.add_option("-M", "--machine", metavar="CONTAINER",
  6210. # help="Operate on local container*")
  6211. _o.add_option("-t","--type", metavar="TYPE", dest="unit_type", default=_unit_type,
  6212. help="List units of a particual type")
  6213. _o.add_option("--state", metavar="STATE", default=_unit_state,
  6214. help="List units with particular LOAD or SUB or ACTIVE state")
  6215. _o.add_option("-p", "--property", metavar="NAME", dest="unit_property", default=_unit_property,
  6216. help="Show only properties by this name")
  6217. _o.add_option("--what", metavar="TYPE", dest="what_kind", default=_what_kind,
  6218. help="Defines the service directories to be cleaned (configuration, state, cache, logs, runtime)")
  6219. _o.add_option("-a", "--all", action="store_true", dest="show_all", default=_show_all,
  6220. help="Show all loaded units/properties, including dead empty ones. To list all units installed on the system, use the 'list-unit-files' command instead")
  6221. _o.add_option("-l","--full", action="store_true", default=_full,
  6222. help="Don't ellipsize unit names on output (never ellipsized)")
  6223. _o.add_option("--reverse", action="store_true",
  6224. help="Show reverse dependencies with 'list-dependencies' (ignored)")
  6225. _o.add_option("--job-mode", metavar="MODE",
  6226. help="Specifiy how to deal with already queued jobs, when queuing a new job (ignored)")
  6227. _o.add_option("--show-types", action="store_true",
  6228. help="When showing sockets, explicitly show their type (ignored)")
  6229. _o.add_option("-i","--ignore-inhibitors", action="store_true",
  6230. help="When shutting down or sleeping, ignore inhibitors (ignored)")
  6231. _o.add_option("--kill-who", metavar="WHO",
  6232. help="Who to send signal to (ignored)")
  6233. _o.add_option("-s", "--signal", metavar="SIG",
  6234. help="Which signal to send (ignored)")
  6235. _o.add_option("--now", action="store_true", default=_now,
  6236. help="Start or stop unit in addition to enabling or disabling it")
  6237. _o.add_option("-q","--quiet", action="store_true", default=_quiet,
  6238. help="Suppress output")
  6239. _o.add_option("--no-block", action="store_true", default=False,
  6240. help="Do not wait until operation finished (ignored)")
  6241. _o.add_option("--no-legend", action="store_true", default=_no_legend,
  6242. help="Do not print a legend (column headers and hints)")
  6243. _o.add_option("--no-wall", action="store_true", default=False,
  6244. help="Don't send wall message before halt/power-off/reboot (ignored)")
  6245. _o.add_option("--no-reload", action="store_true", default=_no_reload,
  6246. help="Don't reload daemon after en-/dis-abling unit files")
  6247. _o.add_option("--no-ask-password", action="store_true", default=_no_ask_password,
  6248. help="Do not ask for system passwords")
  6249. # _o.add_option("--global", action="store_true", dest="globally", default=_globally,
  6250. # help="Enable/disable unit files globally") # for all user logins
  6251. # _o.add_option("--runtime", action="store_true",
  6252. # help="Enable unit files only temporarily until next reboot")
  6253. _o.add_option("-f", "--force", action="store_true", default=_force,
  6254. help="When enabling unit files, override existing symblinks / When shutting down, execute action immediately")
  6255. _o.add_option("--preset-mode", metavar="TYPE", default=_preset_mode,
  6256. help="Apply only enable, only disable, or all presets [%default]")
  6257. _o.add_option("--root", metavar="PATH", default=_root,
  6258. help="Enable unit files in the specified root directory (used for alternative root prefix)")
  6259. _o.add_option("-n","--lines", metavar="NUM",
  6260. help="Number of journal entries to show")
  6261. _o.add_option("-o","--output", metavar="CAT",
  6262. help="change journal output mode [short, ..., cat] (ignored)")
  6263. _o.add_option("--plain", action="store_true",
  6264. help="Print unit dependencies as a list instead of a tree (ignored)")
  6265. _o.add_option("--no-pager", action="store_true",
  6266. help="Do not pipe output into pager (mostly ignored)")
  6267. #
  6268. _o.add_option("-c","--config", metavar="NAME=VAL", action="append", default=[],
  6269. help="..override internal variables (InitLoopSleep,SysInitTarget) {%default}")
  6270. _o.add_option("-e","--extra-vars", "--environment", metavar="NAME=VAL", action="append", default=[],
  6271. help="..override settings in the syntax of 'Environment='")
  6272. _o.add_option("-v","--verbose", action="count", default=0,
  6273. help="..increase debugging information level")
  6274. _o.add_option("-4","--ipv4", action="store_true", default=False,
  6275. help="..only keep ipv4 localhost in /etc/hosts")
  6276. _o.add_option("-6","--ipv6", action="store_true", default=False,
  6277. help="..only keep ipv6 localhost in /etc/hosts")
  6278. _o.add_option("-1","--init", action="store_true", default=False,
  6279. help="..keep running as init-process (default if PID 1)")
  6280. opt, args = _o.parse_args()
  6281. logging.basicConfig(level = max(0, logging.FATAL - 10 * opt.verbose))
  6282. logg.setLevel(max(0, logging.ERROR - 10 * opt.verbose))
  6283. #
  6284. _extra_vars = opt.extra_vars
  6285. _force = opt.force
  6286. _full = opt.full
  6287. _log_lines = opt.lines
  6288. _no_pager = opt.no_pager
  6289. _no_reload = opt.no_reload
  6290. _no_legend = opt.no_legend
  6291. _no_ask_password = opt.no_ask_password
  6292. _now = opt.now
  6293. _preset_mode = opt.preset_mode
  6294. _quiet = opt.quiet
  6295. _root = opt.root
  6296. _show_all = opt.show_all
  6297. _unit_state = opt.state
  6298. _unit_type = opt.unit_type
  6299. _unit_property = opt.unit_property
  6300. _what_kind = opt.what_kind
  6301. # being PID 1 (or 0) in a container will imply --init
  6302. _pid = os.getpid()
  6303. _init = opt.init or _pid in [ 1, 0 ]
  6304. _user_mode = opt.user
  6305. if os.geteuid() and _pid in [ 1, 0 ]:
  6306. _user_mode = True
  6307. if opt.system:
  6308. _user_mode = False # override --user
  6309. #
  6310. for setting in opt.config:
  6311. nam, val = setting, "1"
  6312. if "=" in setting:
  6313. nam, val = setting.split("=", 1)
  6314. elif nam.startswith("no-") or nam.startswith("NO-"):
  6315. nam, val = nam[3:], "0"
  6316. elif nam.startswith("No") or nam.startswith("NO"):
  6317. nam, val = nam[2:], "0"
  6318. if nam in globals():
  6319. old = globals()[nam]
  6320. if old is False or old is True:
  6321. logg.debug("yes %s=%s", nam, val)
  6322. globals()[nam] = (val in ("true", "True", "TRUE", "yes", "y", "Y", "YES", "1"))
  6323. logg.debug("... _show_all=%s", _show_all)
  6324. elif isinstance(old, float):
  6325. logg.debug("num %s=%s", nam, val)
  6326. globals()[nam] = float(val)
  6327. logg.debug("... MinimumYield=%s", MinimumYield)
  6328. elif isinstance(old, int):
  6329. logg.debug("int %s=%s", nam, val)
  6330. globals()[nam] = int(val)
  6331. logg.debug("... InitLoopSleep=%s", InitLoopSleep)
  6332. elif isinstance(old, basestring):
  6333. logg.debug("str %s=%s", nam, val)
  6334. globals()[nam] = val.strip()
  6335. logg.debug("... SysInitTarget=%s", SysInitTarget)
  6336. else:
  6337. logg.warning("(ignored) unknown target type -c '%s' : %s", nam, type(old))
  6338. else:
  6339. logg.warning("(ignored) unknown target config -c '%s' : no such variable", nam)
  6340. #
  6341. systemctl_debug_log = os_path(_root, expand_path(SYSTEMCTL_DEBUG_LOG, not _user_mode))
  6342. systemctl_extra_log = os_path(_root, expand_path(SYSTEMCTL_EXTRA_LOG, not _user_mode))
  6343. if os.access(systemctl_extra_log, os.W_OK):
  6344. loggfile = logging.FileHandler(systemctl_extra_log)
  6345. loggfile.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
  6346. logg.addHandler(loggfile)
  6347. logg.setLevel(max(0, logging.INFO - 10 * opt.verbose))
  6348. if os.access(systemctl_debug_log, os.W_OK):
  6349. loggfile = logging.FileHandler(systemctl_debug_log)
  6350. loggfile.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
  6351. logg.addHandler(loggfile)
  6352. logg.setLevel(logging.DEBUG)
  6353. logg.info("EXEC BEGIN %s %s%s%s", os.path.realpath(sys.argv[0]), " ".join(args),
  6354. _user_mode and " --user" or " --system", _init and " --init" or "", )
  6355. if _root and not is_good_root(_root):
  6356. logg.warning("the --root=path should have alteast three levels /tmp/test_123/root")
  6357. #
  6358. #
  6359. systemctl = Systemctl()
  6360. if opt.version:
  6361. args = [ "version" ]
  6362. if not args:
  6363. if _init:
  6364. args = [ "default" ]
  6365. else:
  6366. args = [ "list-units" ]
  6367. logg.debug("======= systemctl.py " + " ".join(args))
  6368. command = args[0]
  6369. modules = args[1:]
  6370. try:
  6371. modules.remove("service")
  6372. except ValueError:
  6373. pass
  6374. if opt.ipv4:
  6375. systemctl.force_ipv4()
  6376. elif opt.ipv6:
  6377. systemctl.force_ipv6()
  6378. found = False
  6379. # command NAME
  6380. if command.startswith("__"):
  6381. command_name = command[2:]
  6382. command_func = getattr(systemctl, command_name, None)
  6383. if callable(command_func) and not found:
  6384. found = True
  6385. result = command_func(*modules)
  6386. command_name = command.replace("-","_").replace(".","_")+"_modules"
  6387. command_func = getattr(systemctl, command_name, None)
  6388. if callable(command_func) and not found:
  6389. found = True
  6390. result = command_func(*modules)
  6391. command_name = "show_"+command.replace("-","_").replace(".","_")
  6392. command_func = getattr(systemctl, command_name, None)
  6393. if callable(command_func) and not found:
  6394. found = True
  6395. result = command_func(*modules)
  6396. command_name = "system_"+command.replace("-","_").replace(".","_")
  6397. command_func = getattr(systemctl, command_name, None)
  6398. if callable(command_func) and not found:
  6399. found = True
  6400. result = command_func()
  6401. command_name = "systems_"+command.replace("-","_").replace(".","_")
  6402. command_func = getattr(systemctl, command_name, None)
  6403. if callable(command_func) and not found:
  6404. found = True
  6405. result = command_func()
  6406. if not found:
  6407. logg.error("Unknown operation %s.", command)
  6408. sys.exit(1)
  6409. #
  6410. exitcode = print_result(result)
  6411. exitcode |= systemctl.error
  6412. sys.exit(exitcode)