Section 4.3. Kernel Build System


4.3. Kernel Build System

The Linux kernel configuration and build system is rather complicated, as one would expect of software projects containing more than six million lines of code! In this section, we cover the foundation of the kernel build system for developers who need to customize the build environment.

A recent Linux kernel snapshot showed more than 800 makefiles[5] in the kernel source tree. This might sound like a large number, but it might not seem so large when you understand the structure and operation of the build system. The Linux kernel build system has been significantly updated since the days of Linux 2.4 and earlier. For those of you familiar with the older kernel build system, we're sure you will find the new Kbuild system to be a huge improvement. We limit our discussion in this section to this and later kernel versions based on Kbuild.

[5] Not all these makefiles are directly involved in building the kernel. Some, for example, build documentation files.

4.3.1. The Dot-Config

Introduced earlier, the dot-config file is the configuration blueprint for building a Linux kernel image. You will likely spend significant effort at the start of your Linux project building a configuration that is appropriate for your embedded platform. Several editors, both text based and graphical, are designed to edit your kernel configuration. The output of this configuration exercise is written to a configuration file named .config, located in the top-level Linux source directory that drives the kernel build.

You have likely invested significant time perfecting your kernel configuration, so you will want to protect it. Several make commands delete this configuration file without warning. The most common is make mrproper. This make target is designed to return the kernel source tree to its pristine, unconfigured state. This includes removing all configuration data from the source treeand, yes, it deletes your .config.

As you might know, any filename in Linux preceded by a dot is a hidden file in Linux. It is unfortunate that such an important file is marked hidden; this has brought considerable grief to more than one developer. If you execute make mrproper without having a backup copy of your .config file, you, too, will share our grief. (You have been warnedback up your .config file!)

The .config file is a collection of definitions with a simple format. Listing 4.5 shows a snippet of a .config from a recent Linux kernel release.

Listing 4-5. Snippet from Linux 2.6 .config

... # USB support # CONFIG_USB=m # CONFIG_USB_DEBUG is not set # Miscellaneous USB options # CONFIG_USB_DEVICEFS=y # CONFIG_USB_BANDWIDTH is not set # CONFIG_USB_DYNAMIC_MINORS is not set # USB Host Controller Drivers # CONFIG_USB_EHCI_HCD=m # CONFIG_USB_EHCI_SPLIT_ISO is not set # CONFIG_USB_EHCI_ROOT_HUB_TT is not set CONFIG_USB_OHCI_HCD=m CONFIG_USB_UHCI_HCD=m ...

To understand the .config file, you need to understand a fundamental aspect of the Linux kernel. Linux has a monolithic structure. That is, the entire kernel is compiled and linked as a single statically linked executable. However, it is possible to compile and incrementally link[6] a set of sources into a single object module suitable for dynamic insertion into a running kernel. This is the usual method for supporting most common device drivers. In Linux, these are called loadable modules. They are also generically called device drivers. After the kernel is booted, a special application program is invoked to insert the loadable module into a running kernel.

[6] Incremental linking is a technique used to generate an object module that is intended to be linked again into another object. In this way, unresolved symbols that remain after incremental linking do not generate errorsthey are resolved at the next link stage.

Armed with that knowledge, let's look again at Listing 4-5. This snippet of the configuration file (.config) shows a portion of the USB subsystem configuration. The first configuration option, CONFIG_USB=m, declares that the USB subsystem is to be included in this kernel configuration and that it will be compiled as a dynamically loadable module(=m), to be loaded sometime after the kernel has booted. The other choice would have been =y, in which case the USB module would be compiled and statically linked as part of the kernel image itself. It would end up in the .../drivers/built-in.o composite binary that you saw in Listing 4-3 and Figure 4-1. The astute reader will realize that if a driver is configured as a loadable module, its code is not included in the kernel proper, but rather exists as a stand-alone object module, a loadable module, to be inserted into the running kernel after boot.

Notice in Listing 4-5 the CONFIG_USB_DEVICEFS=y declaration. This configuration option behaves in a slightly different manner. In this case, USB_DEVICEFS (as configuration options are commonly abbreviated) is not a stand-alone module, but rather a feature to be enabled or disabled in the USB driver. It does not necessarily result in a module that is compiled into the kernel proper (=y); instead, it enables one or more features, often represented as additional object modules to be included in the overall USB device driver module. Usually, the help text in the configuration editor, or the hierarchy presented by the configuration editor, makes this distinction clear.

