OS-9/OSK Answers! by Joel Mathew Hegberg
September 1994 issue


September is a great month! Okay, yeah I was born in September, but Autumn is a great time of year -- not too hot like Summer, not too cold like Winter, not too muddy like Spring, but just right and colorful. And, as many avid programmers note, the nights are longer! It seems most computer programmers tend to enjoy computing late at night. So remember during this lovely Fall season, be sure to keep your 68'Micros subscription up-to-date, so you'll have plenty of interesting computing sessions in the months to come!

Last issue we talked a bit about named pipes, and how they could be used for interprocess communication. Another great use for named pipes is a temporary data storage, similar to a ram disk. Why not just use a ram disk? Because not everyone has a ram disk installed on their system. Pipes, on the other hand, are used by the OS-9 Shell and many other system utilities, so pipes almost have to be installed on everyone's OS-9 system.

One possible use of this "named pipe" storage area is an installation program. Many installation programs copy programs directly from one disk to another. This is great if you have more than one floppy or a floppy and a hard drive, but what about those few people that only have one floppy? If this is the case, why not copy all or some of the data to a named pipe and allow the user to swap disks, and copy the data to the second floppy disk? Sounds easy, but is it? Yes, but proceed with caution...

It's very simple to copy files directly to a named pipe. Your installation program may just use the OS-9 "copy" utility to "copy * -w=/pipe" (copies all files in the current directory to named pipes), then after the user swaps disks, use "copy /pipe/* -w=/d0" (copy files in the pipe to drive /d0). The "copy" utility actually looks at the size of the file it's copying, and sets the initial size of the named pipe, so write-blocking won't occur. This would work, but there are a couple problems. First, named pipes are used by a few programs, such as our "headlines" program in the last issue. How do you know named pipes for other programs do not exist, as they would be copied by referencing "/pipe/*" in the copy command? (You can use "dir /pipe" to see if there are any named pipe files currently on your system.) Once a pipe file is read, it disappears... and that could really confuse another program, not to mention you're copying files to the user's disk that aren't part of your installation.

So how about specifically naming your pipes, such as "copy program /pipe/program" and then "copy datafile /pipe/datafile" and then "copy script /pipe/script", etc., and then after the user swaps disks, "copy /pipe/program /d0/program", etc.? That is much better, and should solve the program I noted above, but there's something you may want to know about pipes, and my thanks to John Wainwright for bringing this to my attention. John noticed some of his files actually grew in size by a few bytes after being copied to a named pipe and then back. What's going on? I used the "dump" utility to examine a file copied in this fashion, and noticed a few null-characters were added at the end of the file. I also noted the the "dump" display, which displays 16 bytes per line, did not show any odd number of bytes for the final line. I remembered reading about this in the book, "The OS-9 Guru" by Paul S. Dayan (a book no OS-9 programmer should be without), so let me quote a brief portion for you...

"The actual size of the pipe buffer allocated may be greater than the requested size -- the request is rounded up to the nearest multiple of the minimum allocatable block size (16 bytes)."

That explains the additional bytes, and why the "dump" output did not have an odd line at the end. In all the cases I've tried, the appended bytes have always been nulls (value $00), but I have found no official documentation stating that this is guaranteed to be the case.

Well that's kind of annoying. In many cases the added null-bytes don't affect anything, but in some cases it certainly would. It looks like we need a more intelligent installation program! One that will store the exact size of each file to prevent any padding from taking place. Let's store all of the files to be installed on the user's disk in a single file ahead of time, which we can call an "install file" or "i-file". We need to define the i-file format, so let's make something up that makes sense...

The first 5 characters of the file should be "IFILE", so we can detect if the file is a valid i-file or not. Next, we'll store 29 bytes for the null-terminated filename, followed by an integer (4 bytes in OSK) for the exact size of the file stored within the i-file. The actual file contents then follows ('size' bytes long). Then, the next file is stored beginning with the 29 bytes for the filename again. If the first character of the filename field is a null ($00), there are no more files in the i-file. There, that was simple. It is important to keep in mind this is merely an example. If you were writing an installation program for commercial-quality software, you would want to have longer pathnames than 28 characters (to specify various directories) and record file attributes so executable files would run properly.

