nutmon.pp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. Program nutmon;
  2. {
  3. Simple nut ups monitor for netware, see http://www.networkupstools.org
  4. This program can be used to shut down a netware server on power
  5. failure. It requires nut to be installed on a *nix server (the serial
  6. or usb ups control is not connected to the netware server, this will
  7. be handled by the upsd on a *nix server)
  8. FreePascal >= 1.9.5 (http://www.freepascal.org) is needed to compile this.
  9. This source is free software; you can redistribute it and/or modify
  10. it under the terms of the GNU General Public License as published by
  11. the Free Software Foundation; either version 2 of the License, or
  12. (at your option) any later version.
  13. This code is distributed in the hope that it will be useful, but
  14. WITHOUT ANY WARRANTY; without even the implied warranty of
  15. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. General Public License for more details.
  17. A copy of the GNU General Public License is available on the World
  18. Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also
  19. obtain it by writing to the Free Software Foundation,
  20. Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  21. First Version 2004/12/16 Armin Diehl <[email protected]>
  22. **********************************************************************}
  23. {$mode objfpc}
  24. {$M 65535,0,0}
  25. {$if defined(netware)}
  26. {$if defined(netware_clib)}
  27. {$description nut ups monitor - clib}
  28. {$else}
  29. {$description nut ups monitor - libc}
  30. {$endif}
  31. {$copyright Copyright 2004 Armin Diehl <[email protected]>}
  32. {$screenname DEFAULT} // dont use none because writeln will not work with none
  33. {$version 1.0.0}
  34. {$endif netware}
  35. uses
  36. sysutils, nutconnection, inifiles
  37. {$if defined(netware_libc)}
  38. ,libc
  39. {$elseif defined(netware_clib)}
  40. ,nwserv,nwnit
  41. {$endif}
  42. ;
  43. const
  44. CMD_NONE = 0;
  45. CMD_STATUS = 1;
  46. CMD_TESTSHUTDOWN = 2;
  47. var
  48. nut : TNutConnection;
  49. nutUser : string;
  50. nutPassword : string;
  51. nutPollfreq : integer;
  52. nutPollfreqAlert : integer;
  53. nutReconnectFreq : integer;
  54. nutUpsName : string;
  55. terminated : boolean = false;
  56. upsStatus,lastupsStatus : TUpsStatus;
  57. waitSemaphore: longint;
  58. commandAfterDown,powerOffFileName : ansistring;
  59. downIfCapaityBelow:integer = 0;
  60. {$if defined(netware)}
  61. CmdParserStruct : TcommandParserStructure;
  62. CurrentCommand : byte;
  63. oldNetwareUnloadProc : pointer;
  64. MainLoopTerminated : boolean = false;
  65. {$endif}
  66. const mainSection = 'nutmon';
  67. procedure readConfig;
  68. var fn : string;
  69. t : tiniFile;
  70. begin
  71. fn := ChangeFileExt(paramstr(0),'.ini');
  72. t := TIniFile.Create (fn);
  73. try
  74. nut.host := t.readString (mainSection,'host','');
  75. if nut.host = '' then
  76. begin
  77. writeln (stderr,paramstr(0)+': host= not specified in '+fn+' exiting');
  78. halt;
  79. end;
  80. nut.port := word (t.readInteger (mainSection,'port',NutDefaultPort));
  81. nutUser := t.readString (mainSection,'user','');
  82. if nutUser = '' then
  83. begin
  84. writeln (stderr,paramstr(0)+': user= not specified in '+fn+' exiting');
  85. halt;
  86. end;
  87. nutPassword := t.readString (mainSection,'password','');
  88. if nutPassword = '' then
  89. begin
  90. writeln (stderr,paramstr(0)+': password= not specified in '+fn+' exiting');
  91. halt;
  92. end;
  93. nutUpsName := t.readString (mainSection,'upsname','');
  94. if nutUpsname = '' then
  95. begin
  96. writeln (stderr,paramstr(0)+': upsname= not specified in '+fn+' exiting');
  97. halt;
  98. end;
  99. nutPollfreq := t.readInteger (mainSection,'pollfreq',10);
  100. nutPollfreqAlert := t.readInteger (mainSection,'pollfrqalert',5);
  101. nut.Debug := (t.readInteger (mainSection,'debug',0) > 0);
  102. commandAfterDown := t.readString (mainSection,'commandAfterDown','');
  103. nutReconnectFreq := t.readInteger (mainSection,'reconnectFreq',30);
  104. powerOffFileName := t.readString (mainSection,'createPoweroffFile','');
  105. downIfCapaityBelow := t.readInteger (mainSection,'downIfCapacityBelow',0);
  106. finally
  107. t.free;
  108. end;
  109. end;
  110. {$if defined(netware)}
  111. procedure onNetwareUnload;
  112. var i : integer;
  113. begin
  114. terminated := true;
  115. SignalLocalSemaphore (waitSemaphore); // this ends doDelay
  116. // here we wait for the main thread to terminate
  117. // we have to wait because system.pp will deinit winsock
  118. // to allow unload in case a blocking winsock call is
  119. // active. In case we wont wait here, our tcp socket
  120. // will be destroyed before we have the chance to send
  121. // a logout command to upsd
  122. i := 500;
  123. System.NetwareUnloadProc := oldNetwareUnloadProc;
  124. while (i > 0) and (not MainLoopTerminated) do
  125. begin
  126. dec(i);
  127. delay(500);
  128. end;
  129. end;
  130. {$endif}
  131. procedure doDelay (seconds : integer);
  132. {$if defined(netware)}
  133. begin
  134. TimedWaitOnLocalSemaphore (waitSemaphore,seconds*1000);
  135. end;
  136. {$else}
  137. var i : integer;
  138. begin
  139. i := seconds * 2;
  140. while (not terminated) and (i > 0) do
  141. begin
  142. sysutils.sleep(500);
  143. dec(i);
  144. end;
  145. end;
  146. {$endif}
  147. var lastAlert : TUpsStatus = [UPS_Online];
  148. procedure doAlert (status : TUpsStatus);
  149. {$if defined(netware)}
  150. var nwAlert : TNetWareAlertStructure;
  151. s : AnsiString;
  152. begin
  153. FillChar(nwAlert, sizeof(nwAlert),0);
  154. nwAlert.nwAlertID := ALERT_UPS;
  155. nwAlert.nwTargetNotificationBits := NOTIFY_ERROR_LOG_BIT+NOTIFY_CONSOLE_BIT;
  156. nwAlert.nwAlertLocus := LOCUS_UPS;
  157. nwAlert.nwAlertClass := CLASS_GENERAL_INFORMATION;
  158. nwAlert.nwAlertSeverity := SEVERITY_CRITICAL;
  159. if UPS_lowBatt in Status then
  160. s := 'UPS low Battery, shutting down' else
  161. if UPS_FSD in Status then
  162. s := 'UPS Forced Shuttdown' else
  163. if UPS_online in Status then
  164. s := 'Power/communication Restored, UPS is online' else
  165. if UPS_onBatt in Status then
  166. s := 'Power Failure, UPS is on battery' else
  167. if UPS_Stale in Status then
  168. s := 'Lost communication to UPS' else
  169. if UPS_Disconnected in Status then
  170. s := 'Lost communication to upsd';
  171. if lastAlert <> status then
  172. if (UPS_onBatt in Status) or
  173. (UPS_lowBatt in Status) or
  174. (UPS_FSD in Status) or
  175. (UPS_Online in Status) then
  176. nwAlert.nwTargetNotificationBits := nwAlert.nwTargetNotificationBits + NOTIFY_EVERYONE_BIT;
  177. lastAlert := status;
  178. nwAlert.nwControlString := pchar(s);
  179. NetWareAlert(GetNlmHandle, @nwAlert, 0, []);
  180. end;
  181. {$else}
  182. begin
  183. end;
  184. {$endif}
  185. procedure doStatusChange (newStatus,oldStatus : TUpsStatus);
  186. begin
  187. writeln (#13'nutmon: ups status change from '+UpsStatus2Txt (oldStatus)+' to '+UpsStatus2Txt (newStatus));
  188. doAlert (newStatus);
  189. end;
  190. procedure doShutdown (Reason : AnsiString = 'Server shutting down because of power failure');
  191. var err:integer;
  192. begin
  193. if poweroffFileName <> '' then
  194. begin
  195. err := FileCreate (powerOffFileName);
  196. if err <> -1 then
  197. FileClose (err)
  198. else
  199. writeln (#13,'nutmon: warning, can not create power off flag file ('+powerOffFileName+')');
  200. end;
  201. {$if defined(netware_clib)}
  202. SendConsoleBroadcast(pchar(Reason),0,nil);
  203. err := DownFileServer (1);
  204. try
  205. nut.login := false; // notify upds that we are shutting down
  206. writeln (#13'numon: informed upsd that we have done shutdown');
  207. except
  208. on e:Exception do
  209. begin
  210. writeln (#13'nutmon: got exception while trying to logout (',e.Message,')');
  211. try
  212. nut.connected := false;
  213. except
  214. end;
  215. end;
  216. end;
  217. if err = 0 then
  218. writeln (#13'nutmon: Server is down')
  219. else
  220. writeln (#13'nutmon: DownFileServer returned error ',Err);
  221. if commandAfterDown <> '' then
  222. nwserv._system (pchar(commandAfterDown));
  223. repeat
  224. sysutils.sleep(30);
  225. until false;
  226. {$elseif defined(netware_libc)}
  227. ShutdownServer(nil,false,nil,0);
  228. repeat
  229. sysutils.sleep(30);
  230. until false;
  231. {$else}
  232. writeln (stderr,'no shutdown call available, terminating');
  233. halt;
  234. {$endif}
  235. end;
  236. procedure mainLoop;
  237. var s : string;
  238. begin
  239. while not terminated do
  240. begin
  241. if not nut.connected then
  242. begin
  243. try
  244. nut.connected := true;
  245. try
  246. nut.upsName := nutUpsName;
  247. except
  248. if nut.LastResult <> NutDataStale then
  249. begin
  250. writeln(stderr,#13'invalid ups name, terminating');
  251. nut.free;
  252. halt;
  253. end else
  254. begin // special case: on start UPS is in stale status, disconnect and try later
  255. upsStatus := [UPS_Stale];
  256. if (upsStatus <> lastUpsStatus) then doStatusChange (upsStatus, lastUpsStatus);
  257. lastUpsStatus := upsStatus;
  258. nut.connected := false;
  259. end;
  260. end;
  261. try
  262. nut.UpsStatus;
  263. except
  264. on e:exception do
  265. begin
  266. writeln(stderr,#13'unable get ups status ('+e.Message+'), terminating');
  267. nut.free;
  268. halt;
  269. end;
  270. end;
  271. try
  272. nut.Username := nutUser;
  273. nut.Password := nutPassword;
  274. nut.Login := true;
  275. except
  276. on e:exception do
  277. begin
  278. writeln(stderr,#13'unable to login ('+e.Message+'), terminating');
  279. nut.free;
  280. halt;
  281. end;
  282. end;
  283. lastUpsStatus := [UPS_disconnected];
  284. WriteLn(#13'nutmon: connected to '+nutUpsName+'@'+nut.Host);
  285. except
  286. on e:exception do
  287. begin
  288. writeln (stderr,#13'nutmon: connect error, will retry in ',nutReconnectFreq,' seconds ('+e.message+')');
  289. doDelay (nutReconnectFreq);
  290. end;
  291. end;
  292. end else
  293. begin // we are connected, poll status
  294. try
  295. upsStatus := nut.upsStatus;
  296. if (upsStatus <> lastUpsStatus) then doStatusChange (upsStatus, lastUpsStatus);
  297. lastUpsStatus := upsStatus;
  298. if (UPS_lowBatt in upsStatus) or
  299. (UPS_FSD in upsStatus) then doShutdown;
  300. if downIfCapaityBelow > 0 then
  301. if (UPS_onBatt in upsStatus) then
  302. if nut.UpsChargeInt < downIfCapaityBelow then
  303. //writeln ('battery below ',downIfCapaityBelow);
  304. doShutdown ('Server shutting down,power failure and battery < '+IntToStr(downIfCapaityBelow)+'%');
  305. if UPS_online in upsStatus then
  306. doDelay (nutPollfreq)
  307. else
  308. doDelay (nutPollfreqAlert);
  309. except
  310. end;
  311. end;
  312. {$if defined(netware)}
  313. if CurrentCommand <> CMD_NONE then
  314. begin
  315. case CurrentCommand of
  316. CMD_STATUS: begin
  317. if nut.connected then
  318. begin
  319. writeln (#13'UPS Status:');
  320. writeln (' connected to: ',nut.UpsName+'@',nut.host,':',nut.Port);
  321. writeln (' UPS is: ',UpsStatus2Txt(nut.UpsStatus));
  322. try
  323. s := nut.upsMfr;
  324. writeln (' manufacturer: ',s);
  325. except
  326. end;
  327. try
  328. s := nut.upsModel;
  329. writeln (' model: ',s);
  330. except
  331. end;
  332. try
  333. s := nut.UpsLoad;
  334. writeln (' Percent load: ',s);
  335. except
  336. end;
  337. try
  338. s := nut.upsTemperature;
  339. writeln (' temp: ',s);
  340. except
  341. end;
  342. try
  343. s := nut.upsInputVoltage;
  344. writeln (' input Voltage: ',s);
  345. except
  346. end;
  347. try
  348. s := nut.upsOutputVoltage;
  349. writeln (' output Voltage: ',s);
  350. except
  351. end;
  352. try
  353. s := nut.upsInputFrequency;
  354. writeln ('input Frequency: ',s);
  355. except
  356. end;
  357. try
  358. s := nut.upsRuntime;
  359. writeln ('Battery Runtime: ',s);
  360. except
  361. end;
  362. try
  363. s := nut.upsCharge;
  364. writeln (' Battery Charge: ',s);
  365. except
  366. end;
  367. try
  368. s := nut.numLogins;
  369. writeln (' num Logins: ',s);
  370. except
  371. end;
  372. Writeln (nut.Version);
  373. end else
  374. writeln (#13'UPS Status: not connected to upsd');
  375. end;
  376. CMD_TESTSHUTDOWN:
  377. begin
  378. upsStatus := [UPS_FSD];
  379. doStatusChange (upsStatus, lastUpsStatus);
  380. doShutdown;
  381. end;
  382. end;
  383. CurrentCommand := CMD_NONE;
  384. end;
  385. {$endif}
  386. end;
  387. end;
  388. {$if defined(netware)}
  389. // handle the command "UPS STATUS"
  390. // only set the requested command and let the main thread handle it
  391. function UpsCommandlineParser (ScreenId : scr_t; commandLine : pchar) : longint; cdecl;
  392. begin
  393. if strlicomp(commandLine,'ups status',10) = 0 then
  394. begin
  395. result := HANDLEDCOMMAND;
  396. CurrentCommand := CMD_STATUS;
  397. SignalLocalSemaphore (waitSemaphore);
  398. end else
  399. if strlicomp(commandLine,'ups testshutdown',16) = 0 then
  400. begin
  401. result := HANDLEDCOMMAND;
  402. CurrentCommand := CMD_TESTSHUTDOWN;
  403. SignalLocalSemaphore (waitSemaphore);
  404. end else
  405. result := NOTMYCOMMAND;
  406. end;
  407. {$endif}
  408. begin
  409. try
  410. {$if defined(netware)}
  411. waitSemaphore := OpenLocalSemaphore (0);
  412. CmdParserStruct.Link := nil;
  413. CmdParserStruct.parseRoutine := @UpsCommandLineParser;
  414. CmdParserStruct.RTag := AllocateResourceTag (GetNlmHandle,'nutmon command line parser',ConsoleCommandSignature);
  415. if RegisterConsoleCommand(CmdParserStruct) <> 0 then
  416. writeln (stderr,#13'nutmon: RegisterConsoleCommand failed (ups status console command will not work)')
  417. else begin
  418. writeln (#13'nutmon console commands available:');
  419. writeln (#13'ups status - show ups status');
  420. writeln (#13'ups testshutdown - shutdown as if a low power condition is reached');
  421. writeln;
  422. end;
  423. CurrentCommand := CMD_NONE;
  424. oldNetwareUnloadProc := System.NetwareUnloadProc;
  425. System.NetwareUnloadProc := @onNetwareUnload;
  426. {$endif}
  427. nut := TNutConnection.create;
  428. readConfig;
  429. if poweroffFileName <> '' then
  430. if FileExists (powerOffFileName) then
  431. if not DeleteFile (powerOffFileName) then
  432. writeln (#13,'nutmon: warning, can not delete power off flag file ('+powerOffFileName+')');
  433. if downIfCapaityBelow > 0 then
  434. writeln (#13'nutmon: will shutdown if battery < ',downIfCapaityBelow,'%');
  435. mainLoop;
  436. nut.login := false;
  437. nut.connected := false;
  438. nut.free;
  439. finally
  440. {$if defined(netware)}
  441. CloseLocalSemaphore (waitSemaphore);
  442. UnRegisterConsoleCommand (CmdParserStruct);
  443. MainLoopTerminated := true;
  444. {$endif}
  445. end;
  446. end.