4.3.2. Configuration Editor(s)

Early kernels used a simple command line driven script to configure the kernel. This was cumbersome even for early kernels, in which the number of configuration parameters was much smaller. This command line style interface is still supported, but using it is tedious, to say the least. A typical configuration from a recent kernel requires answering more than 600 questions from the command line, entering your choice followed by the Enter key for each query from the script. Furthermore, if you make a mistake, there is no way to back up; you must start from the beginning again. That can be profoundly frustrating if you make a mistake on the 599th entry!

In some situations, such as building a kernel on an embedded system without graphics, using the command line configuration utility is unavoidable, but this author would go to great lengths to find a way around it.

The kernel-configuration subsystem uses several graphical front ends. In fact, a recent Linux kernel release included 10 such configuration targets. They are summarized here, from text taken directly from the output of make help:

  • config Update current config using a line-oriented program

  • menuconfig Update current config using a menu-based program

  • xconfig Update current config using a QT-based front end

  • gconfig Update current config using a GTK-based front end

  • oldconfig Update current config using a provided .config as the base

  • randconfig New config with random answer to all options

  • defconfig New config with default answer to all options

  • allmodconfig New config that selects modules, when possible

  • allyesconfig New config in which all options are accepted with yes

  • allnoconfig New minimal config

The first four of these makefile configuration targets invoke a form of configuration editor, as described in the list. Because of space considerations, we focus our discussion in this chapter and others only on the GTK-based graphical front end. Realize that you can use the configuration editor of your choice with the same results.

The configuration editor is invoked by entering the command make gconfig from the top-level kernel directory.[7] Figure 4-2 shows the top-level configuration menu presented to the developer when gconfig is run. From here, every available configuration parameter can be accessed to generate a custom kernel configuration.

[7] As mentioned, you can use the configuration editor of your choice, such as make xconfig or make menuconfig.

Figure 4-2. Top-level kernel configuration


When the configuration editor is exited, you are prompted to save your changes. If you elect to save your changes, the global configuration file .config is updated (or created, if it does not already exist). This .config file, introduced earlier, drives the kernel build via the top-level makefile. You will notice in this makefile that the .config file is read directly by an include statement.

Most kernel software modules also read the configuration indirectly via the .config file as follows. During the build process, the .config file is processed into a C header file found in the .../include/linux directory, called autoconf.h. This is an automatically generated file and should never be edited directly because edits are lost each time a configuration editor is run. Many kernel source files include this file directly using the #include preprocessor directive. Listing 4-6 reproduces a section of this header file that corresponds to the earlier USB example above. Note that, for each entry in the .config file snippet in Listing 4-5, a corresponding entry is created in autoconf.h. This is how the source files in the kernel source tree reference the kernel configuration.

Listing 4-6. Linux autoconf.h

/*  * USB support  */ #define CONFIG_USB_MODULE 1 #undef CONFIG_USB_DEBUG /*  * Miscellaneous USB options  */ #define CONFIG_USB_DEVICEFS 1 #undef CONFIG_USB_BANDWIDTH #undef CONFIG_USB_DYNAMIC_MINORS /*  * USB Host Controller Drivers  */ #define CONFIG_USB_EHCI_HCD_MODULE 1 #undef CONFIG_USB_EHCI_SPLIT_ISO #undef CONFIG_USB_EHCI_ROOT_HUB_TT #define CONFIG_USB_OHCI_HCD_MODULE 1 #define CONFIG_USB_UHCI_HCD_MODULE 1

If you haven't already done so, execute make gconfig in your top-level kernel source directory, and poke around this configuration utility to see the large number of subsections and configuration options available to the Linux developer. As long as you don't explicitly save your changes, they are lost upon exiting the configuration editor and you can safely explore without modifying your kernel configuration.[8] Many configuration parameters contain helpful explanation text, which can add to your understanding of the different configuration options.

[8] Better yet, make a backup copy of your .config file.

4.3.3. Makefile Targets

If you type make help at the top-level Linux source directory, you are presented with a list of targets that can be generated from the source tree. The most common use of make is to specify no target. This generates the kernel ELF file vmlinux and is the default binary image for your chosen architecture (for example, bzImage for x86). Specifying make with no target also builds all the device-driver modules (kernel-loadable modules) specified by the configuration.

Many architectures and machine types require binary targets specific to the architecture and bootloader in use. One of the more common architecture specific targets is zImage. In many architectures, this is the default target image that can be loaded and run on the target embedded system. One of the common mistakes that newcomers make is to specify bzImage as the make target. The bzImage target is specific to the x86/PC architecture. Contrary to popular myth, the bzImage is not a bzip2-compressed image. It is a big zImage. Without going into the details of legacy PC architecture, it is enough to know that a bzImage is suitable only for PC-compatible machines with an industry-standard PC-style BIOS.

Listing 4-7 contains the output from make help from a recent Linux kernel. You can see from the listing that many targets are available in the top-level Linux kernel makefile. Each is listed along with a short description of its use. It is important to realize that even the help make target (as in make help) is architecture specific. You get a different list of architecture-specific targets depending on the architecture you pass on the make invocation. Listing 4-7 illustrates an invocation that specifies the ARM architecture, as you can see from the make command line.

Listing 4-7. Makefile Targets

$ make ARCH=arm help Cleaning targets:   clean            -  remove most generated files but keep the config   mrproper         -  remove all generated files + config +  various backup files Configuration targets:   config           -  Update current config utilising a line-oriented program   menuconfig       -  Update current config utilising a menu based program   xconfig          -  Update current config utilising a QT based front-end   gconfig          -  Update current config utilising a GTK based front-end   oldconfig        -  Update current config utilising a provided .config as base   randconfig       -  New config with random answer to all options   defconfig        -  New config with default answer to all options   allmodconfig     -  New config selecting modules when possible   allyesconfig     -  New config where all options are accepted with yes   allnoconfig      -  New minimal config Other generic targets:   all              - Build all targets marked with [*] * vmlinux          - Build the bare kernel * modules          - Build all modules   modules_install  - Install all modules   dir/             - Build all files in dir and below   dir/file.[ois]   - Build specified target only   dir/file.ko      - Build module including final link   rpm              - Build a kernel as an RPM package   tags/TAGS        - Generate tags file for editors   cscope           - Generate cscope index   kernelrelease    - Output the release version string Static analysers   buildcheck       - List dangling references to vmlinux discarded sections and                      init sections from non-init sections   checkstack       - Generate a list of stack hogs   namespacecheck   - Name space analysis on compiled kernel Kernel packaging:   rpm-pkg          - Build the kernel as an RPM package   binrpm-pkg       - Build an rpm package containing the compiled kernel and                      modules deb-pkg          - Build the kernel as an deb package   tar-pkg          - Build the kernel as an uncompressed tarball   targz-pkg        - Build the kernel as a gzip compressed tarball   tarbz2-pkg       - Build the kernel as a bzip2 compressed tarball Documentation targets:   Linux kernel internal documentation in different formats:   xmldocs (XML DocBook), psdocs (Postscript), pdfdocs (PDF)   htmldocs (HTML), mandocs (man pages, use installmandocs to install) Architecture specific targets (arm): * zImage           -  Compressed kernel image (arch/arm/boot/zImage)   Image            -  Uncompressed kernel image (arch/arm/boot/Image) * xipImage         - XIP kernel image, if configured (arch/arm/boot/xipImage)   bootpImage       - Combined zImage and initial RAM disk                      (supply initrd image via make variable INITRD=<path>)   install           - Install uncompressed kernel   zinstall          - Install compressed kernel                      Install using (your) ~/bin/installkernel or                      (distribution) /sbin/installkernel or                      install to $(INSTALL_PATH) and run lilo   assabet_defconfig          - Build for assabet   badge4_defconfig           - Build for badge4   bast_defconfig             - Build for bast   cerfcube_defconfig         - Build for cerfcube   clps7500_defconfig         - Build for clps7500   collie_defconfig           - Build for collie   corgi_defconfig            - Build for corgi   ebsa110_defconfig          - Build for ebsa110   edb7211_defconfig          - Build for edb7211   enp2611_defconfig          - Build for enp2611   ep80219_defconfig          - Build for ep80219   epxa10db_defconfig         - Build for epxa10db   footbridge_defconfig       - Build for footbridge   fortunet_defconfig         - Build for fortunet   h3600_defconfig            - Build for h3600   h7201_defconfig            - Build for h7201   h7202_defconfig            - Build for h7202   hackkit_defconfig          - Build for hackkit   integrator_defconfig       - Build for integrator   iq31244_defconfig          - Build for iq31244   iq80321_defconfig          - Build for iq80321   iq80331_defconfig          - Build for iq80331   iq80332_defconfig          - Build for iq80332   ixdp2400_defconfig         - Build for ixdp2400   ixdp2401_defconfig         - Build for ixdp2401   ixdp2800_defconfig         - Build for ixdp2800   ixdp2801_defconfig         - Build for ixdp2801   ixp4xx_defconfig           - Build for ixp4xx   jornada720_defconfig       - Build for jornada720   lart_defconfig             - Build for lart   lpd7a400_defconfig         - Build for lpd7a400   lpd7a404_defconfig         - Build for lpd7a404   lubbock_defconfig          - Build for lubbock lusl7200_defconfig         - Build for lusl7200   mainstone_defconfig        - Build for mainstone   mx1ads_defconfig           - Build for mx1ads   neponset_defconfig         - Build for neponset   netwinder_defconfig        - Build for netwinder   omap_h2_1610_defconfig     - Build for omap_h2_1610   pleb_defconfig             - Build for pleb   poodle_defconfig           - Build for poodle   pxa255-idp_defconfig       - Build for pxa255-idp   rpc_defconfig              - Build for rpc   s3c2410_defconfig          - Build for s3c2410   shannon_defconfig          - Build for shannon   shark_defconfig            - Build for shark   simpad_defconfig           - Build for simpad   smdk2410_defconfig         - Build for smdk2410   spitz_defconfig            - Build for spitz   versatile_defconfig        - Build for versatile   make V=0|1 [targets] 0 => quiet build (default), 1 => verbose build   make O=dir [targets] Locate all output files in "dir", including .config   make C=1   [targets] Check all c source with $CHECK (sparse)   make C=2   [targets] Force check of all c source with $CHECK (sparse) Execute "make" or "make all" to build all targets marked with [*] For further info see the ./README file

Many of these targets you might never use. However, it is useful to know that they exist. As you can see from Listing 4-7, the targets listed with an asterisk are built by default. Notice the numerous default configurations, listed as *_defconfig. Recall from Section 4.2.2, "Compiling the Kernel," the command we used to preconfigure a pristine kernel source tree: We invoked make with an architecture and a default configuration. The default configuration was ixp4xx_defconfig, which appears in this list of ARM targets. This is a good way to discover all the default configurations available for a particular kernel release and architecture.

4.3.4. Kernel Configuration

Kconfig (or a file with a similar root followed by an extension, such as Kconfig.ext) exists in almost 300 kernel subdirectories. Kconfig drives the configuration process for the features contained within its subdirectory. The contents of Kconfig are parsed by the configuration subsystem, which presents configuration choices to the user, and contains help text associated with a given configuration parameter.

The configuration utility (such as gconf, presented earlier) reads the Kconfig files starting from the arch subdirectory's Kconfig file. It is invoked from the Kconfig makefile with an entry that looks like this:

gconfig: $(obj)/gconf         $< arch/$(ARCH)/Kconfig


Depending on which architecture you are building, gconf reads this architecture-specific Kconfig as the top-level configuration definition. Contained within Kconfig are a number of lines that look like this:

source  "drivers/pci/Kconfig"


This directive tells the configuration editor utility to read in another Kconfig file from another location within the kernel source tree. Each architecture contains many such Kconfig files; taken together, these determine the complete set of menu options presented to the user when configuring the kernel. Each Kconfig file is free to source additional Kconfig files in different parts of the source tree. The configuration utilitygconf, in this case, recursively reads the Kconfig file chain and builds the configuration menu structure.

Listing 4-8 is a partial tree view of the Kconfig files associated with the ARM architecture. In a recent Linux 2.6 source tree from which this example was taken, the kernel configuration was defined by 170 separate Kconfig files. This listing omits most of those, for the sake of space and claritythe idea is to show the overall structure. To list them all in this tree view would take several pages of this text.

Listing 4-8. Partial Listing of Kconfig for ARM Architecture

arch/arm/Kconfig <<<<<< (top level Kconfig) |->  init/Kconfig |  ... |->  arch/arm/mach-iop3xx/Kconfig |->  arch/arm/mach-ixp4xx/Kconfig |    ... |->  net/Kconfig |    |->  net/ipv4/Kconfig |    |     |->  net/ipv4/ipvs/Kconfig |    ... |->  drivers/char/Kconfig |    |->  drivers/serial/Kconfig |    ... |->  drivers/usb/Kconfig |    |->  drivers/usb/core/Kconfig |    |->  drivers/usb/host/Kconfig | ... |->  lib/Kconfig

Looking at Listing 4-8, the file arch/arm/Kconfig would contain a line like this:

source "net/Kconfig"


The file net/Kconfig would contain a line like this:

source "net/ipv4/Kconfig"


…and so on.

As mentioned earlier, these Kconfig files taken together determine the configuration menu structure and configuration options presented to the user during kernel configuration. Figure 4-3 is an example of the configuration utility (gconf) for the ARM architecture compiled from the example in Listing 4-8.

Figure 4-3. gconf configuration screen


4.3.5. Custom Configuration Options

Many embedded developers add feature support to the Linux kernel to support their particular custom hardware. One of the most common examples of this is multiple versions of a given hardware platform, each of which requires some compile-time options to be configured in the kernel source tree. Instead of having a separate version of the kernel source tree for each hardware version, a developer can add configuration options to enable his custom features.

The configuration management architecture described in the previous paragraphs makes it easy to customize and add features. A quick peek into a typical Kconfig file shows the structure of the configuration script language. As an example, assume that you have two hardware platforms based on the IXP425 network processor, and that your engineering team had dubbed them Vega and Constellation. Each board has specialized hardware that must be initialized early during the kernel boot phase. Let's see how easy it is to add these configuration options to the set of choices presented to the developer during kernel configuration. Listing 4-9 is a snippet from the top-level ARM Kconfig file.

Listing 4-9. Snippet from …/arch/arm/Kconfig

source "init/Kconfig" menu "System Type" choice         prompt "ARM system type"         default ARCH_RPC config ARCH_CLPS7500         bool "Cirrus-CL-PS7500FE" config ARCH_CLPS711X         bool "CLPS711x/EP721x-based" ... source "arch/arm/mach-ixp4xx/Kconfig

In this Kconfig snippet taken from the top-level ARM architecture Kconfig, you see the menu item System Type being defined. After the ARM System type prompt, you see a list of choices related to the ARM architecture. Later in the file, you see the inclusion of the IXP4xx-specific Kconfig definitions. In this file, you add your custom configuration switches. Listing 4-10 reproduces a snippet of this file. Again, for readability and convenience, we've omitted irrelevant text, as indicated by the ellipsis.

Listing 4-10. File Snippet: arch/arm/mach-ixp4xx/Kconfig

menu "Intel IXP4xx Implementation Options" comment "IXP4xx Platforms" config ARCH_AVILA          bool "Avila"          help            Say 'Y' here if you want your kernel to support... config ARCH_ADI_COYOTE          bool "Coyote"          help            Say 'Y' here if you want your kernel to support           the ADI Engineering Coyote...  # (These are our new custom options)  config ARCH_VEGA          bool "Vega"          help            Select this option for "Vega" hardware support  config ARCH_CONSTELLATION           bool "Constellation"           help             Select this option for "Constellation"             hardware support    ...

Figure 4-4 illustrates the result of these changes as it appears when running the gconf utility (via make ARCH=arm gconfig). As a result of these simple changes, the configuration editor now includes options for our two new hardware platforms.[9] Shortly, you'll see how you can use this configuration information in the source tree to conditionally select objects that contain support for your new boards.

[9] We have intentionally removed many options under ARM system type and Intel IXP4 xx Implementation Options to fit the picture on the page.

Figure 4-4. Custom configuration options


After the configuration editor (gconf, in these examples) is run and you select support for one of your custom hardware platforms, the .config file introduced earlier contains macros for your new options. As with all kernel-configuration options, each is preceded with CONFIG_ to identify it as a kernel-configuration option. As a result, two new configuration options have been defined, and their state has been recorded in the .config file. Listing 4-11 shows the new .config file with your new configuration options.

Listing 4-11. Customized .config File Snippet

... # # IXP4xx Platforms # # CONFIG_ARCH_AVILA is not set # CONFIG_ARCH_ADI_COYOTE is not set CONFIG_ARCH_VEGA=y # CONFIG_ARCH_CONSTELLATION is not set # CONFIG_ARCH_IXDP425 is not set # CONFIG_ARCH_PRPMC1100 is not set ...

Notice two new configuration options related to your Vega and Constellation hardware platforms. As illustrated in Figure 4-4, you selected support for Vega; in the .config file, you can see the new CONFIG_ option representing that the Vega board is selected and set to the value 'y'. Notice also that the CONFIG_ option related to Constellation is present but not selected.

4.3.6. Kernel Makefiles

When building the kernel, the Makefiles scan the configuration and decide what subdirectories to descend into and what source files to compile for a given configuration. To complete the example of adding support for two custom hardware platforms, Vega and Constellation, let's look at the makefile that would read this configuration and take some action based on customizations.

Because you're dealing with hardware specific options in this example, assume that the customizations are represented by two hardware-setup modules called vega_setup.c and constellation_setup.c. We've placed these C source files in the .../arch/arm/mach-ixp4xx subdirectory of the kernel source tree. Listing 4-12 contains the complete makefile for this directory from a recent Linux release.

Listing 4-12. Makefile from …/arch/arm/mach-ixp4xx Kernel Subdirectory

# # Makefile for the linux kernel. # obj-y    += common.o common-pci.o obj-$(CONFIG_ARCH_IXDP4XX)    += ixdp425-pci.o ixdp425-setup.o obj-$(CONFIG_MACH_IXDPG425)   += ixdpg425-pci.o coyote-setup.o obj-$(CONFIG_ARCH_ADI_COYOTE) += coyote-pci.o coyote-setup.o obj-$(CONFIG_MACH_GTWX5715)   += gtwx5715-pci.o gtwx5715-setup.o

You might be surprised by the simplicity of this makefile. Much work has gone into the development of the kernel build system for just this reason. For the average developer who simply needs to add support for his custom hardware, the design of the kernel build system makes these kinds of customizations very straightforward.[10]

[10] In actuality, the kernel build system is very complicated, but most of the complexity is cleverly hidden from the average developer. As a result, it is relatively easy to add, modify, or delete configurations without having to be an expert.

Looking at this makefile, it might be obvious what must be done to introduce new hardware setup routines conditionally based on your configuration options. Simply add the following two lines at the bottom of the makefile, and you're done:

obj-$(CONFIG_ARCH_VEGA)   += vega_setup.o obj-$(CONFIG_ARCH_CONSTELLATION)   += costellation_setup.o


These steps complete the simple addition of setup modules specific to the hypothetical example custom hardware. Using similar logic, you should now be able to make your own modifications to the kernel configuration/build system.

4.3.7. Kernel Documentation

A wealth of information is available in the Linux source tree itself. It would be difficult indeed to read it all because there are nearly 650 documentation files in 42 subdirectories in the .../Documentation directory. Be cautious in reading this material: Given the rapid pace of kernel development and release, this documentation tends to become outdated quickly. Nonetheless, it often provides a great starting point from which you can form a foundation on a particular kernel subsystem or concept.

Do not neglect the Linux Documentation Project, found at www.tldp.org, where you might find the most up-to-date version of a particular document or man page.[11] The list of suggested reading at the end of this chapter duplicates the URL for the Linux Documentation Project, for easy reference. Of particular interest to the previous discussion is the Kbuild documentation found in the kernel .../Documentation/kbuild subdirectory.

[11] Always assume that features advance faster than the corresponding documentation, so treat the docs as a guide rather than indisputable facts.

No discussion of Kernel documentation would be complete without mentioning Google. One day soon, Googling will appear in Merriam Webster's as a verb! Chances are, many problems and questions you might ask have already been asked and answered before. Spend some time to become proficient in searching the Internet for answers to questions. You will discover numerous mailing lists and other information repositories full of useful information related to your specific project or problem. Appendix E contains a useful list of open-source resources.



Embedded Linux Primer(c) A Practical Real-World Approach
Embedded Linux Primer: A Practical Real-World Approach
ISBN: 0131679848
EAN: 2147483647
Year: 2007
Pages: 167

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net