Listing #1 gives a program (make_ifile) to create an i-file and copy files into the i-file. This would be done once and stored to the original diskette. When the user runs the installation program, the i-file should be copied over to a named pipe file, so the user can swap disks if necessary. A sample command-line would be:

make_ifile test.ifile file1 file2 file3

or

make_ifile test2.ifile *.txt

Listing #2 gives a program (install_ifile) to copy all of the files contained within an i-file to a given directory. A sample command-line would be:

install_ifile /pipe/test.ifile /d0

or

install_ifile /pipe/test2.ifile /dd/CMDS

Installation of new software really can be an easy process if the programmer puts in just a little more effort. A named pipe is just one technique able to be used by a good installation program. You can see the source code listings are not very long, so there truly is little excuse for not supporting easy installation.

Listing #1: make_ifile.c
========================
#include <stdio.h>
#include <modes.h>
#include <errno.h>

#define BUFFSIZE 8192

main(argc, argv)
int argc;
char *argv[];
{
	int ipath, path, size, numbytes, t;
	char *data;
	
	if (argc < 3)
	{
		fprintf(stderr, "make_ifile &ltifile> &ltfile> {file ...}\n");
		exit(0);
	}
	
	data = (char *) malloc(BUFFSIZE);
	if (data == (char *)NULL)
	{
		fprintf(stderr, "make_ifile: Can't allocate buffer memory!\n");
		exit(errno);
	}
	
	ipath = creat(argv[1], S_IWRITE);
	if (ipath == -1)
	{
		free(data);
		fprintf(stderr, "make_ifile: Error opening file %s for writing!\n", argv[1]);
		exit(errno);
	}
	write(ipath, "IFILE", 5);
	
	for (t = 2; t < argc; t++)
	{
		printf("copying %s to ifile...\n", argv[t]);
		fflush(stdout);
		path = open(argv[t], S_IREAD);
		if (path == -1)
		{
			free(data);
			close(ipath);
			fprintf(stderr, "make_ifile: Error opening file %s for reading!\n", argv[t]);
			exit(errno);
		}
		
		/* record filename */
		sprintf(data, "%s", argv[t]);
		write(ipath, data, 29);
		
		/* record size of file */
		size = _gs_size(path);
		write(ipath, &size, sizeof(size));
		
		/* copy the file to the ifile */
		while(size > 0)
		{
			if (size > BUFFSIZE) numbytes = BUFFSIZE;
			else numbytes = size;
			read(path, data, numbytes);
			write(ipath, data, numbytes);
			size -= numbytes;
		}
		close(path);
	}
	
	/* record end-of-ifile marker */
	for (t = 0; t < 29; t++) write(ipath, "\x00", 1);
	free(data);
	close(ipath);
	printf("i-file '%s' created.\n", argv[1]);
}


Listing #2: install_ifile.c
===========================
#include <stdio.h>
#include <modes.h>
#include <errno.h>

#define BUFFSIZE 8192

main(argc, argv)
int argc;
char *argv[];
{
	int ipath, path, size, numbytes, t;
	char *data, filepath[255];
	
	if (argc != 3)
	{
		fprintf(stderr, "install_ifile <ifile> <directory>\n");
		exit(0);
	}
	
	data = (char *)malloc(BUFFSIZE);
	if (data == (char *)NULL)
	{
		fprintf(stderr, "install_ifile: Can't allocate buffer memory!\n");
		exit(errno);
	}
	
	ipath = open(argv[1], S_IREAD);
	if (ipath == -1)
	{
		free(data);
		fprintf(stderr, "install_ifile: Can't open file %s for reading.\n", argv[1]);
		exit(errno);
	}
	read(ipath, data, 5);
	if (strncmp(data, "IFILE", 5))
	{
		close(ipath);
		free(data);
		fprintf(stderr, "install_ifile: %s is not an ifile!\n", argv[1]);
		exit(0);
	}
	
	while(1)
	{
		read(ipath, data, 29);
		if (data[0] == 0x00) break;
		printf("Installing file %s...\n", data);
		fflush(stdout);
		
		/* Assemble filepath */
		sprintf(filepath, "%s/%s", argv[2], data);
		path = creat(filepath, S_IWRITE);
		if (path == -1)
		{
			fprintf(stderr, "Error opening file %s for writing.\n", filepath);
			free(data);
			close(ipath);
			exit(errno);
		}
		read(ipath, &size, sizeof(size));
		
		/* copy file from the ifile */
		while(size > 0)
		{
			if (size > BUFFSIZE) numbytes = BUFFSIZE;
			else numbytes = size;
			read(ipath, data, numbytes);
			write(path, data, numbytes);
			size -= numbytes;
		}
		close(path);
	}
	
	close(ipath);
	free(data);
	printf("Installation complete.\n");
}

