# IRC Services Technical Reference Manual

## 10. Compilation

Previous section: The database conversion tool | Table of Contents | Next section: Future work

### 10-1. Compilation overview

Services uses the common `configure; make; make install` method for compilation. The `configure` script in the top directory tests the system compiler and environment to ensure Services can be compiled and to compensate for differences between systems, such as broken or missing implementations of system library functions; once it has run, the source files are compiled using the `make` tool. Once compilation is complete, the command `make install` will install the executable and data files to their final location on the system as specified by parameters to the `configure` script (or using the defaults from that script).

Back to top

### 10-2. The `configure` script

Before the source code can be compiled, certain features of the compiler and environment need to be checked, and appropriate settings made; this is performed by the `configure` script present in the top directory. The script performs the following operations:

- Defines miscellaneous utility functions.
- Creates a temporary directory, for storing files used in the tests. The directory name is defined to be "conf-tmp" at the top of the script. If the directory already exists (because a previous `configure` run was aborted, for example), that directory is used.
- Initializes configuration and command-line option variables.
- Parses command-line options. See table 2-2 in section 2 of the main manual for a list of all recognized options.
- Loads the results of any previous run from the cache file, `config.cache`, unless the `-ignore-cache` option is given. If the cache was created by an earlier version of the script, appropriate adjustments are made to the cached results (usually clearing the affected variable or variables so that the modified test is run again). Note that the loading is done via a `source` command, executing the commands in the cache file directly, which can lead to security problems if the user is tricked into storing a malicious cache file in the Services directory.
- Determines installation directories for executable files (variable `BINDEST`) and data files (variable `DATDEST`), either from the `-prefix`, `-bindest`, and `-datdest` options, or from the defaults included in the script, as follows:
  - If the `-prefix` option is given with a parameter `PATH`, then the executable directory is set to `PATH/sbin` and the data directory is set to `PATH/lib/PROGRAM`, where `PROGRAM` is the program name given to the `-program` option (or the default of "`ircservices`" if the `-program` option is not given).
  - If the `-prefix` option is not given and the `-bindest` option is given, then the executable directory is set to the path given by the `-bindest` option.
  - If neither of the `-prefix` or `-bindest` options are given, then the executable directory is set to the cached value of the `BINDEST` variable if present; otherwise, the default of `/usr/local/sbin` is used.
  - If the `-prefix` option is not given and the `-datdest` option is present, then the data directory is set to the path given by the `-datdest` option.
  - If neither of the `-prefix` or `-datdest` options are given, but the `-bindest` option is given, then the data directory is set to a path derived from the executable directory, by either replacing a trailing `/sbin` or `/bin` with `/lib/PROGRAM` or appending `/lib` if the executable directory does not end in `/sbin` or `/bin`.
  - If none of the `-prefix`, `-bindest`, or `-datdest` options are given, then the data directory is set to the cached value of the `DATDEST` variable if present; otherwise, the default of `/usr/local/lib/PROGRAM` is used.
  Note that the current Services architecture does not allow data files to be stored in multiple separate locations.
