Copyright 1990-2016 by Kevin G. Barkes All rights reserved. This article may be duplicated or redistributed provided no alterations of any kind are made to this file. This edition of DCL Dialogue is sponsored by Networking Dynamics, developers and marketers of productivity software for OpenVMS systems. Contact our website www.networkingdynamics.com to download free demos of our software and see how you will save time, money and raise productivity! Be sure to mention DCL Dialogue! DCL DIALOGUE Originally published March, 1990 Bread Crumb Synchronization By Kevin G. Barkes One of DCL's glaring omissions is a mechanism for inter-process communication and/or synchronization. Programs written in high-level languages can use mailboxes, asynchronous system traps (ASTs) or other VMS facilities to coordinate activities between processes. DCL-based applications must resort to somewhat less elegant methods. To anthropomorphize a bit, high level languages permit the creation of programs that enable processes to talk with each other. DCL command procedures can leave bread crumbs that serve as rudimentary signals, provided other processes know to look for them. NOT MUCH HELP DCL does have one command, SYNCHRONIZE, that can be used to place a process in a wait state until a specified batch job completes execution. The command is a pain to use in everyday operations, however. All sorts of unpleasant contortions must be performed in order to obtain dynamically the necessary queue and entry information. The F$GETQUI lexical function available in VMS 5.n makes the chore a tad less onerous, but, all in all, SYNCHRONIZE is too restricted in scope and complex in invocation to be of real value. LOGICAL CHOICE Logical names provide a viable method of inter-process communication. One process can read entries made by another in the system logical name table or in a group name table, and alter its operation based on the values placed in the tables. Of course, the process making the logical name assignments must have elevated privileges (either GRPNAM or SYSNAM) in order to make the table entries. Entries made in the system logical name table can be read by any process on the system; group table assignments can be accessed only by those processes having the same group uic as the creating process. There are a few drawbacks to using logical names in this manner. A system crash will wipe out the assignments. If the process creating the logical name is killed or improperly exits for some reason, the logical will not be deleted, causing the processes which check for the assignments to "lock up". Malicious or meddling users with sufficient privileges can change the values of the logicals, which will also result in improper operation. USING SYNC FILES Another alternative is the use of "sync files" to coordinate procedure execution. While not totally immune to failure, this method is somewhat more reliable. Here's our scenario: we have two command procedures which should not be executed simultaneously because of potential file access conflicts or other unpleasant side effects. We arbitrarily give one of the procedures execution preference; let's call it SYNC_MASTER. The other procedure, which must not run when SYNC_MASTER is executing, is called SYNC_SLAVE. We create a special directory and assign it the system logical SYNCDIR. This is where our sync files will be placed. Ideally, the directory will have its protection set so that only the SYNC procedures can access them. Let's fire up SYNC_MASTER (see figure 1). After performing some initialization functions, SYNC_MASTER calls the subroutine DO_SYNCHRONIZE. The subroutine first checks to see if there are any spurious sync files left over from previous executions of the procedure. Note that the syncfile must have an explicit version number. If it finds an old syncfile, it attempts to delete it. If the delete fails, we assume it's because another process is executing the same procedure and has a syncfile open and locked, so we exit. There are other reasons why the delete could bomb out-- disk off line, improper file protection, etc.-- but we're assuming we've covered these eventualities. If all goes well, SYNC_MASTER opens its sync file and RETURNs to the portion of the procedure which does its real work. When completed, it closes the sync file and deletes it before executing. SYNC_SLAVE, the subordinate procedure, waits for a minute after executing its initialization code. This provides a "safety zone" to permit SYNC_MASTER to get up and running. The symbol TESTSYNC is used as a flag to determine whether SYNC_MASTER has a syncfile open. We set the flag and go on to test for the existence of the file with the CHECKSYNC subroutine. If SYNC_SLAVE can't find the syncfile, then SYNC_MASTER isn't running; it returns to the main body of the procedure, checks TESTSYNC and continues. Should SYNC_SLAVE discover that there is a sync file, it tries to delete it, since it could be merely the residue of an aborted SYNC_MASTER execution. If SYNC_SLAVE can't delete the file, then SYNC_MASTER is executing and has it locked. SYNC_SLAVE sets the value of TESTSYNC to fail and returns to the main procedure. When TESTSYNC bombs out, the procedure loops back to BEGIN and waits for a minute before attempting to continue operation. If SYNC_SLAVE can erase the file, it still sets TESTSYNC to failure before returning, causing the procedure to wait a minute before attempting to continue. The existence of a spurious sync file indicates something possibly went wrong with the SYNC_MASTER procedure, and the pause allows recovery operations to take place. For added safety, SYNC_SLAVE can call CHECKSYNC repeatedly during its operation to make certain SYNC_MASTER hasn't started up unexpectedly. SYNC_SLAVE could also create its own sync file that SYNC_MASTER would check. OOPS The veteran DCL hacker who showed me this method a few years ago was exceedingly proud of his discovery. "It's foolproof," he said. "Even someone with bypass privilege can't delete the sync file." I logged into the SYSTEM account, enabled BYPASS privilege and entered: $ RENAME SYNCDIR:SYNCFILE.DAT;1 SYNCDIR:FOOLPROOF.DAT;1 You could hear his scream a block away. ---------- Kevin G. Barkes is an independent consultant. He publishes the KGB Report newsletter, operates the www.kgbreport.com website, lurks on comp.os.vms, and can be reached at kgbarkes@gmail.com. **************************************************************************** Figure 1: $! SYNC_MASTER $! . . Initialization code goes here. . $! Create our syncfile before executing: $! $ GOSUB DO_SYNCHRONIZE $! $! Do whatever it is we're supposed to do: $ DO_WORK: . . . $! We're done, so get rid of the syncfile. $ CLOSE SYNCFILE $ DELETE SYNCDIR:SYNCFILE.DAT;1 $ EXIT $! $! This is the subprocedure which creates the syncfile. $ DO_SYNCHRONIZE: $! $! If there's a syncfile left over from a previous $! execution, get rid of it: $ IF F$SEARCH("SYNCDIR:SYNCFILE.DAT;1") .NES. "" - THEN DELETE SYNCDIR:SYNCFILE.DAT;1 $! $! If we can't delete the syncfile, then it would appear $! this procedure is already being run by another process, $! so abort. $! $ IF .NOT. $STATUS THEN EXIT $! $! Otherwise, open a new syncfile and return: $ OPEN/WRITE SYNCFILE SYNCDIR:SYNCFILE.DAT;1 $ RETURN ******************************************************************************* Figure 2: $! SYNC_SLAVE . . Initialization code goes here. . $ BEGIN: $! $! Our procedure waits a minute to make certain $! our "master" process is up and running and $! has had time to open its syncfile: $! $ WAIT 00:01:00 $! We set the value of the symbol TESTSYNC to 1. $! This symbol is used to test whether a syncfile $! exists: $! $ TESTSYNC = 1 $! $! Before executing, our procedure checks to see $! if a syncfile exists: $! $ GOSUB CHECKSYNC $! $! If the CHECKSYNC subroutine resets the value of $! TESTSYNC, it means that a syncfile exists; loop $! back to the beginning and wait to do another test. $! $ IF .NOT. TESTSYNC THEN GOTO BEGIN $! $! Do whatever we're supposed to do: . . . $ EXIT $! $! Subroutine to check for the existence of a checkfile: $! $ CHECKSYNC: $! $! If there's no syncfile, return and proceed with $! our normal processing. $! $ IF F$SEARCH("SYNCDIR:SYNCFILE.DAT;1") .EQS. "" THEN RETURN $! $! If there is a syncfile, make certain it isn't just left $! over from a previous aborted execution: $! $ DELETE SYNCDIR:SYNCFILE.DAT;1 $! $! If we can't delete the file, (assuming protections are $! properly set), then set the value of TESTSYNC and return: $! $ TESTSYNC = 0 $ RETURN