mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-11-20 23:19:42 +01:00
483 lines
15 KiB
ObjectPascal
483 lines
15 KiB
ObjectPascal
Program nutmon;
|
|
{
|
|
|
|
Simple nut ups monitor for netware, see http://www.networkupstools.org
|
|
|
|
This program can be used to shut down a netware server on power
|
|
failure. It requires nut to be installed on a *nix server (the serial
|
|
or usb ups control is not connected to the netware server, this will
|
|
be handled by the upsd on a *nix server)
|
|
|
|
FreePascal >= 1.9.5 (http://www.freepascal.org) is needed to compile this.
|
|
|
|
This source is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This code is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
A copy of the GNU General Public License is available on the World
|
|
Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also
|
|
obtain it by writing to the Free Software Foundation,
|
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
First Version 2004/12/16 Armin Diehl <armin@freepascal.org>
|
|
|
|
**********************************************************************}
|
|
{$mode objfpc}
|
|
{$M 65535,0,0}
|
|
{$if defined(netware)}
|
|
{$if defined(netware_clib)}
|
|
{$description nut ups monitor - clib}
|
|
{$else}
|
|
{$description nut ups monitor - libc}
|
|
{$endif}
|
|
{$copyright Copyright 2004 Armin Diehl <armin@freepascal.org>}
|
|
{$screenname DEFAULT} // dont use none because writeln will not work with none
|
|
{$version 1.0.0}
|
|
{$endif netware}
|
|
|
|
uses
|
|
sysutils, nutconnection, inifiles
|
|
{$if defined(netware_libc)}
|
|
,libc
|
|
{$elseif defined(netware_clib)}
|
|
,nwserv,nwnit
|
|
{$endif}
|
|
;
|
|
|
|
|
|
const
|
|
CMD_NONE = 0;
|
|
CMD_STATUS = 1;
|
|
CMD_TESTSHUTDOWN = 2;
|
|
|
|
var
|
|
nut : TNutConnection;
|
|
nutUser : string;
|
|
nutPassword : string;
|
|
nutPollfreq : integer;
|
|
nutPollfreqAlert : integer;
|
|
nutReconnectFreq : integer;
|
|
nutUpsName : string;
|
|
terminated : boolean = false;
|
|
upsStatus,lastupsStatus : TUpsStatus;
|
|
waitSemaphore: longint;
|
|
commandAfterDown,powerOffFileName : ansistring;
|
|
downIfCapaityBelow:integer = 0;
|
|
{$if defined(netware)}
|
|
CmdParserStruct : TcommandParserStructure;
|
|
CurrentCommand : byte;
|
|
oldNetwareUnloadProc : pointer;
|
|
MainLoopTerminated : boolean = false;
|
|
{$endif}
|
|
|
|
const mainSection = 'nutmon';
|
|
|
|
procedure readConfig;
|
|
var fn : string;
|
|
t : tiniFile;
|
|
begin
|
|
fn := ChangeFileExt(paramstr(0),'.ini');
|
|
t := TIniFile.Create (fn);
|
|
try
|
|
nut.host := t.readString (mainSection,'host','');
|
|
if nut.host = '' then
|
|
begin
|
|
writeln (stderr,paramstr(0)+': host= not specified in '+fn+' exiting');
|
|
halt;
|
|
end;
|
|
nut.port := word (t.readInteger (mainSection,'port',NutDefaultPort));
|
|
nutUser := t.readString (mainSection,'user','');
|
|
if nutUser = '' then
|
|
begin
|
|
writeln (stderr,paramstr(0)+': user= not specified in '+fn+' exiting');
|
|
halt;
|
|
end;
|
|
nutPassword := t.readString (mainSection,'password','');
|
|
if nutPassword = '' then
|
|
begin
|
|
writeln (stderr,paramstr(0)+': password= not specified in '+fn+' exiting');
|
|
halt;
|
|
end;
|
|
nutUpsName := t.readString (mainSection,'upsname','');
|
|
if nutUpsname = '' then
|
|
begin
|
|
writeln (stderr,paramstr(0)+': upsname= not specified in '+fn+' exiting');
|
|
halt;
|
|
end;
|
|
nutPollfreq := t.readInteger (mainSection,'pollfreq',10);
|
|
nutPollfreqAlert := t.readInteger (mainSection,'pollfrqalert',5);
|
|
nut.Debug := (t.readInteger (mainSection,'debug',0) > 0);
|
|
commandAfterDown := t.readString (mainSection,'commandAfterDown','');
|
|
nutReconnectFreq := t.readInteger (mainSection,'reconnectFreq',30);
|
|
powerOffFileName := t.readString (mainSection,'createPoweroffFile','');
|
|
downIfCapaityBelow := t.readInteger (mainSection,'downIfCapacityBelow',0);
|
|
finally
|
|
t.free;
|
|
end;
|
|
end;
|
|
|
|
{$if defined(netware)}
|
|
procedure onNetwareUnload;
|
|
var i : integer;
|
|
begin
|
|
terminated := true;
|
|
SignalLocalSemaphore (waitSemaphore); // this ends doDelay
|
|
// here we wait for the main thread to terminate
|
|
// we have to wait because system.pp will deinit winsock
|
|
// to allow unload in case a blocking winsock call is
|
|
// active. In case we wont wait here, our tcp socket
|
|
// will be destroyed before we have the chance to send
|
|
// a logout command to upsd
|
|
i := 500;
|
|
System.NetwareUnloadProc := oldNetwareUnloadProc;
|
|
while (i > 0) and (not MainLoopTerminated) do
|
|
begin
|
|
dec(i);
|
|
delay(500);
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
|
|
procedure doDelay (seconds : integer);
|
|
{$if defined(netware)}
|
|
begin
|
|
TimedWaitOnLocalSemaphore (waitSemaphore,seconds*1000);
|
|
end;
|
|
{$else}
|
|
var i : integer;
|
|
begin
|
|
i := seconds * 2;
|
|
while (not terminated) and (i > 0) do
|
|
begin
|
|
sysutils.sleep(500);
|
|
dec(i);
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
var lastAlert : TUpsStatus = [UPS_Online];
|
|
|
|
procedure doAlert (status : TUpsStatus);
|
|
{$if defined(netware)}
|
|
var nwAlert : TNetWareAlertStructure;
|
|
s : AnsiString;
|
|
begin
|
|
FillChar(nwAlert, sizeof(nwAlert),0);
|
|
nwAlert.nwAlertID := ALERT_UPS;
|
|
nwAlert.nwTargetNotificationBits := NOTIFY_ERROR_LOG_BIT+NOTIFY_CONSOLE_BIT;
|
|
nwAlert.nwAlertLocus := LOCUS_UPS;
|
|
nwAlert.nwAlertClass := CLASS_GENERAL_INFORMATION;
|
|
nwAlert.nwAlertSeverity := SEVERITY_CRITICAL;
|
|
if UPS_lowBatt in Status then
|
|
s := 'UPS low Battery, shutting down' else
|
|
if UPS_FSD in Status then
|
|
s := 'UPS Forced Shuttdown' else
|
|
if UPS_online in Status then
|
|
s := 'Power/communication Restored, UPS is online' else
|
|
if UPS_onBatt in Status then
|
|
s := 'Power Failure, UPS is on battery' else
|
|
if UPS_Stale in Status then
|
|
s := 'Lost communication to UPS' else
|
|
if UPS_Disconnected in Status then
|
|
s := 'Lost communication to upsd';
|
|
if lastAlert <> status then
|
|
if (UPS_onBatt in Status) or
|
|
(UPS_lowBatt in Status) or
|
|
(UPS_FSD in Status) or
|
|
(UPS_Online in Status) then
|
|
nwAlert.nwTargetNotificationBits := nwAlert.nwTargetNotificationBits + NOTIFY_EVERYONE_BIT;
|
|
lastAlert := status;
|
|
|
|
nwAlert.nwControlString := pchar(s);
|
|
|
|
NetWareAlert(GetNlmHandle, @nwAlert, 0, []);
|
|
end;
|
|
{$else}
|
|
begin
|
|
end;
|
|
{$endif}
|
|
|
|
|
|
procedure doStatusChange (newStatus,oldStatus : TUpsStatus);
|
|
begin
|
|
writeln (#13'nutmon: ups status change from '+UpsStatus2Txt (oldStatus)+' to '+UpsStatus2Txt (newStatus));
|
|
doAlert (newStatus);
|
|
end;
|
|
|
|
|
|
procedure doShutdown (Reason : AnsiString = 'Server shutting down because of power failure');
|
|
var err:integer;
|
|
begin
|
|
if poweroffFileName <> '' then
|
|
begin
|
|
err := FileCreate (powerOffFileName);
|
|
if err <> -1 then
|
|
FileClose (err)
|
|
else
|
|
writeln (#13,'nutmon: warning, can not create power off flag file ('+powerOffFileName+')');
|
|
end;
|
|
{$if defined(netware_clib)}
|
|
SendConsoleBroadcast(pchar(Reason),0,nil);
|
|
err := DownFileServer (1);
|
|
try
|
|
nut.login := false; // notify upds that we are shutting down
|
|
writeln (#13'numon: informed upsd that we have done shutdown');
|
|
except
|
|
on e:Exception do
|
|
begin
|
|
writeln (#13'nutmon: got exception while trying to logout (',e.Message,')');
|
|
try
|
|
nut.connected := false;
|
|
except
|
|
end;
|
|
end;
|
|
end;
|
|
if err = 0 then
|
|
writeln (#13'nutmon: Server is down')
|
|
else
|
|
writeln (#13'nutmon: DownFileServer returned error ',Err);
|
|
if commandAfterDown <> '' then
|
|
nwserv._system (pchar(commandAfterDown));
|
|
repeat
|
|
sysutils.sleep(30);
|
|
until false;
|
|
{$elseif defined(netware_libc)}
|
|
ShutdownServer(nil,false,nil,0);
|
|
repeat
|
|
sysutils.sleep(30);
|
|
until false;
|
|
{$else}
|
|
writeln (stderr,'no shutdown call available, terminating');
|
|
halt;
|
|
{$endif}
|
|
|
|
end;
|
|
|
|
procedure mainLoop;
|
|
var s : string;
|
|
begin
|
|
while not terminated do
|
|
begin
|
|
if not nut.connected then
|
|
begin
|
|
try
|
|
nut.connected := true;
|
|
try
|
|
nut.upsName := nutUpsName;
|
|
except
|
|
if nut.LastResult <> NutDataStale then
|
|
begin
|
|
writeln(stderr,#13'invalid ups name, terminating');
|
|
nut.free;
|
|
halt;
|
|
end else
|
|
begin // special case: on start UPS is in stale status, disconnect and try later
|
|
upsStatus := [UPS_Stale];
|
|
if (upsStatus <> lastUpsStatus) then doStatusChange (upsStatus, lastUpsStatus);
|
|
lastUpsStatus := upsStatus;
|
|
nut.connected := false;
|
|
end;
|
|
end;
|
|
|
|
try
|
|
nut.UpsStatus;
|
|
except
|
|
on e:exception do
|
|
begin
|
|
writeln(stderr,#13'unable get ups status ('+e.Message+'), terminating');
|
|
nut.free;
|
|
halt;
|
|
end;
|
|
end;
|
|
|
|
try
|
|
nut.Username := nutUser;
|
|
nut.Password := nutPassword;
|
|
nut.Login := true;
|
|
except
|
|
on e:exception do
|
|
begin
|
|
writeln(stderr,#13'unable to login ('+e.Message+'), terminating');
|
|
nut.free;
|
|
halt;
|
|
end;
|
|
end;
|
|
lastUpsStatus := [UPS_disconnected];
|
|
WriteLn(#13'nutmon: connected to '+nutUpsName+'@'+nut.Host);
|
|
except
|
|
on e:exception do
|
|
begin
|
|
writeln (stderr,#13'nutmon: connect error, will retry in ',nutReconnectFreq,' seconds ('+e.message+')');
|
|
doDelay (nutReconnectFreq);
|
|
end;
|
|
end;
|
|
end else
|
|
begin // we are connected, poll status
|
|
try
|
|
upsStatus := nut.upsStatus;
|
|
if (upsStatus <> lastUpsStatus) then doStatusChange (upsStatus, lastUpsStatus);
|
|
lastUpsStatus := upsStatus;
|
|
if (UPS_lowBatt in upsStatus) or
|
|
(UPS_FSD in upsStatus) then doShutdown;
|
|
if downIfCapaityBelow > 0 then
|
|
if (UPS_onBatt in upsStatus) then
|
|
if nut.UpsChargeInt < downIfCapaityBelow then
|
|
//writeln ('battery below ',downIfCapaityBelow);
|
|
doShutdown ('Server shutting down,power failure and battery < '+IntToStr(downIfCapaityBelow)+'%');
|
|
if UPS_online in upsStatus then
|
|
doDelay (nutPollfreq)
|
|
else
|
|
doDelay (nutPollfreqAlert);
|
|
except
|
|
end;
|
|
end;
|
|
{$if defined(netware)}
|
|
if CurrentCommand <> CMD_NONE then
|
|
begin
|
|
case CurrentCommand of
|
|
CMD_STATUS: begin
|
|
if nut.connected then
|
|
begin
|
|
writeln (#13'UPS Status:');
|
|
writeln (' connected to: ',nut.UpsName+'@',nut.host,':',nut.Port);
|
|
writeln (' UPS is: ',UpsStatus2Txt(nut.UpsStatus));
|
|
try
|
|
s := nut.upsMfr;
|
|
writeln (' manufacturer: ',s);
|
|
except
|
|
end;
|
|
try
|
|
s := nut.upsModel;
|
|
writeln (' model: ',s);
|
|
except
|
|
end;
|
|
try
|
|
s := nut.UpsLoad;
|
|
writeln (' Percent load: ',s);
|
|
except
|
|
end;
|
|
try
|
|
s := nut.upsTemperature;
|
|
writeln (' temp: ',s);
|
|
except
|
|
end;
|
|
try
|
|
s := nut.upsInputVoltage;
|
|
writeln (' input Voltage: ',s);
|
|
except
|
|
end;
|
|
try
|
|
s := nut.upsOutputVoltage;
|
|
writeln (' output Voltage: ',s);
|
|
except
|
|
end;
|
|
try
|
|
s := nut.upsInputFrequency;
|
|
writeln ('input Frequency: ',s);
|
|
except
|
|
end;
|
|
try
|
|
s := nut.upsRuntime;
|
|
writeln ('Battery Runtime: ',s);
|
|
except
|
|
end;
|
|
try
|
|
s := nut.upsCharge;
|
|
writeln (' Battery Charge: ',s);
|
|
except
|
|
end;
|
|
try
|
|
s := nut.numLogins;
|
|
writeln (' num Logins: ',s);
|
|
except
|
|
end;
|
|
Writeln (nut.Version);
|
|
end else
|
|
writeln (#13'UPS Status: not connected to upsd');
|
|
end;
|
|
CMD_TESTSHUTDOWN:
|
|
begin
|
|
upsStatus := [UPS_FSD];
|
|
doStatusChange (upsStatus, lastUpsStatus);
|
|
doShutdown;
|
|
end;
|
|
end;
|
|
CurrentCommand := CMD_NONE;
|
|
end;
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
{$if defined(netware)}
|
|
// handle the command "UPS STATUS"
|
|
// only set the requested command and let the main thread handle it
|
|
function UpsCommandlineParser (ScreenId : scr_t; commandLine : pchar) : longint; cdecl;
|
|
begin
|
|
if strlicomp(commandLine,'ups status',10) = 0 then
|
|
begin
|
|
result := HANDLEDCOMMAND;
|
|
CurrentCommand := CMD_STATUS;
|
|
SignalLocalSemaphore (waitSemaphore);
|
|
end else
|
|
if strlicomp(commandLine,'ups testshutdown',16) = 0 then
|
|
begin
|
|
result := HANDLEDCOMMAND;
|
|
CurrentCommand := CMD_TESTSHUTDOWN;
|
|
SignalLocalSemaphore (waitSemaphore);
|
|
end else
|
|
result := NOTMYCOMMAND;
|
|
end;
|
|
{$endif}
|
|
|
|
|
|
|
|
begin
|
|
try
|
|
{$if defined(netware)}
|
|
waitSemaphore := OpenLocalSemaphore (0);
|
|
CmdParserStruct.Link := nil;
|
|
CmdParserStruct.parseRoutine := @UpsCommandLineParser;
|
|
CmdParserStruct.RTag := AllocateResourceTag (GetNlmHandle,'nutmon command line parser',ConsoleCommandSignature);
|
|
|
|
if RegisterConsoleCommand(CmdParserStruct) <> 0 then
|
|
writeln (stderr,#13'nutmon: RegisterConsoleCommand failed (ups status console command will not work)')
|
|
else begin
|
|
writeln (#13'nutmon console commands available:');
|
|
writeln (#13'ups status - show ups status');
|
|
writeln (#13'ups testshutdown - shutdown as if a low power condition is reached');
|
|
writeln;
|
|
end;
|
|
CurrentCommand := CMD_NONE;
|
|
oldNetwareUnloadProc := System.NetwareUnloadProc;
|
|
System.NetwareUnloadProc := @onNetwareUnload;
|
|
{$endif}
|
|
nut := TNutConnection.create;
|
|
readConfig;
|
|
if poweroffFileName <> '' then
|
|
if FileExists (powerOffFileName) then
|
|
if not DeleteFile (powerOffFileName) then
|
|
writeln (#13,'nutmon: warning, can not delete power off flag file ('+powerOffFileName+')');
|
|
if downIfCapaityBelow > 0 then
|
|
writeln (#13'nutmon: will shutdown if battery < ',downIfCapaityBelow,'%');
|
|
mainLoop;
|
|
nut.login := false;
|
|
nut.connected := false;
|
|
nut.free;
|
|
finally
|
|
{$if defined(netware)}
|
|
CloseLocalSemaphore (waitSemaphore);
|
|
UnRegisterConsoleCommand (CmdParserStruct);
|
|
MainLoopTerminated := true;
|
|
{$endif}
|
|
end;
|
|
end.
|