- Opens the log file (`configure.log`). Significant commands run by the script, and the results of all tests, are logged to this file to aid diagnosis in case of problems. Each test is given a distinctive name, written at the beginning of each line in the log; these names are given in the descriptions below.
- (check_test_nt) Checks whether the shell's built-in `test` command, if any, or the system `/bin/test` or `/usr/bin/test` command supports the `-nt` option for testing whether one file is newer than another. Some shells, such as `/bin/sh` on at least some versions of the Solaris operating system, do not support the `-nt` test. This test is required for module compilation (see section 10-3-2).
- (find_cc) Determines the program to use to compile source files. This program is assumed to be able to link object files into an executable as well. If the `-cc` option or a cached result is available, they are used in that order of preference, and subsequent compiler checks are bypassed. Otherwise, the commands `gcc`, `icc`, and `cc` are checked in that order, and the first one that is able to compile a short test program is used as the C compiler. (However, if `gcc` is chosen and the version cannot be confirmed to be at least 3.2, the script aborts; likewise, if another compiler is chosen and it is unable to compile ANSI C programs, the script aborts.) Finally, the chosen compiler is tested to ensure that it understands variadic-argument macros and the `va_copy` function, C99 features that Services makes use of.
- (find_ccopts) Determines the command-line options to use with the C compiler for compiling source code. If the `-cflags` option is given, the specified option string is used; if `-cflags` is not given and the cached result for the C compiler program name was chosen, the cached option string is used; otherwise, a default option string is chosen by the `def_cc_flags` function. In the latter case, the compiler is checked for the presence of the GCC stack-protector extension [www.research.ibm.com]; at least some versions of this extension have a bug which causes incorrect code to be generated, and if the extension is present and this bug is detected, `-fno-stack-protector` is added to the default option string to disable this extension. A test is made with the default flags to ensure that the compiler accepts them, and if the test fails, an empty option string is used instead.
  Regardless of the method used to find the flags, a final test is made to ensure that the compilation with the selected flag set does not cause the stack-protector bug to appear. (This bug relates to the GCC `__builtin_apply` and `__builtin_return` facility for passing a function call through to a different function, used in Services to handle imported functions in the `database/version4` module. There have been other bugs in the implementation of this facility as well, so the test is carefully written to avoid triggering them.)
