
Normally, IF interpreters are all Linux "main" programs, in the sense that they
contain the symbol "main", to which Linux transfers control on process startup.

More accurately, when they become Glk programs, their "main" comes from the Glk
libraries, and they implement glkunix_startup_code() and glk_main(), to which
the Glk "main" links.  But, they remain "main"-ish in their nature.

They take an argument count and some string arguments, the last of which is
always the game file to open and run, and they run until completion, either by
returning from glk_main(), the Glk analog to "main", or by calling glk_exit(),
the Glk analog to "exit(1)" (and quite why Glk always exits a program with a
status code of 1 is a mystery, since 0 would be much more normal).
 
The core concept of IFP is to build these programs as DSO libraries instead of
as main programs, and to then dlopen() each to determine if it can run a game.
When opening an interpreter DSO, IFP always uses RTLD_NOW to ensure right away
that all of the interpreters required symbols are present.

Rather than using normal dynamic symbol lookup, Glk and other function
addresses are passed between the main program and interpreter DSOs explicitly,
by negotiation between libifp and libifppi.  This avoids the need for the main
program to export all of its symbols, something it that might be error-prone
since symbols could clash between the main program and the DSO (and also
between multiple loaded DSOs).

IFP must export three symbols, though, so that Glk plugins can be loaded.
These are glk_main, glkunix_startup_code, and glkunix_arguments.  This lets
IFP use Glkloader's Glk library plugins directly.

When it starts, the first thing an IFP binary does is to decide which Glk
library to load, and load it.  It does this either by following a request for
an explicit library, or by selecting a default from its environment or
configuration file.  For this to work, IFP itself must contain the real main()
function.  After IFP has loaded its Glk library, it calls the Glk library's
main() function through shared object symbol lookup.  That main() function will
then call glkunix_startup_code() and glk_main() back inside IFP.

In this way, IFP can run both multiple Interactive Fiction game types and
multiple Glk schemes, all from within a single executable program.  This lets
you combine Glk libraries and interpreters in any combination you like.


When a game interpreter calls glk_exit(), it has finished running.  It is
expecting that the entire process will stop there (what else could remain to be
done?).  However, for IFP, this is not entirely satisfactory.  It is useful if
IFP can, when one game plugin finishes, run another, or maybe run the same
game again.  In general, it is undesirable in a GUI for the application to shut
down until the user shuts it down.

IFP handles this by redirecting calls the interpreter makes to glk_exit()
(remember that we pass it an interface structure containing the addresses of
all Glk calls, so IFP gets to see every Glk call that an interpreter makes).
When the interpreter calls glk_exit(), IFP arranges for one of its functions
gets the call instead.  Using the miracle of the 'C' setjmp/longjmp calls, IFP
handles this event as if the interpreter's glk_main() had simply returned.

IFP also redirects any calls the interpreter might make to exit(), though of
course, none should if they adhere to Glk API guidelines.  A call to exit() is
treated as a call to glk_exit().  If an interpreter really wants to stop the
process, it can call _exit().


Because interpreters are expecting glk_exit() to terminate the process, most
will happily call it while still holding system resources.  Chief among these
is malloc()'ed memory.  A "pluginified" interpreter will often not free its
heap memory before exiting.

To deal with this, IFP intercepts all calls that the interpreter code makes to
malloc() and its close relatives.  It does this by defining the functions
malloc(), free(), and so on, in libifppi.  Because these symbols are "weak" in
libc, they can be overridden.

The IFP library passes each intercepted malloc() and similar call from an
interpreter back to the main application.  This calls the real malloc() or
appropriate function, then updates an internal table with information about
what memory addresses the interpreter has malloc()'ed but not yet freed.  When
the interpreter finishes, IFP scans this table, and automatically free()'s any
memory that the interpreter left unfreed when it called glk_exit() (or just
returned from glk_main()).

IFP also uses this same technique to try to reduce file descriptor leaks that
might also be caused by an interpreter opening but not closing a file.  When it
comes to cleaning up Glk, IFP has a much easier time.  Glk is very helpful in
that it offers ways to query and destroy most of the resources it has
accumulated while running a game.

At present, the simple IFP interpreter, ifpe, does not try to repeat games or
start new games when an interpreter plugin completes running.  Only legion
and gamebox currently use this feature.

 
Because IFP contains both a client-side library that knows how to find and load
plugins, and a plugin-side library that knows how to be loaded by the client,
these can be combined into one single plugin.

A chaining plugin is a plugin that takes on a portion of the game interpreting
task, then passes off the real work to some other plugin.  There are two in IFP
at the moment

  unarchive - handles cpio, tar, zip, and ar archives
  uncompress - handles gzipped, bzipped, compressed, and packed data

These do not interpret the game.  What they do is to uncompress or unarchive
the file that they are handed into files in /tmp, then use their built-in IFP
loader functions to search for another plugin that is able to handle the data
they just uncompressed, acting like an middleman in the whole affair.  In this
way, a file such as weather.z5.gz, or an Alan file in its "native" format of
bugged.zip can be handled directly by IFP.

It is also possible for a chaining plugin to load and run another copy of
itself.  This means that uncompression is not limited to one level, and a file
such as weather.z5.gz.gz.gz is seamlessly usable.  In order to support such
chaining plugins, IFP may have to take a complete copy of a plugin DSO on disk
and load this.


IFP contains small, built-in HTTP and FTP clients.  On being handed a URL that
it needs to use, IFP uses the relevant client to download the data from the URL
and store it on a disk temporary file.  That file can then be passed to the
client-side loader functions, to find the right plugin for the downloaded data,
and the whole business of loading and running the right plugin continues as
normal from there.

Having URLs included in IFP allows the main game program to run games from the
IF Archive directly, for example:

    ifpe http://www.ifarchive.org/if-archive/games/zcode/curses.z5

URLs go to some extremes to try to ensure that temporary files are deleted from
/tmp when the interpreter program exits.

URLs can be either synchronous or asynchronous.  Asynchronous  URLs allow the
URL resolve function to return as soon as it has negotiated and begun to
download from the server.  A program using this form of download needs to poll
the URL to see if the download has completed, and will not be able to access
URL data until it has.  It can, however, go off and do other things, and check
back on the progress of the download later.
 

The two most immediately useful IFP manager functions are

  ifp_manager_locate_plugin (const char *filename)
  ifp_manager_run_plugin (ifp_pluginref_t plugin)

Together, they offer enough functionality to build a straightforward IF
interpreter using plugins.  ifp_manager_locate_plugin() is responsible for
finding, loading, and returning a plugin capable of handling a particular input
file.  ifp_manager_run_plugin() then runs this plugin.  There is also a form of
ifp_manager_locate_plugin() that takes a URL for an IF game as its argument.


The IFP loader manipulates a list of loaded plugins.  The functions

  ifp_loader_search_plugins_dir (const char *dir_path)
  ifp_loader_search_plugins_path (const char *load_path)

will search a directory and a path respectively, and load each IFP plugin they
can find.  If a particular plugin is already loaded, the loader will skip it on
a search, ensuring that only one copy of each available plugin is loaded.  The
IFP manager uses these functions to refresh the list of loaded plugins each
time its ifp_manager_locate_plugin() function is called.

The loader functions

  ifp_loader_load_plugin (const char *filename)
  ifp_loader_forget_plugin (ifp_pluginref_t plugin)

can be used to load an individual file as a plugin, and to remove a particular
plugin from the loader, then delete it completely, freeing the memory it is
using.  To iterate round all the plugins in the loader, use

  ifp_loader_iterate_plugins (ifp_pluginref_t current)


A convenient way to resolve URLs is to use one of the following functions:

  ifp_url_new_resolve (const char *urlpath)
  ifp_url_new_resolve_async (const char *urlpath)

These calls create a new URL object, with the data downloaded into a temporary
file if the URL is not a local file.  "file:", "http:", and "ftp:" URLs are all
acceptable, or you can also simply pass in a file path, for example
/home/myfiles/somegame.z5.

If you use asynchronous URLs, the call will return as soon as the download of
data (if remote) has started.  In this case, the program can undertake other
tasks, and you need to check periodically to see if the download has completed.
To do this, use the functions

  ifp_url_poll_status_async (ifp_urlref_t url)
  ifp_url_poll_progress_async (ifp_urlref_t url)

If the URL has completed, ifp_url_poll_status() will return TRUE.  Otherwise,
it will return FALSE, and you can then use

  ifp_url_get_status_async (ifp_urlref_t url)

to get the errno value indicating why the URL failed.  ifp_url_poll_progress()
returns the number of bytes downloaded so far for a remote URL; this is useful
in printing download progress status messages.  To pause while waiting for the
next block of download data for a URL, either delay with Glk timers or call

  ifp_url_pause_async (ifp_urlref_t url)

