Welcome to the FIS GT.M Acculturation Workshop

Copyright © 2011 Fidelity Information Services, Inc. All Rights Reserved.

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts and no Back-Cover Texts.

GT.M™ is a trademark of Fidelity Information Services, Inc. Other trademarks are the property of their respective owners.

This document contains a description of FIS GT.M and the operating instructions pertaining to the various functions that comprise the system. This document does not contain any commitment of FIS. FIS believes the information in this publication is accurate as of its publication date; such information is subject to change without notice. FIS is not responsible for any errors or defects.

Contents – Navigation

Acculturation Workshop Expectations
GT.M
Packaging
Getting Started
The Basics
Security
Journaling
Database Replication
Backup
Replication Briefly Revisited
Unicode (ISO/IEC-10646)
Database Encryption
Pulling it together

Overview

Acculturation Workshop Expectations

The GT.M Acculturation Workshop is a hands-on GT.M “boot camp” for those interested in the configuration, administration and operation of applications on GT.M. This file is the script, or workbook, for the workshop, and consists of the exercises below.

At the end of these exercises, you will have a basic working knowledge of the essential aspects of GT.M administration and operations. While this workshop alone will not make you a GT.M expert by any means, the basic working knowledge will help you quickly understand the concepts explained in the user documentation and put you on the path to becoming an expert.

The workshop is not a course in M programming. Familiarity with Linux® (or at least UNIX®) will allow you to move faster through the material, but is not absolutely required. If you have no experience whatsoever with Linux or UNIX, supplementary tutorial material on the side will increase your level of comfort.

As the differences between GT.M and other M implementations are more in the area of configuration and systems administration rather than M language features, the former topic is the major thrust of the workshop.

GT.M

FIS GT.M™ is an implementation of the ISO standard scripting & application development language M, commonly known as MUMPS, developed and released by FIS. As the platform for the FIS Profile application, GT.M is the most widely used M implementation in banking and finance, including two of the largest real time core processing systems we know of that are live at any bank anywhere in the world. GT.M is increasingly used in healthcare. The implementation of GT.M on the GNU/Linux operating system on industry standard x86 architecture hardware is the M implementation used for the FOSS (Free / Open Source Software) stack for VistA.

GT.M is architected with the following objectives:

Free support for GT.M is available from the community on various mailing lists and electronic forums. Support with assured service levels is available from FIS on a commercial basis.

GT.M provides:

With the exception of Structured System Variable Names (SSVNs), GT.M mostly implements ISO standard M (ISO 11756–1999), including a full implementation of transaction processing (TP) that provides ACID (Atomic, Consistent, Isolated, Durable) transactions. As with any M implementation, there are extensions. IO parameters are implementation specific, as are parameters of the VIEW command, and commands & variables starting with the letter Z.

Despite the fact that the dialect of M implemented by GT.M shares so much in common with other M implementations, operationally, GT.M is unlike any other M implementation.

This Acculturation Workshop is based on GT.M V5.4-002B.

Packaging

The exercises are carried out by booting guest virtual machines (also called software appliances) on your host computer. Think of a virtual machine as a “computer within a computer”. A guest virtual machine can run a different operating system from that of the host computer. The host computer might itself run Windows, Solaris, OS X, or any other operating system and the guest can run Linux with GT.M as well as other applications. "Emulation" or "virtualization" software helps you set up a guest system on a host computer. On the host computer, the disk images of the GT.M Acculturation Workshop guest look like ordinary files in the file system.

Linux

Linux is the common name for the GNU/Linux operating system, consisting of the GNU utilities and libraries on the Linux kernel, available across the broadest range of hardware of any operating system. It is most widely used on industry standard architecture x86 hardware (i.e., based on popular CPUs from Intel, AMD and other vendors), and is increasingly popular around the world for applications that include embedded computing (appliances); personal desktops; file, print & web servers; supercomputing; and to deploy mission critical software. Linux is the operating system for the VistA FOSS stack.

Free support for Linux is available on numerous mailing lists and electronic forums. Commercial support is widely available from multiple vendors.

Although dated, Linux: Rute User's Tutorial and Exposition is still a very useful and usable tutorial for anyone getting started with Linux. The Debian Project maintains a page of books on Linux. The Debian Wiki has useful reference information and a having a paper copy of the Debian Reference Card handy would be useful for anyone not entirely comfortable with Linux.

The GT.M Acculturation Workshop is virtual machine which starts with the disk image of a minimal Ubuntu 11.10 (“Oneiric Ocelot”) Linux distribution, with additional documentation resources.

Conventions

Text in blue monospace font is intended to be typed in at a GT.M prompt (including the main GTM> prompt as well as utility programs such as GDE). Text in red monospace font is intended to be typed in at a shell prompt, as input to a command executed from the shell, or elsewhere in Linux – but not to GT.M. Commands to be executed on the host are in green monospace font.

Control of the Keyboard & Mouse

When you boot a guest virtual machine, ownership of the keyboard or mouse may need to toggle between the host and guest. The software you use for virtualization determines how to transfer control.

With kvm / QEMU, use the Ctrl-Alt key combination to toggle ownership of the mouse and keyboard between host and guest. Even if the host owns the keyboard, you can type into the guest console when it has focus, but not the other way around. Mouse clicks are visible to only the machine, host or guest, that owns the mouse.

Terminal Emulation

We recommend that you boot and minimize the virtual machine, and connect to your virtual machines with terminal sessions from a terminal emulator. On Windows, you can use a terminal emulator such as putty. Linux distributions include terminal emulation. Terminal emulators are available for, and frequently included with, other computer platforms.

For the Unicode exercises, you will either need a terminal emulator that can be switched between UTF-8 and single-byte characters, or you will need two emulators. If you intend to use languages that write right to left, you will need a terminal emulator with bidirectional capabilities.

Virtualization

The software used for virtualization and used in the examples herein is QEMU which is available for many popular computing platforms, including Windows, Linux and more. Instructions are provided below for Windows and Linux hosts. On Linux hosts, kvm may be the preferred choice (kvm and QEMU provide a very similar user interface). VirtualBox is another popular FOSS virtualization application. There is also proprietary virtualization software. Even though the examples herein are kvm/QEMU, you should be able to use the virtualization software of your choice.

The exercises have been tested using kvm on Ubuntu Linux 11.10.

Disk formats

The GT.M Acculturation Workshop is distributed as a vmdk format disk image file (e.g., ubuntu-11.10_gtmworkshop7.vmdk) that should work with most virtualization software, both FOSS and proprietary and a qcow2 format disk image that works with QEMU/kvm.

Virtual machine configuration

Virtualization software configures virtual machines either with their own IP addresses where the network connection (wired or wireless) of the host has multiple IP addresses, or, more commonly using network address translation (NAT). In the latter case, the network connection of the host has one IP address that it presents to the outside world, but each virtual machine has an IP address in a subnet within the host (the host acts just like a home Wifi access point / router).

You will need to configure your virtual machine for outbound and inbound network access. While outbound access should require no configuration to work with either type of virtual machine network connection, inbound network access in a NAT'd environment will require a TCP port on the host to be forwarded to the virtual machine for each port at which a service on the virtual machine needs to respond. For example each virtual machine has a secure shell (ssh) server listening at port 22 for incoming connections, and you might choose to forward port 2222 on the host to port 22 on your virtual machine.

Refer to the user documentation for your virtualization software to set up virtual machine networking. For example, using kvm on a Linux host, the following command boots a vmdk image with the necessary port forwarding:

kvm -enable-kvm -cpu host -net nic -net user,hostfwd=tcp::2222-:22 -hda /Distrib/Ubuntu/ubuntu-11.10_gtmworkshop7.vmdk &

Legal Stuff

GT.M is owned and copyrighted by Fidelity Information Services, Inc., and is available for the x86 GNU/Linux platform under the terms of the GNU Affero General Public License version 3. Source and binary can be downloaded from the GT.M project page at Source Forge.

The core of VistA (so called “FOIA VistA”) is in the public domain through the US Freedom of Information Act. Source and object code are available on one of the hard drive images. As noted above, no understanding of VistA itself is required or assumed for the workshop.

The Linux kernel, GNU utilities, the WorldVistA EHR extensions to VistA and all other software on the CD-ROM and hard drive images are FOSS and available under their respective FOSS licenses. Copyrights and trademarks of all content are hereby acknowledged as being held by their owners.

Getting Started

With a terminal emulator initiate an ssh connection to port 2222 on localhost and login with userid gtmuser, password GT.M Rocks! (including a space and an exclamation point). For example, on Linux, you can use the command: ssh -p 2222 -X gtmuser@localhost to connect as user gtmuser to port 2222 on the host which is forwarded to port 22 on the guest. Here is a screenshot of a terminal session logged into the virtual machine using ssh from a Linux host.


Illustration 1: ssh initial connection




Note that the screenshot shows the initial connection. Subsequent connections are less verbose:

$ ssh -X -p 2222 gtmuser@localhost
gtmuser@localhost's password:
Welcome to Ubuntu 11.10 (GNU/Linux 3.0.0-12-generic i686)

 * Documentation:  https://help.ubuntu.com/
Last login: Thu Nov 17 01:50:08 2011 from 10.0.2.2
gtmuser@gtmworkshop7:~$ 

Download and install GT.M

First download GT.M for 32-bit GNU/Linux on the x86 platform from Source Forge. The current GT.M release is V5.4-002B, and you can download it to a temporary directory, e.g., /tmp, with the wget program: wget -P /tmp http://sourceforge.net/projects/fis-gtm/files/GT.M-x86-Linux/V5.4-002B/gtm_V54002B_linux_i686_pro.tar.gz

Then create a temporary directory and unpack the contents of the tarball into it.

gtmuser@gtmworkshop7:~$ mkdir /tmp/tmp
gtmuser@gtmworkshop7:~$ cd /tmp/tmp
gtmuser@gtmworkshop7:/tmp/tmp$ tar zxf ../gtm_V54002B_linux_i686_pro.tar.gz
gtmuser@gtmworkshop7:/tmp/tmp$ 

Then install GT.M in /usr/lib/fis-gtm/V5.4-002B_x86. Note that this must be done as root.

gtmuser@gtmworkshop7:/tmp/tmp$ sudo ./configure
[sudo] password for gtmuser: 
                     GT.M Configuration Script
Copyright 2009, 2011 Fidelity National Information Services, Inc. Use of this
software is restricted by the provisions of your license agreement.

What user account should own the files? (bin) root
What group should own the files? (bin) root
Should execution of GT.M be restricted to this group? (y or n) n
In what directory should GT.M be installed? /usr/lib/fis-gtm/V5.4-002B_x86

Directory /usr/lib/fis-gtm/V5.4-002B_x86 does not exist. Do you wish to create it as part of
this installation? (y or n) y

Installing GT.M....

Should unicode support be installed? (y or n) y
Should an ICU version other than the default be used? (y or n) n

All of the GT.M MUMPS routines are distributed with uppercase names.
You can create lowercase copies of these routines if you wish, but
to avoid problems with compatibility in the future, consider keeping
only the uppercase versions of the files.

Do you want uppercase and lowercase versions of the MUMPS routines? (y or n)y

Creating lowercase versions of the MUMPS routines.
./CHK2LEV.m --->  ./chk2lev.m
./CHKOP.m --->  ./chkop.m
./GENDASH.m --->  ./gendash.m

...

./_UCASE.m --->  ./_ucase.m
./_UTF2HEX.m --->  ./_utf2hex.m
./_XCMD.m --->  ./_xcmd.m

Compiling all of the MUMPS routines. This may take a moment.


GTM>
%GDE-I-GDUSEDEFS, Using defaults for Global Directory 
        /usr/lib/fis-gtm/V5.4-002B_x86/gtmhelp.gld

GDE> 
GDE> 
GDE> 
%GDE-I-VERIFY, Verification OK

%GDE-I-GDCREATE, Creating Global Directory file 
        /usr/lib/fis-gtm/V5.4-002B_x86/gtmhelp.gld

GTM>
%GDE-I-GDUSEDEFS, Using defaults for Global Directory 
        /usr/lib/fis-gtm/V5.4-002B_x86/gdehelp.gld

GDE> 
GDE> 
GDE> 
%GDE-I-VERIFY, Verification OK

%GDE-I-GDCREATE, Creating Global Directory file 
        /usr/lib/fis-gtm/V5.4-002B_x86/gdehelp.gld

Installation completed. Would you like all the temporary files
removed from this directory? (y or n) y
gtmuser@gtmworkshop7:/tmp/tmp$ 

GT.M is now installed and operational.

Run GT.M

Default Environment

GT.M needs several environment variables to be set up. The virtual machine comes with a script that sets up reasonable defaults and allows you to get right into GT.M. You can source the file /usr/lib/fis gtm/V5.4 002B_x86/gtmprofile to set up reasonable defaults or simply execute the script gtm to execute GT.M. A default environment is created only if it does not exist already.

gtmuser@gtmworkshop7:~$ source /usr/lib/fis-gtm/V5.4-002B_x86/gtmprofile
%GDE-I-GDUSEDEFS, Using defaults for Global Directory 
        /home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.gld

GDE> 
%GDE-I-EXECOM, Executing command file /usr/lib/fis-gtm/V5.4-002B_x86/gdedefaults

GDE> 
%GDE-I-VERIFY, Verification OK

%GDE-I-GDCREATE, Creating Global Directory file 
        /home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.gld
Created file /home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.dat
%GTM-I-JNLCREATE, Journal file /home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.mjl created for region DEFAULT with BEFORE_IMAGES
%GTM-I-JNLSTATE, Journaling state for region DEFAULT is now ON
gtmuser@gtmworkshop7:~$ 

Sourcing gtmprofile also defines gtm as an alias to the script gtm that runs GT.M (but more on that later).

gtmuser@gtmworkshop7:~$ alias gtm
gtm='/usr/lib/fis-gtm/V5.4-002B_x86/gtm'
gtmuser@gtmworkshop7:~$ gtm
GTM>

Now you are in the GT.M “direct mode” where you can execute commands interactively. For example:

GTM>set ^Capital("USA")="Washington"

GTM>set ^Capital("India")="New Delhi"

GTM>set ^Capital("Jordan")="Amman"

The commands perform database updates, which are shared between processes. You can see this if you start a new terminal session, start a new GT.M process and ask it to dump the “global variable” (a key-value association) ^Capital. The halt command takes you back to the Linux shell.

GTM>zwrite ^Capital
^Capital("India")="New Delhi"
^Capital("Jordan")="Amman"
^Capital("USA")="Washington"
GTM>halt
gtmuser@gtmworkshop7:~$

The operation of GT.M is controlled by a number of environment variables. In our exercise, the gtmprofile script automatically sets a number of environment variables:

gtmuser@gtmworkshop7:~$ env | grep gtm
gtm_repl_instance=/home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.repl
gtm_log=/tmp/fis-gtm/V5.4-002B_x86
gtm_prompt=GTM>
gtm_retention=42
gtmver=V5.4-002B_x86
USER=gtmuser
gtm_icu_version=4.2
gtmgbldir=/home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.gld
MAIL=/var/mail/gtmuser
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/lib/fis-gtm/V5.4-002B_x86
gtmroutines=/home/gtmuser/.fis-gtm/V5.4-002B_x86/o(/home/gtmuser/.fis-gtm/V5.4-002B_x86/r /home/gtmuser/.fis-gtm/r) /usr/lib/fis-gtm/V5.4-002B_x86
PWD=/home/gtmuser
gtmdir=/home/gtmuser/.fis-gtm
HOME=/home/gtmuser
gtm_principal_editing=EDITING
LOGNAME=gtmuser
gtm_tmp=/tmp/fis-gtm/V5.4-002B_x86
gtm_dist=/usr/lib/fis-gtm/V5.4-002B_x86
gtmuser@gtmworkshop7:~$ 

GT.M databases can also be configured so that they can be recovered after a system crash. Simulate a crash right now by clicking on the “X” in the top right corner of your virtual machine console window to instantly “power down” your virtual machine. Then boot it, run gtm and use a ZWRite ^Capital command to confirm that the data in the database is still intact.

The tree program shows the default environment GT.M creates in your home directory. A screenshot illustrate tree's color output:




We will get into this in more detail below.

UTF-8 Mode

With GT.M, you can write applications that implement international character sets using Unicode or ISO/IEC-10646 (the two standards track each other). Connect to the virtual machine with your terminal emulator configured to support the UTF-8 character set. In a fresh terminal session execute the following (the non-printable characters may look different on your session from the screen here, depending on how your terminal emulator renders them):

gtmuser@gtmworkshop7:~$ export gtm_chset=UTF-8 LC_CTYPE=en_US.utf8
gtmuser@gtmworkshop7:~$ source /usr/lib/fis-gtm/V5.4-002B_x86/gtmprofile
gtmuser@gtmworkshop7:~$ gtm
GTM>write $zchset
UTF-8
GTM>for i=1040:16:1072 write ! for j=0:1:15 write $char(i+j)," "

А Б В Г Д Е Ж З И Й К Л М Н О П
Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я
а б в г д е ж з и й к л м н о п
GTM>

Note that Unicode support requires additional infrastructure, such as Unicode enabled terminal emulators, and is likely to require custom collation modules to be written to ensure that strings such as names are sorted in linguistically and culturally correct order.

In the exercises below, we will set up environments for use with Unicode.

The Basics

To use GT.M, at a minimum you need:

User Documentation

GT.M user documentation consists of manuals and technical bulletins. Manuals are updated periodically, and technical bulletins serve as user documentation updates that are available in a more timely manner. Current GT.M documentation is available online at the GT.M documentation site as well as a local copy on this CD. All current GT.M documentation is accessible on the Internet Go to the GT.M home page and click on the User Documentation tab.

GT.M documentation is organized into manuals, technical bulletins, suggested practices and advisories:

Routines in the File System

Routines in GT.M are simply files in the file system; they do not reside in databases. You can edit routines from the GTM> prompt. Start GT.M and at the GTM> prompt, type zedit "welcome" and hit ENTER. This starts the vi editor editing the source routine for ^hello, /home/gtmuser/.fis-gtm/V5.4-002B_x86/r/welcome.m. Use the five key sequence ESCAPE : q ! ENTER to exit vi without changing the file.

Note: although vi always puts a newline at the end of your file; other editors may not. A GT.M program file should always end with a newline.

The philosophy of GT.M is to focus on what it does well, providing a robust, scalable, transaction processing database and a compiler for the M language, and to leverage tools and capabilities of the underlying operating system for the rest. This is powerful because whenever there are enhancements to the underlying operating environment, GT.M can benefit from them. It can also be a little uncomfortable for M programmers migrating to GT.M, because traditional M implementations carry their environments around with them, like the shell on a snail's back.

As you saw when executing M commands interactively, even though GT.M is a true compiler it still provides an interactive direct mode – GT.M simply compiles and executes each line.

Exercise – compiling and linking

The purpose of this exercise is to understand compiling and linking routines. Use the command find .fis-gtm -iname hello.[mo] to confirm that your default GT.M environment does not have a program called hello.

gtmuser@gtmworkshop7:~$ find .fis-gtm -iname hello.[mo]
gtmuser@gtmworkshop7:~$ 

You can also perform the same operation from inside GT.M.

GTM>ZSYSTEM "find .fis-gtm -iname hello.[mo]"
GTM>

or

GTM>do SILENT^%RSEL("hello") zwrite %ZR
%ZR=0

GTM>

Had there been a routine, the response might look like this:

GTM>do SILENT^%RSEL("hello") zwrite %ZR    
%ZR=1
%ZR("hello")="/home/gtmuser/.fis-gtm/r/"

GTM>

If you are not comfortable with the terse commands of the default vi editor, you can install your preferred editor. Two other editors that are installed on the virtual machine are jed and fte.

gtmuser@gtmworkshop7:~$ export EDITOR=`which jed`
gtmuser@gtmworkshop7:~$ gtm
GTM>

Instruct GT.M to run the routine ^hello and note that it reports an error:

GTM>Do ^hello
%GTM-E-ZLINKFILE, Error while zlinking "hello"
%GTM-E-FILENOTFND, File hello not found
GTM>

Within GT.M, use ZEDit "hello" to start the editor. Create a simple “Hello, World” program. Now notice that the source file exists (you can use the arrow key to recall the previous command within GT.M) but there is no object file.

GTM>do SILENT^%RSEL("hello") zwrite %ZR
%ZR=1
%ZR("hello")="/home/gtmuser/.fis-gtm/V5.4-002B_x86/r/"

GTM>do SILENT^%RSEL("hello","OBJ") zwrite %ZR
%ZR=0

GTM>

Now run the program – it runs as expected.

GTM>do ^hello
Hello, World

GTM>

Now run the find command to see that you now also have an object file. GT.M dynamically, and automatically, compiles the source program into the object program when you execute do ^hello.

[Note that since GT.M is a compiler, it can generate error messages at compile time as well as at run time. Indeed when compiling an application such as VistA, there may be hundreds of lines of error messages triggered by lines of code that are legal for other M implementations but not for GT.M. These lines are protected in VistA and are inside conditional statements that are executed only on the appropriate M implementation, so they are nothing to be concerned about.]

In this case, also get time stamps of the files.

GTM>do SILENT^%RSEL("hello") zwrite %ZR
%ZR=1
%ZR("hello")="/home/gtmuser/.fis-gtm/V5.4-002B_x86/r/"

GTM>do SILENT^%RSEL("hello","OBJ") zwrite %ZR
%ZR=1
%ZR("hello")="/home/gtmuser/.fis-gtm/V5.4-002B_x86/o/"

GTM>

Let's also get the time stamps of the files:

GTM>zsystem "find .fis-gtm -name hello.[mo] -exec ls -l {} \;"
-rw-rw-r-- 1 gtmuser gtmuser 401 2011-11-07 12:05 .fis-gtm/V5.4-002B_x86/o/hello.o
-rw-rw-r-- 1 gtmuser gtmuser 33 2011-11-07 11:19 .fis-gtm/V5.4-002B_x86/r/hello.m

GTM>

Now edit the program with zedit "hello" then change it, e.g., make it print "Aloha, World" instead and save it. Again execute do ^hello and note that GT.M still prints "Hello, World". This is because GT.M already has an hello module linked in its address space, and does not go out every time to check if there is a new version. This is “clobber protection” and a GT.M feature.

Execute zLink "hello" which tells GT.M to re-link hello even if it already has one linked in its address space, followed by do ^hello and note that it now prints "Aloha, World" . Verify that the source file is newer and that GT.M has created a new object file.

GTM>zedit "hello"

GTM>do ^hello
Hello,world

GTM>zlink "hello"

GTM>do ^hello
Aloha, world

GTM>zsystem "find .fis-gtm -name hello.[mo] -exec ls -l {} \;"
-rw-rw-r-- 1 gtmuser gtmuser 405 2011-11-07 12:09 .fis-gtm/V5.4-002B_x86/o/hello.o
-rw-rw-r-- 1 gtmuser gtmuser 34 2011-11-07 12:08 .fis-gtm/V5.4-002B_x86/r/hello.m

GTM>

To avoid being surprised by running an old version of a routine that you have just edited, it is important to understand how dynamic compilation and linking work on GT.M.

The $zroutines ISV tells GT.M where to find routines:

GTM>write $zroutines
/home/gtmuser/.fis-gtm/V5.4-002B_x86/o(/home/gtmuser/.fis-gtm/V5.4-002B_x86/r /home/gtmuser/.fis-gtm/r) /usr/lib/fis-gtm/V5.4-002B_x86
GTM>

At process startup, $zroutounes is initialized from the environment variable $gtmroutines, but it can be altered from within the GT.M process.

GTM>set $zroutines=". "_$ztrnlnm("gtm_dist")

GTM>write $zroutines
. /usr/lib/fis-gtm/V5.4-002B_x86
GTM>write $ztrnlnm("gtmroutines")
/home/gtmuser/.fis-gtm/V5.4-002B_x86/o(/home/gtmuser/.fis-gtm/V5.4-002B_x86/r /home/gtmuser/.fis-gtm/r) /usr/lib/fis-gtm/V5.4-002B_x86
GTM>

The ZEDit command always puts new routines in the first source directory in the search path. Use it to create a new routine to print the current date and time at the Universal Time Coordinate. After the change to $zroutines above, notice how a newly created program and object file are created in the current directory (.).

GTM>zedit "UTC"
GTM>zprint ^UTC
UTC ZSYstem "TZ=UTC date"
Quit
GTM>do ^UTC
Wed Sep  1 19:57:13 UTC 2010
GTM>zsystem "find . -name UTC\* -exec ls -l {} \;"
-rw-r--r-- 1 gtmuser gtmuser 377 2010-09-01 15:57 ./.fis-gtm/V5.4-002B_x86/o/UTC.o
-rw-r--r-- 1 gtmuser gtmuser 32 2010-09-01 15:57 ./.fis-gtm/V5.4-002B_x86/r/UTC.m

GTM>

The Programmers Guide explains the use of $ZROutines in more detail.

Exercise – Default directory structure for an application

Use the tree -d .fis-gtm command from the shell to look at the default directory structure under .fis-gtm. What is the purpose of each directory?

Global Directories point to Global Variables

Routines in GT.M reside in the file system rather than in the database, whereas global variables reside in databases. Routines are completely independent of global variables. In this respect, GT.M is different from other M implementations.

Given a person's name, a telephone directory helps you find the person by giving you the phone number, and sometimes their address as well. Analogously, given an M global variable name, a global directory helps a GT.M process find the variable by giving it the database file where that variable resides, as well as other pertinent information.

The global directory is a binary file pointed to by the ISV $zgbldir. The GDE utility program (invoked with do ^GDE inside GT.M or mumps -run ^GDE from the shell) is used to manage global directories. [Note that the input to GDE can be a text file. In a production environment, FIS recommends that text files be used to define database configurations, and that these text files be put under version control.]

In GT.M, sets of M global variables (Names or Name spaces) are mapped to Regions that define properties relating to the M global. Each Region is mapped to a Segment that defines properties relating to the file system. Consider the example in the figure below:




In this example, there are four M global variables that we would like to separate from the rest (e.g., for purposes of sharing globals between applications, or for reasons of protection – perhaps they contain special information, so that only herpetologists are to have access to globals ^Gharial and ^Jacare, and only ornithologists are to have access to globals ^Hoopoe and ^Trogon). This is accomplished by creating five Name Spaces (note that a name space can contain a single variable, as in this example, or a range of global variables, e.g., everything starting with ^A through ^Gharial). There is always a default (*) name space.

One or more name spaces are mapped to a Region. All global variables in a region share a common set of M global variable properties, such as the maximum record length, whether null subscripts are permitted, etc. In this case ^Gharial and ^Jacare are mapped to the region REPTILES, whereas ^Hoopoe and ^Trogon are mapped to the region BIRDS. The default name space * is mapped to a region called DEFAULT.