- (check_gcc_builtin) Checks for bugs in the implementation of the GCC `__builtin_apply` and `__builtin_return` facility, if understood by the compiler. At least three bugs are present in various versions of GCC, noted in the GCC Bugzilla tracker as bugs 8028, 11151, and 20076 [gcc.gnu.org]. Bug 20076 is not relevant to Services, but if one of the other bugs is detected, the script will activate assembly-language workarounds for i386, Sparc, and PowerPC CPUs, and abort on other processors since no workaround is available.
- (find_lflags) Determines what options to use when linking executables. Uses the option string given with the `-lflags` option, the previous cached result, or an empty string, in that order of preference.
- (find_exe_suffix) Checks what filename extension, if any, is appended to executable filenames by the compiler/linker.
- (find_libs) Determines what additional libraries need to be specified on the link command line. Checks are made for `-lm`, `-lsocket`, `-lresolv`, `-lnsl`, and `-lcrypt`; if relevant functions from these libraries cannot be called without the corresponding `-l` option, the option is added to the library option string. If the `-libs` option is passed to the script, its parameter is appended to the library option string after all library checks are done.
- (check_shared) Checks whether dynamic (shared-object) modules can be used. If the `-use-static-modules` option is given, dynamic modules are disabled; if not, and if a cached result is available, that result is used. Otherwise, a check is first made for the presence of the `dlfcn.h` header file, using the `test_include` function (see the `check_stdint` test below). If this file is present, the script proceeds to check whether `-ldl` is needed to access the dynamic loader functions and ensure that `-rdynamic` and `-shared` can be used with the compiler. A check is then made that attempts to open shared objects with unresolved symbols fail, and that shared objects with no unresolved symbols can in fact be used properly. If these tests succeed, a final check is made to determine whether symbol lookups require an underscore prepended to the symbol name or not. If any test fails, dynamic modules are disabled.
- (check_ranlib) Checks whether the `ranlib` program exists on the system. If a cached result is not available, a dummy file is created, and the `ar` program is used to add it to a dummy archive (if `ar` is not available, the script aborts). `ranlib` is then run on this archive, and the success or failure of the command is recorded.
- (check_stdint) Checks for the presence of the `stdint.h` header. This is done by calling the `test_include` function, which first checks whether the given file exists in /usr/include, then, if the file is not found, checks whether a test program that includes the file with `#include` can be compiled. The variable `HAVE_header_name`, where `header_name` is the header filename converted to upper case with non-alphanumeric characters replaced by underscores, is then set to 1 or 0 if the test succeeded or failed, respectively. The return value of the function itself is the success or failure of the test.
- (check_strings) Checks for the presence of the `strings.h` header.
- (check_sysselect) Checks for the presence of the `sys/select.h` header.
- (check_sysproto) Checks for the presence of the `sys/proto.h` header.
- (check_int8) Determines what type can be used for 8-bit integers. The `char` type, and the `int8_t` type if the `stdint.h` header is present, are checked first, giving preference to `int8_t` if available; if neither is an 8-bit type, a `byte` type is checked for next. If that type does not exist or is not 8 bits wide, the script aborts. The selected type is used to declare the `int8` and `uint8` types. (As described in section 11-1, Services development was started before `stdint.h` and the sized integer types were standardized, hence the use of nonstandard type names.)
- (check_int16) Determines what type can be used for 16-bit integers, selecting among `int16_t` (if present), `int`, and `short` in that order. The selected type is used to declare the `int16` and `uint16` types.
- (check_int32) Determines what type can be used for 32-bit integers, selecting among `int32_t` (if present), `int`, and `long` in that order. The selected type is used to declare the `int32` and `uint32` types.
- (check_int_size) Finds the size of the `int` type. If the `int` type is less than 16 bits wide (a violation of the C standard), the script aborts.
- (check_long_size) Finds the size of the `long` type. If the `long` type is less than 32 bits wide (a violation of the C standard), the script aborts.
- (check_ptr_size) Finds the size of pointer types. If pointers are smaller than integers, the script aborts (see section 10-5 regarding this assumption).
- (check_time_t) Finds the size of the `time_t` type. If the `time_t` type is less than 32 bits wide, the script aborts.
- (check_gid_t) Checks for the presence of the `gid_t` type and finds its size.
- (check_socklen_t) Checks for the presence of the `socklen_t` type.
- (check_aix_intNN) Checks whether system headers define the `int16` and `int32` types, which clash with type names used in Services. The `sys/systypes.h` header in at least some versions of the AIX operating system seems to define these.
- (check_strerror) Checks for how system error numbers can be converted to descriptive strings. The `strerror()` function is used preferentially if present; if not, a compatibility function in `compat.c` implements `strerror()`, using either the system's `sys_errlist[]` array if present, or a built-in message list if not. However, if the `-use-local-funcs` option was given, this test is skipped and the compatibility function (assuming no `sys_errlist[]`) is used.
- (check_compat) Checks for compatibility functions that need to be enabled. At this stage, the `-use-local-funcs` option is checked and, if present, compatibility functions for all functions below through `strsignal()` are enabled. If not, and cached results are available for all functions, those results are used. If not, testing proceeds to each individual function test.
- (check_hstrerror) Checks for the presence of the `check_hstrerror()` function. This is done by calling the `test_function` function, which attempts to compile a test program which calls the function (the contents of the test program are specified by the called). If the compilation succeeds and the test program returns a successful exit code (0), the function is assumed to exist. As with `test_include`, the variable `HAVE_function_name` (where `function_name` is the function name in upper-case) and return value are set according to the test result.
- (check_snprintf) Checks for the presence and behavior of the `snprintf()` function (if found, the `vsnprintf()` function is assumed to also exist). Services ignores the possibility of string truncation—which is probably a security issue in some cases; see also section 11-1—and expects `snprintf()` to return the number of characters actually written into the buffer. If `snprintf()` is available but uses a different return value scheme, a compatibility function translates the return value to what Services expects. If `snprintf()` is not available at all, the version included in `vsnprintf.c` is used.
- (check_strtok) Checks for the presence and behavior of the `strtok()` function. Some system library implementations of `strtok()` have been found to contain bugs, which are checked for by this test.
- (check_stricmp) Checks for the presence of the `stricmp()` function. This is equivalent to the POSIX `strcasecmp()` function, but has a more concise and clearer name (I always have to stop and remember that the "case" in `strcasecmp` means that case is ignored rather than that it is compared). If `stricmp()` is not available but `strcasecmp()` is available, the former is made an alias for the latter via `#define`.
- (check_strdup) Checks for the presence of the `strdup()` function.
- (check_strspn) Checks for the presence of the `strspn()` and `strcspn()` functions (the variable set is `HAVE_STRSPN`).
- (check_strsignal) Checks for the presence of the `strsignal()` function.
- (check_gettimeofday) Checks for the presence of the `gettimeofday()` function.
- (check_setgrent) Checks for the presence of the `setgrent()` function. However, if the `gid_t` type does not exist, the test is not executed and the function is assumed to not exist.
- (check_setregid) Checks for the presence of the `setregid()` function. However, if the `gid_t` type does not exist, the test is not executed and the function is assumed to not exist.
- (check_umask) Checks for the presence of the `umask()` function.
- (check_fork) Checks for the presence of the `fork()` function.
- (check_gethostbyname) Checks for the presence of the `gethostbyname()` function.
- (check_getsetrlimit) Checks for the presence of the `getrlimit()` and `setrlimit()` functions.
- (check_crypt) Checks for the presence of the `crypt()` function.
- (check_install) Checks whether a working `install` program exists on the system. If not, the `install-script` script included in the top source directory is used instead.
- (check_install-d) Checks whether `install -d` can be used to create directories. If not, `mkdir` is tried as a workaround, falling back to the `install-script` script if necessary.
- (check_copy_recurse) Determines what command should be used to copy entire directories. The default command is `cp -dpr` in Linux and Cygwin environments, and `cp -pr` in other environments. If this command does not work, the `tar` command is used as a substitute if possible (see the `cp-recursive` script in the top source directory). If `tar` cannot be used either, the script aborts.
- Creates the file `config.h`, containing the results of configuration relevant to the source code (such as compilation options and required compatibility functions) as `#define` preprocessor macros. If the `config.h` file already exists and the content of the new `config.h` to be written is unchanged from that in the existing file, the existing file is left untouched, so as not to cause an unneeded recompile of all source files if `configure` is re-run with identical parameters.
- Creates the file `Makefile.inc`, containing the results of configuration relevant to compilation commands (such as the compiler command and options and installation directories) as `make` variables. As above, if `Makefile.inc` already exists and its content has not changed, the old file is left alone.
- Creates the cache file `config.cache`, saving the results of configuration so that a subsequent run of the `configure` script will execute faster. The file is written as a sequence of shell commands, so that it can simply be sourced at runtime rather than parsing each line individually.

Note that a deliberate decision was made to not use the GNU `autoconf`/`automake`/`libtool` suite of tools, as they are overly complex for the range of systems Services is expected to be used on. (I have seen far too many programs where running the autoconf-generated `configure` script takes longer than compiling the program itself.)

Back to top

### 10-3. The compilation process

Overall compilation is controlled by the `Makefile` in the top source directory. When run with no target specified, the default target `all` is used, which first checks that the `configure` script has been run (aborting with a notice to the user if not), then proceeds to actual compilation with the `myall` target. This target compiles the main program (`ircservices` or `ircservices.exe`), then generates the language files and creates the `convert-db` and `ircservices-chk` tools.

Additional targets available are: `install`, which installs program and data files to the appropriate directories (see section 10-4); `clean`, which removes most generated files, such as object and executable files; and `spotless`, or `distclean` in the GNU style, which removes all generated files (including `config.cache`). All of these targets are called recursively for the `modules`, `lang`, and `tools` directories.

The main Makefile also includes two rules, for `services.h` and `language.h`, which cause those files to be `touch`ed (the file timestamp updated) whenever any sub-header file changes. This is done because it is considered easier than ensuring that every dependency list stays up to date with all sub-header files.

Back to top

#### 10-3-1. Core source files

Compilation of the main executable starts with compilation of the core source files, stored in the top source directory. The object files to be created are listed in alphabetical order in the `$(OBJS)` variable. The options used for compilation are `-DSTATIC_MODULES` if using static modules, followed by the options selected by the `configure` script (in the `$(BASE_CFLAGS)` variable), followed by the options in the `$(MORE_CFLAGS)` variable (defined at the top of the Makefile, intended for users to add or change extra options on the fly).

After compiling all of the core source files, compilation proceeds to the modules, as described below; finally, a `version.c` file is generated by the `version.sh` script, containing the program version number and a build number (extracted from the previous contents of `version.c`) which is incremented by one each build, and this file is compiled and linked with the rest of the main source files, as well as the modules when compiling modules statically, to produce the main executable.

Note that one core header file, `langstrs.h`, is copied from the language file subdirectory, where it is first generated if necessary; see section 10-3-3 below for details.

Back to top

#### 10-3-2. Modules

Compilation of modules is handled by `modules/Makefile` and its auxiliary file, `modules/Makerules`. The top directory's Makefile calls one of two targets in `modules/Makefile` to compile all available modules: `all-dynamic` if dynamic linking is in use, `all-static` for static linking. Both of these targets perform the same basic function: search for all subdirectories of the `modules` directory that contain Makefiles, and call the `all-dynamic` or `all-static` target in each subdirectory's Makefile.

Since the subdirectory search is performed at compilation time, it is possible to add new modules to Services by simply copying the directory containing the module source code and Makefile into the `modules` directory. This is in fact the recommended method for installing third-party modules, and the module compilation system was designed with this aim in mind.

Of the two main targets in `modules/Makefile`, the `all-dynamic` target is the simpler of the two. It loops through all subdirectories, calling the `all-dynamic` target in each, then updates a dummy `.stamp` file if the subdirectory's `.stamp` file was updated. This file is used by the main Makefile to determine whether `version.c` should be regenerated.

For static modules, the process is slightly more complicated, since a list of all modules and exported symbols must be provided to the core module manager. In addition to calling each subdirectory's `all-static` target, the `.modext-*`, `.modsyms-*`, and `.modlist-*` files in each subdirectory are concatenated to form module and symbol lists; these are then compiled into an additional object file, `modlist.o`, and an archive (`modules.a`) is created containing this file and all module object files. This archive is then linked into the final executable.

The compilation of individual modules is handled by the `modules/Makerules` file, which is included by each subdirectory's Makefile. This file is designed so that individual module Makefiles need only set up some variables with lists of files to be compiled and include the `Makerules` file. However, due in part to limitations of the GNU `make` program, the actual implementation is quite complex, and as a side effect it takes `make` considerable time to process the module subdirectories even when no files need to be recompiled.

The limitation in `make` which I found no easy way around during development is that there is (or was; see the relevant note in section 11-1 for a way that works with newer versions of GNU `make`) no way to specify a rule in which the name of a variable specifying a dependency varies with the target name. For example, suppose a subdirectory contains two modules, `module1` and `module2`. If each module consists of only one source file and has no other dependencies, the rule is simple (assuming dynamic modules, and assuming the presence of a `%.o: %.c` compilation rule):

%.so: %.o $(CC_SHARED) $^ -o $@

However, suppose that `module1` depends on file `aux1.o` and `module2` depends on files `aux2.o` and `aux3.o`. If these are specified in `$(OBJECTS-module1.so)` and `$(OBJECTS-module2.so)`, one might then be tempted to write:

%.so: %.o $(OBJECTS-%.so) $(CC_SHARED) $^ -o $@

Unfortunately, this does not substitute `module1` or `module2` in the `$(OBJECTS-%.so)` reference, but uses the value of the variable literally named `OBJECTS-%.so`. Nor does it work to use `$@` instead of `%.so` in the variable name:

%.so: OBJECTS = $(OBJECTS-$@) %.so: %.o $(OBJECTS) $(CC_SHARED) $^ -o $@

Here, `$(OBJECTS)` is only defined for commands inside the pattern rule, so it cannot be used in the dependency list. Likewise, `$@` is only defined for the command list, so specifying `$(OBJECTS-$@)` in the dependency list also does not work.

The upshot of all this is that in order to have a dependency list that varies with the target, it is necessary to call `make` recursively. The bulk of the `Makerules` file is dedicated to handling this recursive calling and setting variables appropriately for each target.

The Makefile for a module directory typically consists only of variable definitions followed by the line "`include ../Makerules`". Chief among the variables required is the `$(MODULES)` variable, which lists the modules contained in the directory; each module is specified with a `.so` extension (static modules are compiled to `.a` archives, but the filename translation is handled transparently by the `Makerules` file), and is assumed to be compiled from a source file of the same name with a `.c` extension. If any additional object files are required by the module, they are specified in a variable named `$(OBJECTS-module.so)`, where `module` is replaced by the module name. Header file dependencies can be specified through the `$(INCLUDE)` variable for dependencies common to all object files, and `$(INCLUDE-object.o)` for a particular object file. If any object requires a special rule for compilation, that rule can be written in the Makefile, but rules are not needed for the ordinary case of compiling a `.c` source file to a `.o` object file.

When the subdirectory's `all-dynamic` or `all-static` rule is first invoked, the script first checks, via pattern rules, that no modules have names ending in `_static` (which would conflict with the static module compilation procedure, as described below) or beginning with a period (which would conflict with the temporary files created during compilation). Then, for each module, `make` is called recursively with the `$(TARGET)` variable set to the target module name (with no filename extension), `$(OBJECTS)` variable set to the contents of the particular module's object list (`$(OBJECTS-module.so)`), and `$(REALLY_COMPILE)`, the recursion level variable, set to 1.

When called with `$(REALLY_COMPILE)` equal to 1, `Makerules` instead defines a rule for the target file, `$(TARGET).so` or `$(TARGET).a` For dynamic modules, this simply involves compiling the relevant files and linking them into a shared object; for static modules, however, the objects will eventually be archived into a single `modules.a` archive, so no `.a` file for the individual module is actually needed. What the rule does instead is to record each object file's name in the `.$(DIRNAME).lst` file, which is then used by the `all-static` rule to link all object files in the directory into a single object `$(DIRNAME).o` for storing in the `modules.a` archive. (A dummy `$(TARGET).a` file is also created so that `make` can perform its file timestamp checks.) In addition, since the static module manager requires a list of names and pointers for all variables and functions exported by each module, the static module compilation rule searches through all source files—each object file is assumed to correspond to a single source file with the same name and a `.c` extension, and no header files are checked—for `EXPORT_VAR()`, `EXPORT_ARRAY()`, and `EXPORT_FUNC()` macro invocations. The export information given to these macros, along with declarations for the implicitly exported variables `_this_module_ptr` and `module_version`, array `module_config[]`, and functions `init_module()` and `exit_module()`, are written to two temporary files: `.modext-module.h`, containing `extern` declarations, and `.modsyms-module.c`, containing the actual symbol entries. A line with the module name and symbol array pointer is also written to `.modlist-module.c` for later inclusion in the overall module list.

For each individual object file, `Makerules` first (at recursion level 1) generates a rule for compiling the object file that calls `make` recursively, with `$(TARGET)` now set to the object file's base filename (with the `.o` extension stripped), `$(INCLUDES2)` set to the particular object file's dependency list from `$(INCLUDES-object.o)`, `-DMODULE` and `-DMODULE_ID=module-id` appended to `$(CFLAGS)` (`module-id` is a C-style identifier derived from the directory and module name, used to make common module identifiers unique), and `$(REALLY_COMPILE)` set to 2. In addition, for the main file of a module (the source file with the same name as the module), `-DMODULE_MAIN_FILE` is also appended to `$(CFLAGS)`.

In this second recursion level, the actual source file compilation is performed. The `object.o` rule does not perform the actual compilation, but depends on a `.compiled-object.o` dummy file whose rule performs the compilation and on a dummy `FRC` (force) rule; this hack prevents `make` from outputting "nothing to do" messages for every unchanged object file. The compilation command itself is similar to that used for the core source code, but the command is prefixed by a `cd` to the top directory, so that the relative path to the source file is saved in the object file's debug information. This allows debuggers to easily find the proper source file, even if multiple module subdirectories have identically-named source files.