This returns on the next block of downloaded data, or after a short pause.  You
can set the pause length explicitly with the environment variable
IFP_URL_TIMEOUT, giving the timeout in microseconds.  Once the URL has finished
downloading, it can be used in calls to other functions.  The functions

  ifp_url_get_url_path (ifp_urlref_t url)
  ifp_url_get_data_file (ifp_urlref_t url)

return the path used to resolve a URL, and the file containing URL data,
respectively.  To free memory in use by a URL, call

  ifp_url_forget (ifp_urlref_t url)

This will not, however, delete any temporary file associated with a remote URL.
These files are kept in a cache in case they are needed again.
 

Whenever IFP needs to download URL data into a temporary file, it keeps a
record in an internal cache of the URL, and the temporary file containing the
data.  If another request is made for the same data, the same temporary file
path is used, to save downloading the URL data multiple times.  This makes it
convenient to play a game more than once using a remote URL.

At present, this is a temporary cache; it exists only while the main IFP
application is running.  On program exit, all cached URL temporary files are
deleted.  The current default cache size is 10Mb; you can use the environment
variable IFP_CACHE_LIMIT to set a cache size limit in bytes.


When the IFP manager begins to use an IF plugin, it must construct a set of
startup options to pass to the interpreter's glkunix_startup_code() function.
It does this by consulting a list that IFP keeps of the preferences to be used
for particular plugins.

The two main functions to add and delete preferences for interpreter plugins
are

  ifp_pref_register (const char *engine_name,
                     const char *engine_version, const char *preference)
  ifp_pref_unregister (const char *engine_name,
                       const char *engine_version, const char *preference)

These functions tell the IFP library how to construct a set of startup
arguments when running a plugin.  Preferences are added by ifp_pref_register().
Each call to this function needs to supply the name and version of the engine;
IFP will match these to a plugin before starting to run it.  The preference is
simply a text string to be added to the glkunix_startup_code arguments.

Preferences appear in the arguments in the same order as they are entered into
the IFP library list.  To register preferences for all versions of a plugin,
the engine_version may be NULL.  To enter global preferences, both engine_name
and engine_version may be NULL.

ifp_pref_unregister() removes a preference from the list.  Engine_name,
engine_version, or both may be NULL, indicating wildcard values.  Preference
may also be NULL.  A call with three NULL arguments will clear the entire list.

Initially, the preferences list is empty.  Here is how IFP can be told to add
the "-ignore" argument each time it needs to start an instance of the Nitfol
interpreter plugin:
 
  ifp_pref_register ("nitfol", NULL, "-ignore");
 
IFP automatically passes preferences lists through chaining plugins, so a set
of preferences written to the main program are used globally.

To find out which preferences a plugin understands, call the function

  ifp_pref_list_arguments (const char *engine_name,
                           const char *engine_version)

The function returns a glkunix_argumentlist_t result.  To accomplish this, the
function will load all available plugins into the loader, so if you're not
expecting this, you might want to follow up with a call to the function
ifp_loader_forget_all_plugins() when you have finished using the result of the
ifp_pref_list_arguments() call.
 

In addition, there are many functions for returning information about a
particular plugin, such as its name, version, author, home page URL, and so on. 
Some of these, such as ifp_plugin_engine_name() and ifp_plugin_engine_version()
may be of interest; others may be useful only in the implementation of IFP
itself.  See the libifp manual page for more details.

 
The default plugin search path, if no value is set in the environment variable
IF_PLUGIN_PATH, is "/usr/local/lib/ifp:/usr/lib/ifp", and the default
IFP_GLK_LIBRARIES if not set is "xglk", "glkterm", or "cheapglk" depending on
$DISPLAY and $TERM.  The IFP libraries complain if no value is set for
IF_PLUGIN_PATH or IFP_GLK_LIBRARIES.

 
The macro definition of strdup() in string2.h converts the function call into
a call to __strdup().  IFP can only intercept calls to strdup(), since this
is defined as a weak symbol in glibc.  It cannot catch or redefine __strdup(),
however, since __strdup() is not a weak glibc symbol.

The result of this is that if an interpreter uses strdup() and is compiled with
optimizations, IFP does not get to see the __strdup() call.  However, the
subsequent free() of the address returned by string duplication is caught by
IFP.  As far as IFP is concerned, this looks like an attempt to free memory
that has not been allocated from the heap.

To avoid this, if an interpreter uses strdup(), either compile it without
optimizations, or disable the optimizations in string2.h by passing the option
-D__NO_STRING_INLINES to the gcc compiler.
