tcllauncher is a way to have Tcl programs run out of /usr/local/bin under their own name, be installed in one place with their support files, and provides commands to facilitate server-oriented application execution.
Now you might think, why bother? I'll just put my script in there and do a #! thing to invoke Tcl.
Well, OK, but this has certain problems:
You'd like to be able to have stuff show up as its script name.
You could just copy or even link tclsh to the name of your program. Say, for instance, trackserver.
But then you have to invoke trackserver with arguments and do stuff to prep it, like:
cd ...somewhere... /usr/local/bin/trackserver main.tcl
That's the original purpose for tcllauncher, just to make that reasonable.
cp /usr/local/bin/tcllauncher /usr/local/bin/trackserver trackserver
How does it find its files? It cd's to the corresponding lib directory and a directory underneath that of the same name as the application, and sources the file main.tcl with the variable tcl_interactive set to 0.
SO when tcllauncher is installed as trackserver and you run trackserver, what happens, /usr/local/bin/trackserver starts up like the Tcl shell, except that it sources in the file /usr/local/bin/trackserver/main.tcl.
Also a global variable called launchdir is set, containing the "launch directory," i.e. the directory where main.tcl was loaded from. (In the above example, /usr/local/lib/trackserver.)
Tcllauncher doesn't change your directory behind your back, so wherever you are when you run it, you're still in that directory.
But a lot of times you want to go to your application directory, so you can justcd $::launchdir
Then you can source in all of your various files and stuff like that. A common tcllauncher coding pattern is to store the current directory in a variable, cd to launchdir, source in files, then cd back to the original directory.
If you are going to fork off children, exec them, or whatever, you should probably become your own process group so hopefully your children might inherit your signals and Do The Right Thing.
id process group set
The id command is from the TclX extension.
Lots of apps write a file with the server's process ID in it. Upon relaunch, the program can come along and look in its own pid file to see if it's already alive or not, and also to potentially kill it.
Our pidfile support is a studied Tcl-based copy of BSD's pidfile C library.
::tcllauncher::pidfile_open ?path? ?mode?
Given an optional path to a directory and optional permissions, pidfile_open opens (or creates) a file specified by the path and locks it with TclX's interface to the flock system call.
If the file cannot be locked, the PID of an already running daemon is returned.
Otherwise zero is returned and you've got the lock. You can now call pidfile_write to get your pid into the lock file.
This function does not write your process' PID into the file, so it can be used before forking if needed.
If path is not specified, /var/run is used, and if mode is not specified, 0600 is used.
::tcllauncher::pidfile_write
Writes your pid into the pid file previously opened by [cmd pidfile_open].
::tcllauncher::pidfile_close
closes a pidfile. It should be used after your daemon forks to start
a child process.
::tcllauncher::pidfile_remove
closes and removes a pidfile.
set pid [lb]::tcllauncher::pidfile_open "/var/run/daemon.pid 0600[rb] if {$pid > 0} { puts stderr "pid $pid already has the lock" exit 1 } ::tcllauncher::daemonize ::tcllauncher::pidfile_write ...do work... ::tcllauncher::pidfile_remove exit
Sometimes you want your program to spawn itself off into the background in a way that when you logout it doesn't kill the process, etc.
To daemonize a tcllauncher app,
::tcllauncher::daemonize] ?-noclose? ?-nochdir?
By default this forks off a child and exits the parent. In the child, it changes the current directory to /, and redirects stdin, stdout and stderr to/from /dev/null.
Specifying -noclose prevents the closing and redirecting of stdin, stdout and stderr, while -nochdir prevents the changing of the working dir to /.
This is a rough copy of BSD 4.4's daemon library routine.
If a program needs to be run as a certain user, it can invoke
::tcllauncher::require_user userName
This requires the program to either be run as fred or as root or something like that, by a user that has permissions to become fred.
If the program is running as user fred or can change the user id (suid) to fred, it continues, else it aborts.
This means if the superuser invokes the program, it will change user to the correct user. If the correct user invokes the program, it will correctly do nothing and proceed. Handy.
::tcllauncher::require_group groupName does for groups what require_user does for users.
::tcllauncher::require_user_and_group] userName groupName
combines changing the group and user into a single procedure.
Note that if you require user first then require group, the process may have lost the privileges necessary to change groups after changing users. Either require the group ID first or use ::tcllauncher::require_user_and_group to do both.
Get it on github: http://github.com/flightaware/tcllauncher
You can sometimes find tcllauncher on IRC in the #tcl
channel on the Freenode network
or you can file issues with github here.
You can also clone the project with Git by running:
$ git clone git://github.com/flightaware/tcllauncher
Copyright (c) 2010-2011, FlightAware LLC All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the FlightAware LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.