Each region is mapped to a Segment. Just as a region defines properties pertaining to M global variables, the segment defines properties pertaining to the database file for that region, such as the file name, the initial allocation, number of global buffers, etc. The database file is just an ordinary file in the file system of the underlying operating system.

Each database file can have a single active journal file. A journal file can be linked to a previous journal files to form a chain of journal files.

The ISV $zgbldir points a GT.M process to the global directory. $zgbldir is initialized from $gtmgbldir at process startup, but it can be modified by the process during execution.

GTM>write $ztrnlnm("gtmgbldir")

/home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.gld
GTM>write $zgbldir

/home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.gld
GTM>

GDE, the Global Directory Editor, is a program used to manipulate global directories. GDE is itself written in M, and you can invoke it from the shell with $gtm_dist/mumps -run GDE (or, with the gtm alias, gtm-run GDE) or from inside the direct mode with Do ^GDE.

gtmuser@gtmworkshop7:~$ $gtm_dist/mumps -run GDE
%GDE-I-LOADGD, Loading Global Directory file
        /home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.gld
%GDE-I-VERIFY, Verification OK


GDE>

You can use the show command to examine name spaces, regions and segments.

GDE> show -name

         *** NAMES ***
 Global                             Region
 ------------------------------------------------------------------------------
 *                                  DEFAULT
GDE>

In this case, there is only one name space, the default. There is also only one region, DEFAULT. Region and segment names are case insensitive, but name spaces are case sensitive, since M variable names are case sensitive.

GDE> show -region

                                *** REGIONS ***
                                 Dynamic                          Def    Rec   Key Null       Standard
 Region                          Segment                         Coll   Size  Size Subs       NullColl  Journaling
 ------------------------------------------------------------------------------------------------------------------
 DEFAULT                         DEFAULT                            0   4080   255 NEVER      Y         Y

                          *** JOURNALING INFORMATION ***
 Region                          Jnl File (def ext: .mjl)  Before Buff      Alloc Exten
 ---------------------------------------------------------------------------------------
 DEFAULT                         $gtmdir/$gtmver/g/gtm.mjl
                                                           Y       128        100   100

GDE>

Notice the region parameters – review them in the Administration and Operations Guide. Since there is one region, there is also one segment, also called DEFAULT (the region and segment names can be different; it is good practice to keep them the same).

GDE> show -segment

                                *** SEGMENTS ***
 Segment                         File (def ext: .dat)Acc Typ Block      Alloc Exten Options
 -------------------------------------------------------------------------------------------
 DEFAULT                         $gtmdir/$gtmver/g/gtm.dat
                                                     BG  DYN  4096       1000  1000 GLOB=1000
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
GDE>

Notice how the database file is defined using the environment variables $gtmdir and $gtmver. This means that, as long as the environment variables are defined, one global directory can point to a database file wherever it happens to be on the system. This can allow two processes to share a global directory, but to have different database files.

Important: the parameters in the global directory are used only by mupip create to create a new database file. At other times, the global directory is used only to map global variable names to database files. So, if you change the global directory, existing database files are not changed. If you change a parameter in a database file, unless you also change the global directory used to create the database file, the next time you create that file, it will use old parameters in the global directory.

The show map command gives a good visualization of mapping of names to database files in the global directory.

GDE> show -map

                                  *** MAP ***
   -  -  -  -  -  -  -  -  -  - Names -  -  - -  -  -  -  -  -  -
 From                            Up to                            Region / Segment / File(def ext: .dat)
 -----------------------------------------------------------------------------------------------------------------------------------
 %                               ...                              REG = DEFAULT
                                                                  SEG = DEFAULT
                                                                  FILE = $gtmdir/$gtmver/g/gtm.dat
 LOCAL LOCKS                                                      REG = DEFAULT
                                                                  SEG = DEFAULT
                                                                  FILE = $gtmdir/$gtmver/g/gtm.dat
GDE>

Exercise – Set up the Global Directory for Herpetologists and Ornithologists

Start from the shell. Assign a value to $gtmgbldir so as to not overwrite any existing global directory in the Acculturation Workshop and then invoke GDE.

gtmuser@gtmworkshop7:~$ export gtmgbldir=/home/gtmuser/gtm.gld
gtmuser@gtmworkshop7:~$ $gtm_dist/mumps -run GDE
%GDE-I-GDUSEDEFS, Using defaults for Global Directory
        /home/gtmuser/gtm.gld

GDE>

While not essential, it may be conceptually helpful to build the global directory from the bottom up – first create the segments, then the regions, and then the name spaces. First edit the default to make the parameters more useful – the out-of-box defaults are suitable for experimentation but not real use. Using a template reduces the work needed to create multiple regions and segments. Notice the use of different access methods for BIRDS and REPTILES.

GDE> change -segment DEFAULT -block_size=4096 -allocation=1000 -extension=1000 -global_buffer_count=1000 -file_name=/home/gtmuser/gtm.dat
GDE> template -segment -access_method=bg -block_size=4096 -allocation=1000 -extension=1000 -global_buffer_count=1000
GDE> template -segment -access_method=mm -block_size=4096 -allocation=1000 -extension=1000 -global_buffer_count=1000
GDE> add -segment BIRDS -access_method=mm -file_name=/home/gtmuser/flap.dat
GDE> add -segment REPTILES -access_method=bg -file_name=/home/gtmuser/creep.dat
GDE> show -segment

                                *** SEGMENTS ***
 Segment                         File (def ext: .dat)Acc Typ Block      Alloc Exten Options
 -------------------------------------------------------------------------------------------
 BIRDS                           /home/gtmuser/flap.dat      MM  DYN  4096       1000  1000 DEFER
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
 DEFAULT                         /home/gtmuser/gtm.dat     BG  DYN  4096       1000  1000 GLOB=1000
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
 REPTILES                        /home/gtmuser/creep.dat     BG  DYN  4096       1000  1000 GLOB=1000
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
GDE>

Then we can map the regions to the segments. Notice that the segment names (specified with the -dynamic qualifier) are converted to and displayed in upper case.

GDE> change -region DEFAULT -stdnull -key_size=255 -record_size=4080 -journal=(before,file="/home/gtmuser/gtm.mjl")
GDE> template -region -stdnull -key_size=255 -record_size=4080 -journal=nobefore
GDE> add -region BIRDS -dynamic=birds -journal=(nobefore,file="/home/gtmuser/flap.mjl")
GDE> add -region REPTILES -dynamic=reptiles -journal=(before,file="/home/gtmuser/creep.mjl")
GDE> show -region

                                *** REGIONS ***
                                 Dynamic                          Def    Rec   Key Null       Standard
 Region                          Segment                         Coll   Size  Size Subs       NullColl  Journaling
 ------------------------------------------------------------------------------------------------------------------
 BIRDS                           BIRDS                              0   4080   255 NEVER      Y         Y
 DEFAULT                         DEFAULT                            0   4080   255 NEVER      Y         Y
 REPTILES                        REPTILES                           0   4080   255 NEVER      Y         Y

                          *** JOURNALING INFORMATION ***
 Region                          Jnl File (def ext: .mjl)  Before Buff      Alloc Exten
 ---------------------------------------------------------------------------------------
 BIRDS                           /home/gtmuser/flap.mjl            N       128        100   100

 DEFAULT                         /home/gtmuser/gtm.mjl           Y       128        100   100

 REPTILES                        /home/gtmuser/creep.mjl           Y       128        100   100

GDE>

Now map the name spaces to the regions.

GDE> add -name Gharial -region=reptiles
GDE> add -name Jacare -region=reptiles
GDE> add -name Hoopoe -region=birds
GDE> add -name Trogon -region=birds
GDE> show -name

         *** NAMES ***
 Global                             Region
 ------------------------------------------------------------------------------
 *                                  DEFAULT
 Gharial                            REPTILES
 Hoopoe                             BIRDS
 Jacare                             REPTILES
 Trogon                             BIRDS
GDE>

You can examine the entire map, and ask GDE to perform a check for consistency.

GDE> show -map

                                  *** MAP ***
   -  -  -  -  -  -  -  -  -  - Names -  -  - -  -  -  -  -  -  -
 From                            Up to                            Region / Segment / File(def ext: .dat)
 -----------------------------------------------------------------------------------------------------------------------------------
 %                               Gharial                          REG = DEFAULT
                                                                  SEG = DEFAULT
                                                                  FILE = /home/gtmuser/gtm.dat
 Gharial                         Gharial0                         REG = REPTILES
                                                                  SEG = REPTILES
                                                                  FILE = /home/gtmuser/creep.dat
 Gharial0                        Hoopoe                           REG = DEFAULT
                                                                  SEG = DEFAULT
                                                                  FILE = /home/gtmuser/gtm.dat
 Hoopoe                          Hoopoe0                          REG = BIRDS
                                                                  SEG = BIRDS
                                                                  FILE = /home/gtmuser/flap.dat
 Hoopoe0                         Jacare                           REG = DEFAULT
                                                                  SEG = DEFAULT
                                                                  FILE = /home/gtmuser/gtm.dat
 Jacare                          Jacare0                          REG = REPTILES
                                                                  SEG = REPTILES
                                                                  FILE = /home/gtmuser/creep.dat
 Jacare0                         Trogon                           REG = DEFAULT
                                                                  SEG = DEFAULT
                                                                  FILE = /home/gtmuser/gtm.dat
 Trogon                          Trogon0                          REG = BIRDS
                                                                  SEG = BIRDS
                                                                  FILE = /home/gtmuser/flap.dat
 Trogon0                         ...                              REG = DEFAULT
                                                                  SEG = DEFAULT
                                                                  FILE = /home/gtmuser/gtm.dat
 LOCAL LOCKS                                                      REG = DEFAULT
                                                                  SEG = DEFAULT
                                                                  FILE = /home/gtmuser/gtm.dat
GDE> verify
%GDE-I-VERIFY, Verification OK


GDE>

Exiting GDE creates the global directory. You can then use a mupip create command to create the database files. Notice that journal files must be separately created.

GDE> exit
%GDE-I-VERIFY, Verification OK

%GDE-I-GDCREATE, Creating Global Directory file
        /home/gtmuser/gtm.gld
gtmuser@gtmworkshop7:~$ ls -l *.dat *.mjl
ls: *.dat: No such file or directory
ls: *.mjl: No such file or directory
gtmuser@gtmworkshop7:~$ $gtm_dist/mupip create
Created file /home/gtmuser/flap.dat
Created file /home/gtmuser/gtm.dat
Created file /home/gtmuser/creep.dat
gtmuser@gtmworkshop7:~$ ls -l *.dat *.mjl
ls: cannot access *.mjl: No such file or directory
-rw-rw-rw- 1 gtmuser gtmuser 4170240 2011-11-07 14:57 creep.dat
-rw-rw-rw- 1 gtmuser gtmuser 4170240 2011-11-07 14:57 flap.dat
-rw-rw-rw- 1 gtmuser gtmuser 4170240 2011-11-07 14:57 gtm.dat
gtmuser@gtmworkshop7:~$

Then you can turn on journaling. Separate commands have to be used depending on the type of journaling – before image and no-before image journaling.

gtmuser@gtmworkshop7:~$ $gtm_dist/mupip set -journal=nobefore -region BIRDS
%GTM-I-JNLCREATE, Journal file /home/gtmuser/flap.mjl created for region BIRDS with NOBEFORE_IMAGES
%GTM-I-JNLSTATE, Journaling state for region BIRDS is now ON
gtmuser@gtmworkshop7:~$ $gtm_dist/mupip set -journal=before -region REPTILES,DEFAULT
%GTM-I-JNLCREATE, Journal file /home/gtmuser/gtm.mjl created for region DEFAULT with BEFORE_IMAGES
%GTM-I-JNLSTATE, Journaling state for region DEFAULT is now ON
%GTM-I-JNLCREATE, Journal file /home/gtmuser/creep.mjl created for region REPTILES with BEFORE_IMAGES
%GTM-I-JNLSTATE, Journaling state for region REPTILES is now ON
gtmuser@gtmworkshop7:~$ ls -l *.dat *.mjl
-rw-rw-rw- 1 gtmuser gtmuser 4170240 2011-11-07 14:59 creep.dat
-rw-rw-rw- 1 gtmuser gtmuser   69632 2011-11-07 14:59 creep.mjl
-rw-rw-rw- 1 gtmuser gtmuser 4170240 2011-11-07 14:58 flap.dat
-rw-rw-rw- 1 gtmuser gtmuser   69632 2011-11-07 14:58 flap.mjl
-rw-rw-rw- 1 gtmuser gtmuser 4170240 2011-11-07 14:59 gtm.dat
-rw-rw-rw- 1 gtmuser gtmuser   69632 2011-11-07 14:59 gtm.mjl
gtmuser@gtmworkshop7:~$

For production environments, we suggest that you put your GDE commands in a text file and invoke them with a heredoc or using GDE's @ command. Put the text file under version control.

$ZROUTINES and $zgbldir vs. UCI & Volume set

The GT.M environment is defined by $ZROutines (initialized from $gtmroutines) and $zgbldir (initialized from $gtmgbldir). Concepts from other M implementations such as UCI and volume set do not exist on GT.M.

The GT.M separation between routines and the database is very powerful, especially in real-world environments. Apart from the flexibility this offers, it enables the practice of “defensive programming”, not unlike defensive driving. We are human beings who are prone to err, and defensive practices reduce the probability of errors.

Exercise – Set up a Simulated ASP Environment

In an Application Service Provider (ASP) environment, the same application code can be used for a number of sites, but each site has its own database. Sometimes parts of the database may also be common and used on a read-only basis for normal operation, such as a data dictionary, an approved drug formulary, or a table of sales tax rates for location. Each site may also have a small set of custom routines. Let us consider an ASP serving two institutions, called gh (for General Hospital) and cc (for Cancer Center).

The majority of routines are shared, with:

Custom routines for General Hospital in /var/opt/EHR/1.0/gh/r and /var/opt/EHR/1.0/gh/V5.4-002B_x86/r with object code in /var/opt/EHR/1.0/gh/V5.4-002B_x86/o.

Similarly, custom routines for the Cancer Center are in /var/opt/EHR/1.0/cc/r and /var/opt/EHR/1.0/cc/V5.4-002B_x86/r with object code in /var/opt/EHR/1.0/cc/V5.4-002B_x86/o.

What should $gtmroutines be for a GH user and what should it be for a CC user? Create a shell script to be sourced by a GH user and another to be sourced by a CC user. [The shell scripts can reside in /var/opt/EHR/1.0/cc/V5.4-002B_x86 and /var/opt/EHR/1.0/gh/V5.4-002B_x86.]

The approved National Drug File is in the global variable ^PSNDF and is shared by both institutions with read only access to users. The National Drug File is in the database file /opt/EHR/1.0/V5.4-002B_x86/g/psndf.dat. All other globals are in database files that are specific to GH and CC, in /var/opt/EHR/1.0/gh/V5.4-002B_x86/g/main.dat and /var/opt/EHR/1.0/cc/V5.4-002B_x86/g/main.dat.

First, create the directory structure.

gtmuser@gtmworkshop7:~$ sudo mkdir -p /opt/EHR/VOE10
gtmuser@gtmworkshop7:~$ sudo chown -R gtmuser.users /opt/EHR/VOE10
gtmuser@gtmworkshop7:~$ cd /opt/EHR/VOE10 ; mkdir -p r V5.4-002B_x86/r V5.4-002B_x86/o V5.4-002B_x86/g
gtmuser@gtmworkshop7:/opt/EHR/VOE10$ sudo mkdir -p /var/opt/EHR/VOE10
gtmuser@gtmworkshop7:/var/opt/EHR/VOE10$ sudo chown -R gtmuser.users /var/opt/EHR/VOE10
gtmuser@gtmworkshop7:/var/opt/EHR/VOE10$ cd /var/opt/EHR/VOE10 ; mkdir -p gh/r gh/V5.4-002B_x86/r gh/V5.4-002B_x86/o gh/V5.4-002B_x86/g
gtmuser@gtmworkshop7:/var/opt/EHR/VOE10$ mkdir -p cc/r cc/V5.4-002B_x86/r cc/V5.4-002B_x86/o cc/V5.4-002B_x86/g
gtmuser@gtmworkshop7:/var/opt/EHR/VOE10$ cd
gtmuser@gtmworkshop7:~$

What should $gtmgbldir be for a GH user and what should it be for a CC user? Add these to the command files you created earlier. Create a file of commands to be fed to GDE either with a heredoc or with GDE's @ command that will create the global directories and then create the global directories.

Create the three database files with mupip create (remember that the database file /opt/EHR/1.0/V5.4-002B_x86/ g/psndf.dat will be created by the first mupip create, and the second mupip create will only create the institution specific database file.

In one environment assign values to the global variables ^PSNDF and ^X. In the other environment, confirm that you are able to read the value of ^PSNDF (i.e., it is shared), but the not the value in ^X (i.e., it is not shared).

Set a value for ^X in the second environment, and in the first environment confirm that you still see the original value of ^X that you set up in that environment.

Create a program ABC.m to write “Hello, World” in /opt/EHR/1.0/r and two programs with the same name DEF.m in /var/opt/EHR/1.0/gh to write “Hello, General Hospital” and in /var/opt/EHR/1.0/cc to say “Hello, Cancer Center”. Verify that a process in either environment gets “Hello, World” when it executes ABC.m and either “Hello, General Hospital” or “Hello, Cancer Center” depending on its environment when it executes DEF.m.

No special startup or shut down

The first process to open a database file sets up all the shared memory control structures needed. The last one out tears it down. There is no daemon that must run as root that can be a single point of failure, a performance bottleneck, or a potential security vulnerability. If replication is in use, then at least one Source Server process (see below) must be brought up first.

Upon bringing the system back up, if the system crashes, or is forcibly brought down: if journaling is in use, mupip journal -recover (or mupip journal -rollback if replication is in use) will recover the database. If journaling is not in use, mupip rundown -region "*" will clean up the database control structures in the file header, but cannot fix any integrity errors resulting from shutting down a computer without cleanly terminating GT.M processes.

[Note: Do not use mupip rundown if journaling is in use and you plan to recover the database after a crash with a mupip journal operation.]

GT.M Environment Variables

The operation of GT.M is controlled by a number of environment variables. The most important ones are $gtm_dist, $gtmroutines and $gtmgbldir, which are discussed above. The file gtmprofile (for sh type shells) that is supplied with GT.M, and which must be sourced rather than executed, attempts to provide reasonable default values. By setting environment variables either before sourcing it or after (the former is preferred, because gtmprofile can attempt to deal with interactions), you can provide your own values instead of using the defaults.

Review the file /usr/lib/fis-gtm/V5.4-002B_x86/gtmprofile to see how they are set. Study the order in which they are set and see if you can understand why.

System administrators sometimes customize gtmprofile (as well as the gtm script) for their environments. An alternative is to set up your own file to be sourced, and to source gtmprofile from within it.

The following environment variable is explicitly set by gtmprofile. We suggest that you do not set it, but let gtmprofile set it.

Where possible, defaults for the following are provided by gtmprofile if not set:

The following must be set before gtmprofile is sourced if you want gtmprofile to set up a UTF-8 environment:

GT.M directly or indirectly uses a number of other environment variables that are not touched by gtmprofile (they can be set before or after gtmprofile is sourced). These are documented in the GT.M Administration and Operations Guide. Some worth noting are:

Security

GT.M was designed from the very beginning to be secure. [Caveat: absolute security does not exist in this universe. For a discussion that bridges philosophy and technology, we highly recommend Bruce Schneier's Secrets and Lies, ISBN 0-471-25311-1.]A GT.M process can access a database file only if the file ownership and permissions allow. Under normal operation, there is only one small component of GT.M that operates as the super user (root) – the gtmsecshr helper process. The GT.M security model is simple, well understood and documented.

Review the GT.M Security Philosophy and Implementation technical bulletin.

Exercise – Access controls with ownership and permissions

Start with a fresh session to discard environment variables from the last exercise. In the following, notice how Linux file permissions are used to allow user gtmuser full access to the database, and preventing another user from updating a database, while allowing that user to read from it. For this exercise, start

Verify that you can read and write your default database and change the permissions to make it not accessible to the world, and to make it read-only by others in the group.

gtmuser@gtmworkshop7:~$ ls -l .fis-gtm/V5.4-002B_x86/g/gtm.dat
-rw-rw-rw- 1 gtmuser gtmuser 20587008 2011-11-07 14:29 .fis-gtm/V5.4-002B_x86/g/gtm.dat
gtmuser@gtmworkshop7:~$ chmod o-rw,g-w .fis-gtm/V5.4-002B_x86/g/gtm.dat
gtmuser@gtmworkshop7:~$ ls -l .fis-gtm/V5.4-002B_x86/g/gtm.dat
-rw-r----- 1 gtmuser gtmuser 20587008 2011-11-07 14:29 .fis-gtm/V5.4-002B_x86/g/gtm.dat
gtmuser@gtmworkshop7:~$ gtm

GTM>set ^X=1

GTM>zwrite ^X
^X=1

GTM>halt

Create another user who is also a member of the group users. See that usersuser can read from the database owned by gtmuser, but cannot update it.

gtmuser@gtmworkshop7:~$ sudo useradd -g gtmuser -m staffuser
gtmuser@gtmworkshop7:~$ sudo su - staffuser
staffuser@gtmworkshop7:~$ pwd
/home/staffuser
staffuser@gtmworkshop7:~$ export gtm_dist=/usr/lib/fis-gtm/V5.4-002B_x86
staffuser@gtmworkshop7:~$ export gtmver=V5.4-002B_x86
staffuser@gtmworkshop7:~$ export gtmdir=/home/gtmuser/.fis-gtm
staffuser@gtmworkshop7:~$ export gtmgbldir=$gtmdir/$gtmver/g/gtm.gld
staffuser@gtmworkshop7:~$ $gtm_dist/mumps -dir

GTM>zwrite ^X
^X=1

GTM>set ^X=2
%GTM-E-DBPRIVERR, No privilege for attempted update operation for file: /home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.dat

GTM>halt
staffuser@gtmworkshop7:~$ logout
gtmuser@gtmworkshop7:~$ sudo userdel staffuser
gtmuser@gtmworkshop7:~$ grep staffuser /etc/passwd
gtmuser@gtmworkshop7:~$ sudo rm -rf /home/staffuser

There is an installation option to restrict access to GT.M to a group. If you use this option, only those in the specified group will be able to use GT.M.

It is extremely straightforward to create a user id that can only login, run an application and log out.

Exercise – Simulated ASP Environment with Isolation

For this exercise look at the instructions for the WorldVistA EHR Four Slice Toaster MSC Fileman 1034 edition (you may need to download the file and open it in your browser). Alternatively go to the WorldVistA project at Source Forge click on “View all files”, open WorldVistA EHR VOE_ 1.0 and then open 2008-06 Four Slice Toaster MSC FM 1034 and download the file WVEHRVOE10Release6-08Toaster4SliceMSCFM1034Readme.html. Also, download the file WVEHRVOE10Release6-08Toaster4SliceMSCFM1034.zip, unzip it and open it according to the instructions in the Readme.

Login as vistaadmin / vistaadmin for administrator access. Note how Clinic P users are members of the gtm group and also members of the clinicp group and Clinic Q users are members of the gtm group and the clinicq group and neither has access to the databases of the other.

Journaling

You should journal any databases whose integrity you care about. Conversely, you need not journal any database that you are prepared to delete & recreate anew in the event of an untoward event like a system crash.

GT.M, like virtually all high performance databases, uses journaling (called “logging” by some databases) to restore data integrity and provide continuity of business after an unplanned event such as a system crash.

There are two switches to turn on journaling – ENable / DISable and ON/OFF. Enabling or disabling journaling requires stand alone access to the database. Turning journaling on and off can be done when the database is in use.

Exercise – Journaling with the existing database

In this exercise, we will crash your virtual machine and then recover the database. First, we'll just do it on the existing database; then we will set up journaling from scratch.

First, clean out old journal files. Verify that there are no shared memory segments in use. Then go into GT.M and perform a database operation and verify that there is now a new shared memory segment.

gtmuser@gtmworkshop7:~$ rm -f .fis-gtm/V5.4-002B_x86/g/gtm.mjl_*
gtmuser@gtmworkshop7:~$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      natgtmuserh     status

gtmuser@gtmworkshop7:~$ /usr/lib/fis-gtm/V5.4-002B_x86/gtm

GTM>set ^X=$zdate($horolog,"MON DD, YEAR")

GTM>zwrite ^X ; opens database file and creates a shared memory segment
^X="NOV 07, 2011"

GTM>zsystem "ipcs -m"

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      natgtmuserh     status
0x00000000 458752     gtmuser         660        5713920    1


GTM>

Now kill the virtual machine by clicking on the “X” of the console window, and then reboot it. Go back into GT.M and verify that the data is still there. Instead of running the gtm script (which performs an automatic recovery), run mumps and try to access the database. Note: you should not run the gtm script for this exercise, since it performs a recovery as part of its operation.

gtmuser@gtmworkshop7:~$ source /usr/lib/fis-gtm/V5.4-002B_x86/gtmprofile
gtmuser@gtmworkshop7:~$ mumps -dir

GTM>zwrite ^X
%GTM-E-REQRUNDOWN, Error accessing database /home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.dat.  Must be rundown on cluster node gtmworkshop.
%GTM-I-TEXT, Error with database control shmctl
%SYSTEM-E-ENO22, Invalid argument

GTM>zsystem "ls -l $gtmdir/$gtmver/g" ; notice we have a journal file
total 276
-rw-r----- 1 gtmuser gtmuser 20587008 2011-11-07 17:31 gtm.dat
-rw-rw-r-- 1 gtmuser gtmuser     1536 2011-11-02 18:49 gtm.gld
-rw-r----- 1 gtmuser gtmuser    69632 2011-11-07 17:31 gtm.mjl
-rw-r----- 1 gtmuser gtmuser    69632 2011-11-07 17:14 gtm.mjl_2011311171412

GTM>zsystem "ipcs -m" ; and there are no shared memory segments indicating an open database


------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      


GTM>zsystem "ls -lR $gtm_tmp" ; and no log files from the gtm script
/tmp/fis-gtm/V5.4-002B_x86:
total 0

GTM>halt

Now, try the gtm script instead of running the mumps executable directly.

gtmuser@gtmworkshop7:~$ gtm

GTM>zwrite ^X ; database access works
^X="NOV 07, 2011"

GTM>zsystem "ls -l $gtmdir/$gtmver/g" ; there are two new journal files
total 412
-rw-r----- 1 gtmuser gtmuser 20587008 2011-11-07 17:36 gtm.dat
-rw-rw-r-- 1 gtmuser gtmuser     1536 2011-11-02 18:49 gtm.gld
-rw-r----- 1 gtmuser gtmuser    69632 2011-11-07 17:35 gtm.mjl
-rw-r----- 1 gtmuser gtmuser    69632 2011-11-07 17:14 gtm.mjl_2011311171412
-rw-r----- 1 gtmuser gtmuser    69632 2011-11-07 17:35 gtm.mjl_2011311173125
-rw-r----- 1 gtmuser gtmuser    69632 2011-11-07 17:35 gtm.mjl_2011311173556

GTM>zsystem "ipcs -m" ; there is a shared memory segment for the open database file

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 65536      gtmuser    660        5726208    1 


GTM>zsystem "ls -lR $gtm_tmp" ; and log files from the commands in the gtm script
/tmp/fis-gtm/V5.4-002B_x86:
total 8
srwxrwxr-x 1 gtmuser gtmuser   0 2011-11-07 17:36 gtm_mutex000003B0
-rw-rw-r-- 1 gtmuser gtmuser 613 2011-11-07 17:35 gtmuser_20111107223555UTC_mupip_recover
-rw-rw-r-- 1 gtmuser gtmuser 333 2011-11-07 17:35 gtmuser_20111107223555UTC_mupip_set

GTM>halt

How did the recovery happen? The answer is in the gtm script.

gtmuser@gtmworkshop7:~$ cat `which gtm`
#!/bin/sh
#################################################################
#                                                               #
#       Copyright 2010 Fidelity Information Services, Inc       #
#                                                               #
#       This source code contains the intellectual property     #
#       of its copyright holder(s), and is made available       #
#       under a license.  If you do not know the terms of       #
#       the license, please stop and do not read further.       #
#                                                               #
#################################################################
if [ ! -f "/usr/lib/fis-gtm/V5.4-002B_x86"/gtmprofile ] ; then echo Cannot find file "/usr/lib/fis-gtm/V5.4-002B_x86"/gtmprofile to source
else
    . "/usr/lib/fis-gtm/V5.4-002B_x86"/gtmprofile
    timestamp=`date -u +%Y%m%d%H%M%S`"UTC"
    ( cd `dirname $gtmgbldir` ; \
        $gtm_dist/mupip journal -recover -backward "*" 2>$gtm_tmp/"$USER"_$timestamp"_mupip_recover" && \
        $gtm_dist/mupip set -journal="on,before" -region "*" 2>$gtm_tmp/"$USER"_$timestamp"_mupip_set" && \
        find . -name \*.mjl_\* -mtime +$gtm_retention -exec rm -vf {} \; )
    if [ 0 = $# ] ; then
        $gtm_dist/mumps -direct
    else
        $gtm_dist/mumps $*
    fi
    ( cd `dirname $gtmgbldir` \
        $gtm_dist/mupip rundown -region "*" 2>$gtm_tmp/"$USER"_$timestamp"-"`date -u +%Y%m%d%H%M%S`"UTC_mupip_rundown" )
    find $gtm_tmp -name "$USER"_\* -mtime +$gtm_retention -exec rm -f {} \;
fi

gtmuser@gtmworkshop7:~$ 

The mupip journal recover command performs the recovery. Review the output of the mupip commands – as new journal files are created, older journal files being renamed. Each journal file has a back-pointer to its predecessor. The gtm script removes non-current journal files and temporary files, those older than the number of days specified by the $gtm_retention environment variable.

gtmuser@gtmworkshop7:~$ cat $gtm_tmp/gtmuser_20111107223555UTC_mupip_recover
%GTM-I-MUJNLSTAT, Initial processing started at Mon Nov  7 17:35:55 2011
%GTM-I-MUJNLSTAT, Backward processing started at Mon Nov  7 17:35:55 2011
%GTM-I-MUJNLSTAT, Before image applying started at Mon Nov  7 17:35:55 2011
%GTM-I-FILERENAME, File /home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.mjl is renamed to /home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.mjl_2011311173125
%GTM-I-MUJNLSTAT, Forward processing started at Mon Nov  7 17:35:55 2011
%GTM-S-JNLSUCCESS, Show successful
%GTM-S-JNLSUCCESS, Verify successful
%GTM-S-JNLSUCCESS, Recover successful
%GTM-I-MUJNLSTAT, End processing at Mon Nov  7 17:35:56 2011
gtmuser@gtmworkshop7:~$ cat $gtm_tmp/gtmuser_20111107223555UTC_mupip_set 
%GTM-I-FILERENAME, File /home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.mjl is renamed to /home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.mjl_2011311173556
%GTM-I-JNLCREATE, Journal file /home/gtmuser/.fis-gtm/V5.4-002B_x86/g/gtm.mjl created for region DEFAULT with BEFORE_IMAGES
%GTM-I-JNLSTATE, Journaling state for region DEFAULT is now ON
gtmuser@gtmworkshop7:~$ 

Here is an animation of journaling in action:


Notes on File System Configuration

Robust operation of GT.M recovery after a crash requires robust recovery of the file system. If your file system requires an option to ensure that meta-data is written to disk only after the corresponding data is written, ensure that it is set.

Exercise – Journaling from scratch

Create a directory (e.g., myApp) in your home directory. In it create a global directory that maps all variables starting with A or a in aA.dat and others in others.dat. Create the database files. Then enable and turn on before image journaling for both files. Start a process and update both databases. With the process open, kill the virtual machine. Reboot the virtual machine, see for yourself that you cannot access the database, then recover the database (which consists of two database files) and demonstrate that you can now access the database.

Hints:

Database Replication

When an application must have the best possible continuity of business, use database replication in addition to before image journaling to create a logical multi site configuration. A major restriction of GT.M replication today is the 20,000 kilometer distance restriction on replication (since the circumference of Planet Earth is approximately 40,000 kilometers, it is difficult to place data centers more than 20,000 kilometers apart). In our example, we will simulate data centers in Cape Town (34°S, 19°E), Philadelphia (40°N, 75°W) and Shanghai (31°N, 122°E). Cape Town to Philadelphia is 12,550 kilometers, Philadelphia to Shanghai is 11,900 kilometers, and Shanghai to Cape Town is 12,900 kilometers (information courtesy Great Circle Mapper).

Exercise - Replication

Because replication builds on journaling, use the directory myApp created above. Enhance the shell script env to assign values to two more environment variables, $gtm_repl_instance and $gtm_repl_instname. gtm_repl_instance is the name of a replication instance file where a replicated instance stores information about the state of replication and gtm_repl_instance is the name of an instance – in this case, dummy, but we will change it as we create copies of the instances.

gtmuser@gtmworkshop7:~$ cat myApp/env
export gtm_dist=/usr/lib/fis-gtm/V5.4-002B_x86
export gtmgbldir=/home/gtmuser/myApp/gtm.gld
export gtm_log=/tmp/fis-gtm/V5.4-002B_x86
export gtm_principal_editing=EDITING
export gtm_repl_instance=/home/gtmuser/myApp/gtm.repl
export gtm_repl_instname=dummy
export gtmroutines="/home/gtmuser/myApp $gtm_dist"
export gtm_tmp=$gtm_log
mkdir -p $gtm_tmp
alias mumps=$gtm_dist/mumps
alias mupip=$gtm_dist/mupip

Turn on replication and journaling.

gtmuser@gtmworkshop7:~$ mupip set -replication=on -region "*"
%GTM-I-FILERENAME, File /home/gtmuser/myApp/aA.mjl is renamed to /home/gtmuser/myApp/aA.mjl_2011312170809
%GTM-I-JNLCREATE, Journal file /home/gtmuser/myApp/aA.mjl created for region AA with BEFORE_IMAGES
%GTM-I-PREVJNLLINKCUT, Previous journal file name link set to NULL in new journal file /home/gtmuser/myApp/aA.mjl created for database file /home/gtmuser/myApp/aA.dat
%GTM-I-JNLSTATE, Journaling state for region AA is now ON
%GTM-I-REPLSTATE, Replication state for region AA is now ON
%GTM-I-FILERENAME, File /home/gtmuser/myApp/gtm.mjl is renamed to /home/gtmuser/myApp/gtm.mjl_2011312170809
%GTM-I-JNLCREATE, Journal file /home/gtmuser/myApp/gtm.mjl created for region DEFAULT with BEFORE_IMAGES
%GTM-I-PREVJNLLINKCUT, Previous journal file name link set to NULL in new journal file /home/gtmuser/myApp/gtm.mjl created for database file /home/gtmuser/myApp/gtm.dat
%GTM-I-JNLSTATE, Journaling state for region DEFAULT is now ON
%GTM-I-REPLSTATE, Replication state for region DEFAULT is now ON
gtmuser@gtmworkshop7:~$

Create the following shell scripts inside myApp and make them executable:

gtmuser@gtmworkshop7:~$ cat myApp/originating_stop 
#!/bin/sh
$gtm_dist/mupip replicate -source -shutdown -timeout=0
$gtm_dist/mupip rundown -region "*"
gtmuser@gtmworkshop7:~$ cat myApp/replicating_start 
#!/bin/sh
$gtm_dist/mupip replicate -source -start -passive -instsecondary=dummy -buffsize=1048576 -log=/home/gtmuser/myApp/source_dummy.log
$gtm_dist/mupip replicate -receive -start -listenport=3000 -buffsize=1048576 -log=/home/gtmuser/myApp/receive.log
gtmuser@gtmworkshop7:~$ cat myApp/replicating_stop
#!/bin/sh
$gtm_dist/mupip replicate -receive -shutdown -timeout=0
$gtm_dist/mupip replicate -source -shutdown -timeout=0
$gtm_dist/mupip rundown -region "*"
gtmuser@gtmworkshop7:~$ chmod +x myApp/{originating_stop,replicating_*}
gtmuser@gtmworkshop7:~$ ls -l myApp/{originating_stop,replicating_*}
-rwxrwxr-x 1 gtmuser gtmuser 101 2011-11-08 17:30 myApp/originating_stop
-rwxrwxr-x 1 gtmuser gtmuser 239 2011-11-08 17:31 myApp/replicating_start
-rwxrwxr-x 1 gtmuser gtmuser 157 2011-11-08 17:31 myApp/replicating_stop
gtmuser@gtmworkshop7:~$ 

You can delete the prior generation journal files, just to keep the directory clean:

gtmuser@gtmworkshop7:~$ ls -l myApp/*.mjl_*
-rw-rw-rw- 1 gtmuser gtmuser 69632 2011-11-08 17:08 myApp/aA.mjl_2011312170523
-rw-rw-rw- 1 gtmuser gtmuser 69632 2011-11-08 17:08 myApp/aA.mjl_2011312170809
-rw-rw-rw- 1 gtmuser gtmuser 69632 2011-11-08 17:08 myApp/gtm.mjl_2011312170523
-rw-rw-rw- 1 gtmuser gtmuser 69632 2011-11-08 17:08 myApp/gtm.mjl_2011312170809
gtmuser@gtmworkshop7:~$ rm myApp/*.mjl_*
gtmuser@gtmworkshop7:~$ 

Shut down the Acculturation Workshop virtual machine and make three copies of the Acculturation Workshop called Philadelphia.vmdk, Shanghai.vmdk and CapeTown.vmdk. Alternatively, if your host system is short of disk space, make two copies and rename the original ubuntu-11.10_gtmworkshop7.vmdk file.

If you are using qcow2 or vmdk disk images with QEMU/kvm on Linux, you can use a feature that allows a disk image to be created off a base image so that the base image does not change and all changes go to the new disk image. Check with your virtualization software to determine whether it supports this feature. Execute the following commands on the host (with the guest shut down) – depending on the version of QEMU/kvm on your PC, the exact command may vary

$ qemu-img create -f vmdk -o backing_file=ubuntu-11.10_gtmworkshop7.vmdk CapeTown.vmdk
Formatting 'CapeTown.vmdk', fmt=vmdk size=8589934592 backing_file='ubuntu-11.10_gtmworkshop7.vmdk' compat6=off
$ qemu-img create -f vmdk -o backing_file=ubuntu-11.10_gtmworkshop7.vmdk Philadelphia.vmdk
Formatting 'Philadelphia.vmdk', fmt=vmdk size=8589934592 backing_file='ubuntu-11.10_gtmworkshop7.vmdk' compat6=off
$ qemu-img create -f vmdk -o backing_file=ubuntu-11.10_gtmworkshop7.vmdk Shanghai.vmdk
Formatting 'Shanghai.vmdk', fmt=vmdk size=8589934592 backing_file='ubuntu-11.10_gtmworkshop7.vmdk' compat6=off
$

Now boot the three virtual machines. Each virtual machine will need two ports to be forwarded from the host, one for ssh access forwarded to port 22 on each virtual machine and one for replication forwarded to port 3000 on each virtual machine (i.e., a total of six ports on the host for the three instances). The examples here use host ports 2221 & 4000 for Cape Town, 2222 & 5000 for Philadelphia, and 2223 & 6000 for Shanghai. The commands given here use kvm on Linux – use the commands appropriate to virtualization on your host).

kvm -enable-kvm -cpu host -net nic -net user,hostfwd=tcp::2221-:22,hostfwd=tcp::4000-:3000 -hda CapeTown.vmdk&
kvm -enable-kvm -cpu host -net nic -net user,hostfwd=tcp::2222-:22,hostfwd=tcp::5000-:3000 -hda Philadelphia.vmdk&
kvm -enable-kvm -cpu host -net nic -net user,hostfwd=tcp::2223-:22,hostfwd=tcp::6000-:3000 -hda Shanghai.vmdk&

To avoid confusion when you are working with multiple machine, change the name of each machine from GT.M workshop to its location. The examples here are from the Cape Town machine. You should do likewise with Philadelphia and Shanghai. To effect a name change will need to (as root) edit the files /etc/hosts and /etc/hostname to change gtmworkshop to capetown and then reboot.

gtmuser@capetown:~$ cat /etc/hosts
127.0.0.1       localhost
127.0.1.1       capetown

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
gtmuser@capetown:~$ cat /etc/hostname
capetown
gtmuser@capetown:~$ 

You may also want to change the window / tab labels on your terminal emulator on the host to show which machine each session is connected to, for example:




To make it more realistic (and to reduce the probability of operator error) on each machine, execute sudo dpkg-reconfigure tzdata to specify the “local” time zone. Select New York for Philadelphia and Johannesburg for Cape Town.

In each machine. Edit myApp/env in each instance and change the line export gtm_repl_instname=dummy and the line export gtm_repl_instance=/home/gtmuser/myApp/gtm.repl to an instance file name for that instance. For example, on the Cape Town instance:

gtmuser@capetown:~$ cat myApp/env 
export gtm_dist=/usr/lib/fis-gtm/V5.4-002B_x86
export gtmgbldir=/home/gtmuser/myApp/gtm.gld
export gtm_log=/tmp/fis-gtm/V5.4-002B_x86
export gtm_principal_editing=EDITING
export gtm_repl_instance=/home/myApp/capetown.repl
export gtm_repl_instname=CapeTown
export gtmroutines="/home/gtmuser/myApp $gtm_dist"
export gtm_tmp=$gtm_log
mkdir -p $gtm_tmp
alias mumps=$gtm_dist/mumps
alias mupip=$gtm_dist/mupip
gtmuser@capetown:~$

Then on each instance create a replication instance file.

gtmuser@capetown:~$ ls -l myApp/*.repl
ls: cannot access myApp/*.repl: No such file or directory
gtmuser@capetown:~$ source myApp/env 
gtmuser@capetown:~$ mupip replicate -instance_create
gtmuser@capetown:~$ ls -l myApp/*.repl
-rw-rw-r-- 1 gtmuser gtmuser 1536 2011-11-10 00:47 myApp/capetown.repl
gtmuser@capetown:~$  

Start the configuration with Philadelphia as the originating primary instance, and Cape Town and Shanghai in replicating secondary roles. The following commands, on the three instances can be executed in any order.

Start Shanghai as a replicating instance.

gtmuser@shanghai:~$ myApp/replicating_start 
Thu Nov 10 06:56:40 2011 : Initiating START of source server for secondary instance [dummy]
gtmuser@shanghai:~$ 

Start Cape Town as a replicating instance.

gtmuser@capetown:~$ myApp/replicating_start
Thu Nov 10 00:57:33 2011 : Initiating START of source server for secondary instance [dummy]
gtmuser@capetown:~$

Start Philadelphia as an originating instance replicating to Cape Town and Shanghai (notice the use of ports on the host to reach the different replicating instances in the virtual machines).

gtmuser@philadelphia:~$ mupip replicate -source -start -instsecondary=CapeTown -secondary=10.0.2.2:4000 -buffsize=1048576 -log=/home/gtmuser/myApp/source_CapeTown.log
Wed Nov  9 17:59:39 2011 : Initiating START of source server for secondary instance [CapeTown]
gtmuser@philadelphia:~$ mupip replicate -source -start -instsecondary=Shanghai -secondary=10.0.2.2:6000 -buffsize=1048576 -log=/home/gtmuser/myApp/source_Shanghai.log
Wed Nov  9 18:00:35 2011 : Initiating START of source server for secondary instance [Shanghai]
gtmuser@philadelphia:~$

Start a GT.M process in Philadelphia and perform some database updates:

gtmuser@philadelphia:~$ mumps -dir

GTM>set ^Weather("Philadelphia",$Piece($Horolog,",",1),$Piece($Horolog,",",2))="Rainy"

Verify that the data is replicated at Cape Town and Shanghai. Execute the following at both instances:

gtmuser@shanghai:~$ mumps -dir

GTM>zwrite ^Weather
^Weather("Philadelphia",62404,64970)="Rainy"

GTM>

Bring down Shanghai (simulating system maintenance, or a network outage), but leave Cape Town untouched.

gtmuser@shanghai:~$ myApp/replicating_stop 
Signalling immediate shutdown
Thu Nov 10 07:04:29 2011 : Initiating shut down
Thu Nov 10 07:04:30 2011 : Receive pool shared memory removed
Thu Nov 10 07:04:30 2011 : Receive pool semaphore removed
Thu Nov 10 07:04:30 2011 : Signalling shutdown immediate
Thu Nov 10 07:04:30 2011 : Initiating SHUTDOWN operation on source server pid [867] for secondary instance [dummy]
Thu Nov 10 07:04:30 2011 : Waiting for upto [180] seconds for the source server to shutdown
Thu Nov 10 07:04:31 2011 : Journal pool shared memory removed
Thu Nov 10 07:04:31 2011 : Journal pool semaphore removed
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/aA.dat successfully rundown
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/gtm.dat successfully rundown
gtmuser@shanghai:~$ 

Create another update in Philadelphia.

GTM>set ^Weather("Philadelphia",$Piece($Horolog,",",1),$Piece($Horolog,",",2))="Sunny"

Verify that this is updated in Cape Town.

GTM>zwrite ^Weather
^Weather("Philadelphia",62404,64970)="Rainy"
^Weather("Philadelphia",62404,66663)="Sunny"

GTM>

But it is not replicated in Shanghai.

GTM>zwrite ^Weather
^Weather("Philadelphia",62404,64970)="Rainy"

GTM>

Restart Shanghai as a replicating instance and notice that it catches up with updates at the originating instance when replication was not active in Shanghai.

gtmuser@shanghai:~$ myApp/replicating_start 
Thu Nov 10 07:33:47 2011 : Initiating START of source server for secondary instance [dummy]
gtmuser@shanghai:~$ mumps -dir

GTM>zwrite ^Weather
^Weather("Philadelphia",62404,64970)="Rainy"
^Weather("Philadelphia",62404,66663)="Sunny"

GTM>

Now, simulate an unplanned outage of Philadelphia by clicking on the “X” of the virtual machine console window, kill -9 of the process on the host, or otherwise powering down the virtual machine. Make Shanghai the new originating instance Cape Town its replicating instance. [In a controlled switchover / planned outage, bringing down the originating primary first helps to ensure that you do not have two concurrently operating originating primary instances.]

Bring down Shanghai as a replicating instance and bring it up as the originating instance. Notice that you can bring up the Source Server process to replicate to Philadelphia – it will make the connection when Philadelphia comes up.

gtmuser@shanghai:~$ myApp/replicating_stop
Signalling immediate shutdown
Thu Nov 10 07:41:49 2011 : Initiating shut down
Thu Nov 10 07:41:50 2011 : Receive pool shared memory removed
Thu Nov 10 07:41:50 2011 : Receive pool semaphore removed
Thu Nov 10 07:41:50 2011 : Signalling shutdown immediate
Thu Nov 10 07:41:50 2011 : Initiating SHUTDOWN operation on source server pid [885] for secondary instance [dummy]
Thu Nov 10 07:41:50 2011 : Waiting for upto [180] seconds for the source server to shutdown
Thu Nov 10 07:41:52 2011 : Journal pool shared memory removed
Thu Nov 10 07:41:52 2011 : Journal pool semaphore removed
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/aA.dat successfully rundown
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/gtm.dat successfully rundown
gtmuser@shanghai:~$ mupip replicate -source -start -instsecondary=CapeTown -secondary=10.0.2.2:4000 -buffsize=1048576 -log=/home/gtmuser/myApp/source_CapeTown.log
Thu Nov 10 07:42:36 2011 : Initiating START of source server for secondary instance [CapeTown]
gtmuser@shanghai:~$ mupip replicate -source -start -instsecondary=Philadelphia -secondary=10.0.2.2:5000 -buffsize=1048576 -log=/home/gtmuser/myApp/source_Philadelphia.log
Thu Nov 10 07:43:20 2011 : Initiating START of source server for secondary instance [Philadelphia]
gtmuser@shanghai:~$ 

Both Cape Town and Philadelphia should perform a rollback fetchresync before they become secondary instances to Shanghai. First Cape Town (since Philadelphia has crashed and is down; notice that the times look very different because they show times in their local timezones).

gtmuser@capetown:~$ myApp/replicating_stop
Signalling immediate shutdown
Thu Nov 10 01:46:06 2011 : Initiating shut down
Thu Nov 10 01:46:07 2011 : Receive pool shared memory removed
Thu Nov 10 01:46:07 2011 : Receive pool semaphore removed
Thu Nov 10 01:46:07 2011 : Signalling shutdown immediate
Thu Nov 10 01:46:07 2011 : Initiating SHUTDOWN operation on source server pid [1010] for secondary instance [dummy]
Thu Nov 10 01:46:07 2011 : Waiting for upto [180] seconds for the source server to shutdown
Thu Nov 10 01:46:09 2011 : Journal pool shared memory removed
Thu Nov 10 01:46:09 2011 : Journal pool semaphore removed
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/aA.dat successfully rundown
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/gtm.dat successfully rundown
gtmuser@capetown:~$ mupip journal -rollback -backward -fetchresync=3000 -losttrans=/home/gtmuser/myApp/Unreplic_Trans_Report_`date +%Y%m%d%H%M%S`.txt "*"
%GTM-I-MUJNLSTAT, Initial processing started at Thu Nov 10 01:46:40 2011
%GTM-I-MUJNLSTAT, FETCHRESYNC processing started at Thu Nov 10 01:46:40 2011
Thu Nov 10 01:46:40 2011 : Assuming primary supports multisite functionality. Connecting using multisite communication protocol.
Thu Nov 10 01:46:40 2011 : Waiting for a connection...
Thu Nov 10 01:46:41 2011 : Connection established, using TCP send buffer size 16384 receive buffer size 87380
Thu Nov 10 01:46:41 2011 : Connection information:: Local: 10.0.2.15:3000 Remote: 10.0.2.2:36568
Thu Nov 10 01:46:41 2011 : Sending REPL_FETCH_RESYNC message with seqno 3 [0x3]
Thu Nov 10 01:46:41 2011 : Received REPL_NEED_INSTANCE_INFO message from primary instance [Shanghai]
Thu Nov 10 01:46:41 2011 : Sending REPL_INSTANCE_INFO message
Thu Nov 10 01:46:41 2011 : Received REPL_NEED_TRIPLE_INFO message for seqno 3 [0x3]
Thu Nov 10 01:46:41 2011 : Sending REPL_TRIPLE_INFO1 message with seqno 1 [0x1]
Thu Nov 10 01:46:41 2011 : Sending REPL_TRIPLE_INFO2 message with seqno 1 [0x1]
Thu Nov 10 01:46:41 2011 : Triple Sent with Start Seqno = 1 [0x1] : Root Primary = [Philadelphia] : Cycle = [1]
Thu Nov 10 01:46:41 2011 : Received REPL_RESYNC_SEQNO message
Thu Nov 10 01:46:41 2011 : Received RESYNC SEQNO is 3 [0x3]
%GTM-I-MUJNLSTAT, Backward processing started at Thu Nov 10 01:46:41 2011
%GTM-I-RESOLVESEQNO, Resolving until sequence number 0x0000000000000003
%GTM-I-MUJNLSTAT, Before image applying started at Thu Nov 10 01:46:41 2011
%GTM-I-FILERENAME, File /home/gtmuser/myApp/aA.mjl is renamed to /home/gtmuser/myApp/aA.mjl_2011313002048
%GTM-I-FILERENAME, File /home/gtmuser/myApp/gtm.mjl is renamed to /home/gtmuser/myApp/gtm.mjl_2011314014608
%GTM-I-MUJNLSTAT, Forward processing started at Thu Nov 10 01:46:42 2011
%GTM-I-FILECREATE, Lost transactions extract file /home/gtmuser/myApp/Unreplic_Trans_Report_20111110014640.txt created
%GTM-I-RLBKJNSEQ, Journal seqno of the instance after rollback is 3 [0x0000000000000003]
%GTM-S-JNLSUCCESS, Show successful
%GTM-S-JNLSUCCESS, Verify successful
%GTM-S-JNLSUCCESS, Rollback successful
%GTM-I-MUJNLSTAT, End processing at Thu Nov 10 01:46:42 2011
gtmuser@capetown:~$ myApp/replicating_start
Thu Nov 10 01:46:59 2011 : Initiating START of source server for secondary instance [dummy]
gtmuser@capetown:~$ 

Note that the Unreplicated Transaction File has no transactions rolled back:

gtmuser@capetown:~$ cat myApp/Unreplic_Trans_Report_20111110014640.txt 
GDSJEX05 ROLLBACK SECONDARY CapeTown
02\62405,6368\4\1010\0
03\62405,6368\4\1010\0\3

gtmuser@capetown:~$ 

Now reboot Philadelphia to simulate its recovery. When the system comes up (before performing any other database access), perform a rollback fetchresync.

gtmuser@philadelphia:~$ mupip journal -rollback -backward -fetchresync=3000 -losttrans=/home/gtmuser/myApp/Unreplic_Trans_Report_`date +%Y%m%d%H%M%S`.txt "*"
%GTM-I-MUJNLSTAT, Initial processing started at Wed Nov  9 18:53:00 2011
%GTM-I-MUJNLSTAT, FETCHRESYNC processing started at Wed Nov  9 18:53:00 2011
Wed Nov  9 18:53:00 2011 : Assuming primary supports multisite functionality. Connecting using multisite communication protocol.
Wed Nov  9 18:53:00 2011 : Waiting for a connection...
Wed Nov  9 18:53:00 2011 : Connection established, using TCP send buffer size 16384 receive buffer size 87380
Wed Nov  9 18:53:00 2011 : Connection information:: Local: 10.0.2.15:3000 Remote: 10.0.2.2:49063
Wed Nov  9 18:53:00 2011 : Sending REPL_FETCH_RESYNC message with seqno 3 [0x3]
Wed Nov  9 18:53:00 2011 : Received REPL_NEED_INSTANCE_INFO message from primary instance [Shanghai]
Wed Nov  9 18:53:00 2011 : Sending REPL_INSTANCE_INFO message
Wed Nov  9 18:53:00 2011 : Received REPL_NEED_TRIPLE_INFO message for seqno 3 [0x3]
Wed Nov  9 18:53:00 2011 : Sending REPL_TRIPLE_INFO1 message with seqno 1 [0x1]
Wed Nov  9 18:53:00 2011 : Sending REPL_TRIPLE_INFO2 message with seqno 1 [0x1]
Wed Nov  9 18:53:00 2011 : Triple Sent with Start Seqno = 1 [0x1] : Root Primary = [Philadelphia] : Cycle = [1]
Wed Nov  9 18:53:00 2011 : Received REPL_RESYNC_SEQNO message
Wed Nov  9 18:53:00 2011 : Received RESYNC SEQNO is 3 [0x3]
%GTM-I-MUJNLSTAT, Backward processing started at Wed Nov  9 18:53:00 2011
%GTM-I-RESOLVESEQNO, Resolving until sequence number 0x0000000000000003
%GTM-I-MUJNLSTAT, Before image applying started at Wed Nov  9 18:53:00 2011
%GTM-I-FILERENAME, File /home/gtmuser/myApp/aA.mjl is renamed to /home/gtmuser/myApp/aA.mjl_2011312172048
%GTM-I-FILERENAME, File /home/gtmuser/myApp/gtm.mjl is renamed to /home/gtmuser/myApp/gtm.mjl_2011313183109
%GTM-I-MUJNLSTAT, Forward processing started at Wed Nov  9 18:53:01 2011
%GTM-I-FILECREATE, Lost transactions extract file /home/gtmuser/myApp/Unreplic_Trans_Report_20111109185300.txt created
%GTM-I-RLBKJNSEQ, Journal seqno of the instance after rollback is 3 [0x0000000000000003]
%GTM-S-JNLSUCCESS, Show successful
%GTM-S-JNLSUCCESS, Verify successful
%GTM-S-JNLSUCCESS, Rollback successful
%GTM-I-MUJNLSTAT, End processing at Wed Nov  9 18:53:01 2011
gtmuser@philadelphia:~$ myApp/replicating_start
Wed Nov  9 18:53:15 2011 : Initiating START of source server for secondary instance [dummy]
gtmuser@philadelphia:~$ 

Now, create a database update in Shanghai.

GTM>set ^Weather("Shanghai",$Piece($Horolog,",",1),$Piece($Horolog,",",2))="Stormy"

And confirm that it is replicated to both Cape Town and Philadelphia.

GTM>zwrite ^Weather
^Weather("Philadelphia",62404,64970)="Rainy"
^Weather("Philadelphia",62404,66663)="Sunny"
^Weather("Shanghai",62406,2715)="Stormy"

Shut down all three instances cleanly to end the exercise. On the originating instance in Shanghai.

gtmuser@shanghai:~$ myApp/originating_stop
Fri Nov 11 00:47:24 2011 : Signalling shutdown immediate
Fri Nov 11 00:47:24 2011 : Initiating SHUTDOWN operation on source server pid [896] for secondary instance [CapeTown]
Fri Nov 11 00:47:24 2011 : Initiating SHUTDOWN operation on source server pid [898] for secondary instance [Philadelphia]
Fri Nov 11 00:47:24 2011 : Waiting for upto [180] seconds for the source server to shutdown
Fri Nov 11 00:47:25 2011 : Journal pool shared memory removed
Fri Nov 11 00:47:25 2011 : Journal pool semaphore removed
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/aA.dat successfully rundown
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/gtm.dat successfully rundown
gtmuser@shanghai:~$

And on the replicating instances in Cape Town and Philadelphia, execute myApp/replicating_stop to stop the replication.

gtmuser@philadelphia:~$ myAppSignalling immediate shutdown
Thu Nov 10 11:50:38 2011 : Initiating shut down
Thu Nov 10 11:50:39 2011 : Receive pool shared memory removed
Thu Nov 10 11:50:39 2011 : Receive pool semaphore removed
Thu Nov 10 11:50:39 2011 : Signalling shutdown immediate
Thu Nov 10 11:50:39 2011 : Initiating SHUTDOWN operation on source server pid [862] for secondary instance [dummy]
Thu Nov 10 11:50:39 2011 : Waiting for upto [180] seconds for the source server to shutdown
Thu Nov 10 11:50:40 2011 : Journal pool shared memory removed
Thu Nov 10 11:50:40 2011 : Journal pool semaphore removed
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/aA.dat successfully rundown
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/gtm.dat successfully rundown
gtmuser@philadelphia:~$

Replication and Backlogs

In an ideal world, an originating instance never goes down with a backlog. In the real world, it may well go down with a backlog of updates that have not been replicated. In order to provide continuity of business, when an originating instance goes down a former replicating instance must come up as the new originating to keep the application available. When the former primary comes up as the new secondary, the updates that were part of the backlog must be handled. GT.M provides the hooks needed to create applications that are continuously available, but the application must take advantage of these hooks. Consider the following two-instance example (the notation P: 100 means that the site is operating as the primary and has committed update number 100):

Cape Town

Shanghai

P:100

S: 95 (backlog of 5 updates)

Crashes

P: 95 (becomes the originating instance, starts processing and keeps the organization operational)

Repaired and brought back up

P: 120 (processing has moved it ahead)



This situation needs to be remedied, because updates (transactions) 96-100 on Cape Town are different from updates 96-100 on Shanghai. This has a GT.M part and an application software part. The GT.M part is to rollback the transactions on the former originating with the mupip journal -rollback -fetchresync command. These rolled back updates (“unreplicated” or “lost” transactions) are placed in a file and must be transmitted to the new originating instance for reprocessing / reconciliation by application logic.

Cape Town

Shanghai

S: 95 (database rolled back; updates 96-100 in unreplicated transaction file)

P: 120

S: 120 (catches up with Shanghai once replication resumes)

P: 120 (receives unreplicated transaction file)

S: 125 (unreplicated transactions make it back after reprocessing)

P: 125 (processing unreplicated transactions moves it ahead)



Adding Philadelphia to the example above complicates it only slightly. There are two cases to consider when Cape Town crashes:

Exercise – Replication and Backlogs

This exercise simulates a replication with a backlog (smaller than the five updates in the example above). Start with Cape Town as the originating instance replicating to Philadelphia and Shanghai as replicating instances. Since Cape Town most recently was a secondary instance, you should start with mupip journal -rollback -fetchresync in Shanghai and Philadelphia.

Start Cape Town as the originating instance:

gtmuser@capetown:~$ mupip replicate -source -start -instsecondary=Philadelphia -secondary=10.0.2.2:5000 -buffsize=1048576 -log=/home/gtmuser/myApp/source_Philadelphia.log
Thu Nov 10 19:01:51 2011 : Initiating START of source server for secondary instance [Philadelphia]
gtmuser@capetown:~$ mupip replicate -source -start -instsecondary=Shanghai -secondary=10.0.2.2:6000 -buffsize=1048576 -log=/home/gtmuser/myApp/source_Shanghai.log
Thu Nov 10 19:02:04 2011 : Initiating START of source server for secondary instance [Shanghai]
gtmuser@capetown:~$ 

At Philadelphia (and also in Shanghai) perform the fetchresync operation and then start replication. You can ask GT.M to tell you the health of replication and also the replication backlog.

gtmuser@philadelphia:~$ mupip journal -rollback -backward -fetchresync=3000 -losttrans=/home/gtmuser/myApp/Unreplic_Trans_Report_`date +%Y%m%d%H%M%S`.txt "*"
%GTM-I-MUJNLSTAT, Initial processing started at Thu Nov 10 12:03:02 2011
%GTM-I-MUJNLSTAT, FETCHRESYNC processing started at Thu Nov 10 12:03:02 2011
Thu Nov 10 12:03:02 2011 : Assuming primary supports multisite functionality. Connecting using multisite communication protocol.
Thu Nov 10 12:03:02 2011 : Waiting for a connection...
Thu Nov 10 12:03:03 2011 : Connection established, using TCP send buffer size 16384 receive buffer size 87380
Thu Nov 10 12:03:03 2011 : Connection information:: Local: 10.0.2.15:3000 Remote: 10.0.2.2:51018
Thu Nov 10 12:03:03 2011 : Sending REPL_FETCH_RESYNC message with seqno 4 [0x4]
Thu Nov 10 12:03:03 2011 : Received REPL_NEED_INSTANCE_INFO message from primary instance [CapeTown]
Thu Nov 10 12:03:03 2011 : Sending REPL_INSTANCE_INFO message
Thu Nov 10 12:03:03 2011 : Received REPL_NEED_TRIPLE_INFO message for seqno 4 [0x4]
Thu Nov 10 12:03:03 2011 : Sending REPL_TRIPLE_INFO1 message with seqno 3 [0x3]
Thu Nov 10 12:03:03 2011 : Sending REPL_TRIPLE_INFO2 message with seqno 3 [0x3]
Thu Nov 10 12:03:03 2011 : Triple Sent with Start Seqno = 3 [0x3] : Root Primary = [Shanghai] : Cycle = [1]
Thu Nov 10 12:03:03 2011 : Received REPL_RESYNC_SEQNO message
Thu Nov 10 12:03:03 2011 : Received RESYNC SEQNO is 4 [0x4]
%GTM-I-MUJNLSTAT, Backward processing started at Thu Nov 10 12:03:03 2011
%GTM-I-RESOLVESEQNO, Resolving until sequence number 0x0000000000000004
%GTM-I-MUJNLSTAT, Before image applying started at Thu Nov 10 12:03:03 2011
%GTM-I-FILERENAME, File /home/gtmuser/myApp/aA.mjl is renamed to /home/gtmuser/myApp/aA.mjl_2011313185301
%GTM-I-FILERENAME, File /home/gtmuser/myApp/gtm.mjl is renamed to /home/gtmuser/myApp/gtm.mjl_2011314115040
%GTM-I-MUJNLSTAT, Forward processing started at Thu Nov 10 12:03:04 2011
%GTM-I-FILECREATE, Lost transactions extract file /home/gtmuser/myApp/Unreplic_Trans_Report_20111110120302.txt created
%GTM-I-RLBKJNSEQ, Journal seqno of the instance after rollback is 4 [0x0000000000000004]
%GTM-S-JNLSUCCESS, Show successful
%GTM-S-JNLSUCCESS, Verify successful
%GTM-S-JNLSUCCESS, Rollback successful
%GTM-I-MUJNLSTAT, End processing at Thu Nov 10 12:03:04 2011
gtmuser@philadelphia:~$ myApp/replicating_start
Thu Nov 10 12:06:10 2011 : Initiating START of source server for secondary instance [dummy]
gtmuser@philadelphia:~$ mupip replicate -receiver -checkhealth
PID 910 Receiver server is alive
PID 911 Update process is alive
gtmuser@philadelphia:~$ mupip replicate -receiver -showbacklog
0 : number of backlog transactions received by receiver server and yet to be processed by update process
3 : sequence number of last transaction received from Source Server and written to receive pool
3 : sequence number of last transaction processed by update process
gtmuser@philadelphia:~$ 

You can also check replication health and the backlog on the originating instance, Cape Town. Notice that if you do not specify which replication connection you want details for, you get information on all.

gtmuser@capetown:~$ mupip replicate -source -checkhealth
Thu Nov 10 20:37:07 2011 : Initiating CHECKHEALTH operation on source server pid [1090] for secondary instance name [Philadelphia]
PID 1090 Source server is alive in ACTIVE mode
Thu Nov 10 20:37:07 2011 : Initiating CHECKHEALTH operation on source server pid [1092] for secondary instance name [Shanghai]
PID 1092 Source server is alive in ACTIVE mode
gtmuser@capetown:~$ mupip replicate -source -showbacklog
Thu Nov 10 20:37:24 2011 : Initiating SHOWBACKLOG operation on source server pid [1090] for secondary instance [Philadelphia]
0 : backlog number of transactions written to journal pool and yet to be sent by the source server
3 : sequence number of last transaction written to journal pool
3 : sequence number of last transaction sent by source server
Thu Nov 10 20:37:24 2011 : Initiating SHOWBACKLOG operation on source server pid [1092] for secondary instance [Shanghai]
0 : backlog number of transactions written to journal pool and yet to be sent by the source server
3 : sequence number of last transaction written to journal pool
3 : sequence number of last transaction sent by source server
gtmuser@capetown:~$ 

Now create an update in Cape Town.

GTM>set ^Weather("Cape Town",$Piece($Horolog,",",1),$Piece($Horolog,",",2))="Snowing"

Verify that it is replicated in Philadelphia and Shanghai.

GTM>ZWRite ^Weather
^Weather("Cape Town",62405,74316)="Snowing"
^Weather("Philadelphia",62404,64970)="Rainy"
^Weather("Philadelphia",62404,66663)="Sunny"
^Weather("Shanghai",62406,2715)="Stormy"

Now simulate a failure with a backlog by first shutting down replication in Shanghai, and then making an update in Cape Town. In Shanghai:

gtmuser@shanghai:~$ myApp/replicating_stop
Signalling immediate shutdown
Fri Nov 11 02:40:38 2011 : Initiating shut down
Fri Nov 11 02:40:39 2011 : Receive pool shared memory removed
Fri Nov 11 02:40:39 2011 : Receive pool semaphore removed
Fri Nov 11 02:40:39 2011 : Signalling shutdown immediate
Fri Nov 11 02:40:39 2011 : Initiating SHUTDOWN operation on source server pid [941] for secondary instance [dummy]
Fri Nov 11 02:40:39 2011 : Waiting for upto [180] seconds for the source server to shutdown
Fri Nov 11 02:40:40 2011 : Journal pool shared memory removed
Fri Nov 11 02:40:40 2011 : Journal pool semaphore removed
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/aA.dat successfully rundown
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/gtm.dat successfully rundown
gtmuser@shanghai:~$ 

In Cape Town:

GTM>set ^Weather("Cape Town",$Piece($Horolog,",",1),$Piece($Horolog,",",2))="Blizzards"

GTM>zsystem "$gtm_dist/mupip replicate -source -showbacklog"
Thu Nov 10 20:42:36 2011 : Initiating SHOWBACKLOG operation on source server pid [1090] for secondary instance [Philadelphia]
0 : backlog number of transactions written to journal pool and yet to be sent by the source server
5 : sequence number of last transaction written to journal pool
5 : sequence number of last transaction sent by source server
Thu Nov 10 20:42:36 2011 : Initiating SHOWBACKLOG operation on source server pid [1092] for secondary instance [Shanghai]
1 : backlog number of transactions written to journal pool and yet to be sent by the source server
5 : sequence number of last transaction written to journal pool
4 : sequence number of last transaction sent by source server

GTM>

Notice that there is a backlog to Shanghai, but none to Philadelphia. Now shut down replication in Philadelphia and make another update in Cape Town. Verify that there is a backlog of 1 to Philadelphia and 2 to Shanghai.

In Philadelphia:

gtmuser@philadelphia:~$ myApp/replicating_stop
Signalling immediate shutdown
Thu Nov 10 14:43:05 2011 : Initiating shut down
Thu Nov 10 14:43:06 2011 : Receive pool shared memory removed
Thu Nov 10 14:43:06 2011 : Receive pool semaphore removed
Thu Nov 10 14:43:06 2011 : Signalling shutdown immediate
Thu Nov 10 14:43:06 2011 : Initiating SHUTDOWN operation on source server pid [908] for secondary instance [dummy]
Thu Nov 10 14:43:06 2011 : Waiting for upto [180] seconds for the source server to shutdown
Thu Nov 10 14:43:07 2011 : Journal pool shared memory removed
Thu Nov 10 14:43:07 2011 : Journal pool semaphore removed
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/aA.dat successfully rundown
%GTM-I-MUFILRNDWNSUC, File /home/gtmuser/myApp/gtm.dat successfully rundown
gtmuser@philadelphia:~$ 

In Cape Town:

gtmuser@capetown:~$ mumps -dir

GTM>set ^Weather("Cape Town",$Piece($Horolog,",",1),$Piece($Horolog,",",2))="Cloudy"

GTM>zwrite ^Weather
^Weather("Cape Town",62405,74316)="Snowing"
^Weather("Cape Town",62405,74509)="Blizzards"
^Weather("Cape Town",62405,78542)="Cloudy"
^Weather("Philadelphia",62404,64970)="Rainy"
^Weather("Philadelphia",62404,66663)="Sunny"
^Weather("Shanghai",62406,2715)="Stormy"

GTM>zsystem "$gtm_dist/mupip replicate -source -showbacklog"
Thu Nov 10 21:50:21 2011 : Initiating SHOWBACKLOG operation on source server pid [1090] for secondary instance [Philadelphia]
1 : backlog number of transactions written to journal pool and yet to be sent by the source server
6 : sequence number of last transaction written to journal pool
5 : sequence number of last transaction sent by source server
Thu Nov 10 21:50:21 2011 : Initiating SHOWBACKLOG operation on source server pid [1092] for secondary instance [Shanghai]
2 : backlog number of transactions written to journal pool and yet to be sent by the source server
6 : sequence number of last transaction written to journal pool
4 : sequence number of last transaction sent by source server

GTM>

Now crash Cape Town. You have a choice of bringing up Philadelphia or Shanghai. If you don't have time to make decision as to which replicating instance to make the new primary, just choose the most convenient. If you have time to make a decision, you can see which one is further ahead by looking at the “Region Seqno” field in the database file header with DSE (in a multi-region database, you need to look at all replicated regions and take the maximum).

In Philadelphia (note the use of the find -region DSE command):

gtmuser@philadelphia:~$ $gtm_dist/dse


File    /home/gtmuser/myApp/aA.dat
Region  AA

DSE> dump -fileheader

File            /home/gtmuser/myApp/aA.dat
Region          AA
Date/Time       10-NOV-2011 14:52:59 [$H = 62405,53579]
  Access method                          BG  Global Buffers                1000
  Reserved Bytes                          0  Block size (in bytes)         4096
  Maximum record size                  4080  Starting VBN                   129
  Maximum key size                      255  Total blocks            0x00001392
  Null subscripts                     NEVER  Free blocks             0x00001384
  Standard Null Collation              TRUE  Free space              0x00000000
  Last Record Backup     0x0000000000000001  Extension Count              10000
  Last Database Backup   0x0000000000000001  Number of local maps            10
  Last Bytestream Backup 0x0000000000000001  Lock space              0x00000028
  In critical section            0x00000000  Timers pending                   0
  Cache freeze id                0x00000000  Flush timer            00:00:01:00
  Freeze match                   0x00000000  Flush trigger                  938
  Current transaction    0x0000000000000002  No. of writes/flush              7
  Maximum TN             0xFFFFFFFFE3FFFFFF  Certified for Upgrade to        V5
  Maximum TN Warn        0xFFFFFFFF73FFFFFF  Desired DB Format               V5
  Master Bitmap Size                    112  Blocks to Upgrade       0x00000000
  Create in progress                  FALSE  Modified cache blocks            0
  Reference count                         1  Wait Disk                        0
  Journal State               [inactive] ON  Journal Before imaging        TRUE
  Journal Allocation                   2048  Journal Extension             2048
  Journal Buffer Size                   128  Journal Alignsize             2048
  Journal AutoSwitchLimit           8386560  Journal Epoch Interval         300
  Journal Yield Limit                     8  Journal Sync IO              FALSE
  Journal File: /home/gtmuser/myApp/aA.mjl
  Mutex Hard Spin Count                 128  Mutex Sleep Spin Count         128
  Mutex Spin Sleep Time                2048  KILLs in progress                0
  Replication State                      ON  Region Seqno    0x0000000000000004
  Zqgblmod Seqno         0x0000000000000003  Zqgblmod Trans  0x0000000000000002
  Endian Format                      LITTLE  Commit Wait Spin Count          16
  Database file encrypted             FALSE
DSE> find -region
List of global directory:       


File    /home/gtmuser/myApp/aA.dat
Region  AA

File    /home/gtmuser/myApp/gtm.dat
Region  DEFAULT
DSE> find -region=DEFAULT

File    /home/gtmuser/myApp/gtm.dat
Region  DEFAULT

DSE> dump -fileheader

File            /home/gtmuser/myApp/gtm.dat
Region          DEFAULT
Date/Time       10-NOV-2011 14:54:59 [$H = 62405,53699]
  Access method                          BG  Global Buffers                1000
  Reserved Bytes                          0  Block size (in bytes)         4096
  Maximum record size                  4080  Starting VBN                   129
  Maximum key size                      255  Total blocks            0x00001392
  Null subscripts                     NEVER  Free blocks             0x00001382
  Standard Null Collation              TRUE  Free space              0x00000000
  Last Record Backup     0x0000000000000001  Extension Count              10000
  Last Database Backup   0x0000000000000001  Number of local maps            10
  Last Bytestream Backup 0x0000000000000001  Lock space              0x00000028
  In critical section            0x00000000  Timers pending                   0
  Cache freeze id                0x00000000  Flush timer            00:00:01:00
  Freeze match                   0x00000000  Flush trigger                  938
  Current transaction    0x0000000000000007  No. of writes/flush              7
  Maximum TN             0xFFFFFFFFE3FFFFFF  Certified for Upgrade to        V5
  Maximum TN Warn        0xFFFFFFFF73FFFFFF  Desired DB Format               V5
  Master Bitmap Size                    112  Blocks to Upgrade       0x00000000
  Create in progress                  FALSE  Modified cache blocks            0
  Reference count                         1  Wait Disk                        0
  Journal State               [inactive] ON  Journal Before imaging        TRUE
  Journal Allocation                   2048  Journal Extension             2048
  Journal Buffer Size                   128  Journal Alignsize             2048
  Journal AutoSwitchLimit           8386560  Journal Epoch Interval         300
  Journal Yield Limit                     8  Journal Sync IO              FALSE
  Journal File: /home/gtmuser/myApp/gtm.mjl
  Mutex Hard Spin Count                 128  Mutex Sleep Spin Count         128
  Mutex Spin Sleep Time                2048  KILLs in progress                0
  Replication State                      ON  Region Seqno    0x0000000000000006
  Zqgblmod Seqno         0x0000000000000003  Zqgblmod Trans  0x0000000000000004
  Endian Format                      LITTLE  Commit Wait Spin Count          16
  Database file encrypted             FALSE
DSE> exit
gtmuser@philadelphia:~$ 

And in Shanghai:

gtmuser@shanghai:~$ $gtm_dist/dse

File    /home/gtmuser/myApp/aA.dat
Region  AA

DSE> dump -fileheader

File            /home/gtmuser/myApp/aA.dat
Region          AA
Date/Time       11-NOV-2011 03:58:49 [$H = 62406,14329]
  Access method                          BG  Global Buffers                1000
  Reserved Bytes                          0  Block size (in bytes)         4096
  Maximum record size                  4080  Starting VBN                   129
  Maximum key size                      255  Total blocks            0x00001392
  Null subscripts                     NEVER  Free blocks             0x00001384
  Standard Null Collation              TRUE  Free space              0x00000000
  Last Record Backup     0x0000000000000001  Extension Count              10000
  Last Database Backup   0x0000000000000001  Number of local maps            10
  Last Bytestream Backup 0x0000000000000001  Lock space              0x00000028
  In critical section            0x00000000  Timers pending                   0
  Cache freeze id                0x00000000  Flush timer            00:00:01:00
  Freeze match                   0x00000000  Flush trigger                  938
  Current transaction    0x0000000000000002  No. of writes/flush              7
  Maximum TN             0xFFFFFFFFE3FFFFFF  Certified for Upgrade to        V5
  Maximum TN Warn        0xFFFFFFFF73FFFFFF  Desired DB Format               V5
  Master Bitmap Size                    112  Blocks to Upgrade       0x00000000
  Create in progress                  FALSE  Modified cache blocks            0
  Reference count                         1  Wait Disk                        0
  Journal State               [inactive] ON  Journal Before imaging        TRUE
  Journal Allocation                   2048  Journal Extension             2048
  Journal Buffer Size                   128  Journal Alignsize             2048
  Journal AutoSwitchLimit           8386560  Journal Epoch Interval         300
  Journal Yield Limit                     8  Journal Sync IO              FALSE
  Journal File: /home/gtmuser/myApp/aA.mjl
  Mutex Hard Spin Count                 128  Mutex Sleep Spin Count         128
  Mutex Spin Sleep Time                2048  KILLs in progress                0
  Replication State                      ON  Region Seqno    0x0000000000000004
  Zqgblmod Seqno         0x0000000000000001  Zqgblmod Trans  0x0000000000000002
  Endian Format                      LITTLE  Commit Wait Spin Count          16
  Database file encrypted             FALSE
DSE> find -region=DEFAULT

File    /home/gtmuser/myApp/gtm.dat
Region  DEFAULT

DSE> dump -fileheader

File            /home/gtmuser/myApp/gtm.dat
Region          DEFAULT
Date/Time       11-NOV-2011 03:59:38 [$H = 62406,14378]
  Access method                          BG  Global Buffers                1000
  Reserved Bytes                          0  Block size (in bytes)         4096
  Maximum record size                  4080  Starting VBN                   129
  Maximum key size                      255  Total blocks            0x00001392
  Null subscripts                     NEVER  Free blocks             0x00001382
  Standard Null Collation              TRUE  Free space              0x00000000
  Last Record Backup     0x0000000000000001  Extension Count              10000
  Last Database Backup   0x0000000000000001  Number of local maps            10
  Last Bytestream Backup 0x0000000000000001  Lock space              0x00000028
  In critical section            0x00000000  Timers pending                   0
  Cache freeze id                0x00000000  Flush timer            00:00:01:00
  Freeze match                   0x00000000  Flush trigger                  938
  Current transaction    0x0000000000000006  No. of writes/flush              7
  Maximum TN             0xFFFFFFFFE3FFFFFF  Certified for Upgrade to        V5
  Maximum TN Warn        0xFFFFFFFF73FFFFFF  Desired DB Format               V5
  Master Bitmap Size                    112  Blocks to Upgrade       0x00000000
  Create in progress                  FALSE  Modified cache blocks            0
  Reference count                         1  Wait Disk                        0
  Journal State               [inactive] ON  Journal Before imaging        TRUE
  Journal Allocation                   2048  Journal Extension             2048
  Journal Buffer Size                   128  Journal Alignsize             2048
  Journal AutoSwitchLimit           8386560  Journal Epoch Interval         300
  Journal Yield Limit                     8  Journal Sync IO              FALSE
  Journal File: /home/gtmuser/myApp/gtm.mjl
  Mutex Hard Spin Count                 128  Mutex Sleep Spin Count         128
  Mutex Spin Sleep Time                2048  KILLs in progress                0
  Replication State                      ON  Region Seqno    0x0000000000000005
  Zqgblmod Seqno         0x0000000000000001  Zqgblmod Trans  0x0000000000000002
  Endian Format                      LITTLE  Commit Wait Spin Count          16
  Database file encrypted             FALSE
DSE> exit
gtmuser@shanghai:~$ 

Since the largest Region Seqno is 6 (region DEFAULT in Philadelphia), that is the preferred new originating primary instance. So, make it the new originating primary.

gtmuser@philadelphia:~$ mupip replicate -source -start -instsecondary=Shanghai -secondary=10.0.2.2:6000 -buffsize=1048576 -log=/home/gtmuser/myApp/source_Shanghai.log
Thu Nov 10 15:02:01 2011 : Initiating START of source server for secondary instance [Shanghai]
gtmuser@philadelphia:~$ mupip replicate -source -start -instsecondary=CapeTown -secondary=10.0.2.2:4000 -buffsize=1048576 -log=/home/gtmuser/myApp/source_CapeTown.log
Thu Nov 10 15:02:28 2011 : Initiating START of source server for secondary instance [CapeTown]
gtmuser@philadelphia:~$ 

On Shanghai, perform the mupip journal -rollback -fetchresync operation and start operation as a replicating instance.

gtmuser@shanghai:~$ mupip journal -rollback -backward -fetchresync=3000 -losttrans=/home/gtmuser/myApp/Unreplic_Trans_Report_`date +%Y%m%d%H%M%S`.txt "*"
%GTM-I-MUJNLSTAT, Initial processing started at Fri Nov 11 04:03:17 2011
%GTM-I-MUJNLSTAT, FETCHRESYNC processing started at Fri Nov 11 04:03:17 2011
Fri Nov 11 04:03:17 2011 : Assuming primary supports multisite functionality. Connecting using multisite communication protocol.
Fri Nov 11 04:03:17 2011 : Waiting for a connection...
Fri Nov 11 04:03:18 2011 : Connection established, using TCP send buffer size 16384 receive buffer size 87380
Fri Nov 11 04:03:18 2011 : Connection information:: Local: 10.0.2.15:3000 Remote: 10.0.2.2:54701
Fri Nov 11 04:03:18 2011 : Sending REPL_FETCH_RESYNC message with seqno 5 [0x5]
Fri Nov 11 04:03:18 2011 : Received REPL_NEED_INSTANCE_INFO message from primary instance [Philadelphia]
Fri Nov 11 04:03:18 2011 : Sending REPL_INSTANCE_INFO message
Fri Nov 11 04:03:18 2011 : Received REPL_NEED_TRIPLE_INFO message for seqno 5 [0x5]
Fri Nov 11 04:03:18 2011 : Sending REPL_TRIPLE_INFO1 message with seqno 4 [0x4]
Fri Nov 11 04:03:18 2011 : Sending REPL_TRIPLE_INFO2 message with seqno 4 [0x4]
Fri Nov 11 04:03:18 2011 : Triple Sent with Start Seqno = 4 [0x4] : Root Primary = [CapeTown] : Cycle = [1]
Fri Nov 11 04:03:18 2011 : Received REPL_RESYNC_SEQNO message
Fri Nov 11 04:03:18 2011 : Received RESYNC SEQNO is 5 [0x5]
%GTM-I-MUJNLSTAT, Backward processing started at Fri Nov 11 04:03:18 2011
%GTM-I-RESOLVESEQNO, Resolving until sequence number 0x0000000000000005
%GTM-I-MUJNLSTAT, Before image applying started at Fri Nov 11 04:03:18 2011
%GTM-I-FILERENAME, File /home/gtmuser/myApp/aA.mjl is renamed to /home/gtmuser/myApp/aA.mjl_2011315010416
%GTM-I-FILERENAME, File /home/gtmuser/myApp/gtm.mjl is renamed to /home/gtmuser/myApp/gtm.mjl_2011315024040
%GTM-I-MUJNLSTAT, Forward processing started at Fri Nov 11 04:03:18 2011
%GTM-I-FILECREATE, Lost transactions extract file /home/gtmuser/myApp/Unreplic_Trans_Report_20111111040317.txt created
%GTM-I-RLBKJNSEQ, Journal seqno of the instance after rollback is 5 [0x0000000000000005]
%GTM-S-JNLSUCCESS, Show successful
%GTM-S-JNLSUCCESS, Verify successful
%GTM-S-JNLSUCCESS, Rollback successful
%GTM-I-MUJNLSTAT, End processing at Fri Nov 11 04:03:19 2011
gtmuser@shanghai:~$ myApp/replicating_start
Fri Nov 11 04:04:12 2011 : Initiating START of source server for secondary instance [dummy]
gtmuser@shanghai:~$ 

Perform an update in Philadelphia and verify that there is a backlog to Cape Town (the actual number may not be correct because Cape Town was not recently a replicating instance to Philadelphia, but it shows a non-zero value), but there is no backlog to Shanghai.

gtmuser@philadelphia:~$ mumps -dir

GTM>set ^Weather("Philadelphia",$Piece($Horolog,",",1),$Piece($Horolog,",",2))="Heat Wave"

GTM>zsystem "$gtm_dist/mupip replicate -source -showbacklog" 
Thu Nov 10 15:06:08 2011 : Initiating SHOWBACKLOG operation on source server pid [945] for secondary instance [CapeTown]
4 : backlog number of transactions written to journal pool and yet to be sent by the source server
6 : sequence number of last transaction written to journal pool
2 : sequence number of last transaction sent by source server
Thu Nov 10 15:06:08 2011 : Initiating SHOWBACKLOG operation on source server pid [941] for secondary instance [Shanghai]
0 : backlog number of transactions written to journal pool and yet to be sent by the source server
6 : sequence number of last transaction written to journal pool
6 : sequence number of last transaction sent by source server

GTM>

Now boot the Cape Town machine and perform a fetchresync:

gtmuser@capetown:~$ source myApp/env
gtmuser@capetown:~$ mupip journal -rollback -backward -fetchresync=3000 -losttrans=/home/gtmuser/myApp/Unreplic_Trans_Report_`date +%Y%m%d%H%M%S`.txt "*"
%GTM-I-MUJNLSTAT, Initial processing started at Thu Nov 10 22:13:56 2011
%GTM-I-MUJNLSTAT, FETCHRESYNC processing started at Thu Nov 10 22:13:56 2011
Thu Nov 10 22:13:56 2011 : Assuming primary supports multisite functionality. Connecting using multisite communication protocol.
Thu Nov 10 22:13:56 2011 : Waiting for a connection...
Thu Nov 10 22:13:57 2011 : Connection established, using TCP send buffer size 16384 receive buffer size 87380
Thu Nov 10 22:13:57 2011 : Connection information:: Local: 10.0.2.15:3000 Remote: 10.0.2.2:47583
Thu Nov 10 22:13:57 2011 : Sending REPL_FETCH_RESYNC message with seqno 7 [0x7]
Thu Nov 10 22:13:57 2011 : Received REPL_NEED_INSTANCE_INFO message from primary instance [Philadelphia]
Thu Nov 10 22:13:57 2011 : Sending REPL_INSTANCE_INFO message
Thu Nov 10 22:13:57 2011 : Received REPL_NEED_TRIPLE_INFO message for seqno 7 [0x7]
Thu Nov 10 22:13:57 2011 : Sending REPL_TRIPLE_INFO1 message with seqno 4 [0x4]
Thu Nov 10 22:13:57 2011 : Sending REPL_TRIPLE_INFO2 message with seqno 4 [0x4]
Thu Nov 10 22:13:57 2011 : Triple Sent with Start Seqno = 4 [0x4] : Root Primary = [CapeTown] : Cycle = [1]
Thu Nov 10 22:13:57 2011 : Received REPL_RESYNC_SEQNO message
Thu Nov 10 22:13:57 2011 : Received RESYNC SEQNO is 6 [0x6]
%GTM-I-MUJNLSTAT, Backward processing started at Thu Nov 10 22:13:57 2011
%GTM-I-RESOLVESEQNO, Resolving until sequence number 0x0000000000000006
%GTM-I-MUJNLSTAT, Before image applying started at Thu Nov 10 22:13:57 2011
%GTM-I-FILERENAME, File /home/gtmuser/myApp/aA.mjl is renamed to /home/gtmuser/myApp/aA.mjl_2011314190359
%GTM-I-FILERENAME, File /home/gtmuser/myApp/gtm.mjl is renamed to /home/gtmuser/myApp/gtm.mjl_2011314214908
%GTM-I-MUJNLSTAT, Forward processing started at Thu Nov 10 22:13:58 2011
%GTM-I-FILECREATE, Lost transactions extract file /home/gtmuser/myApp/Unreplic_Trans_Report_20111110221356.txt created
%GTM-I-RLBKJNSEQ, Journal seqno of the instance after rollback is 6 [0x0000000000000006]
%GTM-S-JNLSUCCESS, Show successful
%GTM-S-JNLSUCCESS, Verify successful
%GTM-S-JNLSUCCESS, Rollback successful
%GTM-I-MUJNLSTAT, End processing at Thu Nov 10 22:13:58 2011
gtmuser@capetown:~$ cat /home/gtmuser/myApp/Unreplic_Trans_Report_20111110221356.txt 
GDSJEX05 ROLLBACK PRIMARY CapeTown
05\62405,78542\7\1107\0\6\0\0\^Weather("Cape Town",62405,78542)="Cloudy"

gtmuser@capetown:~$ 

Now, notice that the Unreplicated Transaction File has meaningful content – the update that was made when Cape Town was an originating instance, but which did not get replicated. This file will now need to be processed by the new originating instance in Philadelphia.

Cape Town can now start as a replicating instance:

gtmuser@capetown:~$ myApp/replicating_start
Thu Nov 10 22:16:18 2011 : Initiating START of source server for secondary instance [dummy]
gtmuser@capetown:~$ 

Notice that Philadelphia now reports no backlog:

GTM>zsystem "$gtm_dist/mupip replicate -source -showbacklog"
Thu Nov 10 15:17:00 2011 : Initiating SHOWBACKLOG operation on source server pid [945] for secondary instance [CapeTown]
0 : backlog number of transactions written to journal pool and yet to be sent by the source server
6 : sequence number of last transaction written to journal pool
6 : sequence number of last transaction sent by source server
Thu Nov 10 15:17:00 2011 : Initiating SHOWBACKLOG operation on source server pid [941] for secondary instance [Shanghai]
0 : backlog number of transactions written to journal pool and yet to be sent by the source server
6 : sequence number of last transaction written to journal pool
6 : sequence number of last transaction sent by source server

GTM> 

If your application uses the $ZQGBLMOD() function to process unreplicated transactions, read the logical multi site replication technical bulletin for information about the mupip replicate source losttncomplete command to be executed after processing unreplicated transactions from all originating instances.

Backup

Backup when an application is not running is straightforward – just copy the database files. [However, remember that the copy will have the same journal file name in the database file header and the system now potentially has two database files pointing to the same journal file. Before using that file on the same computer system as the original database file, disable journaling and re-enable it if appropriate (do not simply switch journal files).]

Backup when an application is operating normally, without impacting the application (except of course for the additional IO load of the backup activity) is easy with GT.M, and can be accomplished in two ways, one non-GT.M and other GT.M:

Exercise – Backup

Work with whichever instance was your last originating instance (although it does not really matter, since you can always bring the others up as replicating instances no matter which was the last originating instance because you will always start with a mupip journal rollback fetchresync step; in this case the example shows Philadelphia).

Create a directory where you can put your backups: mkdir backup

In the myApp directory of that instance, create a GT.M program XYZ in file XYZ.m that creates updates, e.g.:

XYZ    Set (^a,^b)=0
       For  Do
       . Hang 0.1
       . Tstart ()
       .   Set r=$Random(2147483646)
       .   Set ^a($horolog)=^a-r
       .   Set ^b($horolog)=^b+r
       . Tcommit
       Quit

Note that the global variables ^a and ^b go into different database files, but the use of transaction processing provides ACID properties across regions.

Since the database is a replicated environment, if no Source Servers are running, start at least one, e.g.: mupip replicate -source -start -instsecondary=Shanghai -secondary=10.0.2.2:6000 -buffsize=1048576 -log=/home/gtmuser/myApp/source_Shanghai.log [A replicated environment needs at least one source server to be running before updates are permitted. You can certainly start the second Source Server now – or you can start it up later for the Replication Briefly Revisited exercise below.]

Start the program as a background process from the shell: mumps -run XYZ </dev/null 1>/dev/null 2>&1 &

Notice that the journal files are growing, indicating that the program is running:

gtmuser@philadelphia:~$ ls -l myApp/*.mjl
-rw-rw-rw- 1 gtmuser gtmuser 77824 2011-11-10 15:35 myApp/aA.mjl
-rw-rw-rw- 1 gtmuser gtmuser 77824 2011-11-10 15:35 myApp/gtm.mjl
gtmuser@philadelphia:~$ ls -l myApp/*.mjl
-rw-rw-rw- 1 gtmuser gtmuser 77824 2011-11-10 15:35 myApp/aA.mjl
-rw-rw-rw- 1 gtmuser gtmuser 81920 2011-11-10 15:35 myApp/gtm.mjl
gtmuser@philadelphia:~$ ls -l myApp/*.mjl
-rw-rw-rw- 1 gtmuser gtmuser 86016 2011-11-10 15:35 myApp/aA.mjl
-rw-rw-rw- 1 gtmuser gtmuser 86016 2011-11-10 15:35 myApp/gtm.mjl
gtmuser@philadelphia:~$ 

Take a backup of the entire database (a “comprehensive backup”):

gtmuser@philadelphia:~$ mupip backup -nojournal "*" backup/
%GTM-I-FILERENAME, File /home/gtmuser/myApp/aA.mjl is renamed to /home/gtmuser/myApp/aA.mjl_2011314153705
%GTM-I-JNLCREATE, Journal file /home/gtmuser/myApp/aA.mjl created for region AA with BEFORE_IMAGES
%GTM-I-FILERENAME, File /home/gtmuser/myApp/gtm.mjl is renamed to /home/gtmuser/myApp/gtm.mjl_2011314153705
%GTM-I-JNLCREATE, Journal file /home/gtmuser/myApp/gtm.mjl created for region DEFAULT with BEFORE_IMAGES
%GTM-I-JNLSTATE, Journaling state for database file backup//aA.dat is now DISABLED
%GTM-I-JNLSTATE, Journaling state for database file backup//gtm.dat is now DISABLED
DB file /home/gtmuser/myApp/aA.dat backed up in file backup//aA.dat
Transactions up to 0x000000000000020E are backed up.
DB file /home/gtmuser/myApp/gtm.dat backed up in file backup//gtm.dat
Transactions up to 0x0000000000000214 are backed up.


BACKUP COMPLETED.

gtmuser@philadelphia:~$ 

Take a backup of that part of the database that has changed (a “bytestream” backup). Note the use of the -since=database qualifier to only backup those database blocks that have changed since the last backup of the entire database):

gtmuser@philadelphia:~$ mupip backup -bytestream -since=database "*" backup/aA`date +%Y%m%d%H%M%S`.bck,backup/gtm`date +%Y%m%d%H%M%S`.bck
%GTM-I-FILERENAME, File /home/gtmuser/myApp/aA.mjl is renamed to /home/gtmuser/myApp/aA.mjl_2011314153900
%GTM-I-JNLCREATE, Journal file /home/gtmuser/myApp/aA.mjl created for region AA with BEFORE_IMAGES
%GTM-I-FILERENAME, File /home/gtmuser/myApp/gtm.mjl is renamed to /home/gtmuser/myApp/gtm.mjl_2011314153900
%GTM-I-JNLCREATE, Journal file /home/gtmuser/myApp/gtm.mjl created for region DEFAULT with BEFORE_IMAGES
MUPIP backup of database file /home/gtmuser/myApp/aA.dat to backup/aA20111110153859.bck
DB file /home/gtmuser/myApp/aA.dat incrementally backed up in file backup/aA20111110153859.bck
4 blocks saved.
Transactions from 0x000000000000020E to 0x000000000000044F are backed up.
MUPIP backup of database file /home/gtmuser/myApp/gtm.dat to backup/gtm20111110153859.bck
DB file /home/gtmuser/myApp/gtm.dat incrementally backed up in file backup/gtm20111110153859.bck
4 blocks saved.
Transactions from 0x0000000000000214 to 0x0000000000000455 are backed up.


BACKUP COMPLETED.

gtmuser@philadelphia:~$ ls -l backup/
total 992
-rw-rw-rw- 1 gtmuser gtmuser    90624 2011-11-10 15:39 aA20111110153859.bck
-rw-rw-rw- 1 gtmuser gtmuser 20587008 2011-11-10 15:37 aA.dat
-rw-rw-rw- 1 gtmuser gtmuser    90624 2011-11-10 15:39 gtm20111110153859.bck
-rw-rw-rw- 1 gtmuser gtmuser 20587008 2011-11-10 15:37 gtm.dat
gtmuser@philadelphia:~$ 

Take further bytestream backups of that part of the database that has changed – as many as desired (note the use of the -since=bytestream qualifier to backup only those blocks that have changed since the last bytestream backup):

gtmuser@philadelphia:~$ mupip backup -bytestream -since=bytestream "*" backup/aA`date +%Y%m%d%H%M%S`.bck,backup/gtm`date +%Y%m%d%H%M%S`.bck
%GTM-I-FILERENAME, File /home/gtmuser/myApp/aA.mjl is renamed to /home/gtmuser/myApp/aA.mjl_2011314154301
%GTM-I-JNLCREATE, Journal file /home/gtmuser/myApp/aA.mjl created for region AA with BEFORE_IMAGES
%GTM-I-FILERENAME, File /home/gtmuser/myApp/gtm.mjl is renamed to /home/gtmuser/myApp/gtm.mjl_2011314154301
%GTM-I-JNLCREATE, Journal file /home/gtmuser/myApp/gtm.mjl created for region DEFAULT with BEFORE_IMAGES
MUPIP backup of database file /home/gtmuser/myApp/aA.dat to backup/aA20111110154300.bck
DB file /home/gtmuser/myApp/aA.dat incrementally backed up in file backup/aA20111110154300.bck
6 blocks saved.
Transactions from 0x000000000000044F to 0x0000000000000913 are backed up.
MUPIP backup of database file /home/gtmuser/myApp/gtm.dat to backup/gtm20111110154300.bck
DB file /home/gtmuser/myApp/gtm.dat incrementally backed up in file backup/gtm20111110154300.bck
6 blocks saved.
Transactions from 0x0000000000000455 to 0x0000000000000919 are backed up.


BACKUP COMPLETED.

gtmuser@philadelphia:~$

Take as many additional bytestream backups as you want, in each case specifying that the updates since the preceding bytestream backup should be backed up.

gtmuser@philadelphia:~$ mupip backup -bytestream -since=bytestream "*" backup/aA`date +%Y%m%d%H%M%S`.bck,backup/gtm`date +%Y%m%d%H%M%S`.bck
%GTM-I-FILERENAME, File /home/gtmuser/myApp/aA.mjl is renamed to /home/gtmuser/myApp/aA.mjl_2011314154356
%GTM-I-JNLCREATE, Journal file /home/gtmuser/myApp/aA.mjl created for region AA with BEFORE_IMAGES
%GTM-I-FILERENAME, File /home/gtmuser/myApp/gtm.mjl is renamed to /home/gtmuser/myApp/gtm.mjl_2011314154356
%GTM-I-JNLCREATE, Journal file /home/gtmuser/myApp/gtm.mjl created for region DEFAULT with BEFORE_IMAGES
MUPIP backup of database file /home/gtmuser/myApp/aA.dat to backup/aA20111110154356.bck
DB file /home/gtmuser/myApp/aA.dat incrementally backed up in file backup/aA20111110154356.bck
6 blocks saved.
Transactions from 0x0000000000000913 to 0x0000000000000A25 are backed up.
MUPIP backup of database file /home/gtmuser/myApp/gtm.dat to backup/gtm20111110154356.bck
DB file /home/gtmuser/myApp/gtm.dat incrementally backed up in file backup/gtm20111110154356.bck
6 blocks saved.
Transactions from 0x0000000000000919 to 0x0000000000000A2B are backed up.


BACKUP COMPLETED.

gtmuser@philadelphia:~$

When you are satisfied, terminate the mumps process updating the database:

gtmuser@philadelphia:~$ ps -ef | grep mumps | grep -v grep
gtmuser   1074   756 10 15:35 pts/0    00:01:01 /usr/lib/fis-gtm/V5.4-002B_x86/mumps -run XYZ
gtmuser@philadelphia:~$ mupip stop 1074
STOP issued to process 1074
gtmuser@philadelphia:~$ 

Take a final backup, and note the values of the final nodes of ^a and ^b (and verify that they still sum to zero). After the final restore, we will verify that the values restored from the backup is the same as these values.

gtmuser@philadelphia:~$ mupip backup -bytestream -since=bytestream "*" backup/aA`date +%Y%m%d%H%M%S`.bck,backup/gtm`date +%Y%m%d%H%M%S`.bck
%GTM-I-FILERENAME, File /home/gtmuser/myApp/aA.mjl is renamed to /home/gtmuser/myApp/aA.mjl_2011314154829
%GTM-I-JNLCREATE, Journal file /home/gtmuser/myApp/aA.mjl created for region AA with BEFORE_IMAGES
%GTM-I-FILERENAME, File /home/gtmuser/myApp/gtm.mjl is renamed to /home/gtmuser/myApp/gtm.mjl_2011314154829
%GTM-I-JNLCREATE, Journal file /home/gtmuser/myApp/gtm.mjl created for region DEFAULT with BEFORE_IMAGES
MUPIP backup of database file /home/gtmuser/myApp/aA.dat to backup/aA20111110154828.bck
DB file /home/gtmuser/myApp/aA.dat incrementally backed up in file backup/aA20111110154828.bck
4 blocks saved.
Transactions from 0x0000000000000A25 to 0x0000000000000B83 are backed up.
MUPIP backup of database file /home/gtmuser/myApp/gtm.dat to backup/gtm20111110154828.bck
DB file /home/gtmuser/myApp/gtm.dat incrementally backed up in file backup/gtm20111110154828.bck
4 blocks saved.
Transactions from 0x0000000000000A2B to 0x0000000000000B89 are backed up.


BACKUP COMPLETED.

gtmuser@philadelphia:~$ mumps -run %XCMD 'set x=$order(^a(""),-1) write x," ",^a(x)," ",^b(x)," ",^a(x)+^b(x),!'
62405,56706 -2026531582 2026531582 0
gtmuser@philadelphia:~$ 

Now it's time to work on restoring the backup. The first backup (the database backup) provides a complete, ready-to-run database. The subsequent bytestream backups can be applied to the database backup using the mupip restore command.

Create an environment to restore the backup. It may be easiest if you simply use the backup directory you created, and working in a new shell session, copy myApp/env and myApp/gtm.gld to the backup directory and edit them to reflect their new locations. [Note that since global directories are used only to create databases, there is no reason to change the default journal file names in the regions of the global directory file in backup. Nevertheless, it is good hygiene to keep global directories correct, since that global directory may be used at a future time to create new database files.]

gtmuser@philadelphia:~$ cp myApp/{env,gtm.gld} backup/
gtmuser@philadelphia:~$ fte myApp/env 
gtmuser@philadelphia:~$ cat backup/env
export gtm_dist=/usr/lib/fis-gtm/V5.4-002B_x86
export gtmgbldir=/home/gtmuser/backup/gtm.gld
export gtm_log=/tmp/fis-gtm/V5.4-002B_x86
export gtm_principal_editing=EDITING
export gtm_repl_instance=/home/gtmuser/backup/philadelphia.repl
export gtm_repl_instname=Philadelphia
export gtmroutines="/home/gtmuser/backup $gtm_dist"
export gtm_tmp=$gtm_log
mkdir -p $gtm_tmp
alias mumps=$gtm_dist/mumps
alias mupip=$gtm_dist/mupip
gtmuser@philadelphia:~$ source backup/env
gtmuser@philadelphia:~$ mumps -run GDE
%GDE-I-LOADGD, Loading Global Directory file 
        /home/gtmuser/backup/gtm.gld
%GDE-I-VERIFY, Verification OK


GDE> show -segment

                                *** SEGMENTS ***
 Segment                         File (def ext: .dat)Acc Typ Block      Alloc Exten Options
 -------------------------------------------------------------------------------------------
 AA                              /home/gtmuser/myApp/aA.dat  BG  DYN  4096       5000 10000 GLOB=1000
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
 DEFAULT                         /home/gtmuser/myApp/gtm.dat
                                                     BG  DYN  4096       5000 10000 GLOB=1000
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
GDE> change -segment AA -file=/home/gtmuser/backup/aA.dat
GDE> change -segment DEFAULT -file=/home/gtmuser/backup/gtm.dat
GDE> show -segment

                                *** SEGMENTS ***
 Segment                         File (def ext: .dat)Acc Typ Block      Alloc Exten Options
 -------------------------------------------------------------------------------------------
 AA                              /home/gtmuser/backup/aA.dat
                                                     BG  DYN  4096       5000 10000 GLOB=1000
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
 DEFAULT                         /home/gtmuser/backup/gtm.dat
                                                     BG  DYN  4096       5000 10000 GLOB=1000
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
GDE> show -region

                                *** REGIONS ***
                                 Dynamic                          Def    Rec   Key Null       Standard
 Region                          Segment                         Coll   Size  Size Subs       NullColl  Journaling
 ------------------------------------------------------------------------------------------------------------------
 AA                              AA                                 0   4080   255 NEVER      Y         Y
 DEFAULT                         DEFAULT                            0   4080   255 NEVER      Y         Y

                          *** JOURNALING INFORMATION ***
 Region                          Jnl File (def ext: .mjl)  Before Buff      Alloc Exten
 ---------------------------------------------------------------------------------------
 AA                              /home/gtmuser/myApp/aA.mjl        Y       128       2048  2048

 DEFAULT                         /home/gtmuser/myApp/gtm.mjl    Y       128       2048  2048

GDE> change -region AA -journal=file=/home/gtmuser/backup/aA.mjl
GDE> change -region DEFAULT -journal=file=/home/gtmuser/backup/gtm.mjl
GDE> show -region

                                *** REGIONS ***
                                 Dynamic                          Def    Rec   Key Null       Standard
 Region                          Segment                         Coll   Size  Size Subs       NullColl  Journaling
 ------------------------------------------------------------------------------------------------------------------
 AA                              AA                                 0   4080   255 NEVER      Y         Y
 DEFAULT                         DEFAULT                            0   4080   255 NEVER      Y         Y

                          *** JOURNALING INFORMATION ***
 Region                          Jnl File (def ext: .mjl)  Before Buff      Alloc Exten
 ---------------------------------------------------------------------------------------
 AA                              /home/gtmuser/backup/aA.mjl       Y       128       2048  2048

 DEFAULT                         /home/gtmuser/backup/gtm.mjl   Y       128       2048  2048

GDE> exit
%GDE-I-VERIFY, Verification OK

%GDE-I-GDUPDATE, Updating Global Directory file 
        /home/gtmuser/backup/gtm.gld
gtmuser@philadelphia:~$ 

Confirm the values of the last global nodes of ^a and ^b are equal and opposite (demonstrating the value of transaction processing):

gtmuser@philadelphia:~$ mumps -run %XCMD 'set x=$order(^a(""),-1) write x," ",^a(x)," ",^b(x)," ",^a(x)+^b(x),!'
62405,56224 -2124406191 2124406191 0
gtmuser@philadelphia:~$ 

Notice the file names of the bytestream backups that we will restore one by one.

gtmuser@philadelphia:~$ ls -l backup/*.bck
-rw-rw-rw- 1 gtmuser gtmuser 90624 2011-11-10 15:39 backup/aA20111110153859.bck
-rw-rw-rw- 1 gtmuser gtmuser 98816 2011-11-10 15:43 backup/aA20111110154300.bck
-rw-rw-rw- 1 gtmuser gtmuser 98816 2011-11-10 15:43 backup/aA20111110154356.bck
-rw-rw-rw- 1 gtmuser gtmuser 86528 2011-11-10 15:48 backup/aA20111110154828.bck
-rw-rw-rw- 1 gtmuser gtmuser 90624 2011-11-10 15:39 backup/gtm20111110153859.bck
-rw-rw-rw- 1 gtmuser gtmuser 98816 2011-11-10 15:43 backup/gtm20111110154300.bck
-rw-rw-rw- 1 gtmuser gtmuser 98816 2011-11-10 15:43 backup/gtm20111110154356.bck
-rw-rw-rw- 1 gtmuser gtmuser 86528 2011-11-10 15:48 backup/gtm20111110154828.bck
gtmuser@philadelphia:~$ 

Restore the first bytestream backup (with the since=database qualifier), and check the values of ^a and ^b:

gtmuser@philadelphia:~$ mupip restore backup/aA.dat backup/aA20111110153859.bck 

RESTORE COMPLETED
5 blocks restored
gtmuser@philadelphia:~$ mupip restore backup/gtm.dat backup/gtm20111110153859.bck 

RESTORE COMPLETED
5 blocks restored
gtmuser@philadelphia:~$ 

Confirm new values for ^a and ^b. Notice that the time stamp is later than that of the database before the first bytestream backup (with a since=database qualifier) was restored and the values of the nodes in ^a and ^b still add up to zero.

gtmuser@philadelphia:~$ mumps -run %XCMD 'set x=$order(^a(""),-1) write x," ",^a(x)," ",^b(x)," ",^a(x)+^b(x),!'
62405,56339 -409750439 409750439 0
gtmuser@philadelphia:~$ 

Restore the second bytestream backup (the first with a since=bytestream qualifier), and check the values. Repeat for any additional bytestream backups. Notice that the final values are the same as the final values in myApp.

gtmuser@philadelphia:~$ mupip restore backup/aA.dat backup/aA20111110154300.bck 

RESTORE COMPLETED
7 blocks restored
gtmuser@philadelphia:~$ mupip restore backup/gtm.dat backup/gtm20111110154300.bck 

RESTORE COMPLETED
7 blocks restored
gtmuser@philadelphia:~$ mumps -run %XCMD 'set x=$order(^a(""),-1) write x," ",^a(x)," ",^b(x)," ",^a(x)+^b(x),!'
62405,56580 -690269483 690269483 0
gtmuser@philadelphia:~$ mupip restore backup/aA.dat backup/aA20111110154356.bck 

RESTORE COMPLETED
7 blocks restored
gtmuser@philadelphia:~$ mupip restore backup/gtm.dat backup/gtm20111110154356.bck 

RESTORE COMPLETED
7 blocks restored
gtmuser@philadelphia:~$ mumps -run %XCMD 'set x=$order(^a(""),-1) write x," ",^a(x)," ",^b(x)," ",^a(x)+^b(x),!'
62405,56635 -733572328 733572328 0
gtmuser@philadelphia:~$ mupip restore backup/aA.dat backup/aA20111110154828.bck 

RESTORE COMPLETED
4 blocks restored
gtmuser@philadelphia:~$ mupip restore backup/gtm.dat backup/gtm20111110154828.bck 

RESTORE COMPLETED
4 blocks restored
gtmuser@philadelphia:~$ mumps -run %XCMD 'set x=$order(^a(""),-1) write x," ",^a(x)," ",^b(x)," ",^a(x)+^b(x),!'
62405,56706 -2026531582 2026531582 0
gtmuser@philadelphia:~$ 

Note that ending values of ^a and ^b in the environment with the restored backups is exactly the same as in the original database. Also, the values of ^a and ^b always add to zero even though the global variables reside in different databases, demonstrating transaction processing.

Replication Briefly Revisited

We have been ignoring Cape Town and Shanghai during the backup exercise, and we have created quite a backlog. Here is the backlog report in the myApp environment in Philadelphia. [If you are using the same shell session to restore backups, you will need to source myApp/env to get the environment variables for the original environment.]

gtmuser@philadelphia:~$ mupip replicate -source -showbacklog
Thu Nov 10 17:55:23 2011 : Initiating SHOWBACKLOG operation on source server pid [945] for secondary instance [CapeTown]
2946 : backlog number of transactions written to journal pool and yet to be sent by the source server
2952 : sequence number of last transaction written to journal pool
6 : sequence number of last transaction sent by source server
Thu Nov 10 17:55:23 2011 : Initiating SHOWBACKLOG operation on source server pid [941] for secondary instance [Shanghai]
2946 : backlog number of transactions written to journal pool and yet to be sent by the source server
2952 : sequence number of last transaction written to journal pool
6 : sequence number of last transaction sent by source server
gtmuser@philadelphia:~$ 

Boot Shanghai and check the state of the database with respect to ^a and ^b – notice the error because no node of ^a is defined.

gtmuser@shanghai:~$ source myApp/env
gtmuser@shanghai:~$ mumps -run %XCMD 'set x=$order(^a(""),-1) write x," ",^a(x)," ",^b(x)," ",^a(x)+^b(x),!'
 
%GTM-E-NULSUBSC, Null subscripts are not allowed for region: AA
%GTM-I-GVIS,            Global variable: ^a("")
%GTM-I-RTSLOC,          At M source location %XCMD+3^%XCMD
gtmuser@shanghai:~$ 

Then execute the myApp/replicating_start script and notice that Shanghai catches up.

gtmuser@shanghai:~$ myApp/replicating_start 
Fri Nov 11 07:12:07 2011 : Initiating START of source server for secondary instance [dummy]
gtmuser@shanghai:~$ mumps -run %XCMD 'set x=$order(^a(""),-1) write x," ",^a(x)," ",^b(x)," ",^a(x)+^b(x),!'
62405,56706 -2026531582 2026531582 0
gtmuser@shanghai:~$

Notice that Philadelphia now shows a zero backlog for Shanghai, but still has a large backlog for CapeTown.

gtmuser@philadelphia:~$ mupip replicate -source -showbacklog
Thu Nov 10 18:13:12 2011 : Initiating SHOWBACKLOG operation on source server pid [945] for secondary instance [CapeTown]
2946 : backlog number of transactions written to journal pool and yet to be sent by the source server
2952 : sequence number of last transaction written to journal pool
6 : sequence number of last transaction sent by source server
Thu Nov 10 18:13:12 2011 : Initiating SHOWBACKLOG operation on source server pid [941] for secondary instance [Shanghai]
0 : backlog number of transactions written to journal pool and yet to be sent by the source server
2952 : sequence number of last transaction written to journal pool
2952 : sequence number of last transaction sent by source server
gtmuser@philadelphia:~$ 

Shut down the instances gracefully.

Unicode (ISO/IEC-10646)

GT.M supports international character sets using Unicode. A mumps process can operate in one of two modes: M mode and UTF-8 mode, which is specified by the environment variable gtm_chset at process startup and which is immutable for the life of the process.

In M mode, the process interprets strings as single byte characters with characters from $Char(0) through $Char(127) encoded as ASCII (M mode). A GT.M process in M mode places no interpretation on characters $Char(128) through $Char (255) – the application is free to use any interpretation it chooses, and ISO-8859 variants are commonly used. There is no distinction between a string of bytes and a string of characters and all bytes are valid characters – the concept of an illegal or a non-canonical character is a non-sequitur in M mode.

In UTF-8 mode, the process by default interprets strings as multi-byte characters encoded using UTF-8 (UTF-8 mode). Some sequences of bytes are not defined by the standard, and are considered illegal characters. The process can also interpret a string as a sequence of bytes, and the same string can have different properties (such as its length) when it is considered a sequence of bytes than as a sequence of characters.

Except when triggers are used, this interpretation is at the level of the process: the database treats strings as binary data and does not care how they are encoded. So, it is possible for the same database to be concurrently accessed by mumps processes operating in both M mode and UTF-8 mode. Mupip and other processes are not concerned with this distinction. If the database has triggers, since triggers are compiled code, all proceses that use a database must have the same mode.

The mode of a process is controlled by the environment variable gtm_chset. If it is not set, or has a value other than "UTF-8", the process operates in M mode. Within a process the ISV $ZCHset can be used to test the mode.

For a process to operate in UTF-8 mode requires:

The locale command provides a list of available locales:

gtmuser@gtmworkshop7:~$ locale -a
C
POSIX
en_US.utf8
gtmuser@gtmworkshop7:~$ 

You can use the localedef command to generate additional UTF-8 locales – in this example, a UTF-8 locale for Arabic as used in Jordan is generated.

gtmuser@gtmworkshop7:~$ sudo localedef -f UTF-8 -i ar_JO ar_JO.utf8
gtmuser@gtmworkshop7:~$ locale -a
C
POSIX
ar_JO.utf8
en_US.utf8
gtmuser@gtmworkshop7:~$

For interactive usage, the terminal emulator must be configured to display characters in UTF-8 mode; otherwise you will become very confused.

This exercise uses two sessions, one with a mumps process in UTF-8 mode and the other using a mumps process in M mode. Create a utf8demo directory and two environment files within it, utf8demo/env_m and utf8demo/env_utf8 as follows:

gtmuser@gtmworkshop7:~$ cat utf8demo/env_m
export gtm_dist=/usr/lib/fis-gtm/V5.4-002B_x86
export gtmgbldir=/home/gtmuser/utf8demo/gtm.gld
export gtm_log=/tmp/fis-gtm/V5.4-002B_x86
export gtm_principal_editing=EDITING
export gtm_repl_instance=/home/utf8demo/gtm_repl
export gtm_repl_instname=dummy
export gtmroutines="/home/gtmuser/utf8demo $gtm_dist"
export gtm_tmp=$gtm_log
mkdir -p $gtm_tmp
unset gtm_chset
export gtm_prompt="GTM>"
alias mumps=$gtm_dist/mumps
alias mupip=$gtm_dist/mupip
gtmuser@gtmworkshop7:~$ cat utf8demo/env_utf8 
export gtm_dist=/usr/lib/fis-gtm/V5.4-002B_x86/utf8
export gtmgbldir=/home/gtmuser/utf8demo/gtm.gld
export gtm_log=/tmp/fis-gtm/V5.4-002B_x86
export gtm_principal_editing=EDITING
export gtm_repl_instance=/home/utf8demo/gtm_repl
export gtm_repl_instname=dummy
export gtmroutines="/home/gtmuser/utf8demo $gtm_dist"
export gtm_tmp=$gtm_log
mkdir -p $gtm_tmp
export gtm_icu_version=`icu-config --version | cut -d . -f 1,2`
export LC_CTYPE=`locale -a | grep -i utf | head -1`
export gtm_chset=UTF-8
export gtm_prompt="UTF8>"
alias mumps=$gtm_dist/mumps
alias mupip=$gtm_dist/mupip
gtmuser@gtmworkshop7:~$ 

Create a global directory and database file, as you did before. Only now, do this in the UTF-8 session to so that you can see that the operation of the GDE utility program is the same.

gtmuser@gtmworkshop7:~$ source utf8demo/env_UTF8
gtmuser@gtmworkshop7:~$ mumps -run GDE
%GDE-I-GDUSEDEFS, Using defaults for Global Directory
        /home/gtmuser/utf8demo/gtm.gld

GDE> @/usr/lib/fis-gtm/V5.4-002B_x86/utf8/gdedefaults
%GDE-I-EXECOM, Executing command file /usr/lib/fis-gtm/V5.4-002B_x86/utf8/gdedefaults

GDE> show -segment

                                *** SEGMENTS ***
 Segment                         File (def ext: .dat)Acc Typ Block      Alloc Exten Options
 -------------------------------------------------------------------------------------------
 DEFAULT                         $gtmdir/$gtmver/g/gtm.dat
                                                     BG  DYN  4096       5000 10000 GLOB=1000
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
GDE> change -segment DEFAULT -file=/home/gtmuser/utf8demo/gtm.dat
GDE> show -segment

                                *** SEGMENTS ***
 Segment                         File (def ext: .dat)Acc Typ Block      Alloc Exten Options
 -------------------------------------------------------------------------------------------
 DEFAULT                         /home/gtmuser/utf8demo/gtm.dat
                                                     BG  DYN  4096       5000 10000 GLOB=1000
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
GDE> exit
%GDE-I-VERIFY, Verification OK

%GDE-I-GDCREATE, Creating Global Directory file
        /home/gtmuser/utf8demo/gtm.gld
gtmuser@gtmworkshop7:~$ mupip create
Created file /home/gtmuser/utf8demo/gtm.dat
gtmuser@gtmworkshop7:~$ 

We use the gtm_prompt environment variable to avoid confusion as to which mode a direct mode prompt is in.

gtmuser@gtmworkshop7:~$ mumps -dir

UTF8>write $zchset
UTF-8
UTF8>for i=0:1:255 set ^Ch(i)=$char(i)

UTF8>for i=0:16:240 write ! for j=0:1:15 write ^Ch(i+j)," : "

 :  :  :  :  :  :  :  : :        : 
 : 
    : 
 :  :  : 
 :  :  :  :  :  :  :  : ▒ :  : ▒ : :  :  :  :  : 
  : ! : " : # : $ : % : & : ' : ( : ) : * : + : , : - : . : / : 
0 : 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : : : ; : < : = : > : ? : 
@ : A : B : C : D : E : F : G : H : I : J : K : L : M : N : O : 
P : Q : R : S : T : U : V : W : X : Y : Z : [ : \ : ] : ^ : _ : 
` : a : b : c : d : e : f : g : h : i : j : k : l : m : n : o : 
p : q : r : s : t : u : v : w : x : y : z : { : | : } : ~ :  : 
 :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  : 
 :  :  :  :  :  :  :  :  :  :  : :  :  :  :  : 
 : ¡ : ¢ : £ : ¤ : ¥ : ¦ : § : ¨ : © : ª : « : ¬ :  : ® : ¯ : 
° : ± : ² : ³ : ´ : µ : ¶ : · : ¸ : ¹ : º : » : ¼ : ½ : ¾ : ¿ : 
À : Á : Â : Ã : Ä : Å : Æ : Ç : È : É : Ê : Ë : Ì : Í : Î : Ï : 
Ð : Ñ : Ò : Ó : Ô : Õ : Ö : × : Ø : Ù : Ú : Û : Ü : Ý : Þ : ß : 
à : á : â : ã : ä : å : æ : ç : è : é : ê : ë : ì : í : î : ï : 
ð : ñ : ò : ó : ô : õ : ö : ÷ : ø : ù : ú : û : ü : ý : þ : ÿ : 
UTF8>halt
gtmuser@gtmworkshop7:~$ 

Start a shell in M mode and display in M mode the characters that you just set in UTF-8 mode (remember to set your terminal emulator to a single-byte encoding such as ISO-8859-1):

gtmuser@gtmworkshop7:~$ mumps -dir

GTM>write $zchset
M
GTM>for i=0:16:240 write ! for j=0:1:15 write ^Ch(i+j)," : "

 :  :  :  :  :  :  :  : :        : 
 : 
    : 
 :  :  : 
 :  :  :  :  :  :  :  : ▒ :  : ▒ : :  :  :  :  : 
  : ! : " : # : $ : % : & : ' : ( : ) : * : + : , : - : . : / : 
0 : 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : : : ; : < : = : > : ? : 
@ : A : B : C : D : E : F : G : H : I : J : K : L : M : N : O : 
P : Q : R : S : T : U : V : W : X : Y : Z : [ : \ : ] : ^ : _ : 
` : a : b : c : d : e : f : g : h : i : j : k : l : m : n : o : 
p : q : r : s : t : u : v : w : x : y : z : { : | : } : ~ :  : 
 :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  : 
 :  :  :  :  :  :  :  :  :  :  : Â:  :  :  :  : 
 : ¡ : ¢ : £ : ¤ : ¥ : ¦ : § : ¨ : © : ª : « : ¬ :  : ® : ¯ : 
° : ± : ² : ³ : ´ : µ : ¶ : · : ¸ : ¹ : º : » : ¼ : ½ : ¾ : ¿ : 
à : à : à : à : à : à : à : à : à : à : à : à : à : à : à : à : 
à : à : à : à : à : à : à : à : à : à : à : Ã: à : à : à : à : 
à : á : â : ã : ä : å : æ : ç : è : é : ê : ë : ì : à : î : ï : 
ð : ñ : ò : ó : ô : õ : ö : ÷ : ø : ù : ú : û : ü : ý : þ : ÿ : 
GTM>Halt
gtmuser@gtmworkshop7:~$ 

Notice that the lengths of the strings are different – the process in UTF-8 mode reports all as being of length 1, whereas the M mode process reports some as being of length 2.

UTF8>For i=0:16:240 Write ! For j=0:1:15 Write $Length(^Ch(i+j))," : "

1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
UTF8>

GTM>for i=0:16:240 write ! for j=0:1:15 write $length(^Ch(i+j))," : "

1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
GTM>

However, both report the same string lengths in bytes using the $Zlength() function instead of the $Length() function.

UTF8>For i=0:16:240 Write ! For j=0:1:15 Write $ZLength(^Ch(i+j))," : "

1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
UTF8>

and

GTM>For i=0:16:240 Write ! For j=0:1:15 Write $ZLength(^Ch(i+j))," : "

1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 : 1 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 : 2 :
GTM>

Remember that Unicode only provides a character encoding. The ordering of strings encoded in UTF-8 may not be linguistically or culturally correct for your language. For example, Chinese names may be encoded in UTF-8 using characters in either the simplified or the traditional Chinese script, but the linguistically and culturally appropriate ordering of names (for example in the Beijing phone book) is the order in pinyin, which uses the Latin script. In order to use GT.M to store strings in the correct order, there may be an additional requirement to provide a collation module.

Database Encryption

Before proceeding with this exercise, it is important to understand the limitations and architecture of GT.M database encryption: GT.M itself includes no cryptographic software whatsoever and performs no encryption – it has a plug-in architecture with a reference plug-in that interfaces with GPG (GNU Privacy Guard) and OpenSSL. Remember also that key management & distribution is a much harder problem than database encryption itself.

The exercise below illustrates the reference implementation and GPG more than it does GT.M's capabilities. Much of the exercises here are scripted with the reference plugin – we go through them here in detail so that you can understand what happens under the covers. Remember that there are actually three layers of encryption, required in order to manage & distribute keys securely:

There are two users, Helen, who administers and distributes keys, and Phil, who is a normal database user. The private key needed by a process to decrypt the file, and get access to the key to the symmetric cipher is in the GPG keyring. Phil and Helen must each generate a public-private key-pair and exchange their public keys.

Note that for this exercise, the creation of public and private key pairs must be done on the host on which you are running the virtual machines (or on another host). The GT.M workshop virtual machines do not generate enough entropy (randomness) for GPG to consistently and successfully generate key pairs. The generated key pairs must then be transferred to the virtual machines.

The following exercise uses GPG on a Linux host; commands for GPG on Windows or other hosts should be similar. If separate user ids on the host are not used for the simulated Phil and Helen users, you can use the environment variable $GNUPGHOME on one userid to point to different GPG keyrings. Note that the Helen GPG key ring is always on the host and there is no need to push it to the virtual machine.

Create the GPG keyrings for Helen and Phil on the host. Note that owing to available functionality with GPG, since the reference plug-in uses RSA exclusively as an asymmetric cipher, the asymmetric keys have to be generated in two steps. First Helen.

$ export GNUPGHOME=~/.helengnupg
$ mkdir $GNUPGHOME
$ chmod go-rx $GNUPGHOME
$ gpg --gen-key
gpg (GnuPG) 1.4.9; Copyright (C) 2008 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

gpg: keyring `/home/gtmuser/.philgnupg/secring.gpg' created
gpg: keyring `/home/gtmuser/.philgnupg/pubring.gpg' created
Please select what kind of key you want:
   (1) DSA and Elgamal (default)
   (2) DSA (sign only)
   (5) RSA (sign  only)
Your selection? 5
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048)
Requested keysize is 2048 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y

You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
    "Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"

Real name: Helen Keymaster
Email address: helen.keymaster@gt.m
Comment: Demo for GT.M Workshop
You selected this USER-ID:
    "Helen Keymaster (Demo for GT.M Workshop) <helen.keymaster@gt.m>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
You need a Passphrase to protect your secret key.

Enter passphrase: (use a passphrase of your choice)
Repeat passphrase:

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

Not enough random bytes available.  Please do some other work to give
the OS a chance to collect more entropy! (Need 280 more bytes)
......+++++
..+++++
gpg: /home/gtmuser/.helengnupg/trustdb.gpg: trustdb created
gpg: key 40736E89 marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
pub   2048R/40736E89 2009-12-15
      Key fingerprint = 579E 6342 1856 F9E5 EFA9  ECAE D039 66C8 4073 6E89
uid                  Helen Keymaster (Demo for GT.M Workshop) <helen@gt.m>

Note that this key cannot be used for encryption.  You may want to use
the command "--edit-key" to generate a subkey for this purpose.
$ gpg --edit-key helen@gt.m
gpg (GnuPG) 1.4.9; Copyright (C) 2008 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

pub  2048R/40736E89  created: 2009-12-15  expires: never       usage: SC
                     trust: ultimate      validity: ultimate
[ultimate] (1). Helen Keymaster (Demo for GT.M Workshop) <helen@gt.m>

Command> addkey
Key is protected.

You need a passphrase to unlock the secret key for
user: "Helen Keymaster (Demo for GT.M Workshop) <helen@gt.m>"
2048-bit RSA key, ID 40736E89, created 2009-12-15

Enter passphrase:
Please select what kind of key you want:
   (2) DSA (sign only)
   (4) Elgamal (encrypt only)
   (5) RSA (sign only)
   (6) RSA (encrypt only)
Your selection? 6
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048)
Requested keysize is 2048 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y
Really create? (y/N) y
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

Not enough random bytes available.  Please do some other work to give
the OS a chance to collect more entropy! (Need 74 more bytes)
...+++++
+++++

pub  2048R/40736E89  created: 2009-12-15  expires: never       usage: SC
                     trust: ultimate      validity: ultimate
sub  2048R/6CC824A4  created: 2009-12-15  expires: never       usage: E
[ultimate] (1). Helen Keymaster (Demo for GT.M Workshop) <helen@gt.m>

Command> save
$

The Helen keyring now has RSA keys for both encryption and signing. And now for the Phil keyring.

$ export GNUPGHOME=~/.philgnupg
$ mkdir $GNUPGHOME
$ chmod go-rx $GNUPGHOME
$ gpg --gen-key
gpg (GnuPG) 1.4.9; Copyright (C) 2008 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

gpg: keyring `/home/gtmuser/.philgnupg/secring.gpg' created
gpg: keyring `/home/gtmuser/.philgnupg/pubring.gpg' created
Please select what kind of key you want:
   (1) DSA and Elgamal (default)
   (2) DSA (sign only)
   (5) RSA (sign only)
Your selection? 5
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048)
Requested keysize is 2048 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y

You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
    "Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"

Real name: Phil Keyuser
Email address: phil@gt.m
Comment: Demo for GT.M Workshop
You selected this USER-ID:
    "Phil Keyuser (Demo for GT.M Workshop) <phil@gt.m>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
You need a Passphrase to protect your secret key.

Enter passphrase: (use a passphrase of your choice)
Repeat passphrase:

Not enough random bytes available.  Please do some other work to give
the OS a chance to collect more entropy! (Need 284 more bytes)
..+++++
..+++++
gpg: /home/gtmuser/.philgnupg/trustdb.gpg: trustdb created
gpg: key 72DFCE93 marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
pub   2048R/72DFCE93 2009-12-15
      Key fingerprint = 6DDA 1679 8BE6 E3A3 67B7  16B7 6F1B DEC1 72DF CE93
uid                  Phil Keyuser (Demo for GT.M Workshop) <phil@gt.m>

Note that this key cannot be used for encryption.  You may want to use
the command "--edit-key" to generate a subkey for this purpose.
$ gpg --edit-key phil@gt.m
gpg (GnuPG) 1.4.9; Copyright (C) 2008 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

pub  2048R/72DFCE93  created: 2009-12-15  expires: never       usage: SC
                     trust: ultimate      validity: ultimate
[ultimate] (1). Phil Keyuser (Demo for GT.M Workshop) <phil@gt.m>

Command> addkey
Key is protected.

You need a passphrase to unlock the secret key for
user: "Phil Keyuser (Demo for GT.M Workshop) <phil@gt.m>"
2048-bit RSA key, ID 72DFCE93, created 2009-12-15

Enter passphrase:

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

Not enough random bytes available.  Please do some other work to give
the OS a chance to collect more entropy! (Need 84 more bytes)
...........+++++
+++++

pub  2048R/72DFCE93  created: 2009-12-15  expires: never       usage: SC
                     trust: ultimate      validity: ultimate
sub  2048R/95884D50  created: 2009-12-15  expires: never       usage: E
[ultimate] (1). Phil Keyuser (Demo for GT.M Workshop) <phil@gt.m>

Command> save
$

Now, you need to move the Phil GPG keyring from the host to the guest. If the host has an appropriate service enabled, such as ssh, you can pull the keyring from the virtual machine (in this example, gtmuser is the userid on the host used to create the Phil keyring).

gtmuser@gtmworkshop7:~$ scp -r gtmuser@10.0.2.2:.philgnupg .
gtmuser@10.0.2.2's password:
gtmuser@gtmworkshop7:~$

Alternatively, if the host does not have an enabled ssh service but has an ssh or scp client (such as WinSCP on Windows), you can push the Phil keyring from the host to the guest. The example here uses scp on Linux.

$ scp -r -P 2222 .philgnupg gtmuser@localhost:./
gtmuser@localhost's password: 
pubring.gpg                                   100% 2398     2.3KB/s   00:00    
random_seed                                   100%  600     0.6KB/s   00:00    
secring.gpg                                   100% 2574     2.5KB/s   00:00    
trustdb.gpg                                   100% 1280     1.3KB/s   00:00    
$ 

After copying the Phil keyring to the virtual machine, you should set the permission of the directory to be those that GPG expects – otherwise, you will see complaints about insufficient security. You can also rename the .philgnupg directory to just .gnupg: mv .philgnupg .gnupg ; chmod -R go-rwx .gnupg

Export the Helen public key on the host, and transfer it to the GT.M Acculturation Workshop guest virtual machine. Although this shows the file being pushed from the host, it could also be pulled from the guest.

$ gpg --armor --output helen.publickey.txt --export helen@gt.m
$ scp -P 2222 helen.publickey.txt gtmuser@localhost:./
gtmuser@localhost's password:
helen.publickey.txt                                  100% 1743     1.7KB/s   00:00
$

On the guest, import Helen's public key to the Phil keyring. Export the Phil public key so that Helen can import it on the host.

gtmuser@gtmworkshop7:~$ export GNUPGHOME=~/.philgnupg
gtmuser@gtmworkshop7:~$ gpg --import helen.publickey.txt
gpg: key 40736E89: public key "Helen Keymaster (Demo for GT.M Workshop) <helen@gt.m>" imported
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)
gtmuser@gtmworkshop7:~$ gpg --armor --output phil.publickey.txt --export phil@gt.m
gtmuser@gtmworkshop7:~$

Import the Phil public key into the Helen GPG keyring on the host. This completes the key exchange required for Helen to later securely transmit to Phil the key to the symmetric cipher. Alternatively, Helen and Phil may both also upload their public keys to a keyserver such as pgp.mit.edu for anyone who might need their public keys. They should directly exchange their public key fingerprints over a different channel, such as a fax or phone call, in order to protect themselves against “man in the middle” attacks.

$ scp -P 2222 gtmuser@localhost:phil.publickey.txt ./
gtmuser@localhost's password:
phil.publickey.txt                                   100% 1738     1.7KB/s   00:00
$ gpg --import phil.publickey.txt
gpg: key 72DFCE93: public key "Phil Keyuser (Demo for GT.M Workshop) <phil@gt.m>" imported
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)
$

Helen can now manually generate a 32-byte key for the symmetric cipher, but it is better to use a truly random key using GPG or other tool (for example, http://www.random.org). Since this is only a workshop, we use a key strength here of 1 to speed up key generation performance and to not drain entropy from the host; 2 is the value to use for real applications. This key is then encrypted with the Phil public key and signed with the Helen private key. Then push the key to the guest.

$ gpg --gen-random 1 32 | gpg --encrypt --recipient phil@gt.m --sign --armor >gtm_workshop_key.txt

You need a passphrase to unlock the secret key for
user: "Helen Keymaster (Demo for GT.M Workshop) <helen@gt.m>"
2048-bit RSA key, ID 40736E89, created 2009-12-15

Enter passphrase:

gpg: 95884D50: There is no assurance this key belongs to the named user

pub  2048R/95884D50 2009-12-15 Phil Keyuser (Demo for GT.M Workshop) <phil@gt.m>
 Primary key fingerprint: 6DDA 1679 8BE6 E3A3 67B7  16B7 6F1B DEC1 72DF CE93
      Subkey fingerprint: 190E A578 8C8C 380E DEB1  7862 55B8 5E61 9588 4D50

It is NOT certain that the key belongs to the person named
in the user ID.  If you *really* know what you are doing,
you may answer the next question with yes.

Use this key anyway? (y/N) y
$ scp -P 2222 gtm_workshop_key.txt gtmuser@localhost:./
gtmuser@localhost's password:
gtm_workshop_key.txt                                 100% 1005     1.0KB/s   00:00
$

On the GT.M Acculturation Workshop guest, create a directory enc, with an env file to set the environment. If you wish, you can copy an env file from another directory and adapt it. Also, create a master key file for the environment variable gtm_dbkeys to point to.

gtmuser@gtmworkshop7:~$ mkdir enc
gtmuser@gtmworkshop7:~$ fte enc/env
gtmuser@gtmworkshop7:~$ cat enc/env
export gtm_dist=/usr/lib/fis-gtm/V5.4-002B_x86
export gtmgbldir=/home/gtmuser/enc/gtm.gld
export gtm_log=/tmp/fis-gtm/V5.4-002B_x86
export gtm_principal_editing=EDITING
export gtm_repl_instance=/home/enc/gtm_repl
export gtm_repl_instname=dummy
export gtmroutines="/home/gtmuser/enc $gtm_dist"
export gtm_tmp=$gtm_log
mkdir -p $gtm_tmp
export gtm_dbkeys=/home/gtmuser/enc/master_keys.txt
alias mumps=$gtm_dist/mumps
alias mupip=$gtm_dist/mupip
gtmuser@gtmworkshop7:~$ source enc/env
gtmuser@gtmworkshop7:~$

Create the database key file from the gtm_workshop_key.txt file from Helen.

gtmuser@gtmworkshop7:~$ gpg --decrypt <gtm_workshop_key.txt | gpg --encrypt --armor --default-recipient-self --output enc/gtm.key

You need a passphrase to unlock the secret key for
user: "Phil Keyuser (Demo for GT.M Workshop) <phil@gt.m>"
2048-bit RSA key, ID 95884D50, created 2009-12-15 (main key ID 72DFCE93)

Enter passphrase:

gpg: encrypted with 2048-bit RSA key, ID 95884D50, created 2009-12-15
      "Phil Keyuser (Demo for GT.M Workshop) <phil@gt.m>"
gpg: Signature made Tue Dec 15 23:50:42 2009 est using RSA key ID 40736E89
gpg: Good signature from "Helen Keymaster (Demo for GT.M Workshop) <helen@gt.m>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 579E 6342 1856 F9E5 EFA9  ECAE D039 66C8 4073 6E89
gtmuser@gtmworkshop7:~$

Now create a global directory that specifies that the database file is encrypted.

gtmuser@gtmworkshop7:~$ mumps -run GDE
%GDE-I-GDUSEDEFS, Using defaults for Global Directory
        /home/gtmuser/enc/gtm.gld

GDE> change -segment default -encryption -file=/home/gtmuser/enc/gtm.dat
GDE> show -segment

                                *** SEGMENTS ***
 Segment                         File (def ext: .dat)Acc Typ Block      Alloc Exten Options
 -------------------------------------------------------------------------------------------
 DEFAULT                         /home/gtmuser/enc/gtm.dat   BG  DYN  1024        100   100 GLOB=1024
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=ON
GDE> change -region default -journal=(before,file="/home/gtmuser/enc/gtm.mjl")
GDE> show -region

                                *** REGIONS ***
                                 Dynamic                          Def    Rec   Key Null       Standard
 Region                          Segment                         Coll   Size  Size Subs       NullColl  Journaling
 ------------------------------------------------------------------------------------------------------------------
 DEFAULT                         DEFAULT                            0    256    64 NEVER      N         Y

                          *** JOURNALING INFORMATION ***
 Region                          Jnl File (def ext: .mjl)  Before Buff      Alloc Exten
 ---------------------------------------------------------------------------------------
 DEFAULT                         /home/gtmuser/enc/gtm.mjl         Y       128        100   100

GDE> exit
%GDE-I-VERIFY, Verification OK

%GDE-I-GDCREATE, Creating Global Directory file
        /home/gtmuser/enc/gtm.gld
gtmuser@gtmworkshop7:~$

Now create the database file. Notice that in order to supply mupip create with the obfuscated GPG keyring password in the environment, we have to invoke mupip through an intermediary mumps process that prompts for the password and places and obfuscated password in the environment.

gtmuser@gtmworkshop7:~$ gtm_passwd="" mumps -dir

Enter Passphrase:

GTM>zsystem "$gtm_dist/mupip create"
Created file /home/gtmuser/enc/gtm.dat

GTM>halt
gtmuser@gtmworkshop7:~$

You can use DSE to verify that the file is encrypted.

gtmuser@gtmworkshop7:~$ $gtm_dist/dse dump -fileheader

File    /home/gtmuser/enc/gtm.dat
Region  DEFAULT


File            /home/gtmuser/enc/gtm.dat
Region          DEFAULT
Date/Time       16-DEC-2009 16:01:07 [$H = 61711,57667]
  Access method                          BG  Global Buffers                1024
  Reserved Bytes                          0  Block size (in bytes)         1024
  Maximum record size                   256  Starting VBN                   129
  Maximum key size                       64  Total blocks            0x00000065
  Null subscripts                     NEVER  Free blocks             0x00000062
  Standard Null Collation             FALSE  Free space              0x00000000
  Last Record Backup     0x0000000000000001  Extension Count                100
  Last Database Backup   0x0000000000000001  Number of local maps             1
  Last Bytestream Backup 0x0000000000000001  Lock space              0x00000028
  In critical section            0x00000000  Timers pending                   0
  Cache freeze id                0x00000000  Flush timer            00:00:01:00
  Freeze magtmuserh                   0x00000000  Flush trigger                  960
  Current transaction    0x0000000000000001  No. of writes/flush              7
  Maximum TN             0xFFFFFFFFE3FFFFFF  Certified for Upgrade to        V5
  Maximum TN Warn        0xFFFFFFFF73FFFFFF  Desired DB Format               V5
  Master Bitmap Size                    112  Blocks to Upgrade       0x00000000
  Create in progress                  FALSE  Modified cache blocks            0
  Reference count                         1  Wait Disk                        0
  Journal State                         OFF  Journal Before imaging        TRUE
  Journal Allocation                    100  Journal Extension              100
  Journal Buffer Size                   128  Journal Alignsize              128
  Journal AutoswitchLimit           8388600  Journal Epoch Interval         300
  Journal Yield Limit                     8  Journal Sync IO              FALSE
  Journal File: /home/gtmuser/enc/gtm.mjl
  Mutex Hard Spin Count                 128  Mutex Sleep Spin Count         128
  Mutex Spin Sleep Time                2048  KILLs in progress                0
  Replication State                     OFF  Region Seqno    0x0000000000000001
  Zqgblmod Seqno         0x0000000000000000  Zqgblmod Trans  0x0000000000000000
  Endian Format                      LITTLE  Commit Wait Spin Count          16
  Database file encrypted              TRUE
gtmuser@gtmworkshop7:~$

Now turn on journaling, and make some updates.

gtmuser@gtmworkshop7:~$ mupip set -journal="before,on" -region "*"
%GTM-I-JNLCREATE, Journal file /home/gtmuser/enc/gtm.mjl created for region DEFAULT with BEFORE_IMAGES
%GTM-I-JNLSTATE, Journaling state for region DEFAULT is now ON
gtmuser@gtmworkshop7:~$ gtm_passwd="" mumps -dir

Enter Passphrase:

GTM>set ^Text="This should be encrypted in the database"

GTM>halt
gtmuser@gtmworkshop7:~$

Confirm that the data is indeed visible in neither the database file nor the journal file.

gtmuser@gtmworkshop7:~$ strings enc/gtm.dat
GDSDYNUNX03
/home/gtmuser/enc/gtm.mjl
TUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU
hgf(
\'&L
gtmuser@gtmworkshop7:~$ strings enc/gtm.mjl
GDSJNL18
;K)K
<K)K
/home/gtmuser/enc/gtm.dat
J)K=
J)KJ
J)KM
;K)KO
;K)K
;K)KD
;K)K
@UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU
;K)K
<K)Kr
<K)Ku
<K)Kw
gtmuser@gtmworkshop7:~$

Pulling it together

Thus far, the Acculturation Workshop has taken you through core concepts. Now, it is time to pull the concepts together. In order to do that, we will go through a series of installations, each more sophisticated than its predecessor, of the VistA application on GT.M.

No knowledge of VistA is assumed or required for the Acculturation Workshop – VistA is simply used as a freely usable sample application to explore configuring an application on GT.M.

About VistA

The US Department of Veterans Affairs (VA) operates one of the largest integrated healthcare networks in the world. Delivering legally mandated high quality care to veterans of the US armed forces, it has repeatedly been recognized not only for the quality, but also for the cost-effectiveness, of the care that it provides. VistA is a healthcare information system (HIS) developed and maintained by the VA. The software is in the public domain and freely available. Many providers support VistA on a commercial basis, and there is an active online VistA community.

For all healthcare organizations, VistA can provide a cost-effective enterprise resource planning (ERP) system. It is written in the ANSI/ISO standard programming language M (also known as MUMPS), With origins in the field of healthcare informatics, M is the de facto standard for healthcare software.

The GT.M Acculturation Workshop uses the WorldVistA EHR® flavor of VistA as a sample GT.M application, and can be downloaded from the WorldVistA project at Source Forge.

Download VistA

To get VistA, you will need to download two archive files, the routines and the global variables. First, create a directory to store the archive files, then download the files into that directory.

gtmuser@gtmworkshop7:~$ sudo mkdir -p /Distrib/VistA
[sudo] password for gtmuser:
gtmuser@gtmworkshop7:~$ sudo chown gtmuser.gtmuser /Distrib/VistA/
gtmuser@gtmworkshop7:~$ wget -P /Distrib/VistA/ http://tinyurl.com/WVEHRVOE10Routines
[multiple lines of output have been suppressed here]
HTTP request sent, awaiting response... 200 OK
Length: 33432464 (32M) [application/x-gzip]
Saving to: `/Distrib/VistA/routines.tgz'

100%[==============================================================================>] 33,432,464   353K/s   in 79s

2010-09-10 16:46:01 (415 KB/s) - `/Distrib/VistA/routines.tgz' saved [33432464/33432464]
gtmuser@gtmworkshop7:~$ wget -P /Distrib/VistA/ http://tinyurl.com/WVEHRVOE10Globals
[multiple lines of output have been suppressed here]
HTTP request sent, awaiting response... 200 OK
Length: 140631465 (134M) [application/x-gzip]
Saving to: `/Distrib/VistA/globals.zwr.gz'

100%[==============================================================================>] 140,631,465  416K/s   in 5m 31s

2010-09-10 16:52:34 (415 KB/s) - `/Distrib/VistA/globals.zwr.gz' saved [140631465/140631465]

gtmuser@gtmworkshop7:~$ 

Simple Environment

The very simplest environment is one where you put everything – source, object, global directory, database and journal files – in one directory (as you did for the environments in the exercises above). But for any real application, this would rapidly become unwieldy.

The first step up from that simplest environment is to have separate sub-directories for the source files, object files, database; with shell scripts in the parent directory. Note that in production environments, you should put the journal files elsewhere, on a file system on disks different from the database, and ideally even on a separate disk controller.

Create a vista directory. Use a g subdirectory for global variables, an o subdirectory for object files and an r subdirectory for routines. Create a file to source to set up environment variables. Create a global directory and a database file into which to load the global variables.

gtmuser@gtmworkshop7:~$ mkdir -p VistA/{g,o,r}
gtmuser@gtmworkshop7:~$ fte VistA/env
gtmuser@gtmworkshop7:~$ cat VistA/env
export gtm_dist=/usr/lib/fis-gtm/V5.4-002B_x86
export gtmgbldir=/home/gtmuser/VistA/gtm.gld
export gtm_log=/tmp/fis-gtm/V5.4-002B_x86
export gtm_principal_editing=EDITING
export gtm_repl_instance=/home/VistA/gtm_repl
export gtm_repl_instname=dummy
export gtmroutines="/home/gtmuser/VistA/o(/home/gtmuser/VistA/r) $gtm_dist"
export gtm_principal_editing="EDITING"
alias mumps=$gtm_dist/mumps
alias mupip=$gtm_dist/mupip
gtmuser@gtmworkshop7:~$ source VistA/env
gtmuser@gtmworkshop7:~$ mumps -run GDE
%GDE-I-GDUSEDEFS, Using defaults for Global Directory 
        /home/gtmuser/VistA/gtm.gld

GDE> @/usr/lib/fis-gtm/V5.4-002B_x86/gdedefaults
%GDE-I-EXECOM, Executing command file /usr/lib/fis-gtm/V5.4-002B_x86/gdedefaults

GDE> change -segment DEFAULT -file=/home/gtmuser/VistA/g/gtm.dat
GDE> show -segment

                                *** SEGMENTS ***
 Segment                         File (def ext: .dat)Acc Typ Block      Alloc Exten Options
 -------------------------------------------------------------------------------------------
 DEFAULT                         /home/gtmuser/VistA/g/gtm.dat
                                                     BG  DYN  4096       5000 10000 GLOB=1000
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
GDE> change -region DEFAULT -journal=file=/home/gtmuser/VistA/g/gtm.mjl
GDE> show -region 

                                *** REGIONS ***
                                 Dynamic                          Def    Rec   Key Null       Standard
 Region                          Segment                         Coll   Size  Size Subs       NullColl  Journaling
 ------------------------------------------------------------------------------------------------------------------
 DEFAULT                         DEFAULT                            0   4080   255 NEVER      Y         Y

                          *** JOURNALING INFORMATION ***
 Region                          Jnl File (def ext: .mjl)  Before Buff      Alloc Exten
 ---------------------------------------------------------------------------------------
 DEFAULT                         /home/gtmuser/VistA/g/gtm.mjl       Y       128       2048  2048

GDE> exit
%GDE-I-VERIFY, Verification OK

%GDE-I-GDCREATE, Creating Global Directory file 
        /home/gtmuser/VistA/g/gtm.gld
gtmuser@gtmworkshop7:~$ mupip create

Created file /home/gtmuser/VistA/g/gtm.dat
gtmuser@gtmworkshop7:~$ 

Since the global variables are in a compressed archive format, if we extract them to a temporary file and then load them into the database, we will use a lot of disk space. You can use a FIFO (named pipe) to avoid the need for this temporary space – the gzip process writes to that FIFOand the mupip load reads from it.

gtmuser@gtmworkshop7:~$ mkfifo /tmp/vistaglobals
gtmuser@gtmworkshop7:~$ gzip -d </Distrib/VistA/globals.zwr.gz >/tmp/vistaglobals &

[1] 5677
gtmuser@gtmworkshop7:~$ mupip load /tmp/vistaglobals 
GT.M MUPIP EXTRACT
10-AUG-2010  14:38:37 ZWR
Beginning LOAD at record number: 3

LOAD TOTAL              Key Cnt: 20337342  Max Subsc Len: 223  Max Data Len: 834
Last LOAD record number: 20337344

[1]+  Done                    gzip -d < /Distrib/VistA/globals.zwr.gz > /tmp/vistaglobals
gtmuser@gtmworkshop7:~$ rm /tmp/vistaglobals 
gtmuser@gtmworkshop7:~$ ls -lR VistA/
VistA:
total 4.0K
-rw-r--r-- 1 gtmuser gtmuser 240 2010-09-13 11:42 env
drwxr-xr-x 2 gtmuser gtmuser  32 2010-09-13 11:49 g
drwxr-xr-x 2 gtmuser gtmuser   1 2010-09-13 11:41 o
drwxr-xr-x 2 gtmuser gtmuser   1 2010-09-13 11:41 r

VistA/g:
total 421M
-rw-rw-rw- 1 gtmuser gtmuser 451M 2010-09-13 11:54 gtm.dat
-rw-r--r-- 1 gtmuser gtmuser 1.5K 2010-09-13 11:49 gtm.gld

VistA/o:
total 0

VistA/r:
total 0
gtmuser@gtmworkshop7:~$ 

Notice that the database is 451MB and contains 20,337,342 global variable nodes. Now, we can unpack the routines into the r directory.

gtmuser@gtmworkshop7:~$ tar zxvf /Distrib/VistA/routines.tgz -C VistA/
[many thousands of lines of output have been suppressed here]
r/DGPFLMT.m
r/PRSEDEL1.m
r/FBCTAU.m
gtmuser@gtmworkshop7:~$ ls VistA/r | wc
25163 25163 251422
gtmuser@gtmworkshop7:~$ 

This tells us that WorldVistA EHR has 25,163 source code modules. We can turn on journaling so that we can recover the database in the event the system crashes. This is handy even for development environments – developers don't like to be kept waiting to recover environments and disk is inexpensive!

gtmuser@gtmworkshop7:~$ mupip set -journal="before,on" -region DEFAULT
%GTM-I-JNLCREATE, Journal file /home/gtmuser/VistA/g/gtm.mjl created for region DEFAULT with BEFORE_IMAGES
%GTM-I-JNLSTATE, Journaling state for region DEFAULT is now ON
gtmuser@gtmworkshop7:~$ ls -l VistA/g
total 430900
-rw-rw-rw- 1 gtmuser gtmuser 472031744 2010-09-13 12:01 gtm.dat
-rw-r--r-- 1 gtmuser gtmuser      1536 2010-09-13 11:49 gtm.gld
-rw-rw-rw- 1 gtmuser gtmuser

You can now run VistA – just enough to convince yourself that it is working, then exit.

gtmuser@gtmworkshop7:~$ mumps -dir

GTM>set DUZ=1

GTM>do P^DI


VA FileMan 22.0


Select OPTION: ^
GTM>halt
gtmuser@gtmworkshop7:~$ 

Note that GT.M has dynamically compiled modules as needed.

gtmuser@gtmworkshop7:~$ ls -l VistA/o
total 352
-rw-r--r-- 1 gtmuser gtmuser 18900 2010-09-13 12:03 DIALOG.o
-rw-r--r-- 1 gtmuser gtmuser  9059 2010-09-13 12:04 DIARB.o
-rw-r--r-- 1 gtmuser gtmuser 18107 2010-09-13 12:04 DIB.o
-rw-r--r-- 1 gtmuser gtmuser 15682 2010-09-13 12:03 DIC0.o
-rw-r--r-- 1 gtmuser gtmuser 10447 2010-09-13 12:03 DIC11.o
-rw-r--r-- 1 gtmuser gtmuser 24949 2010-09-13 12:03 DIC1.o
-rw-r--r-- 1 gtmuser gtmuser 16612 2010-09-13 12:03 DIC2.o
-rw-r--r-- 1 gtmuser gtmuser 16412 2010-09-13 12:04 DICATT2.o
-rw-r--r-- 1 gtmuser gtmuser 10372 2010-09-13 12:03 DICL.o
-rw-r--r-- 1 gtmuser gtmuser 19330 2010-09-13 12:03 DIC.o
-rw-r--r-- 1 gtmuser gtmuser 14901 2010-09-13 12:03 DICRW.o
-rw-r--r-- 1 gtmuser gtmuser 11564 2010-09-13 12:03 DICUIX1.o
-rw-r--r-- 1 gtmuser gtmuser 15624 2010-09-13 12:03 DICUIX2.o
-rw-r--r-- 1 gtmuser gtmuser 14650 2010-09-13 12:03 DICUIX.o
-rw-r--r-- 1 gtmuser gtmuser 16413 2010-09-13 12:03 DII.o
-rw-r--r-- 1 gtmuser gtmuser  7085 2010-09-13 12:03 DILF.o
-rw-r--r-- 1 gtmuser gtmuser 23032 2010-09-13 12:03 DILIBF.o
-rw-r--r-- 1 gtmuser gtmuser  2096 2010-09-13 12:03 DI.o
-rw-r--r-- 1 gtmuser gtmuser 10357 2010-09-13 12:03 DIQGU.o
-rw-r--r-- 1 gtmuser gtmuser 21554 2010-09-13 12:03 _DTC.o
-rw-r--r-- 1 gtmuser gtmuser 18558 2010-09-13 12:03 _ZOSV.o
gtmuser@gtmworkshop7:~$ 

Pre-compiled Routines

VistA is written to be portable across different MUMPS implementations. This means that it is guaranteed to contain code that is syntactically incorrect for every every MUMPS implementation. As modules are dynamically compiled, they will generate compilation errors that are sent to STDERR. Since you may find these disconcerting, you can prevent them by dynamically compiling all the modules.

gtmuser@gtmworkshop7:~$ cd VistA/o
gtmuser@gtmworkshop7:~/VistA/o$ find ../r -name \*.m -print -exec $gtm_dist/mumps {} \;

Ideally, we would simply compile with a mumps *.m command, but 25,163 routines would make for a command line longer than the shell can handle. So, we use the find command; you can also compile using the xargs command. You should also confirm that all routines were compiled by counting the number of source modules and the number of object modules.

gtmuser@gtmworkshop7:~/VistA/o$ ls |wc
  25163   25163  251422
gtmuser@gtmworkshop7:~/VistA/o$ ls ../r|wc
  25163   25163  251422
gtmuser@gtmworkshop7:~/VistA/o$ 

Make sure you can start and run VistA after recompiling the modules.

Multiple GT.M versions

GT.M object files are specific to each release of GT.M – so V5.4-002B cannot use object files generated by V5.4-000A, for example. Although the database format is more stable, a database file can only be concurrently open only by processes of one GT.M. The same source code, however, can be used by an unlimited number of GT.M releases. Also, even within a single GT.M release, the same source code can be used by processes running in M mode and UTF-8 mode – but the object files are different. The directory tree structure implemented in the simple environment allows only processes of only one GT.M release operating in only one mode to use a set of GT.M source modules.

gtmuser@gtmworkshop7:~$ tree -d VistA/
VistA/
├── g
├── o
└── r

3 directories
gtmuser@gtmworkshop7:~$ 

By creating another layer in the directory structure, the same VistA routines can be made to work in multiple GT.M releases, for example, if we had:

gtmuser@gtmworkshop7:~$ mkdir VistA/V5.4-002B_x86
gtmuser@gtmworkshop7:~$ mv VistA/{g,o} VistA/V5.4-002B_x86/
gtmuser@gtmworkshop7:~$ tree -d VistA/
VistA/
├── r
└── V5.4-002B_x86
    ├── g
    └── o

4 directories
gtmuser@gtmworkshop7:~$ 

Notice that we simply moved the object, journal, global directory and database files from one location to another. Here are some rules for when you move files:

Fixing the above (starting with the environment file, since it points to the global directory, and then the global directory, since it points to the database file):

gtmuser@gtmworkshop7:~$ mv VistA/env VistA/V5.4-002B_x86/
gtmuser@gtmworkshop7:~$ fte VistA/V5.4-002B_x86/env
gtmuser@gtmworkshop7:~$ cat VistA/V5.4-002B_x86/env
export gtmdir=/home/gtmuser/VistA
export gtmver=V5.4-002B_x86
export gtm_dist=/usr/lib/fis-gtm/$gtmver
export gtmgbldir=$gtmdir/$gtmver/g/gtm.gld
export gtm_log=/tmp/fis-gtm/$gtmver
export gtm_principal_editing=EDITING
export gtm_repl_instance=/home/VistA/gtm_repl
export gtm_repl_instname=dummy
export gtmroutines="$gtmdir/$gtmver/o($gtmdir/r) $gtm_dist"
export gtm_principal_editing="EDITING"
alias mumps=$gtm_dist/mumps
alias mupip=$gtm_dist/mupip
gtmuser@gtmworkshop7:~$ 

Notice that since the GT.M version occurs in multiple locations, it has been abstracted to the environment variable $gtmdir. Also, /home/gtmuser/VistA can be abstracted into an environment variable $gtmdir. By modify the global directory to use the environment variables, the global directory becomes more portable.

gtmuser@gtmworkshop7:~$ source VistA/V5.4-002B_x86/env
gtmuser@gtmworkshop7:~$ mumps -run GDE
%GDE-I-LOADGD, Loading Global Directory file
        /home/gtmuser/VistA/V5.4-002B_x86/g/gtm.gld
%GDE-I-VERIFY, Verification OK


GDE> change -segment DEFAULT -file=$gtmdir/$gtmver/g/gtm.dat
GDE> show -segment

                                *** SEGMENTS ***
 Segment                         File (def ext: .dat)Acc Typ Block      Alloc Exten Options
 -------------------------------------------------------------------------------------------
 DEFAULT                         $gtmdir/$gtmver/g/gtm.dat
                                                     BG  DYN  4096       5000 10000 GLOB=1000
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
GDE> change -region DEFAULT -journal=file=$gtmdir/$gtmver/g/gtm.mjl
GDE> show -region

                                *** REGIONS ***
                                 Dynamic                          Def    Rec   Key Null       Standard
 Region                          Segment                         Coll   Size  Size Subs       NullColl  Journaling
 ------------------------------------------------------------------------------------------------------------------
 DEFAULT                         DEFAULT                            0   4080   255 NEVER      Y         Y

                          *** JOURNALING INFORMATION ***
 Region                          Jnl File (def ext: .mjl)  Before Buff      Alloc Exten
 ---------------------------------------------------------------------------------------
 DEFAULT                         $gtmdir/$gtmver/g/gtm.mjl
                                                           Y       128       2048  2048

GDE> exit
%GDE-I-VERIFY, Verification OK

%GDE-I-GDUPDATE, Updating Global Directory file
        /home/gtmuser/VistA/V5.4-002B_x86/g/gtm.gld
gtmuser@gtmworkshop7:~$ 

Then disable and re-enable journaling so that the database and journal pointers are correct

gtmuser@gtmworkshop7:~$ mupip set -journal=disable -region DEFAULT
%GTM-I-JNLSTATE, Journaling state for region DEFAULT is now DISABLED
gtmuser@gtmworkshop7:~$ mupip set -journal="enable,on,before,file=$gtmdir/$gtmver/g/gtm.mjl" -region DEFAULT
%GTM-I-FILERENAME, File /home/gtmuser/VistA/V5.4-002B_x86/g/gtm.mjl is renamed to /home/gtmuser/VistA/V5.4-002B_x86/g/gtm.mjl_2010256150621
%GTM-I-JNLCREATE, Journal file /home/gtmuser/VistA/V5.4-002B_x86/g/gtm.mjl created for region DEFAULT with BEFORE_IMAGES
%GTM-I-JNLSTATE, Journaling state for region DEFAULT is now ON
gtmuser@gtmworkshop7:~$ 

And now VistA is again ready for use with the new directory structure:

gtmuser@gtmworkshop7:~$ mumps -dir

GTM>set DUZ=1

GTM>do P^DI


VA FileMan 22.0


Select OPTION: ^
GTM>halt
gtmuser@gtmworkshop7:~$ 

We can add additional directories for other versions of GT.M. e.g.,

gtmuser@gtmworkshop7:~$ mkdir -p VistA/V5.4-00{0A,1}_x86/{g,o}
gtmuser@gtmworkshop7:~$ tree -d VistA/
VistA/
├── r
├── V5.4-000A_x86
│ ├── g
│ └── o
├── V5.4-001_x86
│ ├── g
│ └── o
└── V5.4-002B_x86
    ├── g
    └── o

10 directories
gtmuser@gtmworkshop7:~$ 

This facilitates simple upgrades. For example, if you wanted to migrate from V5.4-002B to (the as yet unreleased as of this writing) V5.4-002, you could effect a rolling upgrade using replicating between the V5.4-002B and V5.4-002 sub-directories within the same directory.

A minor refinement – GT.M version dependent source

In general, program source code is independent of the GT.M version. On occasion, you may want to take advantage of an enhancement in a GT.M version, with modified source code. You can augment the g and o subdirectories with an r subdirectory for such version specific source code modules. Of course, unless the env file is updated accordingly, GT.M will never find the version specific routines.

gtmuser@gtmworkshop7:~$ for i in VistA/V* ; do mkdir $i/r ; done
gtmuser@gtmworkshop7:~$ tree -d VistA/
VistA/
├── r
├── V5.4-000A_x86
│ ├── g
│ ├── o
│ └── r
├── V5.4-001_x86
│ ├── g
│ ├── o
│ └── r
└── V5.4-002B_x86
    ├── g
    ├── o
    └── r

13 directories
gtmuser@gtmworkshop7:~$ fte VistA/V5.4-002B_x86/env
gtmuser@gtmworkshop7:~$ cat VistA/V5.4-002B_x86/env
export gtmdir=/home/gtmuser/VistA
export gtmver=V5.4-002B_x86
export gtm_dist=/usr/lib/fis-gtm/$gtmver
export gtmgbldir=$gtmdir/$gtmver/g/gtm.gld
export gtm_log=/tmp/fis-gtm/$gtmver
export gtm_principal_editing=EDITING
export gtm_repl_instance=/home/VistA/gtm_repl
export gtm_repl_instname=dummy
export gtmroutines="$gtmdir/$gtmver/o($gtmdir/$gtmver/r $gtmdir/r) $gtm_dist"
export gtm_principal_editing="EDITING"
alias mumps=$gtm_dist/mumps
alias mupip=$gtm_dist/mupip
gtmuser@gtmworkshop7:~$ source VistA/V5.4-002B_x86/env
gtmuser@gtmworkshop7:~$ mumps -dir

GTM>write $zroutines
/home/gtmuser/VistA/V5.4-002B_x86/o(/home/gtmuser/VistA/V5.4-002B_x86/r /home/gtmuser/VistA/r) /usr/lib/fis-gtm/V5.4-002B_x86
GTM>halt
gtmuser@gtmworkshop7:~$ 

Segregating local modifications

Installations of large applications often have local or modifications. In such cases, it is important to be able to segregate the local patches from the standard application distribution. This can be effected by placing these routines in p subdirectories, and placing the p subdirectories ahead of the r subdirectories where the standard routines remain untouched.

gtmuser@gtmworkshop7:~$ for i in VistA/V* ; do mkdir $i/p ; done

gtmuser@gtmworkshop7:~$ tree -d VistA/

VistA/
├── r
├── V5.4-000A_x86
│ ├── g
│ ├── o
│ ├── p
│ └── r
├── V5.4-001_x86
│ ├── g
│ ├── o
│ ├── p
│ └── r
└── V5.4-002B_x86
    ├── g
    ├── o
    ├── p
    └── r

16 directories
gtmuser@gtmworkshop7:~$ fte VistA/V5.4-002B_x86/env
gtmuser@gtmworkshop7:~$ cat VistA/V5.4-002B_x86/env
export gtmdir=/home/gtmuser/VistA
export gtmver=V5.4-002B_x86
export gtm_dist=/usr/lib/fis-gtm/$gtmver
export gtmgbldir=$gtmdir/$gtmver/g/gtm.gld
export gtm_log=/tmp/fis-gtm/$gtmver
export gtm_principal_editing=EDITING
export gtm_repl_instance=/home/VistA/gtm_repl
export gtm_repl_instname=dummy
export gtmroutines="$gtmdir/$gtmver/o($gtmdir/$gtmver/p $gtmdir/$gtmver/r $gtmdir/p $gtmdir/r) $gtm_dist"
export gtm_principal_editing="EDITING"
alias mumps=$gtm_dist/mumps
alias mupip=$gtm_dist/mupip
gtmuser@gtmworkshop7:~$ 

Now you can look at this in operation by applying some modifications to VistA that allow it to operate better with GT.M to the /home/gtmuser/VistA/p directory. Download the file KSBVistAPatches.zip from the same location that you obtained the GT.M Acculturation Workshop, and put it in the /Distrib/VistA directory. Then unpack it to /home/gtmuser/VistA/p directory.

gtmuser@gtmworkshop7:~$ ls -l /Distrib/VistA/KSBVistAPatches.zip
-rw-r--r-- 1 gtmuser gtmuser 20530 2010-09-14 14:05 /Distrib//VistA/KSBVistAPatches.zip
gtmuser@gtmworkshop7:~$ unzip -d VistA/p /Distrib/VistA/KSBVistAPatches.zip
Archive:  /Distrib//VistA/KSBVistAPatches.zip
  inflating: VistA/p/XPDR.m
  inflating: VistA/p/XWBTCPM.m
  inflating: VistA/p/ZOSV2GTM.m
  inflating: VistA/p/_ZOSV2.m
  inflating: VistA/p/ZOSVGUX.m
  inflating: VistA/p/_ZOSV.m
  inflating: VistA/p/ZTMGRSET.m
gtmuser@gtmworkshop7:~$

Then compile it with the object files in the VistA/V5.4-002B_x86/o directory

gtmuser@gtmworkshop7:~$ cd VistA/V5.4-002B_x86/o
gtmuser@gtmworkshop7:~/VistA/V5.4-002B_x86/o$ mumps ../../p/*.m
gtmuser@gtmworkshop7:~/VistA/V5.4-002B_x86/o$

Now, you can run VistA with the local modifications. In this case, one of the modifications is a fix to a minor bug in VistA: it treats spaces separating source directories in a parenthesized list as part of the directory name, rather than as a separator. With the change, when you run a function for example to apply a patch, it correctly puts the new routine in the first source directory even if it is within a parenthesized list of directories. In this example, you will run the ^ZTMGRTSET function. Notice that the VistA/V5.4-002B/p directory is initially empty, but has some tens of files afterwards.

gtmuser@gtmworkshop7:~$ ls -l VistA/V5.4-002B_x86/p
total 0
gtmuser@gtmworkshop7:~$ mumps -dir

GTM>do ^ZTMGRSET


ZTMGRSET Version 8.0 Patch level **34,36,69,94,121,127,136,191,275,355**
HELLO! I exist to assist you in correctly initializing the current account.

This is namespace or uci EHR,EHR.
Should I continue? N//y
I think you are using GT.M (Unix)
Which MUMPS system should I install?

1 = VAX DSM(V6), VAX DSM(V7)
2 = MSM-PC/PLUS, MSM for NT or UNIX
3 = Cache (VMS, NT, Linux), OpenM-NT
4 = Datatree, DTM-PC, DT-MAX
5 =
6 =
7 = GT.M (VMS)
8 = GT.M (Unix)
System: 8//

I will now rename a group of routines specific to your operating system.
Routine: ZOSVGUX      Loaded, Saved as %ZOSV
Routine:
Routine: ZIS4GTM      Loaded, Saved as %ZIS4
Routine: ZISFGTM      Loaded, Saved as %ZISF
Routine: ZISHGTM      Loaded, Saved as %ZISH
Routine: XUCIGTM      Loaded, Saved as %XUCI
Routine: ZISETGUX     Missing
Routine: ZOSV2GTM     Loaded, Saved as %ZOSV2
Routine: ZISTCPS      Loaded, Saved as %ZISTCPS

NAME OF MANAGER'S UCI,VOLUME SET: EHR,EHR//
The value of PRODUCTION will be used in the GETENV api.
PRODUCTION (SIGN-ON) UCI,VOLUME SET: EHR,EHR//
The VOLUME name must match the one in PRODUCTION.
NAME OF VOLUME SET: EHR//
The temp directory for the system: '/tmp/'//
^%ZOSF setup


Now to load routines common to all systems.
Routine: ZTLOAD       Loaded, Saved as %ZTLOAD
Routine: ZTLOAD1      Loaded, Saved as %ZTLOAD1
Routine: ZTLOAD2      Loaded, Saved as %ZTLOAD2
Routine: ZTLOAD3      Loaded, Saved as %ZTLOAD3
Routine: ZTLOAD4      Loaded, Saved as %ZTLOAD4
Routine: ZTLOAD5      Loaded, Saved as %ZTLOAD5
Routine: ZTLOAD6      Loaded, Saved as %ZTLOAD6
Routine: ZTLOAD7      Loaded, Saved as %ZTLOAD7
Routine: ZTM          Loaded, Saved as %ZTM
Routine: ZTM0         Loaded, Saved as %ZTM0
Routine: ZTM1         Loaded, Saved as %ZTM1
Routine: ZTM2         Loaded, Saved as %ZTM2
Routine: ZTM3         Loaded, Saved as %ZTM3
Routine: ZTM4         Loaded, Saved as %ZTM4
Routine: ZTM5         Loaded, Saved as %ZTM5
Routine: ZTM6         Loaded, Saved as %ZTM6
Routine: ZTMS         Loaded, Saved as %ZTMS
Routine: ZTMS0        Loaded, Saved as %ZTMS0
Routine: ZTMS1        Loaded, Saved as %ZTMS1
Routine: ZTMS2        Loaded, Saved as %ZTMS2
Routine: ZTMS3        Loaded, Saved as %ZTMS3
Routine: ZTMS4        Loaded, Saved as %ZTMS4
Routine: ZTMS5        Loaded, Saved as %ZTMS5
Routine: ZTMS7        Loaded, Saved as %ZTMS7
Routine: ZTMSH        Loaded, Saved as %ZTMSH
Routine: ZTER         Loaded, Saved as %ZTER
Routine: ZTER1        Loaded, Saved as %ZTER1
Routine: ZIS          Loaded, Saved as %ZIS
Routine: ZIS1         Loaded, Saved as %ZIS1
Routine: ZIS2         Loaded, Saved as %ZIS2
Routine: ZIS3         Loaded, Saved as %ZIS3
Routine: ZIS5         Loaded, Saved as %ZIS5
Routine: ZIS6         Loaded, Saved as %ZIS6
Routine: ZIS7         Loaded, Saved as %ZIS7
Routine: ZISC         Loaded, Saved as %ZISC
Routine: ZISP         Loaded, Saved as %ZISP
Routine: ZISS         Loaded, Saved as %ZISS
Routine: ZISS1        Loaded, Saved as %ZISS1
Routine: ZISS2        Loaded, Saved as %ZISS2
Routine: ZISTCP       Loaded, Saved as %ZISTCP
Routine: ZISUTL       Loaded, Saved as %ZISUTL
Routine: ZTPP         Loaded, Saved as %ZTPP
Routine: ZTP1         Loaded, Saved as %ZTP1
Routine: ZTPTCH       Loaded, Saved as %ZTPTCH
Routine: ZTRDEL       Loaded, Saved as %ZTRDEL
Routine: ZTMOVE       Loaded, Saved as %ZTMOVE
Want to rename the FileMan routines: No//y
Routine: DIDT         Loaded, Saved as %DT
Routine: DIDTC        Loaded, Saved as %DTC
Routine: DIRCR        Loaded, Saved as %RCR
Setting ^%ZIS('C')

Now, I will check your % globals...........
ALL DONE
GTM>halt
gtmuser@gtmworkshop7:~$ ls -l VistA/V5.4-002B_x86/p|wc
     57     450    3356
gtmuser@gtmworkshop7:~$

Creating a Development Environment

When you work on an application, either to enhance it or to fix a bug, you typically modify only a small part of the application. With GT.M, you do not need to make a copy of an entire application environment to work on your project. Nor do you need to work in the same environment as other developers, with the risk of stepping on one another's toes. All you need is to to set up your processes so that their $ZROUTINES search path finds your development routines before finding the main application routines. If your work involves changes to global variables, you can set up your own copy of the database – or, even, if it makes sense, a part of the database with the remaining globals mapped to the parent environment. Of course, in a large project, your environment's parent may itself have a parent.

To simplify the exercise, remove the subdirectories for GT.M versions other than V5.4-002B_x86 – although you could conceivably be doing development and migrating GT.M versions in parallel, that's one further step in complexity.

gtmuser@gtmworkshop7:~$ ls -l VistA/
total 2720
drwxr-xr-x 2 gtmuser gtmuser     56 2010-09-14 14:07 p
drwxr-xr-x 2 gtmuser gtmuser 405504 2010-09-13 13:51 r
drwxr-xr-x 6 gtmuser gtmuser     32 2010-09-14 11:49 V5.4-000A_x86
drwxr-xr-x 6 gtmuser gtmuser   4096 2010-09-14 11:54 V5.4-001_x86
drwxr-xr-x 6 gtmuser gtmuser     32 2010-09-14 11:49 V5.4-002B_x86
gtmuser@gtmworkshop7:~$ rm -rf VistA/V5.4-00{0A,1}_x86
gtmuser@gtmworkshop7:~$ ls -l VistA/
total 2720
drwxr-xr-x 2 gtmuser gtmuser     56 2010-09-14 14:07 p
drwxr-xr-x 2 gtmuser gtmuser 405504 2010-09-13 13:51 r
drwxr-xr-x 6 gtmuser gtmuser   4096 2010-09-14 11:54 V5.4-002B_x86
gtmuser@gtmworkshop7:~$

Now copy the files inc and install to the VistA environment from the same location as this Acculturation Workshop, and make install executable.

gtmuser@gtmworkshop7:~$ ls -l VistA/
total 2732
-r--r--r-- 1 gtmuser gtmuser    894 2010-09-14 15:56 inc
-r-xr-x--x 1 gtmuser gtmuser   4416 2010-09-14 15:56 install
drwxr-xr-x 2 gtmuser gtmuser     56 2010-09-14 14:07 p
drwxr-xr-x 2 gtmuser gtmuser 405504 2010-09-13 13:51 r
drwxr-xr-x 6 gtmuser gtmuser   4096 2010-09-14 11:54 V5.4-002B_x86
gtmuser@gtmworkshop7:~$

Similarly, to VistA/V5.4-002B_x86 copy the files wvehrstop, wvehrstart, run, newjnls and env (yes, the latter overwrites the env file you already have there). Make wvehrstop, wvehrstart, run and newjnls executable. Also, replace the env in VistA/V5.4-002B_x86 with the new env file. Create a symbolic link called gtm to /usr/lib/fis-gtm/V5.4-002B_x86.

gtmuser@gtmworkshop7:~$ ls -l VistA/V5.4-002B_x86/
total 1816
-rw-r--r-- 1 gtmuser gtmuser    731 2010-09-14 16:56 env
drwxr-xr-x 2 gtmuser gtmuser     40 2010-09-14 17:31 g
lrwxrwxrwx 1 gtmuser gtmuser     29 2010-09-14 17:30 gtm -> /usr/lib/fis-gtm/V5.4-002B_x86
-r-xr-xr-x 1 gtmuser gtmuser    181 2010-09-14 16:04 newjnls
drwxr-xr-x 3 gtmuser gtmuser 208896 2010-09-14 17:31 o
drwxr-xr-x 2 gtmuser gtmuser   4096 2010-09-14 14:24 p
drwxr-xr-x 2 gtmuser gtmuser      1 2010-09-13 17:34 r
-r-xr-x--x 1 gtmuser gtmuser    340 2010-09-14 16:05 run
-r-xr-xr-x 1 gtmuser gtmuser    277 2010-09-14 16:14 wvehrstart
-r-xr-xr-x 1 gtmuser gtmuser    161 2010-09-14 16:14 wvehrstop
gtmuser@gtmworkshop7:~$

Now create a child environment of VistA called dev using the install script.

gtmuser@gtmworkshop7:~$ VistA/install dev
Creating environment in dev as child of environment in /home/gtmuser/VistA
Default permission for development environment is for all to read and group to write - please alter as needed
gtmuser@gtmworkshop7:~$ ls -lR dev
dev:
total 16
-r--r--r-- 1 gtmuser gtmuser  894 2010-09-14 17:33 inc
-r-xr-x--x 1 gtmuser gtmuser 4416 2010-09-14 17:33 install
drwxrwxr-x 2 gtmuser gtmuser    1 2010-09-14 17:33 p
lrwxrwxrwx 1 gtmuser gtmuser   19 2010-09-14 17:33 parent -> /home/gtmuser/VistA
drwxrwxr-x 2 gtmuser gtmuser    1 2010-09-14 17:33 r
drwxrwxr-x 7 gtmuser gtmuser   88 2010-09-14 17:33 V5.4-002B_x86

dev/p:
total 0

dev/r:
total 0

dev/V5.4-002B_x86:
total 20
-r--r--r-- 1 gtmuser gtmuser 731 2010-09-14 17:33 env
drwxrwxr-x 2 gtmuser gtmuser   8 2010-09-14 17:33 g
lrwxrwxrwx 1 gtmuser gtmuser  36 2010-09-14 17:33 gtm -> /home/gtmuser/VistA/V5.4-002B_x86/gtm
-r-xr-xr-x 1 gtmuser gtmuser 181 2010-09-14 17:33 newjnls
drwxrwxr-x 2 gtmuser gtmuser   1 2010-09-14 17:33 o
drwxrwxr-x 2 gtmuser gtmuser   1 2010-09-14 17:33 p
drwxrwxr-x 2 gtmuser gtmuser   1 2010-09-14 17:33 r
-r-xr-x--x 1 gtmuser gtmuser 340 2010-09-14 17:33 run
drwxrwxr-x 2 gtmuser gtmuser   1 2010-09-14 17:33 tmp
-r-xr-xr-x 1 gtmuser gtmuser 277 2010-09-14 17:33 wvehrstart
-r-xr-xr-x 1 gtmuser gtmuser 161 2010-09-14 17:33 wvehrstop

dev/V5.4-002B_x86/g:
total 4
-r--r--r-- 1 gtmuser gtmuser 1536 2010-09-13 18:22 gtm.gld

dev/V5.4-002B_x86/o:
total 0

dev/V5.4-002B_x86/p:
total 0

dev/V5.4-002B_x86/r:
total 0

dev/V5.4-002B_x86/tmp:
total 0
gtmuser@gtmworkshop7:~$

Now run the dev environment and notice the values of the environment variables. In particular notice how the database used is that of the parent.

gtmuser@gtmworkshop7:~$ dev/V5.4-002B_x86/run

GTM>write $zgbldir
/home/gtmuser/dev/V5.4-002B_x86/g/gtm.gld
GTM>write $zroutines
/home/gtmuser/dev/V5.4-002B_x86/o(/home/gtmuser/dev/V5.4-002B_x86/p /home/gtmuser/dev/V5.4-002B_x86/r /home/gtmuser/dev/p /home/gtmuser/dev/r) /home/gtmuser/dev/parent/V5.4-002B_x86/o(/home/gtmuser/dev/parent/V5.4-002B_x86/p /home/gtmuser/dev/parent/V5.4-002B_x86/r /home/gtmuser/dev/parent/p /home/gtmuser/dev/parent/r) /home/gtmuser/dev/V5.4-002B_x86/gtm
GTM>zsystem "env | grep gtm"
gtm_repl_instance=/home/gtmuser/dev/parent/V5.4-002B_x86/g/gtm.repl
gtm_log=/tmp/fis-gtm/V5.4-002B_x86
gtm_prompt=GTM>
gtm_retention=42
gtmver=V5.4-002B_x86
USER=gtmuser
gtm_icu_version=4.2
routines=/home/gtmuser/dev/V5.4-002B_x86/o(/home/gtmuser/dev/V5.4-002B_x86/p /home/gtmuser/dev/V5.4-002B_x86/r /home/gtmuser/dev/p /home/gtmuser/dev/r) /home/gtmuser/dev/parent/V5.4-002B_x86/o(/home/gtmuser/dev/parent/V5.4-002B_x86/p /home/gtmuser/dev/parent/V5.4-002B_x86/r /home/gtmuser/dev/parent/p /home/gtmuser/dev/parent/r)
gtmgbldir=/home/gtmuser/dev/V5.4-002B_x86/g/gtm.gld
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/lib/fis-gtm/V5.4-002B_x86
MAIL=/var/mail/gtmuser
PWD=/home/gtmuser/dev/V5.4-002B_x86/tmp
gtmroutines=/home/gtmuser/dev/V5.4-002B_x86/o(/home/gtmuser/dev/V5.4-002B_x86/p /home/gtmuser/dev/V5.4-002B_x86/r /home/gtmuser/dev/p /home/gtmuser/dev/r) /home/gtmuser/dev/parent/V5.4-002B_x86/o(/home/gtmuser/dev/parent/V5.4-002B_x86/p /home/gtmuser/dev/parent/V5.4-002B_x86/r /home/gtmuser/dev/parent/p /home/gtmuser/dev/parent/r) /home/gtmuser/dev/V5.4-002B_x86/gtm
gtmdir=/home/gtmuser/dev/parent
HOME=/home/gtmuser
gtm_principal_editing=EDITING
LOGNAME=gtmuser
gtm_tmp=/tmp/fis-gtm/V5.4-002B_x86
gtm_dist=/usr/lib/fis-gtm/V5.4-002B_x86


GTM>do ^GDE
%GDE-I-LOADGD, Loading Global Directory file
        /home/gtmuser/dev/V5.4-002B_x86/g/gtm.gld
%GDE-I-VERIFY, Verification OK


GDE> show -segment

                                *** SEGMENTS ***
 Segment                         File (def ext: .dat)Acc Typ Block      Alloc Exten Options
 -------------------------------------------------------------------------------------------
 DEFAULT                         $gtmdir/$gtmver/g/gtm.dat
                                                     BG  DYN  4096       5000 10000 GLOB=1000
                                                                                    LOCK=  40
                                                                                    RES =   0
                                                                                    ENCR=OFF
GDE> quit
%GDE-I-NOACTION, Not updating Global Directory /home/gtmuser/dev/V5.4-002B_x86/g/gtm.gld
gtmuser@gtmworkshop7:~$

Further Investigation on Your Own

Use the ZEDIT command in the dev environment to create a “Hello, world” program and show that you can run it. Now, try executing that program in the parent VistA environment, and notice that it does not exist.

Investigate what happens if you use the --separate-globals flag of the install script. Work through the scripts to see how the environment variables are set up.

Examine the install script and see how it sets up a child environment.