Back in the June 1994 issue, I gave some C source code for determining if any special keys were being pressed on the MM/1's keyboard using K-Windows (CONTROL, SHIFT, ALT, etc.). Ted Jaeger has been kind enough to send us some BASIC source code to do the same thing. My thanks to him for letting me include it here! Ted also has sent me some BASIC code to show how to use K-Windows graphical buttons, which I'll be including in the next issue. As always, everyone is welcome to send in any code that they may feel will benefit other OS-9 programmers.

Listing #3 (mainloop) is a simple program which calls "keydata" (listing #4) and displays any special keys being pressed. Examine listing #4 (keydata) to learn where the keyboard data is found.

Listing #3: mainloop.bas
========================
PROCEDURE mainloop
(* demo loop for checking keys that don't alter the
(* keyboard buffer
(* July 6, 1994

DIM shft,scrlck,ctrl,alt,capslck,numlck:BYTE

PRINT CHR$(12)
PRINT "Pressing some keys does not change the status of the keyboard"
PRINT "buffer. These special keys include the shift, scroll lock, control,"
PRINT "alternate, caps lock, and num lock keys. Here is how they can"
PRINT "be detected in BASIC."
PRINT
PRINT "Press one of the special keys - I'll report key status!"
PRINT "Press any regular key to exit."
PRINT

(* enter main loop
LOOP

(* let's exit if anything other than
(* a special key is pressed
EXITIF INKEY(#0)<>0 THEN
ENDEXIT

(* check status of special keys
RUN keydata(shft,scrlck,ctrl,alt,capslck,numlck)

(* special key pressed
(* go report which
IF shft<>0 OR scrlck<>0 OR ctrl<&gt0 OR alt<>0 OR capslck<>0 OR numlck<>0 THEN
GOSUB 10
ENDIF

ENDLOOP

END

10 (* determine which key pressed
IF shft=1 THEN
PRINT "Left shift"
ENDIF
IF shft=2 THEN
PRINT "Right shift"
ENDIF
IF scrlck=255 THEN
PRINT "Scroll lock engaged - press again"
ENDIF
IF ctrl=255 THEN
PRINT "Control key"
ENDIF
IF alt=255 THEN
PRINT "Alternate key"
ENDIF
IF capslck=255 THEN
PRINT "Caps lock engaged - press again"
ENDIF
IF numlck=255 THEN
PRINT "Numlock engaged - press again"
ENDIF
RETURN


Listing #4: keydata.bas
=======================
PROCEDURE keydata
(* detects special keys
(* July 6, 1994
PARAM shft,scrlck,ctrl,alt,capslck,numlck:BYTE
TYPE registers=d(8),a(8),pc:INTEGER
DIM regs:registers


DIM callcode:INTEGER
DIM pointer:INTEGER

DIM wdata:STRING[10]
wdata="WData"

(* first locate wdata in memory
(* with link system call
(* module pointer returned in a(3)
callcode=$00
regs.d(1)=0
regs.a(1)=ADDR(wdata)
RUN syscall(callcode,regs)
pointer=regs.a(3)

shft=PEEK(pointer+182)
scrlck=PEEK(pointer+183)
ctrl=PEEK(pointer+184)
alt=PEEK(pointer+185)
capslck=PEEK(pointer+186)
numlck=PEEK(pointer+187)
end

* THE END *