Android is a special distribution of the Linux® operating system, designed to manage the kind of functionality you expect in the smartest of smart phones. To a programmer, everything in Linux — a device, a spreadsheet, or a favorite song — appears as a file. That is, there is a common set of ways to open, extract, manipulate, and save the information content of these diverse and abstract entities. And this commonality forms the core principle of the UNIX® philosophy: Everything is a file.
Files are grouped into useful hierarchies called file systems. An Android cell phone
typically has two file systems: the non-volatile memory of the cell phone and the SD
card plugged into it. Both appear as branches off the
root directory (/). A tool that enabled you to browse
these file systems conveniently from your browser could prove useful. This project
defines a small program, written in the native language of C,
that communicates as a Web server and enables you to browse the Android cell phone's
file systems from your workstation or straight from the built-in browser of the cell
phone itself. It provides pages with hyperlinks that enable you to move up and down
the hierarchical tree. By clicking various file types listed, you can view the individual
items.
Setting up the development environment
First, make sure that you have a "rooted" Android cell phone so you
can do things like run Terminal and execute the su
command to obtain root privileges. If you don't know how to "root" your Android
phone and obtain these privileges, a quick Internet search should give you what you
need. (Note that this process is sometimes called jail-breaking your phone.
See Resources for links on how to do this.)
The Android community primarily uses two SDKs. The most familiar one is the high-level Android SDK, which enables you to write application code in the Java™ language and commonly uses Eclipse for writing, testing, and debugging code. Another, less commonly known SDK is the Android kernel source, which is stored in what is known as a git repository. (For links to this repository, see Resources.)
Since this article focuses on creating a low-level Web server that will typically reside in the system/bin directory of your cell phone, it is important to download and install the entire Android kernel source code and the GNU tools used for building it. The home page of the Android Kernel Source project (see Resources) provides simple instructions for downloading the entire platform with a script called repo.
The tiny cloud software will be cross-compiled to the ARM platform on your workstation,
so make sure all the necessary tools are installed. Using apt-get,
install these tools as shown in Listing 1.
Listing 1. Installing cross-compiling tools
$ sudo apt-get install git-core gnupg \
sun-java5-jdk flex bison gperf libsdl-dev \
libesd0-dev libwxgtk2.6-dev build-essential \
zip curl libncurses5-dev zlib1g-dev
|
Development directory structure
Use the directory name mydroid to install the Android kernel source. This
name makes an effective root directory to work off of, so go to your home
directory and issue a mkdir mydroid command.
Then use cd mydroid and issue the
repo sync command there.
After the repo sync command has downloaded all of the Android source code to your mydriod directory, a few noteworthy subdirectories that were created include:
- You will be creating the cloud directory inside mydroid/external. The source code for this project (cloud.c) will reside there.
- After the long-running
makecommand builds the Android system, you will find the binary file cloud inside the out/target/product/generic/system/bin directory.
When the cloud program starts, it immediately checks to see whether any command-line parameters have been passed to it. The two parameters that are optionally expected are the port to monitor and the home directory to start in. If neither parameter is specified, the program defaults to using the standard port 80, and the default home directory is whatever the current working directory was when the program was started.
After startup, the program initializes the TCP/IP socket to "listen" for calls to it through the aforementioned port, then turns itself into a daemon process, waiting for browser calls and servicing them. When a browser calls the default "page" of this tiny cloud server, the code responds by returning a directory listing of the aforementioned "home" directory. File names are listed with or without a hyperlink, depending on whether they are a known file type or a directory. To be a known file type means (in the WWW world) that the file has a corresponding MIME type. For example, deep in the heart of the Android cell phone, audio ringtones are stored as .ogg files. The MIME type audio/ogg tells the browser to play the file on the loudspeaker, assuming that your browser was configured correctly to do this.
Another hyperlink that appears at the top of the file listings is Parent Directory. Clicking this link allows you to move up the file system hierarchy until you eventually get as high as you can possibly go: the root directory. Along the way, other subdirectory names appear as hyperlinks; if you click them, you will go down into that subdirectory to explore the files contained therein.
So, the cloud application is a useful program for conveniently exploring the file system of the cell phone, and the source code (see Download) provides you with a good template of functionality you can modify as you see fit. Suggested ways to modify the code appear at the end of this article. (You can also compile the code and run it on your workstation, enabling you to browse the workstation's file systems, too. For instructions on how to do this, see Resources.)
Every program's source code should show at minimum the program's name and author. Listing 2 shows the attribution, the include files from which you'll be using definitions, and some useful constants.
Listing 2. Attribution, includes, and useful constants
// Android Cloud Application by Bill Zimmerly.
// Based on "NWEB" by Nigel Griffiths.
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define TRUE -1
#define FALSE 0
#define SBUF 1048576
#define LBUF 4096
#define LAPN 64
#define ERROR 1
#define LOG 2
#define LOGGING FALSE
|
Listing 3 provides the global working storage that is accessible by all functions defined in this program. Most of the buffer pointers are allocated storage when a browser calls the tiny cloud. After returning the content to the browser, the memory allocated is returned to the cell phone. In computers with limited resources, this practice becomes more important than many other considerations.
Listing 3. Global working storage
char* about="</pre>Cloud is a simple application that enables "
"web-based browsing of the Android file system "
"and is intended for use by people who like to "
"explore the lowest levels of their device. It "
"runs on a rooted Android phone and is only for "
"browsing the file system. See the IBM "
"developerWorks article for a full description "
"of this application.<pre>";
char* mainbuf;
char* theDir;
char* thePort[8];
char* theList;
char* fstr;
char logDir[LBUF];
int ret;
|
In this program, a simple table of file extensions map into the MIME types that
browsers need to know to properly handle the file's content. The structure in
Listing 4 is used by the mimeokay
function for one purpose: to set the global string pointer fstr
to the appropriate MIME type string matching the file's extension: fext.
With this information, the tiny cloud can display the file name with a hyperlink or
as plain text without a link. It also serves the dual purpose of providing the MIME
type when a file's content is being sent to the browser.
Listing 4. Determining MIME types
struct
{
char *ext;
char *mimetype;
}
mimes [] =
{
{".htm", "text/html" },
{".html", "text/html" },
{".xml", "text/xml" },
{".gif", "image/gif" },
{".jpg", "image/jpeg" },
{".jpeg", "image/jpeg" },
{".png", "image/png" },
{".log", "text/plain" },
{".conf", "text/plain" },
{".rc", "text/plain" },
{".sh", "text/plain" },
{".prop", "text/plain" },
{".txt", "text/plain" },
{".TXT", "text/plain" },
{".cpp", "text/plain" },
{".c", "text/plain" },
{".h", "text/plain" },
{".ogg", "audio/ogg" },
{0,0}
};
void mimeokay(char* fext)
{
int buflen;
int len;
long i;
buflen=strlen(fext);
fstr = (char*) 0;
for(i=0; mimes[i].ext != 0; i++)
{
len = strlen(mimes[i].ext);
if(!strncmp(&fext[buflen-len], mimes[i].ext, len))
{
fstr=mimes[i].mimetype;
break;
}
}
}
|
Listing 5 shows the optional code that will log Android cloud
activity to a cloud.log file during development and testing. Because the cloud runs
on relatively slow hardware, it is good to leave this functionality shut off unless
you really need to see what's happening in the code while you're modifying it.
The LOGGING constant in Listing 1
should be TRUE if you want this logging to work, but leave it FALSE when it is
running on the cell phone, as the performance hit can be significant.
Listing 5. Logging activity
void aclog(int type, char *s1, char *s2, int num)
{
int fd ;
char aclogbuffer[LBUF];
char logFile[LBUF];
if(!LOGGING)
return;
switch(type)
{
case ERROR:
sprintf(aclogbuffer, "ERROR: %s:%s Error Number=%d, PID=%d",
s1, s2,
errno, getpid());
break;
case LOG:
sprintf(aclogbuffer, "INFO: %s:%s:%d", s1, s2, num);
break;
}
strcpy(logFile, logDir);
strcat(logFile, "/cloud.log");
if((fd = open(logFile, O_CREAT | O_WRONLY | O_APPEND,
0644)) >= 0)
{
ret=write(fd, aclogbuffer, strlen(aclogbuffer));
ret=write(fd, "\n", 1);
close(fd);
}
if(type == ERROR)
exit(3);
}
|
Listing 6 provides several functions defined for the purpose
of creating the HTML output of the tiny cloud. Note that every page of a
well-designed Web site should maintain a common look, and the
buildbuf function is where this overall look is defined.
Listing 6. Generating HTML output
void apname(void)
{
strcat(mainbuf, "Android Cloud Application");
}
void buildbuf(char* data)
{
mainbuf[0]=0;
strcat(mainbuf, "<html>\n<head>\n<title>");
apname();
strcat(mainbuf, "</title>\n</head>\n");
strcat(mainbuf, "<body>\n<h3>");
apname();
strcat(mainbuf, "</h3>\n");
strcat(mainbuf, "<a href=\"/About_\">About</a><br>\n");
strcat(mainbuf, "<a href=\"/Home_\">Home</a><br>\n");
strcat(mainbuf, "<hr>\n");
strcat(mainbuf, "Dir: ");
strcat(mainbuf, theDir);
strcat(mainbuf, "<br>\n<hr>\n<pre>\n");
strcat(mainbuf, data);
strcat(mainbuf, "\n</pre>\n");
strcat(mainbuf, "</body>\n</html>\n");
}
void htmlout(int fd, char* data)
{
fstr=mimes[0].mimetype;
sprintf(mainbuf, "HTTP/1.0 200 OK\r\nContent-Type: %s\r\n\r\n", fstr);
ret=write(fd, mainbuf, strlen(mainbuf));
buildbuf(data);
ret=write(fd, mainbuf, strlen(mainbuf));
}
void error404(int fd)
{
fstr=mimes[0].mimetype;
sprintf(mainbuf, "HTTP/1.0 404 OK\r\nContent-Type: %s\r\n\r\n", fstr);
ret=write(fd, mainbuf, strlen(mainbuf));
buildbuf("404 Error - File not found!");
ret=write(fd, mainbuf, strlen(mainbuf));
}
|
Listing 7 shows how the tiny cloud returns the content of a file
with a defined MIME type. Notice how the browser expects the MIME type to
be sent from the server as a Content-Type: string. By
just clicking the cloud-generated links, this function is called upon to return the
content of that file. If, however, a creative and mischievous soul edits a bad file
name in his browser's address bar, the error404
function (defined above) will inform the user of it.
Listing 7. Returning file contents
void retfile(int fd, int hit)
{
int file_fd;
long ret;
mimeokay(mainbuf);
if(fstr == 0)
{
error404(fd);
return;
}
if((file_fd = open(&mainbuf[4], O_RDONLY)) == -1)
{
error404(fd);
}
else
{
aclog(LOG, "SEND", &mainbuf[4], hit);
sprintf(mainbuf, "HTTP/1.0 200 OK\r\nContent-Type: %s\r\n\r\n", fstr);
ret=write(fd, mainbuf, strlen(mainbuf));
while((ret=read(file_fd, mainbuf, SBUF)) > 0 )
{
ret=write(fd, mainbuf, ret);
}
}
}
|
Listing 8 shows how the tiny cloud builds the main file listing
with hyperlinks. The hyper function is used several
times, so it is prudent to factor it out into its own function. The isDir
value prepends the /CD_ text before the full path
to the file name so that the tiny cloud knows that it must display the contents
of that directory. The fileList function is truly the
heart and soul of this application.
Listing 8. Directory listing functions
void hyper(int isDir, char* name)
{
strcat(theList, "<a href=\"");
if(isDir)
strcat(theList, "/CD_");
strcat(theList, (char*) theDir);
if(strcmp(theDir, "/"))
strcat(theList, "/");
strcat(theList, name);
strcat(theList, "\">");
strcat(theList, name);
strcat(theList, "</a>");
strcat(theList, "\n");
}
char* fileList(void)
{
struct dirent **namelist;
int n;
long i;
long j;
theList[0]=0;
n=scandir(".", &namelist, 0, (void*) alphasort);
if (n < 0)
perror("scandir");
else
{
for(i=0; i<n; i++)
{
if(namelist[i]->d_type == DT_DIR)
{
if(!strcmp(namelist[i]->d_name, "."))
{
// strcat(theList, namelist[i]->d_name);
}
else if(!strcmp(namelist[i]->d_name, ".."))
{
if(strcmp(theDir, "/"))
{
strcat(theList, "<a href=\"");
strcat(theList, "/CD_");
strcat(theList, (char*) theDir);
j=strlen(theList);
while(j--)
{
if(theList[j] == '/')
{
theList[j]=0;
j=1;
}
}
if(!strcmp(&theList[strlen(theList)-4], "/CD_"))
{
strcat(theList, "/");
}
strcat(theList, "\">Parent Directory</a>");
strcat(theList, "\n");
}
}
else
hyper(TRUE, namelist[i]->d_name);
}
else
{
mimeokay(namelist[i]->d_name);
if(fstr == 0)
{
strcat(theList, namelist[i]->d_name);
strcat(theList, "\n");
}
else
hyper(FALSE, namelist[i]->d_name);
}
free(namelist[i]);
}
free(namelist);
}
return theList;
}
|
In Listing 9, the child functionality of the tiny cloud server is defined. This functionality runs every time the server receives a browser request, and it has a simple function: allocate the buffers necessary for satisfying the request, process them, then free them so the allocated system memory is kept low when not needed. On a cell phone, memory is a scarce and precious resource, and should be released back to the system when your program is done servicing a request.
Listing 9. Daemon child functionality
void child(int fd, int hit)
{
long i;
long ret;
char* cret;
mainbuf=malloc(SBUF+1);
theList=malloc(SBUF+1);
theDir=malloc(LBUF+1);
cret=getcwd(theDir, LBUF);
ret=read(fd, mainbuf, SBUF);
if(ret == 0 || ret == -1)
{
error404(fd);
}
else
{
if(ret > 0 && ret < SBUF)
mainbuf[ret]=0;
else
mainbuf[0]=0;
for(i=0; i<ret; i++)
if(mainbuf[i] == '\r' || mainbuf[i] == '\n')
mainbuf[i]='*';
aclog(LOG, "request", mainbuf, hit);
for(i=4; i < SBUF; i++)
{
if(mainbuf[i] == ' ')
{
mainbuf[i]=0;
break;
}
}
if(!strncmp(&mainbuf[0], "GET /\0", 6) ||
!strncmp(&mainbuf[0], "get /\0", 6))
{
htmlout(fd, fileList());
}
else
{
if(!strncmp(&mainbuf[5], "About_", 6))
{
htmlout(fd, about);
}
else if(!strncmp(&mainbuf[5], "Home_", 5))
{
htmlout(fd, fileList());
}
else if(!strncmp(&mainbuf[5], "CD_", 3))
{
if(chdir(&mainbuf[8]) == -1)
{
error404(fd);
}
else
{
if(strcmp(theDir, &mainbuf[8]))
strcpy(theDir, &mainbuf[8]);
htmlout(fd, fileList());
}
}
else
{
retfile(fd, hit);
}
}
}
free(theDir);
free(theList);
free(mainbuf);
sleep(1);
exit(1);
}
|
The main (parent) functionality of the tiny cloud is defined in Listing 10.
It allocates the TCP/IP socket on which it will be listening for browser request
calls. Then it initializes a few global variables like theDir,
where the tiny cloud was started. And finally, it makes itself into a resident
program (known as a daemon) so it can quietly process browser
requests in the background while other processes are running.
Listing 10. Main functionality
int main(int argc, char **argv)
{
char* str;
char* cret;
static struct sockaddr_in cli_addr;
static struct sockaddr_in serv_addr;
socklen_t length;
int i;
int port;
int pid;
int listenfd;
int socketfd;
int hit;
cret=getcwd(logDir, LBUF);
if(argc < 2)
{
strcpy((char*) thePort, "80");
port=atoi((char*) thePort);
}
else
{
if(!strcmp(argv[1], "-?"))
{
printf("Usage: cloud [Port Directory]\n");
exit(0);
}
strcpy((char*) thePort, argv[1]);
port=atoi((char*) thePort);
if(port < 0 || port > 60000)
aclog(ERROR, "Invalid port number (try 1 --> 60000)", argv[1], 0);
if(chdir(argv[2]) == -1)
{
printf("ERROR: Invalid directory %s\n", argv[2]);
exit(4);
}
}
if(fork() != 0)
return 0;
signal(SIGCHLD, SIG_IGN);
signal(SIGHUP, SIG_IGN);
for(i=0; i<32; i++)
close(i);
setpgrp();
aclog(LOG, "Cloud Port/PID=", (char*) thePort, getpid());
if((listenfd=socket(AF_INET, SOCK_STREAM, 0)) < 0)
aclog(ERROR, "syscall", "socket", 0);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(port);
if(bind(listenfd, (struct sockaddr*) &serv_addr,
sizeof(serv_addr)) < 0)
aclog(ERROR, "syscall", "bind", 0);
if(listen(listenfd, 64) <0)
aclog(ERROR, "syscall", "listen", 0);
for(hit=1; ;hit++)
{
length=sizeof(cli_addr);
if((socketfd=accept(listenfd,
(struct sockaddr*) &cli_addr,
(socklen_t*) &length)) < 0)
aclog(ERROR, "syscall", "accept", 0);
if((pid=fork()) < 0)
{
aclog(ERROR, "syscall", "fork", 0);
}
else
{
if(pid == 0)
{
close(listenfd);
child(socketfd, hit);
}
else
{
close(socketfd);
}
}
}
}
|
Compiling, deploying, and testing the application
You need to create one more file to properly build the cloud application using the Android kernel source makefile. Create a file called Android.mk, and paste what you see in Listing 11 into it. (This file is also included in the source available in Download.)
Listing 11. Android.mk
ifneq ($(TARGET_SIMULATOR),true)
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= cloud.c
LOCAL_MODULE := cloud
LOCAL_STATIC_LIBRARIES := libcutils libc
include $(BUILD_EXECUTABLE)
endif # TARGET_SIMULATOR != true
|
Navigate to the external directory of the Android kernel source, create a subdirectory named cloud, and put both the cloud.c and the Android.mk files into it. You're now ready to build a new Android kernel system with the tiny cloud application in it.
Move up to the root directory of the Android kernel source, type make,
then go get some coffee. This process will take a while, so relax and let
the system do its job.
If all goes well and the system has completed the make, you should find the tiny cloud binary in the out/target/product/generic/system/bin directory. You don't really need to go through all the trouble of installing the entire distribution into your rooted Android cell phone: Just copy the cloud binary to the SD card. Listing 12 shows how you can do this on your host computer (assuming that the mydroid directory contains all the Android source kernel code).
Listing 12. Making the kernel and the cloud
$ cd mydroid/external
$ mkdir cloud
$ cd cloud
$ cp ~/src/tinycloud/cloud.c .
$ cp ~/src/tinycloud/Android.mk .
$ cd ../..
$ make
--- Android system "make" messages scroll up the screen for a long time. ---
$ cd out/target/product/generic/system/bin
$ cp cloud /media/ANDROIDSDCARD/.
|
Notice that /media/ANDROIDSDCARD assumes you have the cell phone plugged in and mounted. Also, the name of your SD card may be different. Look around in the /media subdirectory (if running under Ubuntu Linux) for the correct name.
Running one of the freely available Terminal programs found in the Android Market
allows you to run a shell session on the Android phone itself. The Android cloud
would normally reside in the system/bin directory, but it doesn't have to. For the
sake of testing, it's good to experiment with it in another directory. Create a
directory under /data and call it cloud. Copy the cloud binary from the
/sdcard directory to /data/cloud. Then run a chmod
command to make the cloud program executable, and run it by typing
cloud. Listing 13 shows you
what these steps look like.
Listing 13. Testing the cloud on your Android cell phone
$ su
# cd data
# mkdir cloud
# cd cloud
# cp /sdcard/cloud .
# chmod 777 cloud
# ./cloud
# ps
--- Listing of resident daemons, and "./cloud" should be one of them. ---
|
Press the Home key, start the browser, and go to the URL http://localhost. You should see the output of the tiny cloud on Android's browser. You can move around the file system of your handset by clicking the links displayed.
Next, if your Android phone Wi-Fi is running on your network, you can use your
computer to call up the tiny cloud. To do this, you need to know the Wi-Fi IP
address of your cell phone. There are various ways to determine this address,
including checking your Wi-Fi router's logs. Another way is to go back to the
Terminal program and type netstat -r; you should see
an entry similar to that in Listing 14.
Listing 14. Using Netstat to obtain a cell phone's IP address
# netstat -r
Proto Recv-Q Send-Q Local Address Foreign Address ...
.
.
.
tcp 0 0 192.168.0.3:80 192.168.0.5:58744 ...
.
.
.
|
Type http://192.168.0.3/ (your cell phone's IP address) in
your browser's address bar. In a moment, you should be looking into your
Android cell phone's file system listing, presented by the tiny cloud.
To use the tiny cloud in your cell phone to browse a Web page you are creating at the same time, simply swap the application you're using by pressing and holding the Home key and clicking the browser or the editor. If you click the browser, do a menu refresh to see your edited changes. By repeating the edit/test/edit/test cycle until it looks how you want it to, you can be Web-mastering while waiting at the dentist's office.
There are as many ways to enhance the project as there are people with ideas, but here are a few suggestions that you may want to try:
- Add a specific menu item called Java that automatically navigates to the directory in which the Java class files reside, allowing you to click any of them for exploratory purposes. (Class files are actually compressed directories and will qualify for hyperlinks.) You can do this for any other specific directory that contains code that interests you.
- Add a top-like page that refreshes itself every minute or so and displays choice information that can be read from the information in the /proc directory.
- Write code that allows you to query SQLite databases from a Web page.
- Whole HTML-based presentations can be hosted on the SD card, and the portability of the cell phone is great, so by using the Web browser built into most modern presentation stages, you can display your slide show presentation on your cell phone.
When you have decided on an idea for improving the tiny cloud, follow these three generic steps to add that functionality:
- Modify the
buildbuffunction in Listing 6 by entering a new menu choice that represents the new functionality you want to add. - Modify the
childfunction in Listing 9 so it can service the new functionality. For example, study how theAbout_menu item works in both of these functions (buildbufandchild), and you'll see how easy it is to add new functionality to the tiny cloud. - Write the function that will service the menu item and be sure to insert a call to the new function in the child.
It's always a good thing to know as much as you can about the devices you use. Furthermore, writing tools that can help you learn more can be a lot of fun. Putting a tiny cloud computing application into the heart of your Android cell phone is a fun and educational activity that will force you to learn a lot about what actually resides "under the hood" of these amazing devices.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code for the tiny cloud app | os-tinycloud-source.zip | 9KB | HTTP |
Information about download methods
Learn
-
Study the MIME media types.
-
Visit the Open Handset Alliance Web
site.
-
See Wikipedia's
entry for Android.
-
Rooting
an Android Phone: Learn how to enable your phone for use with custom
code and applications.
-
Read "nweb: a tiny,
safe Web server (static pages only)" to learn how to serve static pages with nweb.
-
Read the Bible of ARM programming: ARM Architecture
Reference Manual (PDF).
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
Stay current with developerWorks' Technical events and webcasts.
-
Follow developerWorks on Twitter.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products, as well as our most popular articles and tutorials.
-
The My developerWorks community is an example of a successful general community that covers a wide variety of topics.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
Get products and technologies
-
Visit the official Android site for downloads
and more information.
-
Visit the GIT repository for the Android kernel source code.
-
Get the Sourcery G++
Lite package from CodeSourcery.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
- Download
IBM product evaluation versions
or explore
the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from
DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
-
Participate in developerWorks blogs and get involved in the developerWorks community.

Bill Zimmerly is a knowledge engineer, a low-level systems programmer with expertise in various versions of UNIX and Microsoft Windows, and a free thinker who worships at the altar of Logic. Creating new technologies and writing about them are his passions. He resides in rural Hillsboro, Missouri, where the air is fresh, the views are inspiring, and good wineries are all around. You can reach him at bill@zimmerly.com.





