Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Path: utzoo!linus!philabs!cmcl2!harvard!caip!cbmvax!skipper!amiga!robp From: robp@amiga.UUCP (Robert A. Peck) Newsgroups: net.micro.amiga Subject: Re: Orphaned Response (long) Message-ID: <1145@amiga.amiga.UUCP> Date: Mon, 12-May-86 18:03:33 EDT Article-I.D.: amiga.1145 Posted: Mon May 12 18:03:33 1986 Date-Received: Wed, 14-May-86 19:33:46 EDT References: <14257622@cory> <12024@ucla-cs> Reply-To: robp@dudley.UUCP (Robert A. Peck) Followup-To: Questions about running background tasks. Organization: Commodore-Amiga Inc., 983 University Ave #D, Los Gatos CA 95030 Lines: 490 Keywords: RUNBACKGROUND background-tasks dump-initial-CLI Summary: You CAN kill the initial CLI, and run functions as BACKGROUND tasks. SYNOPSIS: This tutorial note deals with the AmigaDOS Execute function, showing in detail how its various FileHandle combinations affect the way the function executes. It also provides the source for a RUNBACKGROUND command that you can add to your workbench to start one or more processes from the s/startup-sequence and still allow the initial CLI to end with the command line "endcli > NIL:". SPECIAL NOTES ABOUT EXECUTE REDIRECTION The Execute function can accept file handles as part of the command, where the file handle is used to redirect the stdin and/or stdout. There are special rules that Execute uses for this redirection. The following specifies the various forms that the Execute function call can take and their effects. Execute("something",0,0) means use "*" (that is, the current CLI window) for output. There is NO input expected for stdin. This form crashes if used under Workbench because there is NO WINDOW to which the output can be directed! The "something" string can include the redirection commands (< and >). If you specify no redirection commands, the string will literally be interpreted as though you asked for the function: Execute("something > * ",0,0); which says "use the current window for output". Since workbench itself has no current window of its own (its process simply has no window assigned), the function cannot terminate correctly. When you perform the Execute function with this calling sequence, it calls the RUN command, which, in turn, uses CreateProc() to load the code for the something you wish to run. This code runs as a child of the CLI and the CLI must hang around until the child finishes. Likewise it also means that the Execute command will not finish until the child exits. If you call Execute as: Execute("RUN something",0,0); you are asking the system to spawn a new CLI process that can handle the child process on its own, thereby seeming to free the originating CLI from handling the process, and control returns to the originating CLI immediately after RUN begins to operate. HOWEVER, even though you've called RUN, the originating CLI still may not be able to "endcli > NIL:" itself, because "something" inherits the stdout (*) from the calling process. This effectively prevents the originating CLI from going away until the user-count of its stdout output handle has gone to zero, since that process has become a user of the stdout (*). If EITHER ONE of the file- handles is a 0, the user-count for * is incremented, regardless of whether the program uses stdin or stdout at all. This happens since Execute cannot tell what will be in the execute-string. Thus, the originating CLI must hang around if either of the handles is zero, just to be sure the input and output paths are available if needed. See Execute("something", nilfh, nilfh) for a way around this. Execute("something",0,fout) means use fout (a valid file handle) for output. There is NO input expected from stdin, although the current CLI window (*) is still marked as having one more user. The FileHandle fout is not closed for you once Execute has finished. You must close fout yourself. This form of the function works either under Workbench or CLI, as long as fout is valid. The "something" can ALWAYS be a command that includes both < or > redirection. The output redirection will be utilized for error messages from the CLI if the comand-string encounters an error. Execute("something",fin,fout) Means do "something", then continue to read commands from "fin". The "something" can just as easily be specified as "" (the null string), which means do nothing, then continue to read commands from the file whose handle is "fin". Again, you'll have to close fin yourself. Control returns when the CLI finishes "something". Anything specified in fin will be done simultaneously with your own task that now is free to run. (The CLI has done "something", and is now free to go on to do something else, while the execute process continues to read fin.) When fin is an interactive input stream (See IsInteractive()), the fin handle becomes the handle that you would get if you opened "*". This means that you have asked the system to create a new interactive CLI process, with prompts and so on. Since a CLI process is now a user of "*", the window cannot be made to go away until you issue an explicit ENDCLI command. It also means that the caller of this form of Execute will NOT regain control until the CLI completes its task. Because fin it is an interactive process, it always has some input pending; thus it cannot be finished with its job until the input actually ends the CLI. So the caller will go to sleep waiting for the interactive CLI to end. Execute("something", fin, (fout = 0)) FileHandle fout should normally be a file handle specifying some output such as a con: window or a file. There is, however, one very special case, which is when fout is 0. This means, as described earlier, use "*" as the output. Note that in this special case, fin MUST be a console handler stream (CON:, RAW:, or "*"). Additionally, if you want to recognize an end-of-file condition on the input, you'll have to use only CON: windows. CON: windows translate CTRL-\ into an end-of-file; RAW: windows, on the other hand, pass on the numeric value of this key combination without treating it as EOF. Execute("something_with_no_stdin_or_stdout", nilfh, nilfh) Here, "nilfh" is a file handle resulting from a call to Open with a filename of NIL:, as: struct FileHandle *nilfh; nilfh = Open("NIL:",MODE_NEWFILE); Several developers and users have expressed an interest in being able to start background processes using the Workbench startup script (s/startup-sequence). An example would be to start things such as the CLOCK or something else, then end up on the Workbench without the originating CLI having to hang around for the user to close down. If you want the originating CLI to go away, you must somehow prevent any process started by that CLI from opening "*" for either input or output as mentioned above. Within the "something..." string, you must spawn an independent CLI process. Additionally the independent process must have its own I/O restricted so that it does not in any way depend on or attempt to communicate with the originating CLI. Here, then, is PART of the something-string: RUN >NIL: This starts an independent process. The rest_of_string can contain the command and its parameters. But be aware that if your command uses the standard startup sequence, it still needs to open its stdin and stdout file handles. You'll need to assure that a proper file handle is actually provided for stdin and stdout, so the rest of the string should look like this: rest_of_string = "MyCommand >NIL: NIL: NIL: NIL: Notice that this program does NOT close the "nilfh", that is, the file handle obtained by opening NIL:. This will tie up a small block of memory somewhere in the system until the next boot, unless someone spawns a process that keeps track of when the CLOCK tool exits, then finally frees this file handle. But this piece of memory is probably not too high a price to pay to obtain the functionality people have requested. Here is a general purpose implementation of the above technique, allowing you to run a named process "in the background". It includes a delay parameter to minimize disk thrashing among sequentially loaded processes. It is a bit more complicated in that we've installed the appropriate error handling. DISCLAIMER: This program is provided as a service to the programmer community to demonstrate one or more features of the Amiga personal computer. These code samples may be freely used for commercial or noncommercial purposes. Commodore Electronics, Ltd ("Commodore") makes no warranties, either expressed or implied, with respect to the program described herein, its quality, performance, merchantability, or fitness for any particular purpose. This program is provided "as is" and the entire risk as to its quality and performance is with the user. (Other standard disclaimers apply) /* -------------- runbackground.c --------------- SUMMARY: A Workbench Disk can be used to autostart an application through the use of the startup script and close the startup CLI. Users have commented that it is not possible to start a process going from the startup script and then cause the initial CLI to go away. Here is the solution to that problem, named appropriately: RUNBACKGROUND which starts and runs a background task. This does indeed allow you to create a startup script that will set up your workbench running any programs you might wish, removing the initial CLI in the process. Your s/startup-sequence can contain lines such as the following: RUNBACKGROUND -3 clock RUNBACKGROUND utilities/calculator RUNBACKGROUND -5 utilities/notepad where RUNBACKGROUND is the command and the second parameter is the filename which may be preceded by a flag-variable that specifies an optional delay time. The delay can be from 0 to 9, for the number of seconds that the startup script should sleep while allowing the background task to load and start. I've put that in to minimize thrashing of the disk as it tryies to load several projects at once. LIMITATIONS: The program that you run cannot require any input from an interactive CLI that starts it. Additionally, you cannot specify any file redirection in the command line since this program provides the redirection for you already. If you need to use redirection for your command, you can modify the source code where shown, thus allowing the redirection to become one of the parameters passed through to your program. RUNBACKGROUND does pass your command line parameters to the program you wish to start, but limits the total length of your command string to 227 (255 minus the 28 characters for "RUN >NIL: NIL: < NIL: " following it.) LINKING INFORMATION: (Amiga/Lattice C) use -v option for pass 2 (lc2 -v filename.q) to disable stack checking code installation. (stack checking code sometimes is incorrect). FROM lib:Astartup.obj runbackground.o TO runbackground LIBRARY lib:amiga.lib, lib:lc.lib **************************** NOTE: ******************************** If you use Lstartup.obj, it won't let the startup CLI go away. This is because the source code for Lstartup.asm either opens its own window or uses an existing CLI window (Open("*",....)), so that it has some guaranteed place to put the output. Astartup.obj does not do this. ********************************************************************* Hope this helps. robp. */ /* runbackground.c */ /* Author: Rob Peck. 5/9/86 */ #include "exec/types.h" #include "exec/memory.h" #include "libraries/dosextens.h" extern struct FileHandle *Open(); extern struct FileLock *Lock(); main(argc, argv) int argc; char *argv[]; { LONG success, delaywillbe; UBYTE commandstring[255]; char *test, *filename; LONG fromparm; struct FileInfoBlock *fib; struct FileHandle *nilfh; /* NOTE: will hang around until next reset */ struct FileLock *lock; fib = NULL; /* No file info block so far. */ delaywillbe = 1; if(argc < 2 ) { usage: printf("Usage: RUNBACKGROUND [ -] []\n"); printf(" where optional loaddelay is 0-9,\n"); printf(" specified in seconds for the CLI\n"); printf(" to sleep, waiting for task to load\n"); printf(" (minimizes inter-task disk-thrashing)\n"); if(fib) FreeMem(fib, sizeof(struct FileInfoBlock)); exit(0); } /* See if there is a delay parameter present */ test = argv[1]; if(*test++ == '-') { filename = argv[2]; /* argv[1] is delay so argv[2] is file */ fromparm = 3; /* Copy parms from 3 to end */ if(*test >= '0' && *test <= '9') { delaywillbe = 1 + (50 * (*test - '0')); } if (argc < 3) goto usage; /* Only a delay, and no filename!! */ argc--; /* one less parm to copy */ } else { filename = argv[1]; fromparm = 2; /* Copy parms from 2 to end */ } /* Now see if the file exists! If not, it can crash the background * CLI and take the system along with it. */ lock = Lock(filename,ACCESS_READ); if(!lock) { test = filename; if(*test == '?') goto usage; else { printf("%ls: Command not found\n",filename); goto usage; } } else { /* If file exists, it better be a file and not a directory */ /* Unfortunately, it is difficult to tell if it is an executable * file. If not executable, we'll still get blown out of the * water, but that is up to the user to do it right! */ fib = (struct FileInfoBlock *) AllocMem(sizeof(struct FileInfoBlock),MEMF_CLEAR); if(!fib) { UnLock(lock); printf("Ran out of memory!\n"); exit(0); } else { success = Examine(lock,fib); if(fib->fib_DirEntryType > 0) /* its a directory!! */ { printf("%ls is a directory, not a file!\n",filename); goto usage; } } FreeMem(fib, sizeof(struct FileInfoBlock)); UnLock(lock); } nilfh = Open("NIL:",MODE_NEWFILE); /* will always succeed */ strcpy( &commandstring[0], "RUN >NIL: somewhere" or "NIL: 0) /* Rebuild parameter string for passing it on */ { strcat( &commandstring[0], " "); /* add a blank */ strcat( &commandstring[0], argv[fromparm++]); } success = Execute( &commandstring[0] , nilfh, nilfh); /* The full command passed to Execute now looks like this: * * "RUN >NIL: NIL: