nutmon.pp 15 KB

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