NAME

CGIBurn::HACKING - Guide for people who want to modify, improve, or extend the functionality of CGIBurn.


HACKING OVERVIEW

In some places (hopefully more in the near future), individual scripts and modules are documented using POD. For those that are, you can read the documentation by typing:

   pod2man filename |nroff -man |less

to see the full, nicely formatted documentation.

The general flow of things is this:

To burn a CD, the user interfaces directly with the ``burn'' CGI script. They select their options in there, and when they press the a button, the burn CGI script looks at the cgiburn.conf configuration file and the CGIBurn::DriverMap module, and figures out what driver to use for that combination (if no driver is available, it returns an error). It then loads the driver via the CGIBurn::Driver module, and that module finds the actual driver somewhere under CGIBurn::Driver, for example in CGIBurn::Driver::cd_copy.

The driver module acts as a go-between for the CGI script and the parameters for the driver program, which is a standalone script. If they have selected Advanced Options, burn asks the driver to return a list of options supported for the source and destination devices. The driver consults the Capabilities parameter for those devices, and its own internal list of options, and returns a list to be displayed to the user. If they have selected ``Burn, baby, burn!'', the driver module returns the name of the driver program to be run, and translates the options sent via the CGI form to command-line options for this program.

The burn CGI script takes the list of program and parameters passed back from the module, and creates a new CGIBurn job for it, with the CGIBurn::Job module. It then returns a copy of the status page for the newly created job, and exits.

The CGIBurn::Job module creates a Job ID, and creates three files for each job. status.jobid.html is the status page which is displayed to the user. log.jobid.txt is a log of everything which is output by the driver program. pid.jobid contains basic information about the job, such as what program was run, when it was started and by whom, and PIDs for different parts of the program. CGIBurn::Job connects the standard output and standard error of the driver program to log.jobid.txt and starts it up, then fires up a copy of ``logprogress'', which monitors log.jobid.txt, and updates the status page at status.jobid.html appropriately.

The job of the driver program is to interpret the command-line parameters sent by the driver module into appropriate commands and command-line switches for the various programs that it will use to complete its task, and to monitor their output and determine how far along the task has progressed, whether any errors have occurred, and when the task has finished. It notes the progress and any errors of the process using the CGIBurn::BurnLog module. The driver program also handles locking; it uses Perl's flock (via CGIBurn::Lock) to lock files corresponding to the device names, and dies if it can't obtain the locks it needs.

The job of ``logprogress'' is to monitor the log from the driver program for special messages (sent via the CGIBurn::BurnLog module) indicating the progress or errors of the job, and update the status page for that job. When it notices a change in the job's status, it runs ``setprogress'' with a series of command-line switches which tell it what to display to the user.


WRITING A PS DRIVER

The PS driver is an interface between Perl and whatever method the system has for gathering process information. If one does not exist for your system, you will have to write your own. For information on doing this, see CGIBurn::PS, CGIBurn::PS::TEMPLATE, CGIBurn::PS::Linux, and testps.


WRITING A DRIVER

A driver is a program that knows how to copy between two devices. It consists of three components --- a driver module, which manages the settings presented to the user, then invokes the driver program with appropriate command-line options; a driver program, which takes the command-line options and converts them into a sequence of UNIX programs (outside of CGIBurn) to run, monitors their progress, and reports status and progress to a logfile; and an entry in the DriverMap.


The driver module

Driver modules are objects beneath CGIBurn::Driver, which inherit from CGIBurn::Driver. To accomplish this, they should be in the directory cgi-lib/lib/CGIBurn/Driver, with a ``.pm'' extension, and should start with the lines:

  package CGIBurn::Driver::CHANGETHIS;
  use vars qw(@ISA);
  @ISA=qw(CGIBurn::Driver);

For a full example of a driver module, see CGIBurn::Driver::TEMPLATE. Additional documentation about driver modules in general can be found in CGIBurn::Driver.

Here are the methods that your driver module can or must have.

GetCommand
(see CGIBurn::Driver::GetCommand).

