UdtService
This program is designed to run as a phantom service on a UD server. It has run successfully on D3 and currently runs on UD. This routine needs one file to run, (DTASERVICES). I'm not a very good programmer so if anyone has suggestions to improve this code, please let me know by posting onto the U2 mail list.
The general idea of this process is to start a service as a phantom within U2. Then, every minute, wake up, read the services file to see if a service needs to be run, if it does phantom that, then go back to sleep. This following file contains the defined service and looks like:
Dictionary of File: DTASERVICES 10:15:38 Jun 27 2007 Dict Name...... Typ # Col-Heading. Field-Definition.............. Conversion............... Formt Assoc... @UQ Phr USER CDATE ACCOUNT COMMANDS OKTORUN OPTIONS @ID D 0 SERVICES 12L S USER D 1 USER 10L S DATETIME D 2 CREATED 12L S ACCOUNT D 3 ACCOUNT 12L S PASSWORD D 4 PASSWORD 12L S COMMANDS D 5 COMMAND(S) 17L S CRON D 6 CRON 5L M DESC D 7 DESC 30L S OKTORUN D 8 OK 1R S OPTIONS D 9 OPTIONS 10L S JOBSID D 10 JOBSID 12L S STATUS D 11 STATUS 30L S CAPTURE D 12 CAPTURE 30L M CDATE I CDATE EXTRACT(@RECORD, 2, 0, 0)[1,5] D2 9R S CTIME I CTIME EXTRACT(@RECORD, 2, 0, 0)[6,5] MTH 7R S
A record in this file would look like:
Top of "[[CABFTP_HAM]]" in "DTASERVICES", 12 lines, 268 characters. *--: P 001: wph 002: 1432900782 003: E:\UDAccounts\Ham 004: 005: RUN DTABP TRINFO.CAB FTP 006: 00 22 * * 5 007: The Local Community Bank Ftp process 008: 1 009: 010: command 011: Process last run at 22:00:00 Jun 22 2007 012: The command completed successfully. PHANTOM Process 3184 started. COMO file is'_PH_\dtaservices79200_3184'. Bottom. *--:
The service program has several includes in it. These includes are used throughout our application to setup common variables, open a couple of files, and initialize variables. An example of one of the variables initialized is PATHSEP$. This variable is a "/" in .nix and a "\" in Windows. Other general variables are NULL$ (empty string), HLDMSG (a common message variable), WKAMT (a common scratch variable), and a few others. Two subroutine calls are to "LOGMSG" (a centralized logging subroutine) and "SERVICE.ISRUNNING", which is a subroutine that looks like:
SUBROUTINE SERVICE.ISRUNNING ([[ServiceName]], [[IsRunning]]) ** [[DataTrust]] phantom service manager ** (C) Copyright 1985-2007, Advantos Systems, Inc. All Rights Reserved. ! ** Last Modified: 08 Feb 2007, wph ** First Created: 28 Sep 2004, wph ** Program Type-: Utility ! ** Notes: ** ** This subroutine tests if a particular service is running in the ** mvDbms environment. ** ** Input-: ** [[ServiceName]] - the name of the service to test for ** ** Output: ** [[IsRunning]] - is the service running? 0-No, 1-Yes ** ** it is read from the DTA,SYSTBL, as the default. ** **-------------------------------------------------------------------** ** ** ** I N I T I A L I Z A T I O N ** ** ** **-------------------------------------------------------------------** * ** Initialization INCLUDE DTABP,INCLUDES SET.COMMON * [[IsRunning]] = 0 ; ** initialize progress cnt * **------------------------------------------------------------------** ** ** ** S T A R T P R O G R A M R U N ** ** ** **------------------------------------------------------------------** * ** Test if service is running *[[TclCmd]] = \COUNT JOBS WITH TCL-COMMAND = "[\ ; ** D3 version *[[TclCmd]] := [[ServiceName]] : \]"\ ; ** D3 version *[[TclCmd]] := \ AND WITH A1 = "R"\ ; ** D3 version *EXECUTE [[TclCmd]] CAPTURING Output ; ** D3 version * ** Extract count from captured output *[[ItemCount]] = FIELD(TRIM(Output), SP1, 2) ; ** D3 version *IF NOT(NUM([[ItemCount]])) THEN [[ItemCount]] = 0 ; ** D3 version *IF [[ItemCount]] THEN [[IsRunning]] = 1 ; ** D3 version * IF [[ServiceName]] = 'DTA.SERVICE' THEN ; ** UD version [[PhPortID]] = OCONV('DTA.SERVICE', 'TDICT DTASERVICES;X;;2') ; ** UD version [[PhIDLoc]] = COUNT([[PhPortID]], PATHSEP$) + 1 ; ** UD version [[PhPortID]] = TRIM(FIELD([[PhPortID]], PATHSEP$, [[PhIDLoc]])) ; ** UD version [[PhPortID]] = FIELD([[PhPortID]], '_', 2) ; ** UD version END ELSE ; ** UD version [[PhPortID]] = OCONV([[ServiceName]], 'TDTASERVICES;X2;;10') ; ** UD version END ; ** UD version IF [[PhPortID]] NE NULL$ THEN ; ** UD version EXECUTE \PORT.STATUS PID \ : [[PhPortID]] CAPTURING OUTPUT ; ** UD version IF TRIM(OUTPUT<7>) NE NULL$ THEN [[IsRunning]] = 1 ; ** UD version END ; ** UD version * **------------------------------------------------------------------** ** ** ** E N D O F P R O G R A M ** ** ** **------------------------------------------------------------------** * *************** END.OF.PROGRAM: *************** * RETURN END
This service manager needs to be started when the dbms starts. In D3, this was very simple as "RUN DTABP DTA.SERVICE" only had to be added to the "dm,, user-coldstart" macro. In U2, however, it's much more complex and I end up having to start it manually every time I restart UD (fortunately, this isn't too often).
! ** [[DataTrust]] phantom service manager ** (C) Copyright 1985-2007, Advantos Systems, Inc. All Rights Reserved. ! ** Last Modified: 21 Jun 2007, wph ** First Created: 24 Sep 2004, wph ** Program Type-: Utility ! ** Notes: ** ** This process runs and phantoms any (SERVICES) entry specified. It ** wakes up every minute (on the minute) and checks if a job should ** be started. If so, it does. Then it goes back to sleep. ** ** The meaning and format of the Cron field in the (SERVICES) file ** is as follows: ** 1 - Minute 0-59 ** 2 - Hour 0-23 (0=midnight) ** 3 - Day 1-31 ** 4 - Month 1-12 ** 5 - Weekday 0-6 (0=Sunday) ** ** When this service starts it updates the DICT item in (DTASERVICES) ** named "DTA.SERVICE", with a "1" in field# 1, the _PH_ item in field# ** 2, and the time/date started in field# 3. It looks like: ** ** 'DTA.SERVICE' ** 001: 1 ** 002: _PH_\wphaskett51560_3884 ** 003: 16:16:00 Jan 08 2007 ** ** In order to terminate this program, simply change field# 1 to "0". ** ** This process should be started from within the USER-COLDSTART, or ** from the 'DM' account, in D3. ** **-------------------------------------------------------------------** ** ** ** I N I T I A L I Z A T I O N ** ** ** **-------------------------------------------------------------------** * CRT "...DTA.SERVICE is now starting at " : TIMEDATE() : "..." * ** Initialization INCLUDE DTABP,INCLUDES SET.COMMON INCLUDE DTABP,INCLUDES SET.FILES INCLUDE DTABP,INCLUDES SET.INIT * [[DisplayCnt]] = 0 ; ** initialize progress cnt * ** assign phantom command [[JobsName]] = '_PH_' ; ** UD version [[PhantomCmd]] = 'PHANTOM ' ; ** U2 version *[[PhantomCmd]] = 'Z' ; ** D3 version *[[JobsName]] = 'JOBS' ; ** D3 version * ** # of seconds to divide into the current time. The remainder is ** subtracted from the sleep time to determine exactly for how long ** the process must sleep for to wake up right on the proper time. ** Don't set for less than a 60 seconds, as this process CANNOT ** run at less than a 60 second interval. [[SleepValue]] = 60 * ** Open File(s) [[FileErr]] = NULL$ OPEN 'DICT', 'DTASERVICES' TO DTASERVICES.D ELSE [[FileErr]] := ' Dict DTASERVICES' OPEN '', 'DTASERVICES' TO DTASERVICES ELSE [[FileErr]] := ' DTASERVICES' OPEN '', [[JobsName]] TO JOBS.FV ELSE [[FileErr]] := SP1:[[JobsName]] IF [[FileErr]] NE NULL$ THEN HLDMSG = "No file(s):" : [[FileErr]] GOTO ABORT.PROGRAM END * **-------------------------------------------------------------------** ** ** ** R E A D C O N F I G U R A T I O N D A T A ** ** ** **-------------------------------------------------------------------** * ** synchronize mvDbms service status with [[DataTrust]] service flag [[PhantomID]] = SYSTEM(48) ; ** UD version [[IsRunning]] = NULL$ CALL SERVICE.ISRUNNING ('DTA.SERVICE', [[IsRunning]]) READU [[ServiceRec]] FROM DTASERVICES.D, 'DTA.SERVICE' THEN [[ServiceRun]] = TRIM([[ServiceRec]]<1>) [[PhantomID]] = TRIM([[ServiceRec]]<2>) [[PhIDLoc]] = COUNT([[PhantomID]], PATHSEP$) + 1 [[PhantomID]] = TRIM(FIELD([[PhantomID]], PATHSEP$, [[PhIDLoc]])) IF NOT([[IsRunning]]) THEN HLDMSG = "[[DataTrust]] service manager flag deleted because service not running." CALL LOGMSG ('', 'N[[/A]]', HLDMSG, '') DELETE DTASERVICES.D, 'DTA.SERVICE' IF [[PhantomID]] NE NULL$ THEN ; ** UD version DELETE JOBS.FV, [[PhantomID]] ; ** UD version END ; ** UD version END ELSE RELEASE DTASERVICES.D, 'DTA.SERVICE' END END ELSE [[ServiceRec]] = NULL$ IF [[IsRunning]] THEN HLDMSG = "[[DataTrust]] service manager flag created because service is running." CALL LOGMSG ('', 'N[[/A]]', HLDMSG, '') [[ServiceRec]]<1> = 1 [[ServiceRec]]<2> = SYSTEM(48) ; ** UD version END WRITE [[ServiceRec]] ON DTASERVICES.D, 'DTA.SERVICE' END * ** log process start [[SleepFor]] = [[SleepValue]] - MOD(TIME(), [[SleepValue]]) HLDMSG = "[[DataTrust]] background service started to " : [[PhantomID]] CALL LOGMSG ('', 'N[[/A]]', HLDMSG, '') * **-------------------------------------------------------------------** ** ** ** S T A R T P R O G R A M R U N ** ** ** **-------------------------------------------------------------------** * *************** START.PROGRAM: *************** * LOOP * ** test if we should shut down the service READU [[ServiceRec]] FROM DTASERVICES.D, 'DTA.SERVICE' THEN [[ServiceRun]] = TRIM([[ServiceRec]]<1>) [[PhantomID]] = TRIM([[ServiceRec]]<2>) [[PhIDLoc]] = COUNT([[PhantomID]], PATHSEP$) + 1 [[PhantomID]] = TRIM(FIELD([[PhantomID]], PATHSEP$, [[PhIDLoc]])) IF NOT([[ServiceRun]]) THEN HLDMSG = "[[DataTrust]] service manager terminated by stop flag." [[ServiceRec]] = NULL$ WRITE [[ServiceRec]] ON DTASERVICES.D, 'DTA.SERVICE' IF [[PhantomID]] NE NULL$ THEN ; ** UD version DELETE JOBS.FV, [[PhantomID]] ; ** UD version END ; ** UD version GOTO ABORT.PROGRAM END [[ServiceRec]]<3> = TIMEDATE() WRITE [[ServiceRec]] ON DTASERVICES.D, 'DTA.SERVICE' END ELSE [[ServiceRec]] = NULL$ [[ServiceRec]]<1> = 1 [[ServiceRec]]<2> = SYSTEM(48) ; ** UD version [[ServiceRec]]<3> = TIMEDATE() WRITE [[ServiceRec]] ON DTASERVICES.D, 'DTA.SERVICE' GOTO CONTINUE.SLEEP END * ** select services to process [[ServiceList]] = NULL$ EXECUTE \SSELECT DTASERVICES\ CAPTURING Output [[NoOfItems]] = SYSTEM(11) IF [[NoOfItems]] THEN SELECT DTASERVICES TO [[ServiceList]] END ELSE GOTO CONTINUE.SLEEP END * ** read services selected LOOP READNEXT [[ServiceId]] FROM [[ServiceList]] ELSE EXIT READ [[ServiceRec]] FROM DTASERVICES, [[ServiceId]] THEN [[LogToAccount]] = TRIM([[ServiceRec]]<3>) [[LogToPassword]] = TRIM([[ServiceRec]]<4>) [[ProgramToRun]] = [[ServiceRec]]<5> [[CronTab]] = [[ServiceRec]]<6> [[OkToStart]] = [[ServiceRec]]<8> + 0 [[PhantomOptions]] = [[ServiceRec]]<9> [[PhantomId]] = [[ServiceRec]]<10> * ** read if phantom currently running READ [[PhantomRec]] FROM JOBS.FV, [[PhantomId]] ELSE [[PhantomRec]] = NULL$ [[PhStatus]] = OCONV([[PhantomRec]]<1>[1,1], 'MCU') IF [[PhStatus]] = 'R' THEN HLDMSG = "Service " : [[ServiceId]] : " already running as job# " : [[PhantomId]] GOTO EXIT.CASE END * ** assign current time setTime = OCONV(TIME(), 'MTS') setDate = DATE() [[CurrMon]] = OCONV(setDate, 'DM') + 0 [[CurrDom]] = OCONV(setDate, 'DD') + 0 [[CurrDow]] = OCONV(setDate, 'DW') + 0 ; IF [[CurrDow]] = 7 THEN [[CurrDow]] = 0 [[CurrHour]] = FIELD(setTime, ':', 1) + 0 [[CurrMin]] = FIELD(setTime, ':', 2) + 0 [[CurrSec]] = FIELD(setTime, ':', 3) + 0 * ** test conditions to either process or abort. [[CronSkip]] = 0 ; ** not yet time to run? [[UpdSw]] = 0 ; ** update services record? HLDMSG = NULL$ ; ** initialize error message BEGIN CASE CASE NOT([[OkToStart]]) HLDMSG = "Service " : [[ServiceId]] : " is on Hold." CASE [[CronTab]] = NULL$ HLDMSG = "No CRON Table for Service " : [[ServiceId]] : "." CASE DCOUNT([[CronTab]], @VM) NE 5 HLDMSG = "Cron format requires five (5) values for Service " : [[ServiceId]] : "." * ** test service for starting CASE 1 Minutes = [[CronTab]]<1,1> Hours = [[CronTab]]<1,2> DOMs = [[CronTab]]<1,3> ; IF DOMs = 0 THEN DOMs = '*' Months = [[CronTab]]<1,4> ; IF Months = 0 THEN Months = '*' DOWs = [[CronTab]]<1,5> ; ** this ORs the DOMs * ** start building actual command(s) to execute [[LogToCmd]] = NULL$ ; ** initialize cron commands IF [[LogToAccount]] NE NULL$ THEN [[LogToCmd]] = \LOGTO \ : [[LogToAccount]] IF [[LogToPassword]] NE NULL$ THEN [[LogToCmd]] := ',' : [[LogToPassword]] END * ** add an extra @AM in [[LogTo]] command so [[D3Linux]] works. It doesn't ** matter for [[D3NT]]. * [[LogToCmd]] := @AM ; ** D3 version END * ** test if we're in a configured month IF Months NE '*' THEN [[CronVal]] = Months ; [[TestVal]] = [[CurrMon]] [[MinVal]] = 1 ; [[MaxVal]] = 12 GOSUB BUILD.CRON.VALUE IF [[CronSkip]] THEN GOTO EXIT.CASE END * ** test if we're in a configured day of month or day of week IF DOMs NE '*' THEN [[CronVal]] = DOMs ; [[TestVal]] = [[CurrDom]] [[MinVal]] = 1 ; [[MaxVal]] = 31 GOSUB BUILD.CRON.VALUE END IF DOWs NE '*' THEN [[CronVal]] = DOWs ; [[TestVal]] = [[CurrDow]] [[MinVal]] = 0 ; [[MaxVal]] = 6 GOSUB BUILD.CRON.VALUE END IF [[CronSkip]] THEN GOTO EXIT.CASE * ** test if we're in a configured hour of the day IF Hours NE '*' THEN [[CronVal]] = Hours ; [[TestVal]] = [[CurrHour]] [[MinVal]] = 0 ; [[MaxVal]] = 23 GOSUB BUILD.CRON.VALUE IF [[CronSkip]] THEN GOTO EXIT.CASE END * ** test if we're in a configured minute of the current hour IF Minutes NE '*' THEN [[CronVal]] = Minutes ; [[TestVal]] = [[CurrMin]] [[MinVal]] = 0 ; [[MaxVal]] = 59 GOSUB BUILD.CRON.VALUE IF [[CronSkip]] THEN GOTO EXIT.CASE END END CASE * *************** EXIT.CASE: *************** * ** update log if we have an error IF HLDMSG NE NULL$ THEN READVU Attr FROM DTASERVICES, [[ServiceId]], 11 ELSE Attr = NULL$ WRITEV HLDMSG ON DTASERVICES, [[ServiceId]], 11 GOTO CONTINUE.LOOP END IF [[CronSkip]] THEN GOTO CONTINUE.LOOP * ** Run the command(s) IF [[LogToCmd]] NE NULL$ THEN [[TclCmd]] = [[LogToCmd]] END ELSE [[TclCmd]] = NULL$ END [[TclCmd]]<-1> = [[PhantomCmd]] : [[PhantomOptions]] : SP1 : [[ProgramToRun]] EXECUTE [[TclCmd]] CAPTURING Output * ** prepare commands for output to log o[[TclCmd]] = CONVERT(@AM, '^', [[TclCmd]]) ; ** U2 version * o[[TclCmd]] = CONVERT([[TclCmd]], @AM, '^') ; ** D3 version oOutput = CONVERT(@AM, @VM, Output) ; ** U2 version * oOutput = CONVERT(Output, @AM, @VM) ; ** D3 version * ** calculate job# [[ColStart]] = INDEX(Output, 'Job #', 1) + 5 IF [[ColStart]] THEN [[JobNo]] = FIELD(Output[[[ColStart]],99], ' ', 1) END ELSE [[JobNo]] = NULL$ END HLDMSG = o[[TclCmd]] : @VM : "Phantomed as Job# " : [[JobNo]] * ** update service item READU [[ServiceRec]] FROM DTASERVICES, [[ServiceId]] ELSE [[ServiceRec]] = NULL$ [[ServiceRec]]<10> = [[JobNo]] [[ServiceRec]]<11> = "Process last run at " : TIMEDATE() [[ServiceRec]]<12> = oOutput WRITE [[ServiceRec]] ON DTASERVICES, [[ServiceId]] * ** log job run CALL LOGMSG ('', 'N[[/A]]', HLDMSG, '') END * *************** CONTINUE.LOOP: *************** * REPEAT * ** sleep until the next [[SleepValue]] (usually the next minute) *************** CONTINUE.SLEEP: *************** * [[DisplayCnt]] += 1 [[SleepFor]] = [[SleepValue]] - MOD(TIME(), [[SleepValue]]) SLEEP [[SleepFor]] REPEAT * GOTO END.OF.PROGRAM * **------------------------------------------------------------------** ** ** ** S U B R O U T I N E S ** ** ** **------------------------------------------------------------------** * *************** BUILD.CRON.VALUE: *************** * ** convert to comma-delimited data IF COUNT([[CronVal]], '-') THEN [[FrNo]] = FIELD([[CronVal]], '-', 1) [[ToNo]] = FIELD([[CronVal]], '-', 2) [[CronVal]] = NULL$ FOR X = [[FrNo]] TO [[ToNo]] [[CronVal]]<1,-1> = X NEXT X [[CronVal]] = CONVERT(@VM, ',', [[CronVal]]) ; ** U2 version * [[CronVal]] = CONVERT([[CronVal]], @VM, ',') ; ** D3 version END * ** initialize variables [[CronSkip]] = 1 ; ** default an error [[NewVal]] = [[CronVal]] ; ** save it to read values [[CronVal]] = NULL$ ; ** clear to rebuild * ** test for valid data (else abort program) xHigh = DCOUNT([[NewVal]], ',') FOR X = 1 TO xHigh wkVal = FIELD([[NewVal]], ',', X) IF NUM(wkVal) THEN IF wkVal > [[MaxVal]] THEN wkVal = [[MaxVal]] IF wkVal < [[MinVal]] THEN wkVal = [[MinVal]] END ELSE HLDMSG = "Non-numeric format for Cron value in " : [[NewVal]] : ". Service stopped." RETURN TO ABORT.PROGRAM END [[CronVal]]<1,-1> = wkVal NEXT X [[CronVal]] = CONVERT(@VM, ',', [[CronVal]]) ; ** U2 version *[[CronVal]] = CONVERT([[CronVal]], @VM, ',') ; ** D3 version * xHigh = DCOUNT([[CronVal]], ',') FOR X = 1 TO xHigh wkVal = FIELD([[CronVal]], ',', X) IF wkVal = [[TestVal]] THEN [[CronSkip]] = 0 NEXT X RETURN * **------------------------------------------------------------------** ** ** ** E N D O F P R O G R A M ** ** ** **------------------------------------------------------------------** * *************** ABORT.PROGRAM: *************** * CALL LOGMSG ('', 'N[[/A]]', HLDMSG, '') * *************** END.OF.PROGRAM: *************** * END