For static modules, a slight change is made for the module's main source file: rather than compiling the source file to `module.o`, the object file is given the filename `module_static.o`, and the five implicitly exported variables/functions (`init_module()`, etc.) are renamed via `-D` options to names containing the `module-id` so that they do not cause symbol conflicts with other modules at link time.

Back to top

#### 10-3-3. Language files

The language files, stored in the `lang` directory, are precompiled into binary format to speed the startup of Services, as described in section 2-8-4. This precompilation is performed by the `langcomp` program, compiled from `langcomp.c`. When called with the `all` target, the Makefile first compiles `langcomp`, then runs it on each language source file to generate the corresponding precompiled binary file. In the special case of the Japanese language files (`ja_*.l`), which correspond to the various encodings common on Japanese computer systems, the EUC file (`ja_euc.l`) is treated as canonical, and when it changes, the `jconv.pl` script is automatically run to regenerate the `ja_sjis.l` file before compiling it to binary format. (There was also a JIS-encoded file, `ja_jis.l`, in the past, but this was dropped because of extra % characters in the text causing `printf()` functions to break.)

The list of standard language strings is taken from the English language file, `en_us.l`; a simple `grep` is used to extract the string names to the `index` file, and this file is then used to generate `langstrs.h`, which contains the string names both as preprocessor (`#define`) constants and as a string array, available if `LANGSTR_ARRAY` is defined. The core source file `language.c` uses this array for looking up string names when loading external language files at runtime.

Back to top

#### 10-3-4. The `tools` and `data` directories

The `tools` directory contains two additional programs: `convert-db`, the database conversion tool discussed in section 9, and `ircservices-chk`, a simple script designed to run from a periodic execution utility such as `cron` to ensure that Services is restarted if it should stop for any reason (such as a crash).

In addition to the main source file `convert-db` and the `convert-*.c` source files that handle particular database types, `convert-db` makes use of three source files from the main source code: `compat.c`, containing compatibility functions; `modules/database/fileutil.c`, containing routines to read and write data in binary database files; and `modules/misc/xml-export.c`, containing routines to generate an XML file from loaded data. These three files are compiled using special rules, which include the `-DCONVERT_DB` compiler option to trigger special handling in the source files for the `convert-db` tool.

The "compilation" of `ircservices-chk` consists of simply replacing the `@PROGRAM@`, `@BINDEST@` and `@DATDEST@` fields in the template file `ircservices-chk.in` with the actual file/pathnames, writing the output to the file `ircservices-chk` (more precisely, `$(PROGRAM)-chk`, where `$(PROGRAM)` is the value of the `-program` option given to the `configure` script), and marking that file executable with `chmod`.

Likewise, the two sample configuration files (`example-ircservices.conf` and `example-modules.conf`) in the `data` subdirectory are generated from template files, replacing occurrences of `@PROGRAM@` with the actual program name to give appropriate defaults for various file names.

Back to top

### 10-4. Installation

The `install` target, which like `all` defers actual work to the `myinstall` target, first creates the target directories, `$(BINDEST)` and `$(DATDEST)`, if they do not exist; copies the main executable file to the `$(BINDEST)` directory; and calls the `install` target in the `modules`, `lang`, `tools`, and `data` directories. However, when static modules are being used, the module install is skipped (since the module objects are linked directly into the executable, there is nothing to install).

The module installation consists of calling the `install` target in each module subdirectory; this target, declared in the `modules/Makerules` file, creates a subdirectory of the same name inside the `modules` directory under `$(DATDEST)`, then copies all module shared-object files into that directory.

The language installation first creates a `language` directory under `$(DATDEST)`; the precompiled language files are then copied into that directory.

The tool installation copies the `ircservices-chk` script to `$(BINDEST)`, and the `convert-db` program to `$(DATDEST)`. The latter is not copied to the executable file directory to avoid the possibility that the executable file name conflicts with another program installed on the system. (A better solution might be to rename the program to something like `ircservices-convert`.)

The data file installation copies the example configuration files, `example-ircservices.conf` and `example-modules.conf`, to `$(DATDEST)`; `example-ircservices.conf` is renamed at this time to `example-$(PROGRAM).conf`. If the `helpfiles` directory does not exist under `$(DATDEST)`, it is copied from the `helpfiles` subdirectory of the `data` directory.

Since the `install` target depends on the `all` (or more precisely, the `myall`) target, it is also possible to perform compilation and installation in one step by simply executing `make install`.

If the variable `INSTALL_PREFIX` is set, its value is prepended to all pathnames used for installation; for example, the `ircservices` executable file is installed to `$(INSTALL_PREFIX)$(BINDEST)/ircservices`. This allows Services to be installed to an alternate root directory, such as when preparing a chroot'd environment or a distribution image. This variable is not set by any of the Makefiles, but can be set on the `make` command line. (Note that there is no slash after `$(INSTALL_PREFIX)`; inserting one would have the side effect of prefixing `$(BINDEST)` and `$(DATDEST)` with a slash when no prefix was given, which could potentially have undesired side effects.)

Back to top

### 10-5. Assumptions

Finally, it is worth noting a few assumptions made by the source code. These are all believed to hold on any system Services is likely to be used on, and some are double-checked by the `configure` script.

- Pointer values are at least as large as `int` values. The module system uses `void *` as type placeholders for module callbacks, as described in section 4-5-3, and some database management routines, such as in the OperServ news and autokill modules (see section 7-2-2-1, for example) store integer values in pointer fields. While these are admittedly very crocky ways of doing things, they do require that pointers be at least large enough to hold values that may be stored in them. The `configure` script checks that pointers are at least as large as `int`s, and aborts if not.
- Character values are exactly 8 bits. More precisely, a `char` variable is exactly one byte in size (whatever size a byte may be). This is not checked by the `configure` script, but it is mandated by the C standard and should not be a problem.
- Unprototyped routines can accept up to five arguments regardless of type. Or to be more accurate, all of the first five arguments to a routine are stored in same-size locations regardless of type. This requirement is a side effect of the module callback calling method mentioned above, and holds on at least x86 (where everything except `long long` is 32 bits) and x86-64 (where the first five arguments are all passed in registers). Note that this is not checked by the `configure` script.
- Pointer aliasing (type-punning) is allowed. The C standard disallows "aliasing" of pointers of different types (C99 6.5 (7)); in other words, if you have a pointer variable `struct foostruct *foo`, you may not assign the value of `foo` to `struct barstruct *bar` and then modify the contents of `*bar`. This sounds like a good idea on the surface, but it turns out to be quite inconvenient in practice; in particular, it prevents the use of "derived structures", where the first member of one structure is a second structure (similar to a derived class in object-oriented languages). It would in theory be possible, if unduly verbose, to rewrite the code to obey the aliasing rules; but for convenience, Services simply assumes that this sort of aliasing is permitted. The `configure` script does not check for this directly, since any effect would be compiler-dependent and difficult (if not impossible) to detect reliably, but the script does enable the `-fno-strict-aliasing` option when compiling with GCC, which disables optimizations that rely on this aliasing rule.
- The `NULL` pointer is bitwise zero for all pointer types. For the sake of efficiency (and concise source code), arrays or structures containing pointers are often cleared by using `calloc()` to allocate precleared memory or `memset()` to fill the memory region with the byte value 0, and it is assumed that these operation are equivalent to individually assigning the `NULL` value to each pointer. The C standard does not require that `NULL` be stored in memory as the value zero, only that it compare equal to zero, but every system I have used so far uses bitwise zero for `NULL`, so I have not made an effort to write "correct" code. Note that the `configure` script does not check for this.

Back to top

Previous section: The database conversion tool | Table of Contents | Next section: Future work