A driver module which handles no advanced options needs only one method, GetCommand, which generally begins like this:

  sub GetCommand
  {
    my $self = shift;
    my($srcdev,$dstdev,$srcOpts,$dstOpts)=@_;
    my @extraopts = ();
  ...

GetCommand should return a list consisting of the name of the driver program to run and all of its arguments. If an error is encountered, GetCommand can return the string ``ERROR'' for its driver program. A driver will usually return

  ("driver_program_name",@extraopts,$srcdev->{readlocation},$dstdev->{writelocation})

, with at least the locking optiosn in @extraopts. This tells the driver program to copy from the source device to the destination device.

If the driver takes other options, and the user chooses to see advanced options, their selections will be passed to GetCommand in $srcOpts and $dstOpts. Both of these are hash references, with keys of the setting names that GetSrcOptions or GetDstOptions returned, and with values that are one of the value choices that GetSrcOptions or GetDstOptions returned. For example, if GetDstOptions had a setting named ``erasefirst'' with possible values of ``yes'' or ``no'', then $dstOpts could contain:

  $dstOpts->{erasefirst} = "yes"

GetCommand is also responsible for handling locking. Fortunately, that's pretty easy; just include these lines before you return:

  # Locking.
  my($lockfiles) = join(",",$LOCKDIR."/".$srcdev->{lockdevice},
                        $LOCKDIR."/".$dstdev->{lockdevice});
  push(@extraopts,"-l",$lockfiles);
GetSrcOptions
(see CGIBurn::Driver::GetSrcOptions).

GetSrcOptions returns the advanced options which should be displayed to the user for the source device they have selected (for the exact format of the list it returns, see CGIBurn::Driver::GetSrcOptions). If no advanced options are available, it should return an empty list. If this method is not defined, the one in CGIBurn::Driver will be run, which just returns an empty string.

GetSrcOptions generally starts like this:

  sub GetSrcOptions
  {
   my $self = shift;
   my($srcDev,$dstDev)=@_;
   my @o = ();
  ...

$srcDev and $dstDev are device objects from %burndev or @burndev (see CGIBurn::Conf), which contain all of the information about the source and destination devices.

GetSrcOptions can look at the information in $srcDev and $dstDev to decide whether to offer certain options. Most usefully, the device type is in $srcDev-{type}>, a hash of capabilities is in %{$srcDev-{caphash}{capability_name}}>, and a list of all capabilities is in @{$srcdev-{caplist}}>.

GetDstOptions
(see CGIBurn::Driver::GetDstOptions).

GetDstOptions returns the advanced options which should be displayed to the user for the destination device they have selected (for the exact format of the list it returns, see CGIBurn::Driver::GetSrcOptions). If no advanced options are available, it should return an empty list. If this method is not defined, the one in CGIBurn::Driver will be run, which just returns an empty string.

GetDstOptions generally starts like this:

  sub GetDstOptions
  {
   my $self = shift;
   my($srcDev,$dstDev)=@_;
   my @o = ();
  ...

$srcDev and $dstDev are device objects from %burndev or @burndev (see CGIBurn::Conf), which contain all of the information about the source and destination devices.

GetSrcOptions can look at the information in $srcDev and $dstDev to decide whether to offer certain options. Most usefully, the device type is in $dstDev-{type}>, a hash of capabilities is in %{$dstDev-{caphash}{capability_name}}>, and a list of all capabilities is in @{$dstdev-{caplist}}>.


The driver program

Driver programs are programs which handle the actual operation of the cd burning process, and translate the progress information from these programs into a standard form, using CGIBurn::BurnLog. They are generally started up by CGIBurn::Job, using information returned from the driver module.

Driver programs should be installed in cgi-lib/bin/real/program_name. To run with normal priority, a symlink should exist from cgi-lib/bin/program_name to real/program_name. To run with increased priority, for example if they are writing to a CD that will be destroyed if they get behind, they should use hipri; to do this, make a symlink from cgi-lib/bin/program-name to hipri instead of the link described earlier.

You can look at other drivers in cgi-lib/bin/real for a starting point to writing your own driver.

They should use the CGIBurn::BurnLog module, which provides some useful functions, and overrides ``die'', ``warn'', and some signal handlers to do the right thing. They should also use the CGIBurn::Lock module, which provides straightforward and consistent device locking.

Standard arguments to a driver program are the jobid, followed by any command-line options they use (which can be parsed using Getopt::Std), followed by the source device path, then the destination device path.

All driver programs must accept at least the -l lock1[,lock2,...] option, which tells it what files it needs to have locks on before it can proceed. This prevents multiple driver programs from trying to use the same device at the same time. This can be implemented with just a few lines:

  use CGIBurn::Lock;

  ...

  # Must be a lexical variable due to limitations of Perl's
  # garbage collector.
  my @locks;

  ...

  use vars qw(%opt);
  # Insert your other options before C<l:>.
  getopts("l:",\%opt)
    or die "Invalid usage";

  ...

  if (defined($opt{l}))
  {
    @locks = CGIBurn::Lock->multilock($job,split(/,/,$opt{l}));
  }

One of the primary functions of the driver program is to keep track of the progress of whatever it's doing, so the user can estimate how things are going. It can report progress via logprogress, and can use other functions from CGIBurn::BurnLog to report that it is finished, has died, etc. It is important to call finished when the program is done, or else the job will never be marked as done, and the user will never know that it has finished.

Some simple calculations can be useful in figuring out progress. For example, if you have a process where the first 10% of the time is used to prepare, then 80% is used to copy files, and a final 10% is used to do cleanup work, you would figure out progress like this:

While preparing
  logprogress int(0.10 * preparation_progress), "Preparing"
While copying
  logprogress 10+int(0.80 * copying_progress), "Copying"
While cleaning up
  logprogress 90+(0.10 * cleanup_progress), "Cleaning up"

A common way to track progress is to run a program you are using in verbose mode, and read its output to see where it's at.


The DriverMap

Your driver module will only be called if it is present in the DriverMap. CGIBurn::Conf constructs the DriverMap , and stores it in the variable $DM.

To add it, edit the configuration (normally, DriverMap information is stored in drivermap.conf), and change or add a block to use your driver. The general layout of this is

  DriverMap srcdev
    WriteTo: dstdev with drivername

You can add new device types for your driver simply by creating them in the DriverMap; if you do this, make sure that you add a DriverMap block for your own device, and add a WriteTo line to all existing blocks for your device. If you use ``unimp'' for the drivername, that means that that functionality has not been implemented; if you use ``cant'', that means that that operation isn't possible (for example, writing to a CDROM drive). If the user requests a combination which isn't present in the DriverMap, it will be treated as if it were set to ``unimp''.


ADDING A CONFIGURABLE VALUE


Adding a Setting or Location

To add a Setting or Location to CGIBurn, you have to edit two modules: CGIBurn::ConfVars, which contains variable declarations for all constants used in the program; and CGIBurn::ConfParse, which translates the configuration file into the variables that are used internally. You should also document it in the CONFIG file.

Options in the configuration file are case insensitive, and the constants used internally are generally in all caps. You should try to use the same word for both of these; many of the old settings don't, because they were used in many modules before the configuration file parsing existed.

Start by deciding whether your option should be a Location (should be used for paths and URLs) or a Setting (for anything else). They are both treated identically, but are kept seperate in the configuration file for clarity. Then choose a name for it in the configuration file, and a name for it internally. Usually, the configuration file name should be mixed case (it is case insensitive), and the internal name will be the same thing in all caps.

ConfParse.pm: Documentation
In the Map of configuration file options to variables section of the POD documentation, add a line saying whether your option is a Setting or Location, what its config file name is, and what its constant name is. For example, if you have a Setting named MySetting stored in the variable $MYSETTING, you would use:

  Setting MySetting -> $MYSETTING

to the documentation.

ConfParse.pm: %var2set
%var2set is a hash which uses the variable name as a key, and the config file setting in all lower case as the value. This hash is looped over to set all of the settings. This list is created by a qw() quoting command, so you just have to add

  $MYSETTING             mysetting

to the end of it.

ConfParse.pm: @SETSETTINGS or @LOCSETTINGS
@SETSETTINGS and @LOCSETTINGS list all of the valid config file options for Settings and Locations, respectively. Options are listed here in lower case, and are quoted with qw(). Just add your config file name to the end of the appropriate list:

  @SETSETTINGS=qw(... mysetting ...);
ConfVars.pm: Documentation
ConfVars.pm contains a POD =item for each constant that is supported, in the section :const. You should document your setting there, too, with a chunk that looks like:

  =item $MYSETTING

  My very own personal CGIBurn setting.
ConfVars.pm: @EXPORT_OK
In order to allow your option to be exported, you'll need to add it to the @EXPORT_OK list. This list is quoted with qw(), so just add your variable name to the end of it:

  @EXPORT_OK = qw(... $MYSETTING ...);
ConfVars.pm: %EXPORT_TAGS
Add your setting to the const part of %EXPORT_TAGS, so it will be imported by modules which use the :const tag:

  %EXPORT_TAGS = ( ...
                   const => [qw(...
                                $MYSETTING
                                ...
                               )]
                   ...
                 );
ConfVars.pm: use vars
Add your setting to the use vars declaration, which allows constants to be used under the restrictions of Perl's <use strict> pragma:

  use vars qw(... $MYSETTING ...);
CONFIG.pod: Advanced Options
Finally, document your setting for the user in the CONFIG.pod file, under the Advanced Options heading.


Adding a device option

Most device options can be handled with the Capabilities option. The parameters to this are not inspected by ConfParse, but are made available unmodified to any driver modules, either via the hash %{$dev->{caphash}}, or the list @{$dev->{caplist}}.

If you find that you have a real need to add an actual device option, you can do so by adding your option to the @DEVSETTINGS list in ConfParse.pm. Make sure you document the purpose of your option in CONFIG.pod.


RCS VERSION

 $Id: HACKING.pod,v 402.1 2001/02/05 11:43:31 sgifford Exp $